携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
之前讲的是构建性能,这里是细粒度更高的性能优化,主要是还是体现在构建速度提升上,之前主要是对标生产环境,这里主要是对标开发环境。
使用最新版
使用最新版的webpack、node,新版本对比旧版本一定是更优的,没有谁说越更新越菜鸡的吧,新版本的更新不一定体现在性能优化上,还有会有新特性的增加。
但是需要注意的是不要盲目升级,例如新版本做了破坏性的变更,使用的插件或依赖不支持新版本的特性,等等一些问题都是需要考虑的。
新版本的更新一定是要了解新版本增加了什么特性,这些特性对于项目是否有影响,影响的强度是否能承受等,考虑好升级带来的利弊,再确定是否需要升级。
下面就是新版本的一些特性讲解了。
lazyCompilation
Webpack 5.17.0 之后引入实验特性 lazyCompilation,用于实现 entry 或异步引用模块的按需编译。
在以前如果webpack配置entry是多入口的情况,那么编译时是会将所有的都进行编辑,实际上这些入口你可能只会用到一到两个,其他的你根本就用不上,这种情况不仅浪费构建时间,同时也耗费内存,lazyCompilation的出现就是解决这个问题的,用法草鸡简单:
module.exports = {
// 忽略其他配置
experiments: {
lazyCompilation: true
}
}
它的作用是如果你定义的entry没有被访问,或者使用异步加载模块(const xxx = import('./xxx.js'))的方式,那么这些模块不会立即被构建,而是在你实际请求到这个模块的时候才会进行构建。
注意:这个不是懒加载,可以理解为懒构建,很明显这个功能不是提供给生产环境使用的,同时这个功能也是实验性功能,后面可能会发生较大的改动,建议使用该功能的锁定版本。
此外,lazyCompilation 支持如下参数:
backend: 设置后端服务信息,一般保持默认值即可;entries:设置是否对entry启动按需编译特性;imports:设置是否对异步模块启动按需编译特性;test:支持正则表达式,用于声明对那些异步模块启动按需编译特性。
约束 Loader 执行范围
loader都打交道很长时间了,作用就是解析各种不同类型的文件,但是往往我们在使用一些第三方库的时候,库的作者都替我们把资源编译好了,我们如果再去解析就是在浪费性能了。
例如在node_modules下面,我们可以看到所以我们使用到的第三方依赖库,node_modules也是动辄上百兆。
但是通常情况下我们是不需要对node_modules下的文件进行编译的,所以我们可以使用module.rules.exclude排除掉node_modules下的编译:
module.exports = {
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
],
},
}
使用这个属性webpack看到是这个文件是这个目录下面的就会直接跳过,也就不会触发对应的loader的处理。
这里的属性值可以是字符串、正则表达式、数组、函数和condition
一般看到exclude都会想到配套的include,include的使用方法和exclude相同,作用相反,同时exclude优先级高于include:
module.exports = {
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
include: /node_modules/, // 无效
use: ["babel-loader"],
},
],
},
}
使用 noParse 跳过文件编译
很多npm的库作者已经帮你提前编译好库的内容了,一般都在 node_modules/lib/dist下面,这个时候我们就不需要再二次编译了:
module.exports = {
module: {
noParse: /lodash|vue/,
},
};
这里需要注意的是并不是所有库的作者都给你提供了编译后的资源,需要自行确认,其次由于跳过了编译,无法发现包中的错误,也无法标记导出值。
开发模式禁用产物优化
webpack默认提供了很多产物优化方案,但是这些都是需要算力支持的,在开发环境中我们其实并不需要关系这些优化的东西,只有在生产环境中为了减少包的体积我们才需要,因此,开发模式下建议关闭这一类优化功能,如下:
- 确保
mode='development'或mode = 'none',关闭默认优化策略; optimization.minimize保持默认值或false,关闭代码压缩;optimization.concatenateModules保持默认值或false,关闭模块合并;optimization.splitChunks保持默认值或false,关闭代码分包;optimization.usedExports保持默认值或false,关闭 Tree-shaking 功能;
最终,建议开发环境配置如:
module.exports = {
mode: "development",
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
minimize: false,
concatenateModules: false,
usedExports: false,
},
};
最小化 watch 监控范围
watch(npx webpack --watch命令)会持续监听文件是否发生改变,如果发生改变会就执行rebuild,但是往往很多文件我们项目持续到结束都不会发生修改,例如node_modules下的文件(万恶之首,又不能干掉,哈哈哈),所以我们需要对他取消监控:
module.exports = {
watchOptions: {
ignored: /node_modules/
},
};
跳过 ts、eslint 检查
上面讲的都是怎么减少构建的内容来提升性能,但是还有性能点可能不在编译时,还有运行时(应该是开发时),ts和eslint在开发时都会进行检查,ts做类型推导,eslint做代码检查,这些都是需要吃算力的。
对于ts可以使用fork-ts-checker-webpack-plugin插件来将类型推导放到子进程中执行:
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
module: {
rules: [{
test: /.ts$/,
use: [
{
loader: 'ts-loader',
options: {
// 设置为“仅编译”,关闭类型检查
transpileOnly: true
}
}
],
}, ],
},
plugins:[
// fork 出子进程,专门用于执行类型检查
new ForkTsCheckerWebpackPlugin()
]
};
对于eslint可以使用eslint-webpack-plugin代替eslint-loader
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
plugins: [
new ESLintPlugin(options)
],
};
个人更推荐使用
ide的插件或其他附件功能实现他们的特性,例如WebStorm对于上面两者的检测是自带的,当然vscode也是有对应的插件。
慎用 source-map
source-map是很消耗性能的一个东东,但是呢在开发环境中还是需要的,生产环境中是不需要有的,所以建议:
- 开发环境使用
eval,确保最佳编译速度; - 生产环境使用
source-map,获取最高质量。
source-map的属性值在之前的文章有讲过,所以这里就没有详细的。
设置 resolve 缩小搜索范围
resolve.extensions
这个属性让我们在使用import、require可以忽略后缀,例如:import('./a'),找到的可能是./a.js、./a.json、./a.wasm、./a/index.js、./a/index.json、./a/index.wasm、 但是需要做到这些程序还是需要做很多推导工作的。
上面列举的这些都是webpack默认支持的,我们可以使用这个属性扩展我们自己的想要的,例如ts,但是不宜过多,同时可以指定文件后缀名来跳过解析。
resolve.modules
这个属性用来告诉 webpack 解析模块时应该搜索的目录,默认的情况下是从根目录下寻找node_modules目录,如果没找到就往上层找,直到全局。
一般情况下,一个项目都是控制node_modules的范围的,所以这个功能实用性不高,建议关闭:
const path = require('path');
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')],
},
};
resolve.mainFiles
这个属性其实和resolve.extensions有点对应,import('./a')如果不带后缀其实就是一个目录,但是这种情况下webpack是要找到对应的文件的,所以这里对应的文件名就是通过这个属性来配置:
module.exports = {
resolve: {
mainFiles: ['index'],
},
};
默认情况下是index,所以上面resolve.extensions属性中就会出现index.xxx了,这里告诉这个属性当然是希望能尽量减少对这个属性的配置。
总结
webpack构建性能这一块一直都被诟病,所以这一块也是一个挑战,对于性能优化建议先使用性能分析工具,来分析性能瓶颈,再通过对应的手段来进行性能优化。