umi 项目增加支持放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录,就比如 vue-cli 的 assetDir
简介:最近需要做一个后台系统,时间又比较充足,antd 和 Ant Design Pro 都已经升级到了 v4 ,于是就果断尝鲜了一波,遇到的问题也比较多,比较难解决的就是类似 vue-cli 的 assetDir 的问题,现把解决方案整理如下。
提示: 不要配置 config.optimization.splitChunks ,会导致 umi 项目运行失败。
1.项目主要依赖
package.json
1 | { |
2 | "dependencies": { |
3 | "@ant-design/icons": "^4.0.0", |
4 | "@ant-design/pro-layout": "^5.0.16", |
5 | "@ant-design/pro-table": "2.3.4", |
6 | "antd": "4.3.3", |
7 | "axios": "^0.19.2", |
8 | "braft-editor": "^2.3.9", |
9 | "braft-polyfill": "^0.0.2", |
10 | "classnames": "^2.2.6", |
11 | "cropperjs": "^1.5.7", |
12 | "font-awesome": "^4.7.0", |
13 | "js-cookie": "^2.2.1", |
14 | "js-md5": "^0.7.3", |
15 | "lodash": "^4.17.11", |
16 | "moment": "^2.25.3", |
17 | "omit.js": "^1.0.2", |
18 | "path-to-regexp": "2.4.0", |
19 | "prop-types": "^15.7.2", |
20 | "qs": "^6.9.0", |
21 | "react": "^16.8.6", |
22 | "react-cropper": "^1.3.0", |
23 | "react-dom": "^16.8.6", |
24 | "react-helmet-async": "^1.0.4", |
25 | "umi": "^3.1.2", |
26 | "umi-request": "^1.0.8", |
27 | "use-merge-value": "^1.0.1" |
28 | } |
29 | } |
2.项目结构
1 | ├── config # 配置文件,包含umi内置功能和插件的配置以及webpack和babel的配置。 |
2 | │ ├── config.dev.js # 开发环境变量 |
3 | │ ├── config.js # 配置文件 |
4 | │ ├── config.prod.js # 生产环境变量 |
5 | │ └── defaultSettings.js # 网站和antd的配置 |
6 | ├── public # 静态文件,不变化的logo和icon等 |
7 | ├── src # 主代码文件 |
8 | ├── assets # 静态文件,网站需要的图片,字体等 |
9 | │ |
10 | ├── components # jsx组件 |
11 | ├── global.jsx # 全局js |
12 | ├── global.less # 全局样式 |
13 | ├── icons # 自定义svg图标 |
14 | ├── layouts # 网站基本布局,权限校验 |
15 | ├── locales # 语言配置 |
16 | ├── models # store数据仓库 |
17 | ├── pages # 网站页面 |
18 | ├── routes # 网站路由文件 |
19 | │ |
20 | ├── service-worker.js # pwa文件 |
21 | ├── services # api接口 |
22 | │ |
23 | ├── styles # 全局样式 |
24 | │ |
25 | └── utils # 工具,包含请求库,本地存储等 |
3.主要修改 chainWebpack
配置 (defaultSettings 和 routes 文件可以根据自己项目删除)
config/config.js
1 | // https://umijs.org/config/ |
2 | import { defineConfig } from "umi"; |
3 | import defaultSettings from "./defaultSettings"; |
4 | import routes from "../src/routes"; |
5 | |
6 | const path = require("path"); |
7 | const CompressionWebpackPlugin = require("compression-webpack-plugin"); |
8 | const isEnvProduction = process.env.NODE_ENV === "production"; |
9 | const isEnvDevelopment = process.env.NODE_ENV === "development"; |
10 | const resolve = (dir) => path.join(__dirname, dir); |
11 | const assetDir = "static"; |
12 | |
13 | const config = defineConfig({ |
14 | history: { type: "hash" }, |
15 | publicPath: "./", |
16 | hash: true, |
17 | antd: {}, |
18 | dva: { |
19 | hmr: true, |
20 | }, |
21 | chainWebpack(config, { env, webpack, createCSSRule }) { |
22 | // 修改js,js chunk文件输出目录 |
23 | config.output |
24 | .filename(assetDir + '/js/[name].[hash:8].js') |
25 | .chunkFilename(assetDir + '/js/[name].[contenthash:8].chunk.js') |
26 | |
27 | // 修改css输出目录 |
28 | config.plugin("extract-css").tap(() => [ |
29 | { |
30 | filename: `${assetDir}/css/[name].[contenthash:8].css`, |
31 | chunkFilename: `${assetDir}/css/[name].[contenthash:8].chunk.css`, |
32 | ignoreOrder: true, |
33 | }, |
34 | ]); |
35 | |
36 | // 修改图片输出目录 |
37 | config.module |
38 | .rule("images") |
39 | .test(/\.(png|jpe?g|gif|webp|ico)(\?.*)?$/) |
40 | .use("url-loader") |
41 | .loader(require.resolve("url-loader")) |
42 | .tap((options) => { |
43 | const newOptions = { |
44 | ...options, |
45 | name: assetDir + "/img/[name].[hash:8].[ext]", |
46 | fallback: { |
47 | ...options.fallback, |
48 | options: { |
49 | name: assetDir + "/img/[name].[hash:8].[ext]", |
50 | esModule: false, |
51 | }, |
52 | }, |
53 | }; |
54 | return newOptions; |
55 | }); |
56 | |
57 | // 修改svg输出目录 |
58 | config.module |
59 | .rule("svg") |
60 | .test(/\.(svg)(\?.*)?$/) |
61 | .use("file-loader") |
62 | .loader(require.resolve("file-loader")) |
63 | .tap((options) => ({ |
64 | ...options, |
65 | name: assetDir + "/img/[name].[hash:8].[ext]", |
66 | })); |
67 | |
68 | // 修改fonts输出目录 |
69 | config.module |
70 | .rule("fonts") |
71 | .test(/\.(eot|woff|woff2|ttf)(\?.*)?$/) |
72 | .use("file-loader") |
73 | .loader(require.resolve("file-loader")) |
74 | .tap((options) => ({ |
75 | ...options, |
76 | name: assetDir + "/fonts/[name].[hash:8].[ext]", |
77 | fallback: { |
78 | ...options.fallback, |
79 | options: { |
80 | name: assetDir + "/fonts/[name].[hash:8].[ext]", |
81 | esModule: false, |
82 | }, |
83 | }, |
84 | })); |
85 | |
86 | // 添加gzip压缩 |
87 | config.when(isEnvProduction, (config) => { |
88 | config |
89 | .plugin("compression-webpack-plugin") |
90 | .use(CompressionWebpackPlugin, [ |
91 | { |
92 | filename: "[path].gz[query]", |
93 | algorithm: "gzip", |
94 | test: new RegExp("\\.(js|css)$"), |
95 | threshold: 10240, |
96 | minRatio: 0.8, |
97 | }, |
98 | ]); |
99 | }); |
100 | }, |
101 | // 生产环境去除console日志打印 |
102 | terserOptions: { |
103 | compress: { |
104 | drop_console: isEnvProduction, |
105 | }, |
106 | }, |
107 | locale: { |
108 | // default zh-CN |
109 | default: "zh-CN", |
110 | // default true, when it is true, will use `navigator.language` overwrite default |
111 | antd: true, |
112 | baseNavigator: true, |
113 | }, |
114 | dynamicImport: { |
115 | loading: "@/components/PageLoading/index", |
116 | }, |
117 | targets: { |
118 | ie: 11, |
119 | }, |
120 | // umi routes: https://umijs.org/docs/routing |
121 | routes, |
122 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn |
123 | theme: { |
124 | // ...darkTheme, |
125 | "primary-color": defaultSettings.primaryColor, |
126 | }, |
127 | // @ts-ignore |
128 | title: defaultSettings.title, |
129 | ignoreMomentLocale: true, |
130 | manifest: { |
131 | basePath: "/", |
132 | }, |
133 | devServer: { |
134 | open: true, |
135 | port: 8367, |
136 | }, |
137 | }); |
138 | |
139 | export default config; |
4.打包后输出的 dist 目录
1 | ├── asset-manifest.json |
2 | ├── favicon.ico |
3 | ├── index.html |
4 | └── static |
5 | ├── css |
6 | ├── fonts |
7 | ├── img |
8 | └── js |
参考链接
1.https://github.com/umijs/umi/blob/master/packages/bundler-webpack/src/getConfig/getConfig.ts
问题来源