本文以umi2.x项目作为示例进行项目优化,其他项目优化方向也是如此,可能配置上有一点差异。
目录
- umi获取配置文件
- 启动项目时的优化
- 优化文件
- 动态加载
- 文件缓存
一、umi 获取配置文件
umi 的配置文件路径为 config/config.js。其中包含了umi的配置与webpack的配置,具体配置项,请查看umi官方文档。
在配置项 chainWebpack 上能够获取到整个 webpack 的配置文件,为了方便调试,可以在每次运行时将文件保存到根目录上:
chainWebpack(config, { webpack }) {
fs.writeFile('./webpackConfig.txt', config.toString(), () => {});
}
二、启动项目时的优化
1. 缓存文件,加速项目重启速度
webpack 的 DLLPlugin 能够缓存一些 bundles 文件,在项目启动时不需要再重新编译,极大提高启动速度。web 课堂项目,使用 DLLPlugin 之后,构建时间从55s左右减少到25s左右。
umi 中打开 DLLPlugin 方式如下:
plugins: [
[
'umi-plugin-react',
{
dll: true,
},
]
]
此处注意,由于dll缓存node_modules中的依赖,所以当node_modules中的文件有更新时,并不会重新打包。若有实时更新依赖包的需要,请先关闭此配置。
2. 分配更多v8内存,确保大项目能正常启动
在Node中通过JavaScript使⽤内存时只能使⽤部分内存(64位系统:1.4
GB,32位系统:0.7 GB)。
如果前端项⽬比较庞⼤,Webpack编译时就会占⽤很多的系统资源,如
果超出了V8引擎对Node默认的内存限制⼤⼩时则启动失败。若碰到此情况,可以手动设置 node 启动参数 --max-old-space-size
,分配更多的内存。
设置方式如下;
方案一
"large-start": "node --max-old-space-size=8192 ./node_modules/.bin/umi start",
"large-build": "node --max-old-space-size=8192 ./node_modules/.bin/umi build",
方案二
"large-start":"cross-env NODE_OPTIONS=--max-old-space-size=4096 npm
start",
"large-build":"cross-env NODE_OPTIONS=--max-old-space-size=4096 npm run
build"
同理,可配置 large-fat-build 、large-analyze 等启动命令。
3. 解决 babel-plugin-react-css-modules 与 cssLoader 在 windows 上协作异常问题
在 windows 上同时使用以上两个工具,会导致不能正确加载样式。原因为 windows 上路径分隔符为'',mac 上分隔符为'/',babel-plugin-react-css-modules会⾃动对''进行转换,⽽cssloader不会,所以⽤于计算hash
的相对路径不⼀致,导致计算结果匹配不上。
解决思路是,手动兼容cssloader的路径,同步两个工具用于计算hash的值。
config.js
const GENERATE_PATH = '[path]___[name]__[local]___[hash:base64:5]';
function generateScopedName(localName, filePath) {
const relativePath = path.relative(process.cwd(), filePath);
return genericNames(GENERATE_PATH)(localName, relativePath);
}
export default {
cssLoaderOptions: {
getLocalIdent: function(context, _, localName) {
return generateScopedName(localName, context.resourcePath);
},
},
extraBabelPlugins: [
[
'react-css-modules',
{
exclude: 'node_modules',
generateScopedName: GENERATE_PATH,
filetypes: {
'.less': {
syntax: 'postcss-less',
},
},
},
],
],
}
三、优化文件
1. 图片大小优化
在前端项目中加入imagemin-linter工具,自动在提交图片前进行压缩。
2. 清除无用文件
项目中,可能存在不会被用到的文件,这部分文件应该被清除。umi 项目可通过设置环境变量 ANALYZE=1 开启 webpack analyze。大部分项目中,以下文件大概率可以清除。
momentjs 国际化
moment 默认会导入国际化文件,一般只需要中文或者英文语言,其他语言可暂时不导入,之后根据需要导入。配置如下:
chainWebpack(config, { webpack }) {
config
.plugin('moment-locale')
.use(webpack.ContextReplacementPlugin, [/moment[\/\\]locale$/, /en|zh-cn/]);
}
这里不使用 umi ignoreMomentLocale 的原因是,ignoreMomentLocale 会去除所有国际化文件,而实际项目中,可能会有两个语言供配置。所以此处使用了 ContextReplacementPlugin。
moment 导入国际化文件位置为 moment/src/lib/locale/locales loadLocale
lodashjs 国际化文件
官⽅⽂档推荐使⽤ babel-plugin-lodash 和 lodash-webpack-plugin。
babel-plugin-lodash 对代码中的 import _ from 'lodash'或者import { add } from 'lodash/fp'进⾏编译优化。确保引⼊最⼩的包。
lodash-webpack-plugin⽤于精简lodash,去除了⼀些特性,如果需要⽤到,则需⼿动开启。具体可参照lodash-webpack-plugin。
配置方式如下:
安装babel-plugin-lodash后配置:
extraBabelPlugins: [
['lodash'],
],
lodash-webpack-plugin配置为:
import lodashModuleReplacementPlugin from 'lodash-webpack-plugin';
chainWebpack(config, { webpack }) {
config.plugin('lodash-shrink').use(lodashModuleReplacementPlugin, [
{
collections: true,
paths: true,
shorthands: true,
},
]);
},
注:lodash优化效果有限:随着系统的增⻓,引⼊的第三⽅库越来越多。⽽第三⽅库中,有可能使⽤require('lodash')引⼊lodash,导致我们项⽬中对lodash的优化⽆效。所以,可以不⽤太过于纠结lodash的优化。
@antd-design icons/lib
antd3.x全量导⼊@ant-design/icons/lib/dist,可通过配置alias,指向本地⽂件,本地⽂件再按需引⼊。
alias: {
'@ant-design/icons/lib/dist$': path.resolve(__dirname,'../src/helpers/antdIcons.js'),
},
src/helpers/antdIcons.js
// 调⽤antd的内置图标,需要在此处载⼊
export { default as LoadingOutline } from '@antdesign/icons/lib/outline/LoadingOutline';
四、动态加载
动态加载技术可以让模块只再需要使用时再请求载入,提升页面响应速度。
webpack 4.x已支持使用 splitchunksPlugin 进行代码分块。以下内容基于 webpack 4.x。
代码上,使用 import()
函数进行动态加载,用法如下:
import(/* webpackChunkName: 'edit-enow'*/ '@enow/editenow').then(({ default: Enow }) => {
new Enow()
}
其中,webpackChunkName 指定分块后生成的 chunk 名字。
注:使用 babel 时,需要引入 babel-plugin-syntax-dynamic-import 插件,避免 babel 讲 import() 转为 require() 导致动态加载无效。umi 项目已自动配置该插件。
webpack 4.x 默认的 splitChunks 配置如下:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
可以看到,默认会将 node_modules 中的异步请求中的第三方依赖独立打成一个个 vendors chunk,其它打包为 default chunk。
与其相关的属性还有一个 chunkFilename,默认为[id].js,id 从0开始。可手动配置为 [name].js。
当chunkFilename为 [name].js 时,最终 chunk 文件名为,[cacheGroupsName] + automaticNameDelimiter + [chunkFilename]。
当chunkFilename为 [id].js 时,最终 chunk 文件名为 chunkFilename。
umi增加了一个name字段,使所有的node_modules依赖包会打在一个上,导致vendors包变得很大。在有大依赖包的情况下,该配置很不人性化。设置配置解除该限制:config.optimization.splitChunks({ name: true })
chunk为initial时 name为true,name为入口文件名称,同一个入口文件的依赖打包为同一个chunk在(名字加上cachegroups)。name为false时,name为id。 name为string时,无论入口,全部打包为同一个chunk。
splitchunks 最重要的配置为 cacheGroups,基本上如何分包就是在这里进行配置的。
cacheGroups 只要有一个属性没被覆盖,就会使用默认值。 即只要一个属性没有被覆盖,就默认使用以上取值。
五、文件缓存
文件缓存分为两部分,一部分是src目录下的文件缓存,另一部分时public目录下的静态资源缓存。
src 文件
src 文件的缓存比较容易处理,由于能在打包时生成文件的hash值,所以理论上可以设置这些文件的缓存时间无限。使用强缓存。
public 文件
该目录下的文件有以下特点:
- 发生更改时,文件名一般不会变化
- 不能使用hash值生成最终文件,因为代码里的引用路径为字符串,而不是将其作为依赖导入
根据以上特点,协商缓存是比较合理的办法。协商缓存有两种方式,一种是根据文件hash的缓存:ETag、If-None-Match,另外一种是根据时间的缓存:LastModified,If-Modified-Since。
由于没有对⽂件进⾏秒级的更改,选择第二种即可。
配置如下:
umi
chainWebpack(config, { webpack }) {
config.plugin('copy-public').tap(() => [
[
{
from: 'public',
to: 'public',
toType: 'dir',
},
],
]);
}
egg
config.static = {
dir: [
{
prefix: '/public',
dir: path.join(appInfo.baseDir, 'app/view/public'),
maxAge: 0,
},
{
prefix: '/',
dir: path.join(appInfo.baseDir, 'app/view/'),
maxAge: 31536000, // dev设置为0
},
],
gzip: true, // dev 去除
};
设置maxAge为0,或者cacheControl为no-cache开启协商缓存。
更改完之后,引⽤public⽂件需要加/public/前缀。