前言
Vue 項(xiàng)目打包優(yōu)化 - CDN、配置分離、路由懶加載。
本例項(xiàng)目是寫的一個(gè)管理系統(tǒng),項(xiàng)目使用的庫版本:
- vue-cli: @3.11.0
- vue: @2.6.10
- vue-router: @3.1.3
另外本項(xiàng)目還用到了 axios、echarts、element-ui、nprogress、vue-quill-editor等,不優(yōu)化直接打包會(huì)非常大。優(yōu)化目的:
- 配置分離:開發(fā)模式使用本地資源,生產(chǎn)模式打包資源使用 CDN 引入。
- 路由懶加載:js包過大會(huì)影響頁面加載速度,路由被訪問才加載對(duì)應(yīng)組件更加高效。
生成打包報(bào)告
有兩種方式:
- 命令行生成打包報(bào)告(build命令后加 --report)
- 利用 vue ui 工具(推薦,非常直觀)
命令行生成報(bào)告
npm vue-cli-service build --report
#或者在 package.json 的 build 命令后添加 --report,然后再
npm run build
此方法會(huì)自動(dòng)在 dist 目錄下生成 report.html,打開如下:

vue ui 圖形化界面生成報(bào)告
vue-cli3 中自帶了 vue ui 圖形化界面,用于創(chuàng)建和管理項(xiàng)目。
# 啟動(dòng) Vue UI
vue ui
【導(dǎo)入項(xiàng)目】-【任務(wù)】-【build】- 【運(yùn)行】,效果如下:

優(yōu)化一:配置分離
從打包報(bào)告中可以看出,依賴項(xiàng)大小還是挺大的,如 element-ui、echarts等,我們可以通過配置分離,在生產(chǎn)模式打包時(shí)不要打包這些依賴以及一些 CSS 本地資源,在 html 模版中引入依賴的 CDN 資源。國內(nèi)的CDN服務(wù)推薦使用 BootCDN
使用 CDN 的好處有以下幾個(gè)方面:
- 加快打包速度。分離公共庫以后,每次重新打包就不會(huì)再把這些打包進(jìn) vendors 文件中。
- CDN減輕自己服務(wù)器的訪問壓力,并且能實(shí)現(xiàn)資源的并行下載。瀏覽器對(duì) src 資源的加載是并行的(執(zhí)行是按照順序的)。
需要了解的知識(shí)有:
- webpack-externals: 防止某些 import 的包打包到 bundle 中。
- vue-cli3 中的 chainWebpack鏈?zhǔn)讲僮?/a>,使用指南webpack-chain。
分離配置
vue-cli3 中需要添加 vue.config.js 配置文件。
- 給不同模式定義不同的入口文件
- 需要 CDN 引入的依賴添加到 externals 中
- 給 htmlWebpackPlugin 添加一個(gè)參數(shù)變量來控制 html 模版生成
// vue.config.js
module.exports = {
chainWebpack: config => {
// 生產(chǎn)模式
config.when(process.env.NODE_ENV === 'production', config => {
// 生產(chǎn)模式加載 main-prod 入口文件
config.entry('app').clear().add('./src/main-prod.js')
// CDN - externals
config.set('externals', {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios',
lodash: '_',
echarts: 'echarts',
nprogress: 'NProgress',
'vue-quill-editor': 'VueQuillEditor'
})
// 首頁自定義,添加一個(gè)變量來控制html模版,是否加載cdn資源。
config.plugin('html').tap(args => {
args[0].isProd = true
return args
})
})
// 開發(fā)模式
config.when(process.env.NODE_ENV === 'development', config => {
// 開發(fā)模式加載 main-dev 入口文件
config.entry('app').clear().add('./src/main-dev.js')
// 首頁自定義
config.plugin('html').tap(args => {
args[0].isProd = false
return args
})
})
}
}
注:vue-cli 中 html 模版是由 html-webpack-plugin 處理的,所以可以通過給插件屬性添加一個(gè)變量,并且 html 模版可以通過插值的方式,根據(jù)變量來決定是否輸出 CDN 資源。
定義不同的入口文件
原入口文件 main.js 復(fù)制兩份分別命名 main-dev.js、main-prod.js。
在 main-prod.js 注釋或刪除 CDN 引入的 CSS。
// 生產(chǎn)模式入口文件 main-prod.js
// css - 生產(chǎn)模式cdn引入
// import 'quill/dist/quill.core.css'
// import 'quill/dist/quill.snow.css'
// import 'quill/dist/quill.bubble.css'
// import 'nprogress/nprogress.css'
// elementUI 按需引入,生產(chǎn)模式 cdn 引入
// import './plugins/element.js'
html 模版引入 CDN
public/index.html 引入 external 忽略的依賴和入口文件刪除的 CSS 對(duì)應(yīng)的 CDN 鏈接。
<title><%= htmlWebpackPlugin.options.isProd ? '' : 'DEV - ' %>管理系統(tǒng)</title>
<!-- 生產(chǎn)模式才加載 CDN -->
<% if(htmlWebpackPlugin.options.isProd) { %>
<!-- nprogress進(jìn)度條 css -->
<link rel="stylesheet" >
<!-- vue-quill-editor富文本編輯器 css -->
<link rel="stylesheet" >
<link rel="stylesheet" >
<link rel="stylesheet" >
<!-- elementUI css -->
<link rel="stylesheet" >
<!-- js -->
<script src="https://cdn.staticfile.org/vue/2.6.10/vue.min.js"></script>
<script src="https://cdn.staticfile.org/vue-router/3.1.3/vue-router.min.js"></script>
<script src="https://cdn.staticfile.org/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.staticfile.org/lodash.js/4.17.13/lodash.min.js"></script>
<script src="https://cdn.staticfile.org/echarts/4.6.0/echarts.min.js"></script>
<script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
<script src="https://cdn.staticfile.org/quill/1.3.7/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.6/dist/vue-quill-editor.min.js"></script>
<script src="https://cdn.staticfile.org/element-ui/2.13.0/index.js"></script>
<% } %>
如上設(shè)置模版,即可通過變量 isProd 在不同模式下是否加載 CDN 資源,以及不同模式下有不同的 title。
優(yōu)化二:路由懶加載
如上操作后可以一定程度減少打包大小,但此時(shí)所有組件仍會(huì)被打包到同一個(gè) chunk-vendors.js 中,如果組件過多打包數(shù)據(jù)仍然會(huì)很大,從而影響首屏加載時(shí)間。
路由懶加載可以將代碼分割打包,且僅當(dāng)路由被訪問時(shí)才加載對(duì)應(yīng)組件。
最簡(jiǎn)單方法:
// import Login from '../views/Login.vue'
const Login = () => import('../views/Login.vue')
以上方式有個(gè)缺點(diǎn),會(huì)將每個(gè)路由組件單獨(dú)打包??梢酝ㄟ^魔法注釋將部分組件打包在同個(gè) chunk 中,相同chunkName為一個(gè)包。如下
// src/router/index.js
// import Login from '../views/Login.vue'
// import Home from '../views/Home.vue'
// import Welcome from '../components/home/Welcome.vue'
// import Users from '../components/users/Users.vue'
// import RightsList from '../components/rights/RightsList.vue'
// import RolesList from '../components/rights/RolesList.vue'
// import GoodsCategories from '../components/goods/GoodsCategories.vue'
// import GoodsList from '../components/goods/GoodsList.vue'
// import CategoriesParams from '../components/goods/CategoriesParams.vue'
// import AddGoods from '../components/goods/AddGoods.vue'
// import OrdersList from '../components/orders/OrdersList.vue'
// import StatisticsReports from '../components/statistics/StatisticsReports.vue'
// 路由懶加載
const Login = () => import(/* webpackChunkName: "Index" */ '../views/Login.vue')
const Home = () => import(/* webpackChunkName: "Index" */ '../views/Home.vue')
const Welcome = () => import(/* webpackChunkName: "Index" */ '../components/home/Welcome.vue')
const Users = () => import(/* webpackChunkName: "Users" */ '../components/users/Users.vue')
const RightsList = () => import(/* webpackChunkName: "Rights" */ '../components/rights/RightsList.vue')
const RolesList = () => import(/* webpackChunkName: "Rights" */ '../components/rights/RolesList.vue')
const GoodsCategories = () => import(/* webpackChunkName: "Goods" */ '../components/goods/GoodsCategories.vue')
const GoodsList = () => import(/* webpackChunkName: "Goods" */ '../components/goods/GoodsList.vue')
const CategoriesParams = () => import(/* webpackChunkName: "Goods" */ '../components/goods/CategoriesParams.vue')
const AddGoods = () => import(/* webpackChunkName: "Goods" */ '../components/goods/AddGoods.vue')
const OrdersList = () => import(/* webpackChunkName: "Orders" */ '../components/orders/OrdersList.vue')
const StatisticsReports = () => import(/* webpackChunkName: "Statistics" */ '../components/statistics/StatisticsReports.vue')
結(jié)果
另外:如果服務(wù)器開啟 Gzip,在 vue.config.js 配置中使用 Gzip 壓縮,能得到更好的壓縮效果,如何開啟這里就不贅述了,自行 Google 吧。