浅谈 webpack 性能优化(内附巨详细 webpack 学习
前言
笔者最近在整理关于 webpack
相关的知识点,一方面是因为自己掌握的知识点比较零碎、不够系统,有时候碰到问题不知从何下手,另外一方面 webpack5.0
已经在路上了,这的确是一个让人头秃的消息。
所以这就促使了我去系统性的回顾了一遍 webpack4.0
的所有知识点,包括 webpack
的由来,各种配置的使用、性能优化、Webpack
的底层原理、相关脚手架的配置分析,都回顾了一波,大致目录如下图:
笔者把系列的文章都扔在了这个仓库:webpack 学习整理文档,有兴趣的同学可以去看一波。
今天这篇文章也是笔者就学习文档中的性能优化这一块内容做的整理与回顾。
文章中使用到的案例代码链接放在了最底部,大家自取。
为什么要优化
先来说说为什么要优化?当然如果你的项目很小,构建很快,其实不需要特别关注性能方面的问题。
但是随着项目涉及到的页面越来越多,功能和业务代码也会越来越多,相应的 webpack
的构建时间也会越来越久,这个时候我们就不得不考虑性能优化的事情了。
因为这个构建时间与我们的日常开发是密切相关,当我们本地开发启动 devServer
或者 build
的时候,如果时间过长,会大大降低我们的工作效率。
试想一个场景,我们突然碰到一个紧急 bug
,项目启动需要花费 3/4
分钟,改完后项目 build
上线也要 3/4
分钟,这个时候脑瓜是不是 duang
、duang
、duang
...
那接下来我们看一下如何优化 webpack
的性能,提升 webpack
的构建速度。
分析工具
在动手优化之前,我们需要有一个量化的指标,得知道影响构建时间的问题究竟出在哪里,是某个 chunk
文件太大了,还是哪一个 loader
或者 plugin
耗时太久了等等。
我们可以对通过一些工具对项目进行相应的 体积 和 速度 分析, 然后对症下药。
体积分析
初级分析
可以通过官方提供的 stat.json
文件帮助我们分析打包结果,stat.json
文件可以通过下面语句快速生成:
webpack --profile --json > stats.json
接着我们通过官网提供的 stats.json 分析工具 进行分析,上传 stats.json
文件之后,就可以得到如下图所示分析结果:
其中包括 webpack
的版本、打包时间、打包过程的 hash
值、模块数量( modules
)、chunk
数量、打包生层的静态文件 assets
以及打包的警告和错误数。
我们可以分析其提供的内容,进行大致问题的定位。
第三方工具
webpack-bundle-analyzer 是打包分析神器,它的界面个人觉得很好看,而且能很直观的给出每一个打包出来的文件的大小以及各自的依赖,能够更加方便的帮助我们对项目进行分析。
image使用如下:
// config/webpack.common.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const commonConfig = {
// ...
plugins: [
new BundleAnalyzerPlugin({
analyzerPort: 8889, // 指定端口号
openAnalyzer: false,
}),
]
// ...
}
webpack-bundle-analyzer
其底层也是依赖stat.json
文件的,通过对stat.json
的分析,得出最后的分析页面
通过分析工具的分析,我们可以知道哪些文件耗时比较多,打包出来的体积比较大,从而对有问题的文件进行优化。
速度分析
我们可以通过 speed-measure-webpack-plugin 这个插件帮助我们分析整个打包的总耗时,以及每一个loader
和每一个 plugins
构建所耗费的时间,从而帮助我们快速定位到可以优化 Webpack
的配置。
如上图,耗时比较长的会以红色标出。
使用
引入此插件,创建一个 plugins
实例 smp
包裹 webpack
配置文件即可,我们修改一下 webpack
的公共配置文件 webpack.common.js
:
// config/webpack.common.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// ...
module.exports = (production) => {
if (production) {
const endProdConfig = merge(commonConfig, prodConfig);
return smp.wrap(endProdConfig);
} else {
const endDevConfig = merge(commonConfig, devConfig);
return smp.wrap(endDevConfig);
}
};
笔者文章演示的代码配置文件分为三个,分别为 开发环境配置文件,生产环境配置文件,以及前二者共用的公共配置文件,如下:
webpack.dev.js
:开发环境使用的配置文件webpack.prod.js
:生产环境使用的配置文件webpack.common.js
:公共配置文件
执行打包之后,可以看到如下效果图:
image注意:
speed-measure-webpack-plugin
对于webpack
的升级还不够完善,暂时还无法与你自己编写的挂载在html-webpack-plugin
提供的hooks
上的自定义Plugin
(add-asset-html-webpack-plugin
就是此类)共存,有人已经在 github 上提了 issue 了,但是貌似还是没有解决。
优化策略
经过相应的体积分析和速度分析之后,我们便可以着手进行优化了。
使用新版本
这个是 webpack
性能优化的万能膏药,升级版本必定能带来性能提升,而且提升很明显。
我们可以看一张对比图:
image image从上图中我们可以看到,
webpack4.0
的构建速度远远快于webpack3.0
,官方也说升级之后,升级版本之后,构建时间可以降低60% - 98%
左右。
在每一个版本的更新,webpack
内部肯定会做很多优化,而 webpack
是依赖 Node
的 js
运行环境,升级他们对应的版本,webpack
的速度肯定也能够获得提升。
说不定在
webpack5.0
出来之后,我们今天讲到的大部分性能优化方法都会被集成到webpack
自身中去,我们只需要通过几个简单的配置就能完成性能配置。
同时新版本的包管理工具(Npm
、Yarn
)也可以更快的帮助我们分析一些包的依赖和引入,从而提高打包速度。
webpack4.0 带来的优化
-
v8
引擎带来的优化(for of
替代forEach
、Map
和Set
替代Object
、includes
替代indexOf
) - 默认使用更快的
md4 hash
算法 -
webpack AST
可以直接从loader
传递给AST
,减少解析时间 - 使用字符串方法替代正则表达式
我们可以在 github
上的 webpack
库的 releases 版本迭代 页面中查看其带来的性能优化:
一个 v8
性能优化例子:
我们可以来看一个例子,比较使用 includes
替代 indexOf
之后带来的速度提升,创建 compare-includes-indexof.js
文件,在这个文件中建一个 10000000
长度的数组,记录两个函数分别消耗的时间:
const ARR_SIZE = 10000000;
const hugeArr = new Array(ARR_SIZE).fill(1);
// includes
const includesTest = () => {
const arrCopy = [];
console.time('includes')
let i = 0;
while (i < hugeArr.length) {
arrCopy.includes(i++);
}
console.timeEnd('includes');
}
// indexOf
const indexOfTest = () => {
const arrCopy = [];
console.time('indexOf');
for (let item of hugeArr) {
arrCopy.indexOf(item);
}
console.timeEnd('indexOf');
}
includesTest();
indexOfTest();
可以发现 includes
的速度远远快于 indexOf
:
-
includes
:12.224ms
-
indexOf
:147.638ms
所以在项目上尽可能使用比较新的 webpack
、Node
、Npm
、Yarn
版本,是我们提升打包速度的第一步。
体积优化
webpack
是个项目打包工具,一般项目打完包以后,需要发布到服务器上供用户使用,为了用户体验,我们的项目体积需要越小越好,所以 webpack
中打包的体积是 webpack
中重要的一环。
js 压缩
webpack4.0
默认在生产环境的时候是支持代码压缩的,即 mode=production
模式下。
实际上 webpack4.0
默认是使用 terser-webpack-plugin 这个压缩插件,在此之前是使用 uglifyjs-webpack-plugin,两者的区别是后者对 ES6 的压缩不是很好,同时我们可以开启 parallel
参数,使用多进程压缩,加快压缩。
// config/webpack.common.js
const TerserPlugin = require('terser-webpack-plugin');
// ...
const commonConfig = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: 4, // 开启几个进程来处理压缩,默认是 os.cpus().length - 1
}),
],
},
// ...
}
CSS 压缩
压缩 CSS
我们可以借助 optimize-css-assets-webpack-plugin
插件来压缩 css
,其默认使用的压缩引擎是 cssnano
。 具体使用如下:
// config/webpack.prod.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
// ...
const prodConfig = {
// ...
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true,
})
]
},
}
擦除无用的 CSS
使用 PurgeCSS
来完成对无用 css
的擦除,它需要和 mini-css-extract-plugin
配合使用。
// config/webpack.common.js
const PurgecssPlugin = require('purgecss-webpack-plugin');
// ...
const PATHS = {
src: path.join(__dirname, './src')
};
const commonConfig = {
// ...
plugins: [
// ...
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
// ...
}
在未使用此插件之前,比如我们只用到了 navcontact
这个类,其他的都没有用到,我们在未引入之前打包一下,发现未用到的 css
还是会被打包进去:
引入插件后,重新进行打包,发现没有用到的 css
都被擦除了:
更多使用大家可参考 PurgeCSS 文档。
图片压缩
一般来说在打包之后,一些图片文件的大小是远远要比 js
或者 css
文件要来的大,所以我们首先要做的就是对于图片的优化,我们可以手动的去通过线上的图片压缩工具,如 tiny png 帮我们来压缩图片。
但是这个比较繁琐,在项目中我们希望能够更加自动化一点,自动帮我们做好图片压缩,这个时候我们就可以借助 image-webpack-loader 帮助我们来实现。它是基于 imagemin 这个 Node 库来实现图片压缩的。
使用很简单,我们只要在 file-loader
之后加入 image-webpack-loader
即可:
// config/webpack.common.js
// ...
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
}
},
{
loader: 'image-webpack-loader',
options: {
// 压缩 jpeg 的配置
mozjpeg: {
progressive: true,
quality: 65
},
// 使用 imagemin**-optipng 压缩 png,enable: false 为关闭
optipng: {
enabled: false,
},
// 使用 imagemin-pngquant 压缩 png
pngquant: {
quality: '65-90',
speed: 4
},
// 压缩 gif 的配置
gifsicle: {
interlaced: false,
},
// 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
webp: {
quality: 75
}
}
}
]
},
]
}
// ...
我们先不使用这个 loader
打包一下,图片大小是 2.1MB
:
使用 image-webpack-loader
之后,图片大小是 666KB
:
压缩的效果还是很明显的。
拆分代码
有时候我们写的某些模块根本没有使用,但是还是被打包了,这样实际上会拖累 webpack
的打包速度,而且也会增加打包文件的体积,所以我们可以使用 tree-shaking
将这些代码剔除掉。
或者也可以使用 splitChunksPlugin
把一个大的文件分割成几个小的文件,这样也可以有效的提升 webpack
的打包速度,详细的配置介绍大家可以看笔者写的 配置 SplitChunksPlugin,里面详细介绍了怎么配置 splitChunks
,以及各参数的用法与意义,在这里就不展开讲了。
速度优化
讲完打包体积的优化,我们来看一下在速度方面的优化。
分离两套配置
一般来说在项目开发中,我们会区分开发和生产环境两套配置,各司其职。
在开发阶段:我们需要 webpack-dev-server
来帮我们进行快速的开发,同时需要 HMR 热更新 帮我们进行页面的无刷新改动,而这些在 生产环境 中都是不需要的。
在生产阶段:我们需要进行 代码压缩、目录清理、计算 hash、提取 CSS 等等;
实现起来很简单,我们前面也提到过,就新建三个 webpack
的配置文件就行:
-
webpack.dev.js
:开发环境的配置文件 -
webpack.prod.js
:生产环境的配置文件 -
webpack.common.js
:公共配置文件
通过 webpack-merge
来整合两个配置文件共同的配置 webpack.common.js
,具体可以参照源码。
减少查找过程
对 webpack
的 resolve
参数进行合理配置,使用 resolve
字段告诉 webpack
怎么去搜索文件。
合理使用 resolve.extensions
在导入语句没带文件后缀时,webpack
会自动带上后缀后去尝试询问文件是否存在,查询的顺序是按照我们配置 的 resolve.extensions
顺序从前到后查找,webpack
默认支持的后缀是 js
与 json
。
举个🌰:如果我们配置 resolve.extensions= ['js', 'json']
,那么 webpack
会先找 xxx.js
如果没有则再查找 xxx.json
,所以我们应该把常用到的文件后缀写在前面,或者 我们导入模块时,尽量带上文件后缀名。
虽然
extensions
会优先查找数组内的值,但是我们不要一股脑儿的把所有后缀都往里面塞,这会调用多次文件的查找,这样就会减慢打包速度。
优化 resolve.modules
这个属性告诉 webpack
解析模块时应该搜索的目录,绝对路径和相对路径都能使用。使用绝对路径之后,将只在给定目录中搜索,从而减少模块的搜索层级:
// config/webpack.common.js
// ...
const commonConfig = {
// ...
resolve: {
extensions: ['.js', '.jsx'],
mainFiles: ['index', 'list'],
alias: {
alias: path.resolve(__dirname, '../src/alias'),
},
modules: [
path.resolve(__dirname, 'node_modules'), // 指定当前目录下的 node_modules 优先查找
'node_modules', // 将默认写法放在后面
]
},
// ...
}
// ...
使用 resolve.alias
减少查找过程
alias
的意思为 别名,能把原导入路径映射成一个新的导入路径。
比如我们项目中可能会有一些相对路径的写法,就可以使用 alias
配置来减少查找过程;
还比如我们经常使用的 react
库,其实我们可以直接使用其 dist
目录下打包好的 react.min.js
,这样就能跳过耗时的模块解析,具体示例配置如下:
// config/webpack.common.js
// ...
const commonConfig = {
// ...
resolve: {
// ...
alias: {
react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
@alias: path.resolve(__dirname, '../src/alias'),
},
},
// ...
}
// ...
这个笔者在实际项目中没有尝试过,不过也是一个思路,大家有机会可以尝试一波。
缩小构建目标
排除 Webpack
不需要解析的模块,即使用 loader
的时候,在尽量少的模块中去使用。
我们可以借助 include
和 exclude
这两个参数,规定 loader
只在那些模块应用和在哪些模块不应用。
我们修改公共配置文件 webpack.common.js
:
// config/webpack.common.js
// ...
const commonConfig = {
// ...
module: {
rules: [
{
test: /\.js|jsx$/,
exclude: /node_modules/,
include: path.resolve(__dirname, '../src'),
use: ['babel-loader']
},
// ...
]
},
}
// ...
首先我们不加 exclude
和 include
两个参数,打包一下 npm run build
,打包时间 3350ms
左右:
接着我们加上这两个参数,意思分别是:
-
exclude: /node_modules/
:排除node_modules
下面的文件 -
include: path.resolve(__dirname, '../src')
:只对src
下面的文件使用
重新打包一下,打包时间变成了 1400ms
左右:
利用多线程提升构建速度
由于运行在 Node.js
之上的 webpack
是单线程模型的,所以 webpack
需要处理的事情需要一件一件的做,不能多件事一起做。
如果 webpack
能同一时间处理多个任务,发挥多核 CPU
电脑的威力,那么对其打包速度的提升肯定是有很大的作用的。
HappyPack
原理:每次 webapck
解析一个模块,HappyPack
会将它及它的依赖分配给 worker
线程中。处理完成之后,再将处理好的资源返回给 HappyPack
的主进程,从而加快打包速度。
在
webpack4.0
中使用happypack
需要使用其5.0
版本。
我们将 HappyPack
引入公共配置文件,他的用法就是将相应的 loader
替换成 happypack/loader
,同时将替换的 loader
放入其插件的 loaders
选项,我们暂且替换一下 babel-loader
:
// config/webpack.common.js
// ...
const makePlugins = (configs) => {
const plugins = [
// ...
new HappyPack({
loaders: ['babel-loader']
}),
];
// ...
return plugins;
}
// ...
const commonConfig = {
entry: {
main: "./src/index.js",
entry2: "./src/entry2.js",
entry3: "./src/entry3.js",
entry4: "./src/entry4.js",
entry5: "./src/entry5.js",
entry6: "./src/entry6.js",
},
// ...
module: {
rules: [{
test: /\.jsx?$/,
// exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'),
use: [
'happypack/loader'
// 'babel-loader'
]
}]
},
// ...
}
// ...
为了让效果更加明显一点,我们在项目下多增加几个入口文件,在不使用 happypack
的情况下,进行一次打包,时间差不多是 8s
多:
开启 happypack
之后,我们可以从控制台中看到,happypack
默认帮我们开启了 3
个进程,打包时间变成了6.5s
左右:
注意:
HappyPack
的作者现在基本上也不维护这个插件了,因为作者对此项目的兴趣正在减弱。他也推荐我们使用webpack
官方 thread-loader。
更多参数大家可以参考 HappyPack 官网
thread-loader
webpack
官方推出的一个多进程方案,用来替代 HappyPack
。
原理和 HappyPack
类似,webpack
每次解析一个模块,thread-loader
会将它及它的依赖分配给 worker
线程中,从而达到多进程打包的目的。
使用很简单,直接在我们使用的 loader
之前加上 thread-loader
就行,我们需要先注释掉 HappyPack
代码:
// config/webpack.common.js
// ...
const commonConfig = {
// ...
module: {
rules: [{
test: /\.jsx?$/,
// exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'),
use: [
{
loader: 'thread-loader',
options: {
workers: 3, // 开启几个 worker 进程来处理打包,默认是 os.cpus().length - 1
}
},
'babel-loader'
]
}]
},
// ...
}
// ...
我们重新运行一下,也是差不多 6.5s
左右:
预先编译资源模块(DllPlugin)
我们在打包的时候,一般来说第三方模块是不会变化的,所以我们想只要在第一次打包的时候去打包一下第三方模块,并将第三方模块打包到一个特定的文件中,当第二次 webpack
进行打包的时候,就不需要去 node_modules
中去引入第三方模块,而是直接使用我们第一次打包的第三方模块的文件就行。
webpack.DllPlugin
就是来解决这个问题的插件,使用它可以在第一次编译打包后就生成一份不变的代码供其他模块引用,这样下一次构建的时候就可以节省开发时编译打包的时间。
添加配置文件
我们在配置文件目录 config
下新建一个 webpack.dll.js
,此文件用于将我们的第三方包文件打包到 dll
文件夹中去:
// config/webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production', // 环境
entry: {
vendors: ['lodash'], // 将 lodash 打包到 vendors.js 下
react: ['react', 'react-dom'], // 将 react 和 react-dom 打包到 react.js 下
},
output: {
filename: '[name].dll.js', // 输出的名字
path: path.resolve(__dirname, '../dll'), // 输出的文件目录
library: '[name]' // 将我们打包出来的文件以全部变量的形式暴露,可以在浏览器变量的名字进行访问
},
plugins: [
// 对生成的库文件进行分析,生成库文件与业务文件的映射关系,将结果放在 mainfest.json 文件中
new webpack.DllPlugin({
name: '[name]', // 和上面的 library 输出的名字要相同
path: path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
- 上面的
library
的意思其实就是将dll
文件以一个全局变量的形式导出出去,便于接下来引用,如下图: -
mainfest.json
文件是一个映射关系,它的作用就是帮助webpack
使用我们之前打包好的***.dll.js
文件,而不是重新再去node_modules
中去寻找。
我们在命令行中打包一下 dll
文件,可以看到根目录生成了一个 dll
文件夹,并且在下面生成了相应的文件,并且 loader
打包到了 vendor.dll.js
中,react
和 react-dom
打包到了 react.dll.js
中了:
接着我们需要去修改公共配置文件 webpack.common.js
,将我们之前生成的 dll
文件导入到 html
中去,如果我们不想自己手动向 html
文件去添加 dll
文件的时候,我们可以借助一个插件 add-asset-html-webpack-plugin
,此插件顾名思义,就是将一些文件加到 html
中去。
同时我们需要使用 webpack
自带的 DllReferencePlugin
插件对 mainfest.json
映射文件进行分析。
// config/webpack.common.js
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// ...
const commonConfig = {
// ...
plugins: [
// ...
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/react.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: require(path.resolve(__dirname, '../dll/vendors.dll.mainfest.json'))
}),
new webpack.DllReferencePlugin({
manifest: require(path.resolve(__dirname, '../dll/react.dll.mainfest.json'))
}),
],
// ...
}
// ...
这里的代码还可以优化,具体大家可以参考笔者整理的笔记中 dll优化 这一节。
我们进行一次打包,可以看到打包耗时为 1450ms
左右,同时可以看到库文件打包到的 vendors.chunk.js
为 1.22MB
。
我们注释掉对 dll
的引用分析之后,重新打包,打包耗时为 1950ms
左右,同时可以看到 vendors.chunk.js
为 5.28MB
。
缓存 Cache 相关
我们可以开启相应 loader
或者 plugin
的缓存,来提升二次构建的速度。一般我们可以通过下面几项来完成:
-
babel-loader
开启缓存 -
terser-webpack-plugin
开启缓存 - 使用
cache-loader
或者 hard-source-webpack-plugin
如果项目中有缓存的话,在 node_modules
下会有相应的 .cache
目录来存放相应的缓存。
babel-loader
首先我们开启 babel-loader
的缓存,我们修改 babel-loader
的参数,将参数 cacheDirectory
设置为 true
:
// config/webpack.common.js
// ...
module: {
rules: [
{
test: /\.jsx?$/,
// exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'),
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
},
]
},
]
}
// ...
首次打包时间为 8.5s
左右,打包完成之后,我们可以发现在 node_modules
下生成了一个 .cache
目录,里面存放了 babel
的缓存文件:
我们重新打包一次,会发现时间变成了 6s
左右:
TerserPlugin
我们通过将 TerserPlugin
中的 cache
设为 true
,就可以开启缓存:
// config/webpack.common.js
const TerserPlugin = require('terser-webpack-plugin');
// ...
const commonConfig = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: 4, // 开启几个进程来处理压缩,默认是 os.cpus().length - 1
cache: true,
}),
],
},
// ...
}
首次打包时间为 8-9s
左右,同时在 .cache
目录下生成了 terser-webpack-plugin
缓存目录:
我们重新打包一次,会发现时间变成了 5s
左右:
HardSourceWebpackPlugin
这个插件其实就是用于给模块提供一个中间的缓存。
使用如下,我们直接在插件中引入就 ok 了:
// config/webpack.common.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// ...
const plugins = [
// ...
new HardSourceWebpackPlugin(),
];
// ...
我们打包一下,可以看到在第一次打包的时候 HardSourceWebpackPlugin
就帮我们开始生成打包文件了,同时在 .cache
目录生成了 hard-source
目录,第一次打包耗时 6.6s
左右:
我们重新打包一次,会发现时间变成了 2.7s
左右:
合理使用 sourceMap
之前我们有讲过,之前我们打包生成 sourceMap
的时候,如果信息越详细,打包速度就会越慢
所以我们要在代码打包过程中的时候,在对应的环境使用对应的 sourceMap
很重要。
其他
除了上述我们提到的一些常用方法,还有其他的一些方法,比如:
- 使用
ES6 Modules
语法,以保证Tree-Shaking
起作用
因为
tree-shaking
只对ES6 Modules
静态引入生效,对于类似于CommonJs
的动态引入方式是无效的
- 合理使用
Ployfill
如果我们对于引入的
polyfill
不做处理的话,Webpack
会把所有的Polyfill
都加载进来,导致产出文件过大。推荐使用@babel/preset-env
的useBuiltIns='usage'
方案,此配置项会根据浏览器的兼容性帮助我们按需引入所需的垫片;此外我们也可以使用动态polyfill
服务,每次根据浏览器的User Agent
,下发不同的Polyfill
,具体可以参考polyfill.io
。
- 预加载资源
webpackPrefetch
使用
webpackPrefetch
来提前预加载一些资源,意思就是 将来可能需要一些模块资源,在核心代码加载完成之后带宽空闲的时候再去加载需要用到的模块代码。
-
icon
类图片使用css Sprite
来合并图片
如果
icon
类图片太多的话,就使用雪碧图合成一张图片,减少网络请求,或者使用字体文件。
html-webpack-externals-plugin
此插件可以将一些公用包提取出来使用
cdn
引入,不打入bundle
中,从而减少打包文件大小,加快打包速度。
- 合理配置
chunk
的哈希值
在生产环境打包,一定要配置文件的
hash
,这样有助于浏览器缓存我们的文件,当我们的代码文件没变化的时候,用户就只需要读取浏览器缓存的文件即可。一般来说javascript
文件使用[chunkhash]
、css
文件使用[contenthash]
、其他资源(例如图片、字体等)使用[hash]
。
更多性能优化方法笔者就不再一一列举了,因为关于 webpack
性能优化的方法实在是太多了,大家可以根据实际遇到的问题制定相关的优化方案。
小结
今天这篇文章介绍了 webpack
打包的一些优化方案,从项目体积再到对项目速度,我们提出了一些优化方案,大家可以在具体的项目中去进行实践。
当然我还要提一嘴,如果你的项目本身构建就比较快,那么你其实就不需要使用文章中提到的方法去对项目进行优化,可能效果会适得其反。
对于文章中有些一笔带过的内容,大家可以在我的 webpack 学习整理文档 找到相应的介绍。
实不相瞒,想要个赞!
image
相关链接
- webpack 学习整理文档
- webpack优化——将你的构建效率提速翻倍
- 带你深度解锁webpack系列(优化篇)
- webpack 构建性能优化策略小结
- 极客时间 玩转webpack
- 三十分钟掌握webpack性能优化
- 使用 webpack 定制前端开发环境
- webpack 官方打包工具
示例代码
示例代码可以看这里: