之前讲的是构建性能,这里是细粒度更高的性能优化,主要是还是体现在构建速度提升上,之前主要是对标生产环境,这里主要是对标开发环境。
使用最新版
使用最新版的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
构建性能这一块一直都被诟病,所以这一块也是一个挑战,对于性能优化建议先使用性能分析工具,来分析性能瓶颈,再通过对应的手段来进行性能优化。