前言
在vue-cli3中已经将webpack等详细配置(config)去除,我们配置webpack只能在vue.config.js里进行配置,这里我个人总结了一套webpack的优化方案模板并且附有我个人的讲解(尚在研究中)。
总体优化这几个方面:
- 提升生产打包的构建速度
- 拆分每个 npm 包
- 将稳定的第三方库(体积比较大的)改用cdn引入,不进行打包
- 安装可视化打包分析器(可选)
1.提升生产打包的构建速度
首先,你要知道运行在 Node.js 之上的 Webpack 是单线程模型的,所以Webpack 需要处理的事情需要一件一件的做,不能多件事一起做。我们需要Webpack 能同一时间处理多个任务,发挥多核 CPU 电脑的威力,HappyPack 就能让 Webpack 做到这点,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
想了解happypack原理机制可以参考下面这篇文章,我主要讲解如何使用,不作过多剖析。
happypack原理详解
运行机制
首页利用npm安装happypack并在package文件的devDependencies节点写入依赖。
npm install happypack --save-dev
或
npm install happypack -D
在vue.config.js中:
const HappyPack = require('happypack')
/*
os 模块提供了一些基本的系统操作函数
os.cpus()
返回一个对象数组,包含所安装的每个 CPU/内核的信息:型号、速度(单位 MHz)、时间
(一个包含 user、nice、sys、idle 和 irq 所使用 CPU/内核毫秒数的对象)。
*/
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
configureWebpack: {
plugins: [
new HappyPack({
//用id来标识 happypack处理那里类文件 要和 rules 中指定的 id 对应起来
id: 'babel',
//如何处理 用法和loader 的配置一样
loaders: ['babel-loader?cacheDirectory=true'],
//共享进程池
threadPool: happyThreadPool
})
],
}
chainWebpack: config => {
//在这里使用loader时候后面跟的id就是happypack处理那一类文件
const jsRule = config.module.rule('js');
jsRule.uses.clear();
jsRule.use('happypack/loader?id=babel')
.loader('happypack/loader?id=babel')
.end();
}
}
这里要注意的是plugins
中的id标识符要和loader中的 ?id=babel
要一致,去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件。
HappyPack 参数
id
: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.
loaders
: Array 用法和 webpack Loader 配置中一样.
threads
: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。
verbose
: Boolean 是否允许 HappyPack 输出日志,默认是 true。
threadPool
: HappyThreadPool 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。
verboseWhenProfiling
: Boolean 开启webpack --profile ,仍然希望HappyPack产生输出。
debug
: Boolean 启用debug 用于故障排查。默认 false。
注:这上面还引用了Node的OS模块,他提供了一些基本的系统操作函数。
2.拆分每个 npm 包
当我们运行项目并且打包的时候,会发现chunk-vendors.js这个文件非常大,那是因为webpack将所有的依赖全都压缩到了这个文件里面,这时我们可以将其拆分,将所有的依赖都打包成单独的js。
这里可以利用splitChunks将每个依赖包单独打包,拆分每个npm包。
module.exports = {
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity, //最大初始化请求
minSize: 20000, // 依赖包超过20000bit将被单独打包
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// 拆分每个 npm 包
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
return `npm.${packageName.replace('@', '')}`
}
}
}
}
}
}
上面的代码会将超过20000bit的依赖包单独打包,并且加上前缀npm.,如果你觉得第三方引入的库或包实在太大,莫方,接下来我要讲的就是解决这个问题的方法。
3.改用CDN引入第三方库
这个方法推荐使用于那些版本稳定,体积较大的第三方库。
webpack中提供了externals配置用于第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,但又不影响运用第三方库的方式,例如import方式等。
module.exports = {
chainWebpack: config => {
config.externals({
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT',
})
}
}
然后你需要在你项目中的根html使用cdn引入第三方库:
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.11.1/index.js"></script>
这种方式大大减少了打包之后的项目体积并且不影响项目中这些库的使用。
关于打包之后的Map文件,可以通过productionSourceMap:boolen来舍取,Map文件占的体积非常大。
4.安装可视化打包分析器(可选)
安装:
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack:{
plugins:[
new BundleAnalyzerPlugin()
]
}
}
效果如图
5.总结
附上上面所有的配置代码,一些没提到的,代码中都有注释。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HappyPack = require('happypack')
/*
os 模块提供了一些基本的系统操作函数
os.cpus()
返回一个对象数组,包含所安装的每个 CPU/内核的信息:型号、速度(单位 MHz)、时间
(一个包含 user、nice、sys、idle 和 irq 所使用 CPU/内核毫秒数的对象)。
*/
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const UglifyPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
publicPath: '',
outputDir: "dist",
assetsDir: 'static',
lintOnSave: false,
productionSourceMap: false,
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin(),
new HappyPack({
//用id来标识 happypack处理那里类文件 要和 rules 中指定的 id 对应起来
id: 'babel',
//如何处理 用法和loader 的配置一样
loaders: ['babel-loader?cacheDirectory=true'],
//共享进程池
threadPool: happyThreadPool
})
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity, //最大初始化请求
minSize: 20000, // 依赖包超过20000bit将被单独打包
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// 拆分每个 npm 包
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
return `npm.${packageName.replace('@', '')}`
}
}
}
}
}
//关闭webpack性能提示
// performance: {
// // hints: 'warning',
// //入口起点的最大体积 整数类型(以字节为单位)
// maxEntrypointSize: 50000000,
// //生成文件的最大体积 整数类型(以字节为单位 300k)
// maxAssetSize: 30000000,
// //只给出 js 文件的性能提示
// assetFilter: function(assetFilename) {
// return assetFilename.endsWith('.js');
// }
// }
},
chainWebpack: config => {
//改用CDN引入模块
config.externals({
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT',
})
//在这里使用loader时候后面跟的id就是happypack处理那一类文件
const jsRule = config.module.rule('js');
jsRule.uses.clear();
jsRule.use('happypack/loader?id=babel')
.loader('happypack/loader?id=babel')
.end();
},
}
参考文章:
https://blog.csdn.net/u014440483/article/details/87267160
https://segmentfault.com/a/1190000021037299?utm_source=tag-newest