记一次真实的webpack优化经历
前言
公司目前现有的一款产品是使用vue v2.0
框架实现的,配套的打包工具为webpack v3.0
。整个项目大概有80
多个vue
文件,也算不上什么大型项目。
只不过每次头疼的就是打包所耗费的时间平均在一分钟
左右,而且打包后有几个文件显示为【big】
,也就是文件体积过大。
最近就想着捣鼓一下,看能不能在此前的基础上做一些优化,顺带记录下来分享给大家。
webpack打包优化
关于webpack
的打包优化一般会从两个方面考虑:缩短打包时长
和降低打包后的文件体积
,这两个方面也刚好是前面我需要解决的问题。
所以我们先来了解一下这两个方面各自有什么具体的实现方式。
缩短打包时长
我们都知道webpack
的运行流程就像一条生产线一样,在这条生产线上会按顺序的执行每一个流程。那很显然如果每一个流程要干的事情越少或者每一个流程有多个人来共同完成,那webpack
打包的执行效率就会提高。
1.减少loader搜索文件范围
我们可以通过配置loader
的exclude
选项,告诉对应的loader
可以忽略某个目录
;或者通过配置loader
的include
选项,告诉loader
只需要处理指定的目录
。因为loader
处理的文件越少,执行速度就会更快。
一般比较常见的就是给babel-loader
配置exclude
选项。
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/ // exclude的值是一个正则
}
]
}
}
以上配置即告诉babel-loader
在转化JS
代码的时候忽略node_modules
目录,这么配置是因为我们引用node_modules
下的包基本都是编译过的,所以不需要在通过babel-loader
处理。
2.利用缓存
关于webpack
的缓存,官方的大致解释为:开启缓存以后,webpack
构建将尝试从缓存中读取数据,以避免每次运行时都需要运行代价高昂的重新编译过程。
那如何在编译代码时开启缓存呢?
◕ cacheDirectory
第一种是配置babel-loader
的cacheDirectory
选项,针对babel-loader
开启缓存。
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
exclude: /node_modules/
}
]
}
}
◕ cache-loader
第二种是利用cache-loader
。
首先需要对其进行安装:npm install cache-loader --save-dev
;接着在webpack
中进行配置:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: [
'cache-loader',
'babel-loader'
]
exclude: /node_modules/
},
{
test: /\.ext$/,
use: [
'cache-loader',
// 其他的loader
// ...
],
}
]
}
}
对于cache-loader
官方给出的使用建议为:在一些性能开销较大的loader之前添加此loader,以将结果缓存到磁盘里;保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的loader使用此 loader
。
可以简单粗暴的认为如果一个
loader
在执行过程中处理的任务较多,较为耗时,即判定此loader
性能开销较大。我们就可以尝试给该loader
开启缓存,当然如果开启缓存以后实际的打包时长
并没有降低,则说明开启缓存对该loader
的性能影响不大。
更多有关
cache-loader
的内容可以查看:https://www.webpackjs.com/loaders/cache-loader/
◕ hard-source-webpack-plugin
第三种是开启缓存的方式是使用hard-source-webpack-plugin
。它是一个webpack
插件,安装命令为:npm install --save-dev hard-source-webpack-plugin
;最基础的配置如下:
// webpack.config.js
// 引入
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// 只在生产环境下开启HardSourceWebpackPlugin
if (process.env.NODE_ENV === "production") {
module.exports.plugins = (module.exports.plugins || []).concat([
new HardSourceWebpackPlugin()
])
}
更多有关
hard-source-webpack-plugin
的用法可以查看:https://github.com/mzgoddard/hard-source-webpack-plugin
以上三种开启缓存
的方式虽然各不相同,但只要做了配置就可以在我们的磁盘中看到它们的缓存结果。
3.多线程
多线程也就是将一件事情交给多个人去做,从理论上来讲是可以提高一件事情的完成效率。
◕ happyhack
我们都知道受限于node
的单线程
模式,webpack
的整个运行
和构建
过程也是单线程
模式的。
所以第一种开启多线程
的方式就是将webpack
中loader
的执行过程从单线程
扩展到多线程
。这种方式的具体实现依赖的是HappyPack
插件。
使用happypack
的第一步依然是安装:npm install --save-dev happypack
;最简单的配置如下:
// webpack.config.js
// 引入
const HappyPack = require('happypack');
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
// 使用loader调起happypack
loader: 'happypack/loader',
exclude: /node_modules/
}
]
}
}
// 只有在生产环境下配置对应的happypack
if (process.env.NODE_ENV === "production") {
module.exports.plugins = (module.exports.plugins || []).concat([
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
})
])
}
这样的配置表示匹配到的.js
源代码将被传递给HappyPack
,HappyPack
将使用loaders
指定的加载器(本例中是babel-loader
)并行地转换它们。
这种最基础的配置,默认是3
个线程并行处理。同时我们也可以通过配置thread
选项,自定义线程个数。
// webpack.config.js
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 自定义线程个数
threads: 2,
})
关于线程
的设置,官方推荐使用共享线程池
的方式来控制线程个数
:However, if you're using more than one HappyPack plugin it can be more optimal to create a thread pool yourself and then configure the plugins to share that pool, minimizing the idle time of threads within it.(但是,如果您使用多个HappyPack插件,那么最好自己创建一个线程池,然后配置这些插件来共享该池,从而最大限度地减少其中线程的空闲时间。)
线程池
的创建也很简单:
// webpack.config.js
const happyThreadPool = HappyPack.ThreadPool({ size: 4 });
除了可以通过上面的这种方式创建具体的线程数,也可以根据CPU
的核数创建:
// webpack.config.js
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length-1 });
线程池
创建好了以后,通过threadPool
进行指定共享线程池
:
// webpack.config.js
// 此处省略一些代码
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 使用共享线程池
threadPool: happyThreadPool
})
最后一个实用的配置项是verbose
选项,可以让happypack
输出执行日志:
// webpack.config.js
// 此处省略一些代码
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 使用共享线程池
threadPool: happyThreadPool,
// 输出执行日志
verbose: true
})
更多有关
HappyPack
的内容的可以查看:https://github.com/amireh/happypack
不过这里很遗憾的是该插件的作者在github
上面宣布他本人不会在对该项目进行更新维护了。
◕ thread-loader
thread-loader
和happypack
类似,也是通过扩展loader
的处理线程来降低打包时间。安装命令:npm install thread-loader --save-dev
;最简单的配置如下:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: [
'thread-loader',
'babel-loader'
]
exclude: /node_modules/
}
]
}
}
即将thread-loader
配置到其他的loader
之前即可。
更多有关
thread-loader
的内容可以查看:https://www.webpackjs.com/loaders/thread-loader
◕ webpack-parallel-uglify-plugin
一般我们为了减少打包后的文件体积,会对文件进行压缩,比如删除换行
、删除中注释
等。那常见的就是对JS
进行压缩,最基本的就是使用webpack
官方提供的uglifyjs-webpack-plugin
插件;不过该插件是单线程压缩代码,效率相对来说比较低。而webpack-parallel-uglify-plugin
就是一款多线程
压缩js
代码的插件;
安装命令:npm install webpack-parallel-uglify-plugin
;简单的配置如下:
// webpack.config.js
// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 只在生产环境中配置ParallelUglifyPlugin
if (process.env.NODE_ENV === "production") {
new ParallelUglifyPlugin({
workerCount: 4,//开启几个子进程去并发的执行压缩。默认是当前运行电脑的cPU核数减去1
cacheDir: './cache/',
uglifyJs:{
output:{
beautify:false,//不需要格式化
comments:false,//不保留注释
},
compress:{
warnings:false,// 在Uglify]s除没有用到的代码时不输出警告
drop_console:true,//删除所有的console语句,可以兼容ie浏览器
collapse_vars:true,//内嵌定义了但是只用到一次的变量
reduce_vars:true,//取出出现多次但是没有定义成变量去引用的静态值
}
}
}),
}
之后在进行打包,可以显著提升JS
代码的压缩效率。
这个插件的使用一定要注意
版本
的问题,如果配置以后在构建代码时出现问题,可以尝试更换低版本。
本次我的webpack
版本为v3.6
,直接安装的webpack-parallel-uglify-plugin
版本为v2.0.0
。后面打包出现错误,于是将其版本降低为0.4.2
后就可以正常打包。
关于多线程
我们特别需要注意,并不是线程数量
越多构建时间就越短。因为子线程处理完成后需要将把结果发送到主进程
中,主进程
在进行汇总处理,这个过程也是需要耗费时间的。所以最适合的线程数量可以尝试通过实践去获得。
4.动态链接库
一般我们在打包一个vue
项目时,会将vue
、vue-router
、axios
等这些插件的代码跟我们的代码打包到一个文件中,而这些插件
的代码除非版本有变化,否则代码内容基本不会发生变化。所以每次在打包项目时,实际上都在重复打包这些插件的代码,很显然浪费了很多时间。
关于动态链接库
这个词实际上借用的是操作系统中的动态链接库
概念,webpack
的具体实现也就是把前面我们描述的那些插件
分别打包成一个独立的文件。当有模块需要引用该插件时会通过生成的json
文件链接到对应的插件。这样不管是我们在开发环境
还是在生成环境
下的打包构建,都不需要在对这些插件做重复的处理。那接下来我们看看动态链接库
的配置和使用。
首先我们需要新建一个webpack.dll.config.js
,该文件本身是一个webpack
配置文件,主要用于分离第三方插件。
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const DllPlugin = require('webpack/lib/DllPlugin');
// 分离出来的第三方库文件存放的目录
const dllPath = "webpackDLL";
module.exports = {
// 入口文件 入口处配置需要分离的第三方插件
entry: {
echarts: ['echarts'], // 该配置表示分离echarts插件
},
// 输出文件
output: {
path: path.join(__dirname, dllPath), // 分离出来的第三方插件保存位置
filename: "[name]-dll.[hash:8].js", // 分离出来的第三方插件文件名称
library: '_dll_[name]' // 第三方插件的名称,后续其他模块需要引用该插件,便用该名称进行引用
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
}
},
plugins: [
// 清除之前的dll文件
new CleanWebpackPlugin(),
// 使用DLLPlugin进行分离
new webpack.DllPlugin({
// 生成的 *.manfest.json 文件的路径
path: path.join(__dirname, dllPath, "[name]-manifest.json"),
// 这里的name需要和前面output.library配置一致
// 之后生成的*.manfest.json 中有一个name字段,值就是我们这里配置的值
name: '_dll_[name]',
})
]
};
关于上面各个配置项
的含义已在注释中说明。接下来我们先用这个配置项做一个打包,看一下结果。在这之前我们需要在package.json
中新增一个script
脚本。
// package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --hot --port 4500 --host 192.168.1.10",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"dll": "webpack -p --progress --config ./webpack.dll.config.js"
}
}
新增的dll
脚本会使用webpack.dll.config.js
作为配置文件执行打包任务。
脚本执行成功以后,本地已经生成了对应的目录和文件。
其中echarts.dll.2a6026f8.js
就是我们分离出来的echarts
插件,echarts-manifest.json
就是前面我们说的json
文件。
第三方库文件分离后,当有模块需要引用echarts
时,该如何引用到对应的echarts.dll.2a6026f8.js
文件呢?
此时就需要DllReferencePlugin
出场了:通过配置DllReferencePlugin
的manifest
文件来把依赖的模块名称映射到对应的插件。这一步需要在webpack.config.js
中进行配置:
// webpack.config.js
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
entry: {},
output: {},
module: {},
plugin: [
new DllReferencePlugin({
// manifest 就是之前打包出来的 *.manifest.json 文件
manifest: path.join(__dirname, 'webpackDll', 'echarts-manifest.json'),
}),
]
}
以上配置完成后,如果我们处于开发环境
,执行npm run dev
打开浏览器
会发现页面无法正常显示,且控制台有报错信息:
这里是因为我们还需要在入口模板文件index.html
中手动引入分离出来的插件
文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<!-- 手动引入 -->
<script type="text/javascript" src="./webpackDLL/echarts-dll.2a6026f9.js"></script>
</body>
</html>
之后在刷新页面就没有问题了。
这里需要特别注意,开发环境
中手动引入对应插件的路径为./webpackDLL/*.2a6026f9.js
,此时如果对项目进行打包部署,打包后index.html
引用的依然是./webpackDLL/*.2a6026f9.js
,很显然单是在本地环境中该资源的引用路径就是错误的;更甚之项目打包后的输出路径
一般都会单独配置,比如dist
目录,部署时也只会部署该目录下的文件。
所以仅仅是前面的配置,项目部署以后根本无法正常运行。
解决这个问题很显然有一个简单粗暴的方式:index.html
中引入的路径依然不变,打包后的代码依然在dist
目录下,只是打包完成后手动将对应的webpackDLL
插件目录以及文件复制到dist
目录下,这样直接将dist
目录部署到服务器即可正常运行。
除了这种方式之外,我们完全可以借助webpack
的一些插件来完成这个功能,这里就不演示了,大家可以自己尝试去完成。
降低打包后的文件体积
1.压缩文件
◕ image-webpack-loader
关于图片的压缩可以选择image-webpack-loader
。正常情况下安装命令为:npm install image-webpack-loader --save-dev
,只不过我在使用该命令安装时出现了很多错误,在网上收集到一些解决方案,最终发现将npm
换成cnpm
去安装image-webpack-loader
才能安装成功:cnpm install image-webpack-loader --save-dev
。
关于image-webpack-loader
的最基础的配置如下:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: [resolve("src/icons")],
use: [
{
loader: "url-loader",
options: {
limit: 1024*10,
name: path.posix.join("assets", "images/[name].[hash:7].[ext]"),
},
},
{
loader: 'image-webpack-loader',// 压缩图片
options: {
bypassOnDebug: true,
}
}
]
}
]
}
}
更多有关
image-webpack-loader
的内容请查看:https://www.npmjs.com/package/image-webpack-loader
cache-loader4.1.0
要求webpack4.0.0
cache-loader 3.0.1
要求3.0.1
◕ webpack-parallel-uglify-plugin
该插件用于压缩JS
代码(多线程压缩),用法前面已经介绍过,这里就不在介绍了。
通过压缩文件来减少文件的体积的同时会导致
webpack
打包时长增加,因为这相当于在做一件事的过程中增加了一些步骤。
2. 抽离第三方库
CommonsChunkPlugin
是webpack
官方提供的一个插件,通过配置这个插件,可以将公共的模块抽离出来。
webpack v4
已经不再支持该插件,使用SplitChunksPlugin
代替。但由于本项目使用的是webpack v3
,因此这里不对SplitChunksPlugin
做介绍。
首先我们需要在webpack
的entry
选项对我们需要分离的公共模块
进行配置。
module.exports = {
entry: {
main: ["./src/main.js"], //原有的入口文件
vender: ['echarts'] // 表示将echarts模块分离出来
},
}
接着需要在plugin
中配置这个公共模块
的输出:
module.exports = {
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: 'vender', // 对应entry中配置的入口vender
filename: '[name].js' // 分离出来的模块以name选项作为文件名,也就是vender.js
})
]
}
配置完成后对项目进行打包,就会看到打包结果中多出一个名为vendor.js
的文件。
此时我们可以去查看有使用过echarts
的组件被打包后的js
文件体积明显减少。
如果我们还需要继续分离其他的一些公共模块
,可以在entry
中继续配置:
module.exports = {
entry: {
main: ["./src/main.js"], //原有的入口文件
vender: ['echarts', 'vue', 'other-lib']
},
}
如果前面配置的plugin
的保持不变,则entry.vendor
配置的公共模块统一会打包到vendor.js
文件中;那如果配置的公共模块
过多,就会导致抽离出来的vendor.js
文件体积过大。
解决这个问题可以使用前面我们介绍过的
动态链接库
对第三方插件进行分离,后面实践部分会提到。
3.删除无用代码
一个产品在迭代的过程中不可避免的会产生一些废弃代码
,或者我们在使用一个前端组件库时,只使用了组件库中的一小部分组件,而打包时会将整个组件库的内容进行打包。那不管是废弃代码
或者未使用到的组件代码
都可以称之为无用的代码
,那很显然删除这些无用的代码也可以减少打包后的文件体积。
◕ purgecss-webpack-plugin
PurgeCSS
是一个用来删除未使用的CSS代码
的工具。首先对其进行安装:npm install purgecss-webpack-plugin -save-dev
。
该插件使用是需要和
mini-css-extract-plugin
插件结合使用,因此还需要安装mini-css-extract-plugin
。不过特别需要注意mini-css-extract-plugin
要求webpack v4
。
接着在webpack
配置文件中进行配置:
// webpack.config.js
const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
}
更多有关
purgecss-webpack-plugin
的内容可以查看:https://www.purgecss.cn/
。
◕ tree-shaking
在webpack的官网中,对tree-shaking
的解释如下:
官方文档有说明在webpack v4
可以通过sideEffects
来实现,同时给我们演示了一些很基础的示例。
关于这个优化方案
,不管是在一些相关概念的理解还是项目的实践中均没有达到我想要的效果,所以在这里仅仅把这个优化点梳理在这里。关于该优化方案在项目中的具体配置和效果就不在演示了,以免误导大家。
最后关于tree-shaking
的一些知识,看到了一些解释的较为详细的文章,贴到这里供大家参考:
1. 【你的Tree-Shaking并没什么卵用】(https://segmentfault.com/a/1190000012794598)
2. 【Tree-Shaking性能优化实践 - 原理篇 】(https://juejin.cn/post/6844903544756109319)
打包分析工具
那除了前面我们介绍的具体的优化方案
之外,还有两个常用的打包分析工具
可以帮助我们分析构建过程
和打包后的文件体积
。
1.speed-measure-webpack-plugin
speed-measure-webpack-plugin
它是一个webpack
插件,用于测量打包的速度,并输出打包过程中每一个loader
和plugin
的执行时间。
首先是对其进行安装:npm install speed-measure-webpack-plugin
;接着在webpack.config.js
中进行配置:
// webpack.config.js
// 引入
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
// 创建实例
const smw = new SpeedMeasureWebpackPlugin();
// 调用实例的smw.wrap并将webpack的配置作为参数传入
module.exports = smw.wrap({
entry: {},
output: {}
module: {},
plugins:[],
})
完成以上步骤以后,我们在执行npm run build
,就能看到该插件输出的打包时长信息。
从这些信息里面我们能很清楚看到每一个plugin
和loader
所花费的时长,这样我们就可以针对耗费时间较长的部分进行优化。
2.webpack-bundle-analyzer
webpack-bundle-analyzer
也是一个webpack
插件,用于创建一个交互式的树形图
可视化所有打包后的文件
,包括文件的体积
和文件里面包含的内容
。
它的使用也非常简单,首先是安装:npm install webpack-budle-analyzer
;安装完成后,只需要在webpack
的配置文件中写入如下内容:
// webpack.config.js
// 引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
之后我们运行npm run build
,打包结束以后webpack
会输出如下日志。
接着会默认弹出浏览器窗口,打开http://127.0.0.1:8888/
。
若没有自动打开,可以手动输入地址。同时需要注意的是该插件默认启动在
8888
端口上,假如出现端口占用情况,可以对默认的端口进行配置,详情可参考:https://www.npmjs.com/package/webpack-bundle-analyzer
。
从页面中我们可以清楚的看到每一个文件的大小
,同时还可以看到该文件中引入了那些模块
、每一个模块
的文件大小
。根据这些内容,我们就可以有针对性的处理一些大文件
和这些大文件
中一些体积较大的模块
。
总结
到此我们已经列举了很多具体的webpack
优化方案和每一种优化方案
的简单配置。接下来我们会将这些方案应用到实际的项目中,在实践开始之前我们先对前面的内容简单做一个回顾和总结。
实践开始
此刻已经是万事具备,只差实践了。上面的优化方案在实际的项目中效果如何,一起来看看吧。
缩短打包时长
首先我们利用speed-measure-webpack-plugin
对整个项目做一个打包时长分析。
这图虽然内容不全,但是重要的部分已经给大家展示出来了。通过这个耗时分析工具输出的日志信息,我们能很清晰的看到整个打包耗时50
秒,其中UglifyJsPlugin
就执行了长达了33
秒的时间,其他相对比较耗时的就是各种loader
的执行。
关于
缩短打包时长
,后一项的优化都是在前面一项优化基础上进行的,所以整体打包时间会不断缩短。
1.使用webpack-parallel-uglify-plugin代替uglifyjs-webpack-plugin
根据前面的分析我们急需优化的第一个点就是使用webpack-parallel-uglify-plugin
代替uglifyjs-webpack-plugin
插件,将js
代码的压缩变成多线程。
将js
代码扩展成多线程压缩以后,在进行打包。
这个效果真的算是非常明显了,整体的打包时间由50秒 -> 36秒
;JS
代码的压缩也由33秒 -> 15秒
,几乎节省了50%
的时间。
2.开启缓存
接下来的一个优化点就是开启缓存
,前面介绍了三种
开启缓存的方式,只不过在本项目的实际应用中,只有hard-source-webpack-plugin
这种方式效果比较明显,其余两种几乎没有效果。
配置了hard-source-webpack-plugin
以后,第一次打包所耗费的时长基本不会发生变化,还是上一步我们优化后的30s
。
它的作用会在发生在下一次打包时。
配置hard-source-webpack-plugin
后第一次打包时长没有发生变化是因为此时还没有缓存文件
,第一次打包完成后才会生成缓存文件
;之后第二次在进行打包,直接读取缓存文件
,整体时间明显缩短;而且通过第二次打包的时长分析结果可以看到已经没有loader
的耗时分析,也说明了本次打包是直接从缓存中读取的结果。
上面测试的第二次打包
是在第一次的打包基础之上且并且没有改动代码。那实际开发时,我们大多数都是对代码做了修改了然后再次打包,那这种情况下缓存对打包时长的影响又是如何呢?我们来试一试便知。
在此我随意修改了两个.vue
文件,分别给其增加了一行代码,然后在进行打包。
文件修改以后,对应的缓存文件
就会失效,因此我们看到对应loader
重新执行,整体的打包时间也有所增加,不过总体来说,该插件是可以有效缩短打包时长的。
3.开启多线程
前面我们说过多线程是通过将webpack
中loader
的执行过程从单线程
扩展到多线程
。因为前面我们开启了缓存,loader
的执行时间已经非常之短,所以在开启缓存
的基础上在开启多线程
基本是没有什么效果的,事实证明也是如此。
因此在这一步我将缓存关掉,使用happypack
分别对babel-loader
和css-loader
开启了多线程,但是最终打包时长并没有太大变化,还是维持在30s
。
开启多线程
这个优化方案在本项目中并没有很明显的效果,可能源于项目本身loader
处理时间就不长。即使开启了多线程,线程
之间的通信以及线程
最后的汇总耗时和单线程处理耗时是一样的。
4.动态链接库
本次我用DLLPlugin
将echarts
和element
这两个组件进行了分离。
// webpack.dll.config.js
module.exports = {
// 入口文件
entry: {
echarts: ['echarts'],
element: ['element-ui']
},
// 其余代码省略
}
最后在进行打包,打包时长明显降低。
最后关于DLL
的配置在实践时,发现有两点特别需要注意:
第一个就是webpack.dll.config.js
中的resolve
配置项,其实在刚开始的时候,参照网上的一些配置对element-ui
这个插件进行了分离,最后对整个项目进行打包部署后发现element-ui
组件的table
无法渲染。
经过一番搜索,发现很多人在element-ui
的github
上提了很多相关的issue
,说自己使用DLLPlugin
分离了element-ui
以后表格不渲染、tooltip
控件失效。不过官方基本上都说不是element-ui
本身的问题并且将issue
至为closed
。
最后翻了翻这些issue
,按照其中的一个办法添加了resolve
配置后发现问题得以解决。
第二点需要注意其实在前面已经说过了,就是我们需要在index.html
入口模板中手动引入分离出来的第三方插件
,同时生产环境
下还需要将分离出来的插件代码
复制到webpack
打包输出目录
下,项目部署后才能正常运行。
5.总结
到此,关于缩短打包时长这方面的优化基本完成了,我们总共尝试了4
种方案,最终将打包时长由最初的50s -> 6s
,可见效果还是非常明显的。
降低打包后的文件体积
在优化之前我们依然是使用webpack-bundle-analyzer
对打包后的文件体积
进行分析。
这里我挑出来两个具有代表性的结果截图给大家,一个是入口文件main.js
,里面引入的体积较大的模块
是element-ui
的核心文件element-ui.common.js
和vue
核心文件vue.esm.js
;另一个是total.js
,该模块是引入了体积较大的echarts
文件。
1.压缩文件
前面我们介绍了对js
和images
进行压缩可以减少文件体积,在本项目中已经配置了webpack-parallel-uglify-plugin
对js
代码进行压缩,所以这里我们仅仅尝试对image
图片进行压缩。
配置image-webpack-loader
以后,再次打包会很惊奇的发现并不是所有的图片体积都会减少,有些图片的体积反正变大了。
对于该异常结果并没有在深入研究,所以暂时判定该项优化方案对本项目无效。
2.抽离第三方库
根据前面的分析,如果对应的文件体积减少,最直接的方式就是将vue
、echarts
、element-ui
这些些体积较大的第三方库用CommonsChunkPlugin
抽离出来。
分离出来以后,main.js
和total.js
的文件体积明显下降:main.js
由1.5MB -> 755kB
;total.js
从819kB->29kB
。
但是分离出来的vendor.js
体积达到了1.56MB
。
3.动态链接库
动态链接库
在前面实际上归类到了缩短打包时长
,但实际上它除了能有效的缩短打包时长,还可以将第三方库分离到不同的文件,同时也解决了CommonsChunkPlugin
出现的问题。
这次我们使用DLLPlugin
将vue
、echarts
、element
这个三个插件进行分离。
// webpack.dll.config.js
module.exports = {
// 入口文件
entry: {
echarts: ['echarts'],
element: ['element-ui'],
vue: ["vue"],
},
// 其余代码省略
}
分离出来的三个插件:
之后在进行打包,main.js
的大小从1.5MB
降低到800kB
,其余引用到echarts
插件的文件体积也由原来的几百kB
降低到十几kB
。
总结
到此,本次关于webpack
的打包优化实践就完成了,整体的打包时间是大大降低;对一些体积较大的文件进行了分离,也有效降低了文件的大小;但是也有一些优化方案在本项目中没有很明显的效果,甚至有些适得其反
,至于原因当下也没有仔细去研究。
本篇文章介绍的一些优化方案
可能并不全,而且大都适用于webpack v3
,wekpack v4
在很多时候已经默认开启一些优化方案,所以大家理性参考。后期有机会的话会尝试将项目的webpack
版本进行升级,到时候在来总结分享。
同时,如果是真实的项目优化,所有的优化方案不能只关注打包时长
是否降低或者文件体积
是否减小,每一个优化方案
实践完成以后还需要在开发环境
和生成环境
中对项目进行简单测试,如果项目运行正常才能说明此项优化方案是成功的。比如前面我们实践的DLL
优化方案,配置完成以后如果只关注打包时间
和文件体积
可能会沾沾自喜,但实则将项目部署到服务器以后发现项目根本无法运行。
最后,若对本篇文章有疑问或者发现错误之处,还望指出,共同进步。
近期文章
JavaScript的执行上下文,真没你想的那么难
骨架屏(page-skeleton-webpack-plugin)初探
文末
如果这篇文章有帮助到你,❤️关注+点赞+收藏+评论+转发❤️鼓励一下作者
文章公众号首发
,关注 不知名宝藏女孩
第一时间获取最新的文章
笔芯❤️~