We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
使用最新版本,带来的优化是最显著的,开发者们会经常进行性能优化。所以第一步先升级 Webpack 以及 所有构建相关 devDependencies 的版本。
保持最新的 Node.js 也能够保证性能。除此之外,保证包管理工具 (例如 npm 或者 yarn ) 为最新也能保证性能。较新的版本能够建立更高效的模块树以及提高解析速度。
必须要有一个量化指标才可以看出前后对比。
speed-measure-webpack-plugin 插件可以测量各个插件和 loader 所花费的时间,使用之后,构建时,会得到类似下面这样的信息:
speed-measure-webpack-plugin
对比前后的信息,来确定优化的效果。 speed-measure-webpack-plugin 的使用很简单,可以直接用来包裹 Webpack 的配置: webpack.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const smp = new SpeedMeasurePlugin(); const config = { //...webpack配置 } module.exports = smp.wrap(config);
上面的 speed-measure-webpack-plugin 主要是测量插件以及 loader 花费的时间(主要是 loader),其自身也是耗时间的,因此在对其分析并相应的做出优化之后,应该去除 speed-measure-webpack-plugin后根据 Webpack 每次打包给出时间作为最后的量化指标。
通过排除 node_modules 下的文件 从而缩小了 loader 加载搜索范围 高概率命中文件。为了尽可能少的让文件被 loader 处理,可以通过 include 去命中只有哪些文件需要被处理
node_modules
include
resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。resolve.modules 的默认值是 ['node modules'],含义是先去当前目录的 ./node modules 目录下去找我们想找的模块,如果没找到,就去上一级目录 ../node modules 中找,再没有就去 ../.. /node modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。当安装的第三方模块都放在项目根目录的 ./node modules 目录下时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方模块的绝对路径,以减少寻找。
resolve.modules
['node modules']
./node modules
../node modules
../.. /node modules
优化后配置:
resolve: { // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 modules: [path.resolve(__dirname,'node_modules')] },
注意:如果配置了上述的 resolve.moudles ,可能会出现问题,例如,依赖中还存在 node_modules 目录,那么就会出现,对应的文件明明在,但是却提示找不到。因此呢,不推荐配置这个。如果其他人不熟悉这个配置,遇到这个问题时,会摸不着头脑。
resolve.moudles
创建 import 或 require 的路径别名,来确保模块引入变得更简单。配置项通过别名来把原导入路径映射成一个新的导入路径 此优化方法会影响使用 Tree-Shaking 去除无效代码
当引入模块时不带文件后缀 webpack会根据此配置自动解析确定的文件后缀。
resolve: { extensions: ['.js'] }
用了 noParse 的模块将不会被 loaders 解析,所以当我们使用的库如果太大,并且其中不包含 import require define 的调用,我们就可以使用这项配置来提升性能, 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理。
import
require
define
// 忽略对jquery lodash的进行递归解析 module: { // noParse: /jquery|lodash/ // 从 webpack 3.0.0 开始 noParse: function(content) { return /jquery|lodash/.test(content) } }
在安装的第三方模块中都会有一个 package.json 文件,用于描述这个模块的属性,其中可以存在多个字段描述入口文件,原因是某些模块可以同时用于多个环境中,针对不同的运行环境需要使用不同的代码。
package.json
resolve.mainFields 的默认值和当前的 target 配置有关系,对应的关系如下。
resolve.mainFields
target
web
webworker
['browser','module','main']
['module','main']
以 target 等于 web 为例, Webpack 会先采用第三方模块中的 browser 字段去寻找模块的入口文件,如果不存在,就采用 module 字段,以此类推。
we
browser
module
为了减少搜索步骤,在明确第三方模块的入口文件描述字段时,可以将它设置得尽量少。由于大多数第三方模块都采用 main 字段去描述入口文件的位置,所以可以这样配置:
main
module.exports = { resolve: { //只采用 main 字段作为入口文件的描述字段,以减少搜索步骤 mainFields: ['main'] } }
把 thread-loader 放置在其它 loader 之前,那么放置在这个 loader 之后的 loader 就会在一个单独的 worker 池中运行。
thread-loader
loader
worker
在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
webpack
thread-loader 使用起来也非常简单,只要把 thread-loader 放置在其他 loader 之前, 那 thread-loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。
module.exports = { // ... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // 创建一个 js worker 池 use: [ 'thread-loader', 'babel-loader' ] }, { test: /\.s?css$/, exclude: /node_modules/, // 创建一个 css worker 池 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 池的优化:预热。
// ... const threadLoader = require('thread-loader'); const jsWorkerPool = { // options // 产生的 worker 的数量,默认是 (cpu 核心数 - 1) // 当 require('os').cpus() 是 undefined 时,则为 1 workers: 2, // 闲置时定时删除 worker 进程 // 默认为 500ms // 可以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在 poolTimeout: 2000 }; const cssWorkerPool = { // 一个 worker 进程中并行执行工作的数量 // 默认为 20 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' ] } // ... ] // ... } // ... }
注意: thread-loader 放在了 style-loader 之后,这是因为 thread-loader 后的 loader 没法存取文件也没法获取 webpack 的选项设置。 请仅在耗时的 loader 上使用。
注意:
style-loader
HappyPack 是让 webpack 对 loader 的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而加速代码构建。与 DLL 动态链接库结合来使用更佳。
开启缓存之后,在第一构建之后,会产生缓存文件,一般默认缓存目录:node_modules/.cache。
node_modules/.cache
将结果缓存到磁盘里,显著提升二次构建速度。
module.exports = { module: { rules: [ { test: /\.ext$/, use: ['cache-loader', ...loaders], include: path.resolve('src'), }, ], }, };
注意: 保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。 thread-loader 和 cache-loader 要一起使用的話,先放 cache-loader,接着是 thread-loader,最后才是其它的 heavy-loader,这样的順序才可以有最好的效能。 如果只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory为 true。
cache-loader
heavy-loader
babel-loader
cacheDirectory
true
HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。
HardSourceWebpackPlugin
node_modules/.cache/hard-source
配置 hard-source-webpack-plugin,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
hard-source-webpack-plugin
在一个动态链接库中可以包含其他模块调用的函数和数据,动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会被重新编译,而是直接使用动态链接库中的代码。
如何使用不记录了,因为现在已经不推荐使用 DllPlugin 了,在最新的 webpack 下带来的性能提升有限。
Webpack4 默认内置使用 terser-webpack-plugin 插件压缩优化代码,而该插件使用 terser 来缩小 JavaScript。所谓 terser,官方给出的定义是用于 ES6+ 的 JavaScript 解析器、mangler/compressor(压缩器)工具包。
terser-webpack-plugin
使用多进程并行运行来提高构建速度。并发运行的默认数量为 os.cpus().length - 1 。
os.cpus().length - 1
module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: true, }), ], }, };
可以显著加快构建速度,因此强烈推荐开启多进程。
Webpack4 已废弃,不推荐 Webpack4 使用。
通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
mini-css-extract-plugin
css-loader
cssnano
减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。
结合stats.json分析打包结果(bundle analyze)
基础包分离:
html-webpack-externals-plugin
打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码 purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)。
构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法
变量提升,可以减少一些变量声明。在生产环境下,默认开启。
另外,大家测试的时候注意一下,speed-measure-webpack-plugin 和 HotModuleReplacementPlugin 不能同时使用,否则会报错。
HotModuleReplacementPlugin
在不配置 @babel/plugin-transform-runtime 时,babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被注入(inject)到需要它的每个文件中。但是这样的结果就是导致构建出来的JS体积变大。
@babel/plugin-transform-runtime
babel
_createClass
inject
我们也并不需要在每个 js 中注入辅助函数,因此我们可以使用 @babel/plugin-transform-runtime,@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件。
js
Babel
因此我们可以在 .babelrc 中增加 @babel/plugin-transform-runtime 的配置。
.babelrc
{ "presets": [], "plugins": [ [ "@babel/plugin-transform-runtime" ] ] }
使用 webpack 的监听模式。不要使用其他工具来监听你的文件和调用 webpack 。在监听模式下构建会记录时间戳并将信息传递给编译让缓存失效。
在某些设置中,监听会回退到轮询模式。有许多监听文件会导致 CPU 大量负载。在这些情况下,你可以使用 watchOptions.poll 来增加轮询的间隔。
watchOptions.poll
需要注意的是不同的 devtool 的设置,会导致不同的性能差异。
devtool
"eval"
cheap-source-map
eval-source-map
=> 在大多数情况下,cheap-module-eval-source-map 是最好的选择。
cheap-module-eval-source-map
某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用。例如,在开发时使用 UglifyJsPlugin 来压缩和修改代码是没有意义的。以下这些工具在开发中通常被排除在外:
UglifyJsPlugin
ExtractTextPlugin
[hash]
[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
以下几个实用工具通过在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
The text was updated successfully, but these errors were encountered:
No branches or pull requests
保持最新的版本
使用最新版本,带来的优化是最显著的,开发者们会经常进行性能优化。所以第一步先升级 Webpack 以及 所有构建相关 devDependencies 的版本。
保持最新的 Node.js 也能够保证性能。除此之外,保证包管理工具 (例如 npm 或者 yarn ) 为最新也能保证性能。较新的版本能够建立更高效的模块树以及提高解析速度。
量化
speed-measure-webpack-plugin
speed-measure-webpack-plugin
插件可以测量各个插件和 loader 所花费的时间,使用之后,构建时,会得到类似下面这样的信息:对比前后的信息,来确定优化的效果。
speed-measure-webpack-plugin
的使用很简单,可以直接用来包裹 Webpack 的配置:webpack.config.js
Webpack 自带时间统计
上面的
speed-measure-webpack-plugin
主要是测量插件以及 loader 花费的时间(主要是 loader),其自身也是耗时间的,因此在对其分析并相应的做出优化之后,应该去除speed-measure-webpack-plugin
后根据 Webpack 每次打包给出时间作为最后的量化指标。缩小文件搜索范围(更快命中)
优化 loader 配置
通过排除
node_modules
下的文件 从而缩小了 loader 加载搜索范围 高概率命中文件。为了尽可能少的让文件被 loader 处理,可以通过include
去命中只有哪些文件需要被处理优化 resolve.modules 配置
resolve.modules
用于配置 Webpack 去哪些目录下寻找第三方模块。resolve.modules 的默认值是['node modules']
,含义是先去当前目录的./node modules
目录下去找我们想找的模块,如果没找到,就去上一级目录../node modules
中找,再没有就去../.. /node modules
中找,以此类推,这和 Node.js 的模块寻找机制很相似。当安装的第三方模块都放在项目根目录的./node modules
目录下时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方模块的绝对路径,以减少寻找。优化后配置:
优化 resolve.alias 配置
创建 import 或 require 的路径别名,来确保模块引入变得更简单。配置项通过别名来把原导入路径映射成一个新的导入路径 此优化方法会影响使用 Tree-Shaking 去除无效代码
优化 resolve.extensions 配置
当引入模块时不带文件后缀 webpack会根据此配置自动解析确定的文件后缀。
优化 module.noParse 配置
用了 noParse 的模块将不会被 loaders 解析,所以当我们使用的库如果太大,并且其中不包含
import
require
define
的调用,我们就可以使用这项配置来提升性能, 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理。优化 resolve.mainFields 配置
在安装的第三方模块中都会有一个
package.json
文件,用于描述这个模块的属性,其中可以存在多个字段描述入口文件,原因是某些模块可以同时用于多个环境中,针对不同的运行环境需要使用不同的代码。resolve.mainFields
的默认值和当前的target
配置有关系,对应的关系如下。target
为web
或者webworker
时,值是['browser','module','main']
。target
为其他情况时,值是['module','main']
。以
target
等于we
b 为例, Webpack 会先采用第三方模块中的browser
字段去寻找模块的入口文件,如果不存在,就采用module
字段,以此类推。为了减少搜索步骤,在明确第三方模块的入口文件描述字段时,可以将它设置得尽量少。由于大多数第三方模块都采用
main
字段去描述入口文件的位置,所以可以这样配置:优化解析时间(开启多进程打包)
thread-loader
把
thread-loader
放置在其它loader
之前,那么放置在这个loader
之后的loader
就会在一个单独的worker
池中运行。在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
loader
不能产生新的文件。loader
不能使用定制的loader
API(也就是说,通过插件)。loader
无法获取webpack
的选项设置。thread-loader
使用起来也非常简单,只要把thread-loader
放置在其他 loader 之前, 那thread-loader
之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。官方上说每个 worker 大概都要花费 600ms ,所以官方为了防止启动 worker 时的高延迟,提供了对 worker 池的优化:预热。
HappyPack(已不维护,不推荐 Webpack4 使用)
HappyPack 是让 webpack 对 loader 的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而加速代码构建。与 DLL 动态链接库结合来使用更佳。
合理利用缓存(缩短连续构建时间,增加初始构建时间)
开启缓存之后,在第一构建之后,会产生缓存文件,一般默认缓存目录:
node_modules/.cache
。cache-loader
将结果缓存到磁盘里,显著提升二次构建速度。
HardSourceWebpackPlugin
HardSourceWebpackPlugin
为模块提供中间缓存,缓存默认的存放路径是:node_modules/.cache/hard-source
。配置
hard-source-webpack-plugin
,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。DllPlugin
在一个动态链接库中可以包含其他模块调用的函数和数据,动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会被重新编译,而是直接使用动态链接库中的代码。
如何使用不记录了,因为现在已经不推荐使用 DllPlugin 了,在最新的 webpack 下带来的性能提升有限。
TerserPlugin
Webpack4 默认内置使用
terser-webpack-plugin
插件压缩优化代码,而该插件使用 terser 来缩小 JavaScript。所谓 terser,官方给出的定义是用于 ES6+ 的 JavaScript 解析器、mangler/compressor(压缩器)工具包。使用多进程并行运行来提高构建速度。并发运行的默认数量为
os.cpus().length - 1
。可以显著加快构建速度,因此强烈推荐开启多进程。
ParallelUglifyPlugin()
Webpack4 已废弃,不推荐 Webpack4 使用。
CSS 代码的压缩
通过
mini-css-extract-plugin
提取 Chunk 中的 CSS 代码到单独文件,通过css-loader
的 minimize 选项开启cssnano
压缩 CSS。控制包文件大小
减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。
结合stats.json分析打包结果(bundle analyze)
提取页面公共资源
基础包分离:
html-webpack-externals-plugin
,将基础包通过 CDN 引入,不打入 bundle 中tree-shaking
打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码
purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)。
scope-hosting
构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法
变量提升,可以减少一些变量声明。在生产环境下,默认开启。
另外,大家测试的时候注意一下,
speed-measure-webpack-plugin
和HotModuleReplacementPlugin
不能同时使用,否则会报错。babel 配置优化
在不配置
@babel/plugin-transform-runtime
时,babel
会使用很小的辅助函数来实现类似_createClass
等公共方法。默认情况下,它将被注入(inject
)到需要它的每个文件中。但是这样的结果就是导致构建出来的JS体积变大。我们也并不需要在每个
js
中注入辅助函数,因此我们可以使用@babel/plugin-transform-runtime
,@babel/plugin-transform-runtime
是一个可以重复使用Babel
注入的帮助程序,以节省代码大小的插件。因此我们可以在
.babelrc
中增加@babel/plugin-transform-runtime
的配置。图片压缩
开发环境的优化
增量编译
使用 webpack 的监听模式。不要使用其他工具来监听你的文件和调用 webpack 。在监听模式下构建会记录时间戳并将信息传递给编译让缓存失效。
在某些设置中,监听会回退到轮询模式。有许多监听文件会导致 CPU 大量负载。在这些情况下,你可以使用
watchOptions.poll
来增加轮询的间隔。Devtool
需要注意的是不同的
devtool
的设置,会导致不同的性能差异。"eval"
具有最好的性能,但并不能帮助你转译代码。cheap-source-map
选项来提高性能eval-source-map
配置进行增量编译。=> 在大多数情况下,
cheap-module-eval-source-map
是最好的选择。避免在生产环境下才会用到的工具
某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用。例如,在开发时使用
UglifyJsPlugin
来压缩和修改代码是没有意义的。以下这些工具在开发中通常被排除在外:UglifyJsPlugin
ExtractTextPlugin
[hash]
/[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
在内存中编译
以下几个实用工具通过在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
参考文档
The text was updated successfully, but these errors were encountered: