Vue打包优化
分析包大小
下载webpack-bundle-analyzer
npm i webpack-bundle-analyzer -D
可以通过命令
npm run build --report
然后会出现这个界面,能帮助我们清晰看到包大小请求 (vue-cli 2的项目)
如果你使用的是Vue官方的vue-cli 3.0+的脚手架,可以直接通过可视化工具来构建并查看。
vendor*.js 是所有第三方依赖js的集合
app*.js 是我们写的所有业务代码的集合,通常可以通过路由懒加载来减少这个文件的大小,可以称为拆包,拆包后会多出很多chunck.js文件
其他的chunck*.js是各个路由对应的js文件
理解Vue项目的开发环境和生产环境
vue-cli 2的项目Webpack配置是暴露在外的,vue-cli 3+的Webpack配置是被隐藏了的。这里简单说一下vue-cli 3+的配置:
首先分析一下vue-cli3
的 构建命令:vue-cli-service serve --mode development
,mode参数的值development会被设置为名为NODE_ENV
的环境变量的值,这就是为什么我们在js中可以获取到process.env.NODE_ENV
.
理解项目打包命令
//开发运行命令:
vue-cli-service serve --mode development
//构建发布命令:
vue-cli-service build --mode production
//mode参数的值会用作修改进程环境变量NODE_ENV
"scripts": {
//默认--mode development,运行该命令后process.env.NODE_ENV==='development'
"serve": "vue-cli-service serve",
//默认--mode production 运行该命令后process.env.NODE_ENV==='production'
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
其次,采用vue-cli3
构建的项目,首先会读取.env.**
的文件(默认是没有这样的文件),通常可以这个文件里设置进程环境变量,(注意:这里设置的环境变量会覆盖vue-cli命令的–mode所设置环境变量值)。当运行 vue-cli-service serve
时会去读 .env.development
文件,而运行vue-cli-service build
会去读 .env.production
文件。
你可以在根目录下新建2个文件.env.development
和.env.production
分别存放这样的内容
NODE_ENV='development'
VUE_APP_BASE_URL='/api'
NODE_ENV='production'
VUE_APP_BASE_URL='http://example.com/api/v1'
这样就相当于分别设置了开发环境和生产环境的变量,接下来在你的axios配置文件中可以这样来使用环境变量:
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
timeout: 10000 // 请求超时时间
})
路由懒加载
在路由中,用这种方式引入组件
const routes = [
{
path:'/home',
name:'home',
component: ()=> import('../pages/home'),
},
]
而不是这种方式
import Home from ''../pages/home''
const routes = [
{
path:'/home',
name:'home',
component: Home,
},
]
清除console
方式一:通过babel插件 (适用vue-cli 3.x)
1)安装babel-plugin-transform-remove-console
npm i babel-plugin-transform-remove-console -D
2)配置 babel.config.js
文件
const prodPlugins = [] //生产环境的开发插件
if(process.env.NODE_ENV === 'production'){ //如果是生产环境(即npm build时)
prodPlugins.push("transform-remove-console")
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
],
...prodPlugins
]
}
方式二:uglifyjs插件 (适用vue-cli 2.x)
安装插件uglifyjs-webpack-plugin
(若没有安装则需单独安装)
npm i uglifyjs-webpack-plugin -D
找到webpack.prod.conf.js,做如下修改
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
// ===========新增==========
drop_debugger: true, //自动删除debugger
drop_console: true //自动删除console.log
// ===========新增==========
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
cdn引入外部资源
对于vue, vuex, vue-router,axios,better-scroll等,因为这些库时可以导入一个全局变量的,所以我们可以利用Webpack的externals参数来配置,让vue在打包时不从node-modules里面引入库而是使用CDN加载外部资源的方式来引入这些库。配置如下:(注意版本号与开发的一致)
修改vue.config.js
const isProduction = (process.env.NODE_ENV === 'production')
const cdn = {
css:[
],
js:[
"https://unpkg.com/vue@2.6.11/dist/vue.min.js",
"https://unpkg.com/vue-router@3.2.0/dist/vue-router.min.js",
"https://unpkg.com/axios@0.21.1/dist/axios.min.js",
"https://unpkg.com/@better-scroll/core@2.2.1/dist/core.min.js",
]
}
module.exports = {
// eslint-loader 是否在保存的时候检查
lintOnSave: false,
// webpack-dev-server 相关配置
devServer: {
...
},
// 链式配置webpack
chainWebpack: config => {
config.plugin('html').tap(args => {
if(isProduction){
// 把cdn对象挂到htmlWebpackPlugin.options上,可以在html中访问到
args[0].cdn = cdn
}
return args
})
},
// 对象配置webpack
configureWebpack: config => {
if(isProduction){
// 打包时取消import加载资源
config.externals = {
'vue' : 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios',
'@better-scroll/core': 'BScroll'
}
}
},
};
如果是在vue-cli 2.x项目中:则是找到webpack.prod.conf
进行修改
// =================新增=================
const cdn = {
css:[
],
js:[
"https://unpkg.com/vue@2.5.2/dist/vue.min.js",
"https://unpkg.com/vue-router@3.0.1/dist/vue-router.min.js",
"https://unpkg.com/vuex@3.0.1/dist/vuex.min.js",
"https://unpkg.com/@better-scroll/core@2.2.1/dist/core.min.js",
"https://unpkg.com/axios@0.18.0/dist/axios.min.js",
]
}
// =================新增=================
const webpackConfig = merge(baseWebpackConfig,{
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency',
// =================新增=================
// 这两个属性都是自定义到HtmlWebpackPlugin上的,从而可以在index.html中获取这些属性数据
cdn: cdn, // cdn配置
// =================新增=================
}),
// =================新增=================
// 构建时忽略的资源
externals: {
'vue' : 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios',
'@better-scroll/core': 'BScroll'
}
// =================新增=================
})
然后是修改index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<title>移动端开发</title>
<!-- ============以下是新增的============= -->
<!-- cnd 加载css start-->
<% if(htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css){
htmlWebpackPlugin.options.cdn.css.forEach(function(item){
%>
<link href="<%= item %>" rel="stylesheet" />
<% })} %>
<!-- cnd 加载css end-->
<!-- ============以上是新增的============= -->
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
// 假设移动端屏幕375px, 则1rem=100px .16rem=16px
// 假设移动端屏幕414px, 则1rem=110.4px .16rem=17.664px
function setFS(){
let htmlWidth = document.documentElement.clientWidth || document.body.clientWidth;
var fontSize = htmlWidth / 3.75 ;
if(fontSize>200){
fontSize = 200
}
document.getElementsByTagName('html')[0].style.fontSize = fontSize +'px';
}
setFS()
window.onresize = setFS
</script>
</head>
<body>
<div id="app"></div>
<!-- ============以下是新增的============= -->
<!-- cnd 加载js start-->
<% if(htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js)
{
htmlWebpackPlugin.options.cdn.js.forEach(function(item){
%>
<script type="text/javascript" src="<%= item %>"></script>
<% })} %>
<!-- cnd 加载js end-->
<!-- ============以上是新增的============= -->
<!-- built files will be auto injected -->
</body>
</html>
最后,推荐两个靠谱的cdn服务
jsdelivr (推荐) https://www.jsdelivr.com/
unpkg (推荐) https://unpkg.com/
第三方库按需加载
vant按需导入
参照官网,安装插件 babel-plugin-import
,修改babel配置
// 在.babelrc 中添加配置
// 注意:webpack 1 无需设置 libraryDirectory
{
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}]
]
}
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
定义vantPlugin.js,在这个文件里引入组件
import Vue from 'vue'
import {
Tabbar,
TabbarItem,
Toast,
Dialog,
}
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.prototype.$toast = Toast
Vue.prototype.$dialog = Dialog
element-ui按需导入
参考官网:https://element.eleme.cn/#/zh-CN/component/quickstart
如果是项目用到的element-ui组件较多,更建议通过cdn的方式引入element-ui,因为element-ui体积还是很大的,不适合打包到项目里。
使用gzip
Vue启用gzip压缩
Vue使用gzip压缩,可以使Vue在打包时为js/css/html打包对应的gz压缩文件。
1)安装compression-webpack-plugin
npm i compression-webpack-plugin@4 -D
安装时需要注意 compression-webpack-plugin
的版本和webpack
的版本是否对应,不对应可能会出错。
compression-webpack-plugin7.x
, 对应 webpack 5.9
compression-webpack-plugin4.x
, 对应 webpack 5.x
compression-webpack-plugin3.x
, 对应 webpack 4.x
compression-webpack-plugin 1.x
对应 webpack 3.x
具体版本要求请参考 Github
如果是vue-cli 3.x项目,修改vue.config.js
// 链式配置webpack
chainWebpack: config => {
if(isProduction){ //生产环境下
// 启用gzip
const CompressionPlugin = require('compression-webpack-plugin')
config.plugin('compressionPlugin').use(new CompressionPlugin({
test: /\.js$|\.css$|\.html$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false // 不删除源文件
}))
}
},
如果是vue-cli 2.x项目,只需要修改config/index.js,设置下面两项
productionGzip: true,
productionGzipExtensions: ['js', 'css', 'html'],
打包成功后,查看项目js目录:
服务端使用gzip压缩
Nginx可以开启gzip,Express等其他服务器也可以。这里使用了Express作为服务器。
问题1: Express如何启动gzip压缩?
回答:先安装compression
,然后启用的代码如下:
const express = require("express");
const compression = require('compression')
const app = express();
// 注意:compression这个中间件一定要放在最开始,否则可能不生效。compression(options)可以通过options进行配置
app.use(compression())
app.use(express.static('public'))
app.listen(8088, function () {
console.log("Express-static started on port 8088");
});
问题2: 如何判断后端gzip生效?
答:在浏览器的Network中,查看资源的响应头的Content-Encoding属性,如果是gzip那么说明生效了。
问题3: 后端开启gzip,就会将相应文件压缩,浏览器就能识别,就已经起到优化的效果了,为什么还需要Vue打包时开启gzip?
回答:服务器拿到请求时会判断是否启动gzip压缩,如果启动则会将请求的资源进行压缩再返回给浏览器,但是压缩这个过程时消耗时间和内存等资源的。所以提前准备好对应的gz文件可以节省时间和服务器资源。
浏览器的请求资源的策略
浏览器第一次访问
浏览器第二次访问
显然浏览器对访问的资源做了一个缓存,第二次访问时只需要发必要的请求确认数据完整性即可,不会去获取实际数据。
另外值得说明的是,cdn资源在浏览器访问一次后会有磁盘缓存,之后访问会直接从磁盘缓存中读取。看下图: