背景:
项目采用vue cli3搭建,集成前端组件以及地图效果,导致项目打包后资源包文件特别大,打包速度慢,首屏渲染耗时长,甚至出现左右界面图表数据不渲染的问题。
优化前准备:
首先我们需要先排查影响性能、导致打包资源文件过大的原因,以及代码的使用率
npm install webpack-bundle-analyzer
// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
chainWebpack: config => {
/* 添加分析工具 */
if (process.env.NODE_ENV === 'production') {
if (process.env.npm_config_report) {
config
.plugin('webpack-bundle-analyzer')
// eslint-disable-next-line global-require
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
.end();
config.plugins.delete('prefetch');
}
}
},
};
通过命令 npm run build --report 会在打包完成后本地启动一个服务,自动打开127.0.0.1:8888网页,可以查看打包后各个依赖包占用的资源大小,我们可以针对各个依赖包的相关大小作出体积的优化,如果开源库过大可以考虑按需引入,不要全部引入,如果是自己公司封装的私有组件库、类库,可以排查哪部分占用文件过大,打包进行优化处理,如下图:
这里拿home路由举例,博主每个页面都是分为左、右、中上、中下四个模块的,所以每个路由中都有四个组件,当然,路由懒加载写法是一样的,通过箭头函数返回一个组件,webpackChunkName就是最早的打包后的文件名,同一路由可以写同一个名字,推荐写路由名,方便我们知道是哪个路由下的。
const MainLeft = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainLeft.vue');
const MainRight = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainRight.vue');
const MainCenterTop = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainCenterTop.vue');
const MainCenterBottom = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainCenterBottom.vue');
而在router.js里就是正常写法:
routes: [
{
path: '/',
name: 'Home',
title: '首页',
components: {
routerLeft: Home.MainLeft,
routerRight: Home.MainRight,
routerCenterTop: Home.MainCenterTop,
// routerCenterBottom: Home.MainCenterBottom,
},
},
]
开启路由懒加载后,vue cli3项目还需要在vue.config.js文件中配置如下
// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件(避免会预先加载模块/路由)
config.plugins.delete('prefetch');
},
};
(注:之前博主遇到个小坑,项目中有个文件夹,里面写了一些全局封装并且全局注册的组件供其他页面使用,而有些组件中使用了require去动态加载图片导致打包后路由懒加载不生效没有生成对应路由的文件,大家留意一下即可,如果遇到此问题,可以找博主提供解决思路)
npm install image-webpack-loader
// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
chainWebpack: config => {
// 开启图片压缩
// config.module
// .rule('images')
// .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
// .use('image-webpack-loader')
// .loader('image-webpack-loader')
// .options({ bypassOnDebug: true });
},
};
module.exports = {
productionSourceMap: true,
}
// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
configureWebpack: {
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 20000,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like @ symbols
return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
},
};
上述代码会把所有的包括异步请求的模块分割,针对大于20000k的文件,最终命名方式是npm.依赖包.js文件,比如npm.echarts.js,命名可以自定义。
完整的vue.config.js配置:
const fs = require('fs');
const CompressionPlugin = require('compression-webpack-plugin');
let devServer = {};
if (fs.existsSync('./dev-config.js')) {
// eslint-disable-next-line global-require
devServer = require('./dev-config');
} else {
console.error('!!!please create the dev-config.js file from dev-config-template.js');
}
module.exports = {
lintOnSave: true,
productionSourceMap: true,
chainWebpack: config => {
// 移除 prefetch 插件(避免会预先加载模块/路由)
config.plugins.delete('prefetch');
// Loader
config.module
.rule('svg')
.test(/\.(swf|ttf|eot|svg|woff(2))(\?[a-z0-9]+)?$/)
.use('file-loader')
.loader('file-loader')
.end();
// 开启图片压缩
// config.module
// .rule('images')
// .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
// .use('image-webpack-loader')
// .loader('image-webpack-loader')
// .options({ bypassOnDebug: true });
/* 添加分析工具 */
if (process.env.NODE_ENV === 'production') {
if (process.env.npm_config_report) {
config
.plugin('webpack-bundle-analyzer')
// eslint-disable-next-line global-require
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
.end();
config.plugins.delete('prefetch');
}
}
},
devServer,
configureWebpack: {
resolve: {
alias: {
src: '@',
components: '@/components',
views: '@/views',
},
},
plugins: [
new CompressionPlugin({
algorithm: 'gzip', // 使用gzip压缩
test: /\.js$|\.html$|\.css$/, // 匹配文件名
filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz)
minRatio: 1, // 压缩率小于1才会压缩
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
}),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 20000,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like @ symbols
return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
},
};