Vue.js 项目打包优化实践
首先上结果:
- 把常用的 Vue,router,vuex,axios 的 runtime 包拆分了出来,改为 cdn;
- 另外就是对于自己编写的业务代码进行分包,根据路由进行懒加载,可以较好的提高首屏加载速度。
- 添加了全局 loading 来提高体验。
优化前,可以看出包含了不少的runtime包
经过优化体积大幅减小
不出意外core-js也可以改成CDN
配合静态资源的CDN加速,目前首页的速度大概是700ms,除了图片外加载的速度大概是200ms左右
abandon.workabandon.work
CDN 优化
CDN 优化是在 webpack 中实现的,主要分为环境切换,webpack 打包技巧和 html-webpack-plugin配置三个部分。
这部分的代码可以点击这个链接
环境切换
通过process.env.NODE_ENV来切换环境,因为在 dev 环境的时候,最好还是使用本地包,方便 debug 等,在生产环境的时候才需要添加 CDN.
本项目的vue.config.js中就是这样安排的,这样就可以在不同环境对 webpack 进行配置,类似于vue-cli2的配置类型:
# cli 2
webpack.dev.js
webpack.prod.js
本项目中;
if (process.env.NODE_ENV === 'production') {
webpackConfig['chainWebpack'] = config => {
config.plugin('html').tap(args => {
args[0].cdn = cdn;
return args;
});
};
webpackConfig['configureWebpack'] = config => {
config['externals'] = {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
axios: 'axios',
};
config['plugins'].push(new BundleAnalyzerPlugin());
};
}
module.exports = webpackConfig;
具体操作涉及vue/cli3+的配置,具体可以看这里; 对于configureWebpack, 有配置式和函数式,当使用函数式的时候,添加插件的方法可以看上面的片段,这是官方文档里面没有的。
打包技巧
那么该怎么设置 cdn 呢?
这里需要两个 webpack 的配置项:
- webpack.externals
- webpack.plugins.html-webpack-plugin 具体也可以看代码,主要的思路就是在区分环境的基础上,在生产环境将需要 cdn 的包放在webpack.externals里面:
接着就是操作 plugin,这里选择链式操作,将需要填入的 cdn 连接的内容传给index.html模板: ```javascript let cdn = { js: [webpackConfig['configureWebpack'] = config => {
config['externals'] = {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
axios: 'axios',
};
};
], };// 可以写成动态版本号
'//cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js',
'//cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js',
'//cdn.bootcss.com/vuex/3.1.2/vuex.min.js',
'//cdn.bootcss.com/axios/0.19.0/axios.min.js',
webpackConfig[‘chainWebpack’] = config => { config.plugin(‘html’).tap(args => { args[0].cdn = cdn; return args; }); };
<a name="QIb6M"></a>
### html-plugin 配置
上述的配置完成之后,需要在public/inedx.html中修改一下:<br /><div id="app"></div><br /><% if(process.env.NODE_ENV==='production'){htmlWebpackPlugin.options.cdn.js.forEach(function(item){ if(item){ %><br /><script type="text/javascript" src="<%= item %>"></script><br /><% }})} %><br /><!-- built files will be auto injected --><br />这个的目的就是当是生产环境的时候,遍历生成 CDN 的元素。这是一个模板语法,可以看看[html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin)
<a name="slpNm"></a>
### 路由懒加载优化
这里主要是根据[官方文档](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html)的实践: 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。<br />// src/router/index.js<br />// 路由懒加载 博客分块<br />const Blog = () => import(/* webpackChunkName: "group-blog" */ '../views/Blog/Blog.vue');<br />const ContentPage = () => import(/* webpackChunkName: "group-blog" */ '../views/Blog/ContentPage.vue');
<a name="X7TGJ"></a>
### loading 插件
为了美化加载的过程,自定义一个Loading插件,主要参考了[这些文章](https://juejin.im/search?query=vue%20loading&type=all)<br />选择了一个[蛮炫酷的动效](https://codepen.io/Chokcoco/pen/XWJPzjw)<br />功能:
- 单例模式
- 可以在axios拦截器中使用。
<a name="tERWF"></a>
### 更新 使用 useBuiltIns external core-js
cli 默认的 @vue/cli-plugin-babel/preset 使用的是 babel-preset-app,而babel-preset-app其实是 extends 的 [@babel/preset-env](https://new.babeljs.io/docs/en/next/babel-preset-env.html) 并且设置了 默认的 uesBuiltIns 为 "usuage"<br />所以我们需要做的就是设置为 “entry即可”<br />![](https://cdn.nlark.com/yuque/0/2021/png/502233/1620803879266-0ec566bc-0678-416e-97cb-785d93f1ac22.png#clientId=u8440a7a0-529b-4&from=paste&height=325&id=u38519931&margin=%5Bobject%20Object%5D&originHeight=325&originWidth=720&originalType=url&status=done&style=none&taskId=u9db9b616-9b25-40d0-a29e-fb50bddd059&width=720)<br />![](https://cdn.nlark.com/yuque/0/2021/png/502233/1620803878842-d357bf91-1193-43b0-9d51-63c92444fcbc.png#clientId=u8440a7a0-529b-4&from=paste&height=325&id=u5ef9a631&margin=%5Bobject%20Object%5D&originHeight=325&originWidth=720&originalType=url&status=done&style=none&taskId=ud6b91a25-4efd-4d90-89e2-09fa0ede5e8&width=720)<br />参考链接: <br />[How to use core-js as an external CDN in webpack?](https://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/59204262/how-to-use-core-js-as-an-external-cdn-in-webpack)<br />[@vue/babel-preset-app](https://link.zhihu.com/?target=https%3A//github.com/vuejs/vue-cli/tree/dev/packages/%2540vue/babel-preset-app)<br />[@vue/cli-plugin-babel](https://link.zhihu.com/?target=https%3A//github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel%23readme)
<a name="KfeN3"></a>
### 参考文章
1. [webpack 使用 HtmlWebpackPlugin 进行 cdn 配置](https://www.jianshu.com/p/9248db0349fb)
2. [vue 打包优化](https://www.jianshu.com/p/70d2fe63c943)
3. [在 vue-cli 3.0 中根据不同环境动态注入 CDN](https://www.jianshu.com/p/62dde86d2b17)
4. [webpack-cdn-plugin](https://github.com/shirotech/webpack-cdn-plugin)
5. [webpack4 配置的最佳实践](https://juejin.im/post/5b304f1f51882574c72f19b0#heading-7)
<a name="bu50V"></a>
## [CDN+Vue-Cli优化打包体积引起的对环境变量的迷思](https://juejin.cn/post/6877044310798254088)
需求: 在打包发布的时候,根据配置打包出将常用的库使用CDN的版本。<br />在vue-cli中,可以使用htmlPlugin配置生成的html模板文件,使用了cli中插值的能力,在模板中嵌入表达式,将预设的cdn的脚本标签添加到模板中,同时使用webpack的external功能将本来项目中间的库文件抽离。<br />html文件的模板:
```html
<div id="app"></div>
<% if(process.env.NODE_ENV==='production'&& process.env.VUE_APP_CDN ==='CDN'){htmlWebpackPlugin.options.cdn.js.forEach(function(item){ if(item){ %>
<script type="text/javascript" src="<%= item %>"></script>
<% }})} %>
<!-- built files will be auto injected -->
vue.config.js:
// 生产环境配置cdn
const useCDN = process.env.VUE_APP_CDN ==='CDN'; // 是否开启CDN
const CDNWEB = "cdn.bootcdn.net/ajax/libs";
let cdn = {
js: [
`//${CDNWEB}/vue/2.6.10/vue.runtime.min.js`,
`//${CDNWEB}/vue-router/3.0.3/vue-router.min.js`,
`//${CDNWEB}/vuex/3.0.1/vuex.min.js`,
`//${CDNWEB}/axios/0.20.0/axios.min.js`,
`//${CDNWEB}/view-design/4.3.2/iview.min.js`,
`//${CDNWEB}/echarts/4.8.0/echarts.min.js`,
],
};
if (NODE_ENV === "production") {
webpackConfig["chainWebpack"] = (config) => {
config.plugin("html").tap((args) => {
args[0].cdn = cdn;
return args;
});
};
webpackConfig["configureWebpack"] = (config) => {
if (useCDN) {
config["externals"] = {
vue: "Vue",
vuex: "Vuex",
"vue-router": "VueRouter",
axios: "axios",
"view-design": "iview",
iview: "ViewUI",
'echarts':'echarts'
};
}
}
}
官方文档在客户端侧代码中使用环境变量里面说:
除了 VUEAPP* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量:
- NODE_ENV - 会是 “development”、”production” 或 “test” 中的一个。具体的值取决于应用运行的模式。
- BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。
看上面的说明,默认添加了NODEENV和BASE_URL,别的挂在process.env上面的变量在打包到客户端的时候,是不起作用的,因此添加CDN的flag需要以VUE_APP开头。
挂载上之后,使用vue inspect > out.js,可以看到,添加了一个常驻的变量:
plugins:[
new DefinePlugin(
{
'process.env': {
NODE_ENV: '"development"',
// 添加了新的变量
VUE_APP_CDN: '"CDN"',
BASE_URL: '"/"'
}
}
),
]
这样配合添加的.env文件,就可以实现配置打包的时候选择是否使用CDN模式。
使用.env.cdn这样的环境配置的时候,如果是打包也需要在这个文件中添加NODE_ENV=production
VUE_APP_CDN = CDN
NODE_ENV = production
不添加的话,因为我使用的打包方式是: vue-cli-service build —mode cdn,这样注入环境变量,不添加NODE_ENV的话就会按照`vue-cli-service build --mode development
打包生成一个app.js文件了。
这样一来,配合gzip等手段,打包尺寸大降低。
// 之前(统计stat size)
All (5.18 MB)
js/chunk-vendors.849a5738.js (5.1 MB)
js/app.f17827af.js (75.19 KB)
// parsed size
All (1.56 MB)
js/chunk-vendors.849a5738.js (1.52 MB)
js/app.f17827af.js (35.36 KB)
// gzip size
All (476.98 KB)
js/chunk-vendors.849a5738.js (469.66 KB)
js/app.f17827af.js (7.32 KB)
// cdn 之后
All (119.18 KB)
js/app.2e115181.js (75.19 KB)
js/chunk-vendors.aedee448.js (43.99 KB)
// parsed size
All (54.01 KB)
js/app.2e115181.js (35.54 KB)
js/chunk-vendors.aedee448.js (18.46 KB)
// cdn gzip size
All (14.42 KB)
js/app.2e115181.js (7.39 KB)
js/chunk-vendors.aedee448.js (7.03 KB)