如何提升webpack打包速度 分析各阶段打包速度 通过 speed-measure-webpack-plugin
测量你的 webpack 构建期间各个阶段花费的时间
1 2 3 4 5 const SpeedMeasurePlugin = require ("speed-measure-webpack-plugin" );const smp = new SpeedMeasurePlugin ();module .exports = smp.wrap (prodWebpackConfig)
影响打包速度的环节 1.获取所有的依赖模块(搜索时间) 搜索所有的依赖项
2.解析所有的依赖模块(解析时间) 解析成浏览器可运行的代码
webpack 根据我们配置的 loader 解析相应的文件。
3.将所有的依赖模块打包到一个文件(压缩时间) 将所有解析完成的代码,打包到一个文件中,会有一个压缩过程(压缩文件体积,以减少加载时间,从而减少白屏时间)。
js压缩使发布编译的最后阶段,需要先将 js 代码解析成 AST 语法树,然后根据规则去分析和处理 AST,最后将 AST 还原成 JS,这个过程涉及大量计算,比较耗时。
4.二次打包(二次打包时间) 当更改项目中一个文件时,所有文件需要重新打包,但实际项目中大部分文件都没有变更,尤其是第三方库。
优化解析时间 - 开启多进程打包 运行在 Node.js 之上的 webpack 是单线程模式的,也就是说,webpack 打包只能逐个文件处理,当 webpack 需要打包大量文件时,打包时间就会比较漫长。
注:项目较小时,多进程打包反而会使打包速度变慢
thread-loader 把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker【worker pool】 池里运行,一个worker 就是一个nodeJS 进程【node.js proces】,每个单独进程处理时间上限为600ms,各个进程的数据交换也会限制在这个时间内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 module .exports = { module : { rules : [ { test : /\.js$/ , exclude : /node_modules/ , use : [ 'thread-loader' , 'babel-loader' ] }, { test : /\.s?css$/ , exclude : /node_modules/ , use : [ 'style-loader' , 'thread-loader' , { loader : 'css-loader' , options : { modules : true , localIdentName : '[name]__[local]--[hash:base64:5]' , importLoaders : 1 } }, 'postcss-loader' ] } ] } }
官方上说每个 worker 大概都要花费 600ms ,所以官方为了防止启动 worker 时的高延迟,提供了对 worker 池的优化:预热
注:预热仅在耗时的 loader 中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 const threadLoader = require ('thread-loader' );const jsWorkerPool = { workers : 2 , poolTimeout : 2000 };const cssWorkerPool = { workerParallelJobs : 2 , poolTimeout : 2000 }; threadLoader.warmup (jsWorkerPool, ['babel-loader' ]); threadLoader.warmup (cssWorkerPool, ['css-loader' , 'postcss-loader' ]);module .exports = { module : { rules : [ { test : /\.js$/ , exclude : /node_modules/ , use : [ { loader : 'thread-loader' , options : jsWorkerPool }, 'babel-loader' ] }, { test : /\.s?css$/ , exclude : /node_modules/ , use : [ 'style-loader' , { loader : 'thread-loader' , options : cssWorkerPool }, { loader : 'css-loader' , options : { modules : true , localIdentName : '[name]__[local]--[hash:base64:5]' , importLoaders : 1 } }, 'postcss-loader' ] } ] } }
缩短二次打包时间,增加初次打包时间 - 利用缓存 1.cache-loader cache-loader 和 thread-loader 一样,使用起来也很简单,以将结果缓存到磁盘里,显著提升二次构建速度。
注:保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。
1 2 3 4 5 6 7 8 9 10 11 module .exports = { module : { rules : [ { test : /\.ext$/ , use : ['cache-loader' , ...loaders], include : path.resolve ('src' ), }, ], }, };
2.HardSourceWebpackPlugin
第一次构建将花费正常的时间
第二次构建将显着加快(大概提升90%的构建速度)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const HardSourceWebpackPlugin = require ('hard-source-webpack-plugin' )const clientWebpackConfig = { plugins : [ new HardSourceWebpackPlugin ({ cacheDirectory : path.join (__dirname, './lib/.cache/hard-source/[confighash]' ), configHash : function (webpackConfig ) { return require ('node-object-hash' )({sort : false }).hash (webpackConfig); }, environmentHash : { root : process.cwd (), directories : [], files : ['package-lock.json' , 'yarn.lock' ], }, info : { mode : 'none' , level : 'debug' , }, cachePrune : { maxAge : 2 * 24 * 60 * 60 * 1000 , sizeThreshold : 50 * 1024 * 1024 }, }), new HardSourceWebpackPlugin .ExcludeModulePlugin ([ { test : /.*\.DS_Store/ } ]), ] }
3.babel缓存 缓存 js 文件。使得修改一个 js 文件,只重新构建它自己,其他 js 文件直接使用缓存。类似 HMR,HMR 基于 dev-server,生产环境不需要 dev-server。
作用:让第二次打包速度更快
配置:cacheDirectory: true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { test : /\.js$/ , exclude : /node_modules/ , loader : 'babel-loader' , options : { presets : [ [ { useBuiltIns : 'usage' , corejs : { version : 3 }, targets : { chrome : '60' , firefox : '50' } } ] ], cacheDirectory : true } },
优化压缩时间 terser-webpack-plugin terser-webpack-plugin
内部封装了 terser 库,用于处理 js 的压缩和混淆,通过 webpack plugin
的方式对代码进行处理
terser-webpack-plugin 开启多进程
1 2 3 4 5 6 7 8 9 module .exports = { optimization : { minimizer : [ new TerserPlugin ({ parallel : true , }), ], }, };
可以显著加快构建速度,因此强烈推荐开启多进程
优化搜索时间 缩小文件搜索范围 减小不必要的编译工作
webpack 打包时,会从配置的 entry 出发,解析入口文件的导入语句,再递归的解析,在遇到导入语句时,webpack 会做两件事:
1.根据导入语句寻找导入文件
例如:require(‘react’),找到 ./node_modules/react/react.js
2.根据找到的要导入文件的后缀,使用配置中的 loader 去处理文件。
例如 ES6 开发的 js 文件,需要 babel-loader 处理
优化 loader 配置 使用 Loader 时可以通过 test
、 include
、 exclude
三个配置项来命中 Loader 要应用规则的文件
优化 resolve.modules 配置 resolve.modules
用于配置 webpack 去哪些目录下寻找第三方模块,resolve.modules
的默认值是 ['node_modules']
,含义是先去当前目录下的 ./node_modules
目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules
中找,再没有就去 ../../node_modules
中找,以此类推。
优化 resolve.alias 配置 resolve.alias
配置项通过别名来把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作。
优化 resolve.extensions 配置 在导入语句没带文件后缀时,webpack 会根据 resolve.extension 自动带上后缀后去尝试询问文件是否存在,所以在配置 resolve.extensions
应尽可能注意以下几点:
resolve.extensions
列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。
优化 resolve.mainFields 配置 有一些第三方模块会针对不同环境提供几分代码。 例如分别提供采用 ES5 和 ES6 的2份代码,这2份代码的位置写在 package.json
文件里,如下:
1 2 3 4 { "jsnext:main" : "es/index.js" , "main" : "lib/index.js" }
webpack 会根据 mainFields
的配置去决定优先采用那份代码,mainFields
默认如下:
1 mainFields: ['browser' , 'main' ]
webpack 会按照数组里的顺序去 package.json
文件里寻找,只会使用找到的第一个。
假如你想优先采用 ES6 的那份代码,可以这样配置:
1 mainFields: ['jsnext:main' , 'browser' , 'main' ]
优化 module.noParse 配置 module.noParse
配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。 原因是一些库,例如 jQuery 、ChartJS, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。
详细配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 module .exports = { module : { noParse : /jquery/ , rules : [ { test : /\.(js|jsx)$/ , use : ['babel-loader?cacheDirectory' ], exclude : /node_modules/ , }, ] }, resolve : { modules : [ path.resolve (`${project} /client/components` ), path.resolve ('h5_commonr/components' ), 'node_modules' ], extensions : ['.js' , '.jsx' , '.react.js' , '.css' , '.json' ], alias : { '@compontents' : path.resolve (`${project} /compontents` ), } }, };
参考 https://juejin.cn/post/6844904071736852487