前言
我第一次从搭建项目环境,到打包准备上线测试,vue对开发者来说学习成本不高,带来了很多的便利。没有经过如何优化,直接打包出来,打开页面时就看到无尽的加载中。。。
啥???是网络不好了???
等了半分钟时间页面终于出现了。我第一次打包,所以百度做功课,原来是vue打包出来文件体积太大,而且会影响到首屏加载。
那么,如何做到优化?
分析打包结果
借助webpack-bundle-analyzer插件,可以看到打包后每个文件的内容的大小。
第一步:安装webpack-bundle-analyzer
npm install webpack-bundle-analyzer –save-dev
第二步:在vue.config.js文件中引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
第三步:在webpack配置中使用该插件
module.exports = {
configureWebpack: config => {
config.plugins.push(
new BundleAnalyzerPlugin()
)
},
}
第四步:运行
npm run build
自动打开浏览器显示一个项目打包分析图,便于直观各个文件的大小
可以看到最大vendors文件达到了2.37M,下面开始瘦身。
一、关闭生产环境的sourceMap
vue项目打包之后js文件夹中,会自动生成一些map文件,是方便于运行时报错可以准确的输出是哪一行哪一列有错,但map文件占用一部分空间。
关闭生产环境,如下配置:
module.exports = {
// 生产环境是否生成 sourceMap 文件
productionSourceMap: false,
}
关闭生产环境对比,减少了很大体积
前:dist大小为13.9MB
后:dist大小为3.44MB
二、路由懒加载
在router.js文件中,原来的静态引用方式
import Echarts from '../views/Echarts.vue'
const routes = [
{
path: "/echarts",
name: "Echarts",
component: Echarts,
},
];
改为
const routes = [
{
path: "/echarts",
name: "Echarts",
component: () => import('../views/Echarts.vue')
},
];
如果没有应用懒加载,打包会非常大,影响首屏页面加载,加载的内容过多,时间过长,会出长时间的白屏。而能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
三、ant-design组件按需加载
首屏需要加载的依赖包,其中ant-design整整占了1.03MB。
首先安装babel-plugin-import
npm install babel-plugin-import –save-dev
然后在babel.config.js文件配置
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[ "import", {
"libraryName": "ant-design-vue",
"libraryDirectory": "es",
"style": "css"
} ]
]
};
然后在main.js中引入需要的部分组件
import { Button } from 'ant-design-vue';
const app = createApp(App);
app.use(Button).mount('#app');
注意:有一些组件的引入方式有所不同,具体的参照ant-design官网介绍。
import { message } from 'ant-design-vue';
const app = createApp(App);
app.config.globalProperties.$message = message;
再运行npm run build,查看项目打包的分析图
可以看到vendors减少到了1.31MB。
四、外部引入模块(CDN)
我们可以把第三方库通过cdn的方式引入项目,这样vendor.js会减少,并且大大提升首屏加载速度。
在vue.config.js配置
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
css: [
'https://cdnjs.cloudflare.com/ajax/libs/ant-design-vue/2.2.8/antd.min.css'
],
js: [
'https://unpkg.com/vue@3.2.20/dist/vue.runtime.global.prod.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.0.0/vue-router.global.prod.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vuex/4.0.0/vuex.global.prod.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/ant-design-vue/2.2.8/antd.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/echarts/4.8.0/echarts.min.js'
]
}
module.exports = {
chainWebpack: (config) => {
// 生产环境配置
if (isProduction || devNeedCdn) {
// 生产环境注入cdn
config.plugin('html')
.tap(args => {
args[0].cdn = cdn;
return args;
});
}
},
configureWebpack: config => {
// 用cdn方式引入,则构建时要忽略相关资源
if (isProduction || devNeedCdn) config.externals = cdn.externals;
if (isProduction) {
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'ant-design-vue': 'antd',
'echarts': 'echarts'
}
}
},
}
说明
这里配置externals字段,是将第三方库抽取出来。
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'ant-design-vue': 'antd',
'echarts': 'echarts'
}
以打包ant-design-vue为例,”ant-design-vue” 指的是你import语句中import Antd from ‘ant-design-vue’中的’ant-design-vue’, “antd”是ant-design-vue这个库暴露出来的全局对象。这个对象你可以从cdn资源的源码中找到或者根据需要引用的文件去node_modules文件夹下去寻找。下面我从node_modules文件夹下的源码找一下ant-design-vue,vue-router和vuex暴露出的全局对象,以下是各源码的截图:
如果没有按需加载,以echarts为例,import * as echarts from “echarts”的echarts是全局对象,如下:
做完这些之后,在public/index.html文件中引入cdn链接,如下:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</body>
</html>
再运行npm run build,查看项目打包的分析图
可以看到vendors减少到了400kb。
五、gzip
安装compression-webpack-plugin
npm install compression-webpack-plugin –save-dev
如果打包时候出现Cannot read property ‘tapPromise’ of undefined 这个报错,是compression-webpack-plugin版本问题,通过降低版本就可以解决此类问题。
在 vue.congig.js中引入并修改webpack配置,如下:
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const productionGzipExtensions = ['js', 'css'];
module.exports = {
configureWebpack: (config) => {
if (isProduction) {
config.plugins.push(
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
}),
new CompressionPlugin({
test: /\.(js|css)(\?.*)?$/i,//需要压缩的文件正则
threshold: 10240,//文件大小大于这个值时启用压缩
deleteOriginalAssets: false//压缩后保留原文件
})
);
};
},
}
服务器也要开gzip优化性能,请自行百度,我不做演示了。
项目开启gzip压缩和性能优化【亲测可用】
六、图片的压缩
请自行百度:图好快。
七、使用uglifyjs-webpack-plugin插件去除console.log打印以及注释
npm install uglifyjs-webpack-plugin –save-dev
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const isProduction = process.env.NODE_ENV === 'production';
configureWebpack: config => {
const plugins = [];
if (isProduction) {
plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
output: {
comments: false, // 去掉注释
},
warnings: false,
compress: {
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log']//移除console
}
}
})
)
}
},
虽然congsole.log()以及注释不会占用太多体积(也就几kb,甚至几十kb)。
八、完整配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin")
const productionGzipExtensions = ['js', 'css'];
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
css: [
'https://cdnjs.cloudflare.com/ajax/libs/ant-design-vue/2.2.8/antd.min.css'
],
js: [
'https://unpkg.com/vue@3.2.20/dist/vue.runtime.global.prod.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.0.0/vue-router.global.prod.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vuex/4.0.0/vuex.global.prod.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/ant-design-vue/2.2.8/antd.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/echarts/4.8.0/echarts.min.js'
]
}
// 本地环境是否需要使用cdn
const devNeedCdn = false
module.exports = {
// 基本路径
publicPath: './',
// 输出文件目录
outputDir: 'dist',
// eslint-loader 是否在保存的时候检查
lintOnSave: false,
// 生产环境是否生成 sourceMap 文件
productionSourceMap: false,
chainWebpack: (config) => {
// 生产环境配置
if (isProduction || devNeedCdn) {
// 生产环境注入cdn
config.plugin('html')
.tap(args => {
args[0].cdn = cdn;
return args;
});
}
},
configureWebpack: config => {
// 用cdn方式引入,则构建时要忽略相关资源
if (isProduction || devNeedCdn) config.externals = cdn.externals;
if (isProduction) {
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'ant-design-vue': 'antd',
'echarts': 'echarts'
},
config.plugins.push(
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
}),
new CompressionPlugin({
test: /\.(js|css)(\?.*)?$/i,//需要压缩的文件正则
threshold: 10240,//文件大小大于这个值时启用压缩
deleteOriginalAssets: false//压缩后保留原文件
})
);
}
// 关于打包后包资源各部分占比的配置相关
// config.plugins.push(
// new BundleAnalyzerPlugin()
// )
},
devServer: {
overlay:{
warning:false,
errors:false
},
open: process.platform === 'darwin',
host: 'localhost',
port: 80,
https: false,
hotOnly: false,
proxy: '',
},
}