Webpack4.x搭配vue-cli3.x配置优化编译速度
cache 缓存
使用缓存永远都是优化的一个很常见的思想,下面列举一下在4.x版本中用到的方法,其他版本的话,额,理论上也是可以吧。。。
tip: 5.x版本可以直接使用自带的cache配置传送门
HardSourceWebpackPlugin 插件
HardSourceWebpackPlugin是webpack的插件,可为模块提供中间缓存步骤。为了查看结果,您需要使用此插件运行两次webpack:第一次构建将花费正常时间。第二个版本将明显更快。
configureWebpack:{
...
plugins: [
new HardSourceWebpackPlugin()
]
}
主要参数如下:
- cacheDirectory: 缓存的路径
- configHash: 转换webpack配置,根据不同的配置生成不同的缓存
- environmentHash:构建的依赖或者插件发送改变是使用心得缓存
- maxAge: 最大缓存时间
- sizeThreshold:最大缓存内存,超出就删除所有缓存
ps: 在构建过程中(build),HardSourceWebpackPlugin 也会对其产生作用,会降低build的时间,嗯,至于会不会对打包后的文件产生影响,暂时不知道,如果怕有问题,可以使用
process.env.NODE_ENV对环境进行判断。
chainWebpack: config => {
if(process.env.NODE_ENV === "development"){
...
config.plugin('HardSourceWebpackPlugin')
.use(HardSourceWebpackPlugin);
}else{
...
}
}
cache-loader
使用 cache-loader 可以将编译结果写入硬盘缓存,Webpack 再次构建时如果文件没有发生变化则会直接拉取缓存
官方提示:请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。
ps:vue-cli默认对vue,babel,ts 使用cache-loader,不需要手动配置
如果你想给别的loader添加cache-loader,怎么添加?其实也很简单,按照下面的步骤即可:
- 建议先在命令行输入
vue inspect > output.js查看原来的配置(vue ui也是个不错的选择) - 使用
chainWebpack对配置进行链式操作(代码如下)
// 一开始我是这么写的
chainWebpack: config =>{
...
config.module
.rule('images')
.use('cache-loader')
.loader('cache-loader')
.end();
}
// 输入 vue inspect > output.js 查看配置文件发现(代码如下)
/* config.module.rule('images') */
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'Static/img/[name].[hash:8].[ext]'
}
}
}
},
{
loader: 'cache-loader'
}
]
},
// loader的调用顺序是反方向的(从右到左,从下到上),如果不把cache-loader放到第一个位置,是没办法对图片进行缓存
// 改进后的配置如下
const imagesRule = config.module.rule('images');
imagesRule.uses.clear(); // 先把之前的loader配置清除
imagesRule.use('cache-loader')
.loader('cache-loader')
.options({
cacheDirectory: path.join(__dirname,'/node_modules/.cache/url-loader')
})
.end()
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: assetsDir+'/img/[name].[hash:8].[ext]'
}
}
})
.end()
多线程加快构建
适用于多线程打包的插件有
happypackandthread-loader。由于happypack的作者已经弃坑了,所以笔者说一下如何使用thread-loader。
提示: thread-loader自身会消耗时间来启动,可以适用于大项目里比较耗时的loader。
在开始之前,我们先来安装一个
speed-measure-webpack-plugin来打印具体的时间。有一点要注意的是,使用这个插件的话,configureWebpack 要使用对象的形式而不是函数形式。
// 使用 speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// 把配置用变量存起来
const configureWebpack = {
externals: {
vue: "Vue",
"element-ui": "ELEMENT"
},
devtool: isDev? 'source-map':'none',
...
}
... //其他配置
configureWebpack: isDev?configureWebpack:smp.wrap(configureWebpack),
// 这里配置configureWebpack,这个笔者不想在开发环境使用,所以就做了一个判断,当然你可以忽略它
好,下面我们就开始配置thread-loader。 提示:不要过于期待这玩意。
// thread-loader配置
//可以通过预热 worker 池(worker pool)来防止启动 worker 时的高延时。
threadLoader.warmup({},[
'babel-loader',
'vue-loader',
'sass-loader'
])
const babelRules = config.module.rule('js');
babelRules.uses.clear();
babelRules.use('thread-loader')
.loader('thread-loader')
.options({
// 产生的 worker 的数量,默认是 cpu 的核心数
workers: 2,
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size=1024'],
// 闲置时定时删除 worker 进程
// 默认为 500ms
// 可以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
// 池(pool)分配给 worker 的工作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升工作分布更均一
poolParallelJobs: 50,
name:'my-pool'
})
.end()
.use('cache-loader')
.loader('cache-loader')
.options({
cacheDirectory: path.join(__dirname,'/node_modules/.cache/babel-loader')
})
.end()
.use('babel-loader')
.loader('babel-loader')
.end();
const vueRules = config.module.rule('vue');
vueRules.uses.clear();
vueRules.use('thread-loader')
.loader('thread-loader')
.options({
name:'my-pool'
})
.end()
.use('cache-loader')
.loader('cache-loader')
.options({
cacheDirectory: path.join(__dirname,'/node_modules/.cache/vue-loader')
})
.end()
.use('vue-loader')
.loader('vue-loader')
.options({
compilerOptions: {
preserveWhitespace: false
},
})
.end();
经过多次测试,发现加不加thread-loader并没有什么差距(由于电脑的不同,有时候启动会慢一点)
排除静态的依赖
对于一些极少变化的第三方插件依赖,可以排除在构建过程中,以此提高构建速度。其实这个的方法和插件也不少,这里笔者主要讲externals和DllPlugin。
externals
externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
这个属性很好理解,而且使用起来也非常方便,非常的nice! 最简单的方法是配置名称,当然你也可以编写一些复杂的配置官方文档
//vue.config.js
...
configureWebpack:{
externals: {
"vue": "Vue",
"element-ui": "ELEMENT"
},
}
// 然后在 index.html 手动引入(或者用插件自动添加)
DllPlugin
这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。
可以简单理解为把一些依赖从项目的bundle中拆分出去,通过映射关系用请求来加载。
配置DllPlugin,可以分为下面几个步骤:
- 新建webpack.dll.config.js文件(其他命名都可以),配置需要拆分的插件;
- 在package.json文件中新建一条命令来专门打包,
"build:dll":"webpack --config webpack.dll.config.js"; 运行该命令; - 在vue.config.js 文件中配置
DllReferencePlugin,主要把dll引用到需要预编译的依赖; - 在index.html手动引入拆分的bundle包
// webpack.dll.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: { // 这里使用了多个入口配置,你也可以使用一个入口
vueRouter: ['vue-router'],
vuex:['vuex'],
axios:['axios'],
},
//打包放到dll,这里不跟项目的打包文件放到同一个文件夹,是因为vue-cli打包会先把输出目录先清除一遍。。。
output: {
path: path.resolve('./dll'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./dll', '[name]-manifest.json'),
name: '[name]_library'
}),
]
}
// vue.config.js
...
configureWebpack:{
plugins:[ // 由于是多个入口打包,所有这里初始化多次
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./dll/vueRouter-manifest.json")
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./dll/vuex-manifest.json")
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./dll/axios-manifest.json")
}),
}
// index.html
<script src="/dll/vueRouter.dll.js"></script>
<script src="/dll/vuex.dll.js"></script>
<script src="/dll/axios.dll.js"></script>
总结
到这来其实就已经优化的差不多了,当然还是有其他一些细节是可以优化的,但是笔者觉得已经差不多了,再优化也无法带来更高的收益了,当然,还有一个办法就是升级webpack版本比任何方法都能提高的快(狗头)。