Vue打包优化详解
分析工具
Coverage:查看代码的使用状况
移除死代码
懒加载代码
webpack-bundle-analyzer:查看资源树
1. productionSourceMap:false
2.路由懒加载
3. 关闭Prefetch
4.element-ui组件按需加载
5.使用 CDN 外部加载资源-vue, vuex, vue-router,axios
6.使用 CDN 外部加载资源-echarts
7.gzip
结论
后记:css是否要拆分
分析工具
Coverage:查看代码的使用状况
Coverage 是chrome开发者工具的一个新功能,从字面意思上可以知道它是可以用来检测代码在网站运行时有哪些js和css是已经在运行,而哪些js和css是还没有用到的,如图,这是我在打开csdn网页时,所显示的已运行和尚未运行的代码情况。
那这个新功能有什么作用呢?
如上图所示,最右边显示的是我们加载的css和js文件数量,红色区域表示已运行的代码,而青色表示已加载但未运行的代码。可用来发现页面中尚未用到的js 和 css代码,你可以为用户只提供必要的代码,这样就可以提升页面的性能。这对于找出可以进行拆分的脚本以及延迟加载非关键脚本来说非常有用。
上面录制的数据中,最大的文件是 vendor.js,其中 55% 的代码都没有执行过,约 80 KB,这已经相当于一张典型图片的文件大小了。
如果某个文件覆盖率低(即未使用代码比例很高),通常意味着用户加载了太多不必要的代码(要么真的是无用代码,要么是当前时点还没执行到的代码),有性能常识的同学不难推断出,这会导致页面的完全加载时间、或单页应用的启动时间变慢,在慢速网络下的性能损耗会尤其明显;此外,更多代码的解析、编译也就意味着更多的硬件资源消耗,在低端设备上也会存在明显的性能问题。
在笔者看来,Coverage 数据至少能从下面 2 个方面指导我们进行 WEB 应用的优化:
移除死代码
以 Coverage 数据为参考,我们能了解页面重无用代码的比例到底有多大。现实世界中,很多工程师可能是在遗留代码库上工作,并且遗留代码库存在的时间还很长,那么很可能这个代码库中存在大量的无用代码,但是谁也不敢删除他们,因为 JS 这门语言的动态性,你不能粗暴的把哪些看起来“没有被使用”的代码直接删掉,除非你很清楚所有的代码执行路径,很显然这对于大型应用或者遗留代码库来说是不现实的。
怎么移除死代码呢?我们可以依赖打包工具,比如 UglifyJS 在压缩代码时支持直接删除死代码的配置项。而 Webpack 2 中引入了 Tree Shaking 的特性,能够自动把项目中没有用到的代码从打包中去掉,但是这种优化仅限于被 export 的代码。总而言之,死代码要尽可能想办法去掉,Coverage 工具能提供一个判断基准。
懒加载代码
如果能删的死代码都删了,但是 Coverage 数据还是居高不下,那么你应该换个角度思考。就像前文所说,JS 是动态语言,可能部分代码在页面加载时并没有用到,但是用户后来的操作会触发这些代码的执行,为什么不让这些代码在需要的时候再加载呢?聪明的你可能已经想到了,这就是懒加载的技术。
使用 Webpack 打包且没有对配置做特别调优的话,它默认会把所有依赖打包成一个巨大的文件,很容易出现首次加载覆盖率很低的情况,在 Webpack 中实现懒加载可以参考 Code Splitting 和 bundle-loader,具体的配置细节这里不展开讲。使用懒加载之后可以极大的减少页面初次下载的代码,从而提高性能。需要注意的是,懒加载优化需要在模块数量和模块大小之间把握一个平衡,否则过多的模块懒加载反而对性能不利,因为每个 HTTP 请求也是有额外开销的。
webpack-bundle-analyzer:查看资源树
我使用的是vue-cli 3.0,需要先安装插件webpack-bundle-analyzer(npm安装会很慢,推荐使用cnpm)
cnpm i webpack-bundle-analyzer
在vue.config.js中添加分析工具的配置:
module.exports = { chainWebpack: (config) => { /* 添加分析工具 */ if (process.env.NODE_ENV === 'production') { config .plugin('webpack-bundle-analyzer') .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) .end() config.plugins.delete('prefetch') } else { } } }
再运行
npm run build --report
会在浏览器打开一个项目打包的情况图,便于直观地比较各个bundle文件的大小。
瘦身开始
1. productionSourceMap:false
修改vue.config.js中的配置
module.exports = { outputDir: `${srcFile}`, // 在npm run build时 生成文件的目录 type:string, default:'dist' productionSourceMap: false, // 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度 }
把productionSourceMap改为false。不然在最终打包的文件中会出现一些map文件,map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
如果不关掉,生产环境是可以通过map文件看到源码的。
2.路由懒加载
在router.js文件中,原来的静态引用方式
import ShowBlogs from '@/components/ShowBlogs' routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]
改为
routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')
以函数的形式动态引入,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件。
3. 关闭Prefetch
因为vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容
在首屏会把这十几个路由文件,都一口气下载了
所以我们要关闭这个功能,在vue.config.js中设置
参考官网的做法:
4.element-ui组件按需加载
首屏需要加载的依赖包,其中element-ui整整占了568k
原本的引进方式引进了整个包:
import ElementUI from 'element-ui' Vue.use(ElementUI)
按需引入
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
首先,安装 babel-plugin-component:
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{ "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:
import Vue from 'vue'; import { Button, Select } from 'element-ui'; import App from './App.vue'; Vue.component(Button.name, Button); Vue.component(Select.name, Select); /* 或写为 * Vue.use(Button) * Vue.use(Select) */ new Vue({ el: '#app', render: h => h(App) });
注意:有一些组件的引入方式有所不同,具体的参照element官网介绍。
Vue.prototype.$alert = MessageBox.alert; Vue.prototype.$confirm = MessageBox.confirm; ...
5.使用 CDN 外部加载资源-vue, vuex, vue-router,axios
对于vue, vuex, vue-router,axios等我们可以利用wenpack的externals参数来配置,这里我们设定只需要在生产环境中才需要使用:
// vue.config.js const isProduction = process.env.NODE_ENV === 'production'; const cdn = { css: [], js: [ 'https://cdn.bootcss.com/vue/2.5.17/vue.runtime.min.js', 'https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js', 'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js', 'https://cdn.bootcss.com/axios/0.18.0/axios.min.js', ] } module.exports = { chainWebpack: config => { // 生产环境配置 if (isProduction) { // 生产环境注入cdn config.plugin('html') .tap(args => { args[0].cdn = cdn; return args; }); } }, configureWebpack: config => { if (isProduction) { // 用cdn方式引入 config.externals = { 'vue': 'Vue', 'vuex': 'Vuex', 'vue-router': 'VueRouter', 'axios': 'axios' } } }, } ```javascript <!DOCTYPE html> <html lang="zh"> <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"> <!-- 使用CDN的CSS文件 --> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style"> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet"> <% } %> <!-- 使用CDN的JS文件 --> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script"> <% } %> </head> <body> <noscript> <strong>We're sorry but eye-admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %> </body> </html>
6.使用 CDN 外部加载资源-echarts
这次优化主要是针对echarts,在其文档里也有提到按需加载,但是这次我们不用按需加载了,我想把echarts彻底干掉。
首先在index.html中引入echarts的外部CDN(如果需要地图组件,也需要一并引入)
//index.html <script src="https://cdn.bootcss.com/echarts/4.1.0/echarts.min.js"></script>
复制代码然后在webpack.base.config.js中,做如下改动
// module.exports中增加externals对象 module.exports = { externals: { "echarts": "echarts" //默认是配置引用的库(这里是echarts)暴露出的全局变量 }, }
7.gzip
拆完包之后,我们再用gzip做一下压缩
安装compression-webpack-plugin
cnmp i compression-webpack-plugin -D
在vue.congig.js中引入并修改webpack配置
const CompressionPlugin = require('compression-webpack-plugin') configureWebpack: (config) => { if (process.env.NODE_ENV === 'production') { // 为生产环境修改配置... config.mode = 'production' return { plugins: [new CompressionPlugin({ test: /\.js$|\.html$|\.css/, //匹配文件名 threshold: 10240, //对超过10k的数据进行压缩 deleteOriginalAssets: false //是否删除原文件 })] } }
在服务器我们也要做相应的配置
如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件
我的服务器是用express框架搭建的
只要安装一下compression就能使用
const compression = require('compression') app.use(compression())
结论
- 遇到webpack打包性能问题,先执行npm run build --report分析一波,然后根据分析结果来做相应的优化,谁占体积大就干谁.路由很多的复杂页面,路由懒加载是肯定要做的
- 现在很多库都有提供按需加载的功能,有需要的话可以按照官方文档的做法来按需加载
- webpack提供的externals可以配合外部资源CDN轻松大幅度减少打包体积,适用于echarts、jQuery、lodash这种暴露了一个全局变量的库
- 千万不要忘了开启Gzip压缩
- 本文讲的只是针对于webpack层面的优化,性能优化不只这些,还有其他方面的优化,比如页面渲染优化(减少重排)、网络加载优化等。
- 确定引入的必要性
- 前端发展到如今时期,倘若项目采用了 MVVM模式框架,数据双向绑定,那么像 jQuery 这般类库,不能说没有丝毫引入的必要,至少可以说确实没有引入的必要。对此,如果还有些顾虑,完全可以参考下 YOU MIGHT NOT NEED JQUERY;用原生写几行代码就可以解决的事儿,实在不易引入这么个庞然大物,平添烦恼。
- 避免类库引而不用
- 倘若这类情况发生,对整个打包体积,不仅大而且亏。项目一旦大了,很难人为保证每个引入的类库,都被有用到,尤其是二次开发。所以工具的利用十分必要,强烈推荐类如 Eslint 这般工具,并且注入对应规则,对声明却未使用的代码,给予强制提醒;这不仅可以有效的规避类似情形发生(也适用于普通变量的检测),而且还能使得团队代码风格,尽可能地保持相似;要知道代码足够遵守规则,也可让压缩工具更有效压缩代码,一举多得,何乐不为?
后记:css是否要拆分
vuecli 3和vuecli2.x还有一个区别是
vuecli 3会默认开启一个css分离插件 ExtractTextPlugin
每一个模块的css文件都会分离出来,整整13个css文件,而我们的首页就请求了4个,花费了不少的资源请求时间
我们可以在vue.config.js中关闭它
css: { // 是否使用css分离插件 ExtractTextPlugin extract: false, // 开启 CSS source maps? sourceMap: false, // css预设器配置项 loaderOptions: {}, // 启用 CSS modules for all css / pre-processor files. modules: false }
打包出来的文件中,直接就没有了css文件夹
取而代之的是整合起来的一个js文件,负责在一开始就注入所有的样式
首屏加载文件数减少,但体积变大,最终测下来速度没有太大差异
所以,是否要css拆分就见仁见智,具体项目具体分析吧