背景
公司项目的体量较大,每次serve需要1分钟左右,build需要3分多钟,这是在电脑资源空闲时的速度,如果浏览器开了10几个标签啥的,更慢了。每次改点东西打包发测试环境都很难受。
项目技术栈
// package.json
{
"dependencies": {
"vue": "^2.6.10",
"vuex": "^3.1.2",
"vue-router": "^3.1.3",
"core-js": "^3.4.3",
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-vuex": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"vue-template-compiler": "^2.6.10"
}
}
项目性能分析
打包耗时分析
// vue.config.js
module.exports = {
chainWebpack: (config) => {
// 分析 webpack 的总打包耗时以及每个 plugin 和 loader 的打包耗时
config.plugin('speed-measure')
.use(require('speed-measure-webpack-plugin'))
}
}
打包后文件体积分析
// vue.config.js
module.exports = {
chainWebpack: (config) => {
// 体积分析
config.plugin('bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [{analyzerPort: 8880}])
}
}
优化方向
查看项目的webpack配置
因为vue脚手架隐藏了默认的webpack配置文件,让你在vue.config.js
中通过打补丁的方式修改配置。
优化的前提,必须要知道原来的配置和你修改后的配置是否生效,这就必须要把webpack配置文件的导出来
idea终端或者CMD终端中运行命令,会生成最终的webpack配置文件
开发环境:npx vue-cli-service inspect --mode development >> webpack.config.development.js
生产环境:npx vue-cli-service inspect --mode production >> webpack.config.production.js
在产生的 js 文件开头,添加:module.exports =
,然后格式化即可查看。
DLL缓存
// vue.config.js
// dll缓存的依赖包
const DllConfig = require('./webpack.dll.config')
// 动态插入资源到html中
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
module.exports = {
configureWebpack: (config) => {
// 缓存依赖包
config.plugins = config.plugins.concat([
...Object.keys(DllConfig.entry).map(key => (
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/${key}-manifest.json`)
})
)),
// 将 dll 注入到 生成的 html 模板中
new AddAssetHtmlPlugin({
// dll文件位置
filepath: require("path").resolve("./public/vendor/*.js"),
// dll 引用路径
publicPath: "./vendor",
// dll最终输出的目录
outputPath: "./vendor",
}),
]);
}
}
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// dll文件存放的目录
const dllPath = "public/vendor";
module.exports = {
entry: {
// 需要提取的库文件
vueMain: ['vue', 'vue-router', 'vuex'],
vuePlugin: ['vue-print-nb', 'vuedraggable', 'vue-i18n'],
element: ['element-ui'],
dll: [
"file-saver",
"html2canvas",
"jszip",
"qrcodejs2",
"socket.io-client",
"viewerjs",
"cropperjs",
"laravel-echo",
"nprogress",
"tinymce"
],
},
output: {
path: path.join(__dirname, dllPath),
filename: "[name].dll.js",
// vendor.dll.js中暴露出的全局变量名
// 保持与 webpack.DllPlugin 中名称一致
library: "[name]_[hash]",
},
plugins: [
// 清除之前的dll文件
new CleanWebpackPlugin(),
// 设置环境变量
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production"),
},
}),
// manifest.json 描述动态链接库包含了哪些内容
new webpack.DllPlugin({
path: path.join(__dirname, dllPath, "[name]-manifest.json"),
// 保持与 output.library 中名称一致
name: "[name]_[hash]",
context: process.cwd(),
}),
],
};
// package.json
{
"scripts": {
"dll": "webpack --progress --config ./webpack.dll.config.js",
},
}
DLL是把一些静态资源包提前打包好,放到public/vendor目录下,打包时跳过它们,直接在index.html中的script标签引用
缺点
有了splitChunks后,这个就不怎么好用了,DLL会把资源全量打包然后插入html,不能按需加载,splitChunks可以把重复使用的资源打包到一个chunk里,然后引用它的地址,做到按需导入和避免资源重复打包
最后
打包速度并没有提升多少,所以PASS
splitChunks分包
// vue.config.js
module.exports = {
chainWebpack: (config) => {
// 拆包
config.optimization.splitChunks({
chunks: "all",
minSize: 20000, // 允许新拆出 chunk 的最小体积,也是异步 chunk 公共模块的强制拆分体积
cacheGroups: {
libs: { // 第三方库
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial", // 只打包初始时依赖的第三方
},
elementUI: { // elementUI 单独拆包
name: "chunk-elementUI",
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 20 // 权重要大于 libs
},
components: { // 组件包
name: `chunk-components`,
test: /[\\/]components[\\/]/,
minChunks: 2,
priority: 1,
reuseExistingChunk: true
},
commons: { // 公共模块包
name: `chunk-commons`,
minChunks: 2,
priority: 0,
reuseExistingChunk: true
},
}
})
}
}
拆包的目的是,分解体积很大的包,复用重复打包的文件,拆分不常改变的模块,更好的复用webpack的文件缓存,避免文件hash受到影响而使缓存失效
缺点
每次打包都要分析文件依赖,执行拆包的操作
自己一顿操作分包,还没webpack默认配置分的均匀,下面是我的分包和webpack默认的分包对比
最后
分包这个还是挑项目的,如果你的项目有什么特殊情况默认配置不能满足的话,或许有点用,但是大部分都不需要另外去配的,webpack团队想的肯定比我们要充分。
这个对打包速度也没有提升,PASS
图片压缩
// vue.config.js
module.exports = {
chainWebpack: (config) => {
// 图片压缩
config.module.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
disable: process.env.NODE_ENV === 'development', // 开发模式下调试速度更快
})
.end()
}
缺点
- 因为另外引入loader处理,所以打包起来会更慢
- 后面开发基本都会把图片放包OSS上,或者用iconfont,不会在本地加图片,所以压缩意义不大
最后
对打包速度没提升,PASS
CDN加载文件
// vue.config.js
const cdn = {
// 开发环境
dev: {
css: [],
js: [],
},
// 生产环境
build: {
css: [
// jsmind
"./themes/jsmind.css"
// element
// "http://xx.css"
],
js: [
// 采用oss
// element-ui@2.13.2.js
// "http://xx.js",
// echarts@4.6.0.min.js
"//xx.js",
// axios@0.19.0.min.js
"//xx.js",
// aliyun-oss-sdk-6.3.1.min.js
"//xx.js",
// xlsx
"//xx.js",
// xlsx-style@0.8.13.core.min.js
"//xx.js",
// jsmind
"//xx.js",
// gojs
"//xx.js",
],
},
};
module.exports = {
chainWebpack: (config) => {
// 注入cdn
config.plugin("html").tap((args) => {
args[0].cdn = cdn.build;
return args;
});
},
configureWebpack: (config) => {
// cdn预加载使用
// 告诉 webpack 这些依赖是外部环境提供的,在打包时可以忽略它们,就不会再打到 chunk-vendors.js 中
// e.g. <script src="http://lib.baomitu.com/echarts/5.3.2/echarts.min.js"></script>
config.externals = {
"echarts": "echarts",
"xlsx-style": "XLSX",
"ali-oss": "OSS",
"jsmind": "jsMind",
"gojs": "go",
"axios": "axios",
"xlsx": "ODS"
};
}
}
这里是原来就有的,我不知道为什么一些上CDN,一些又不上,免得出问题背锅,还是不动的好
最后
PASS
happypack多线程处理
// vue.config.js
const HappyPack = require("happypack");
const os = require("os");
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); // 开辟一个线程池,拿到系统CPU的核数,happypack 将利用所有线程编译
module.exports = {
chainWebpack: (config) => {
// todo plugin的名字要唯一,不然后面同名的会覆盖
config.plugin('happypack-babel').use(HappyPack, [{
id: 'babel',
loaders: ['babel-loader'],
threadPool: happyThreadPool
}])
config.plugin('happypack-styles').use(HappyPack, [{
id: 'styles',
loaders: [{
loader: 'style-resources-loader',
options: {
patterns: [
'./public/themes/default/commom/commonFunc.less',
'./public/themes/global.less'
]
}
}],
threadPool: happyThreadPool
}])
const jsRule = config.module.rule('js')
jsRule.uses.delete('thread-loader')
jsRule.uses.delete('babel-loader')
jsRule.use('happypack').loader('happypack/loader?id=babel')
const oneOfsMap = config.module.rule("less").oneOfs.store;
oneOfsMap.forEach(item => {
item.uses.delete('style-resources-loader')
item.use("happypack").loader('happypack/loader?id=styles')
})
}
}
最后
重复打包,发现没什么效果,不知道是不是我的姿势不对,PASS
thread-loader 多线程处理
// vue.config.js
module.exports = {
chainWebpack: (config) => {
let originUse = config.module.rule('images').toConfig().use
let newLoader = { loader: 'thread-loader' }
originUse.splice(0, 0, newLoader)
config.module.rule('images').uses.clear()
config.module.rule('images').merge({ use: originUse })
}
}
package.json.lock
,vue/cli已经内置了thread-loader,
webpack.config.production.js
,thread-loader默认被用来处理babel-loader
最后
webpack官网上的【thread-loader】支持的node版本是 >= 16.10.0
,但是我们项目统一的node版本是 14.16.0
,虽然用起来没报错,但好像也没什么效果,PASS
Gzip压缩
// vue.config.js
module.exports = {
configureWebpack: (config) => {
// gzip打包压缩
// todo nginx需要开启 gzip_static,才能返回前端打包好的.gz文件,不然默认在服务器压缩
config.plugins.push(
new CompressionWebpackPlugin({
filename: "[path][base].gz", // 压缩后的文件名
algorithm: "gzip",
test: /\.(js|css)(\?.*)?$/i, // 需要压缩的文件正则
threshold: 10240, // 对10K以上的数据进行压缩
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
// 是否删除原文件(.js),只保留压缩文件(.gz)
// 删除原文件后,打包上线 Uncaught SyntaxError: Unexpected token < 【参考 https://stackoverflow.com/questions/54082652/webpack-gzip-bundle-uncaught-syntaxerror-unexpected-token】
deleteOriginalAssets: false,
})
);
}
}
打包时生成
.gz
结尾的文件,nginx服务器开启gzip压缩后,如果生产包上有同名的.gz
结尾的文件,则直接返回前端压缩好的文件,不然用服务器资源来进行动态压缩生成.gz
文件后返回给浏览器。
目的:减少用户请求资源的时间
最后
服务器只配置了gzip: on
,没有配置gzip_static
,所以服务器总是动态压缩,没有用前端打包好的文件,再者说前端压缩还要拖慢我打包的速度,PASS
hard-source-webpack-plugin
// vue.config.js
module.exports = {
chainWebpack: (config) => {
// 重复构建,使用缓存,加快构建
config.plugin('hard-source')
.use(require('hard-source-webpack-plugin'));
}
}
这个插件第一次
build
时会在node_modules/.cache
下面建缓存,后面重复打包可以读缓存,减少构建时间
最后
重复构建能从3分多到1分多,提升还是挺大的,但是改点东西再打包,又会久点,因为要重新建缓存,但都比原来快,【√
】
升级脚手架
当大部分配置都不能有效提升时,直接升脚手架吧,版本的提升带来的效益是巨大的,毕竟人家是专业的
【vue/cli4 升级 vue/cli5】
cli5内置了webpack5,带来了文件【持久缓存】,解析文件时生成缓存,后续没有改变的文件直接读取快照,不重复执行编译,打包速度从3分多到20多秒,提升巨大
最后
面向未来才是关键,新东西就是好用,旧项目也能焕发第二春