以vue-cli2作为开发脚手架为前提,虽然内部做了不少的打包优化来减少文件体积,但是仍然有提升的空间。提升的方向大致就为两种:1. 代码结构;2. webpack的打包配置
调整代码结构
配合vue-router使用异步加载
如何配合vue-router使用异步加载在官方文档中已有说明,简单的来说就是在需要的时候再加载特定的js,这部分js在打包的时候会被单独打包到一个js中,以此达到减少bundle的体积的目的。
1 | const TestView = () => import('@/views/test-view'); |
框架的按需加载
通常我们诸如element-ui,mint-ui,iview等框架时,有可能会直接在main.js中直接导入整个框架,这会造成打包后的体积多出不少,并且使用中有绝大部分的组件用不到。这个时候就需要按需加载。
以element-ui为例:官方文档案例
1 | import Vue from 'vue'; |
取消外链css
vue-cli在打包时默认会抽取css到css文件,然后以外链的方式插入到index.html。这里会产生额外的http请求,所以这里可以考虑 css in js
,不抽取css到单独的文件。
找到 build/webpack.prod.conf.js
文件,将 module
对象修改为以下的代码
1 | const webpackConfig = merge(baseWebpackConfig, { |
重点是 extract
属性改为 false
即可
页面加载期间的loading动画
即使做了以上种种优化,在网络状况不理想的情况下,用户在网页加载的期间仍然能够看到一小段时间的白屏。
针对这种情况,我们可以手动index.html中加点动画元素,一张图片,或者svg等等。
但是这种做法每次打包都需要手动复制粘贴,显然很不优雅。
我们可以利用 html-webpack-plugin
将自定义的 loading 的对象带入到index.html
1 | const fs = require('fs'); |
1 |
|
prerender-spa-plugin
预渲染首屏
在某些项目中,loading本身就可能被设计成一个组件,这个时候就需要先渲染出组件的真实DOM。首先用 headless browser
(比如:puppeteer)执行js,渲染完成后,读取html插入到我们的index.html
。
以上的步骤都已经集成在了 prerender-spa-plugin
1 | const path = require('path') |
更多的高级用法可以去访问仓库的文档查看。
预渲染使用方法参考:Vue单页面骨架屏实践
本地静态资源(图片等)的预加载
如果有不少图片使用的频率较高,使用的范围比较广,可以考虑在 app mounted 后延时一小段时间,将那些图片做一个预加载。
// app.vue (template)
1 | <div> |
// app.vue
1 | export default { |
上述方法需要人肉添加图片到模板中,显然很不优雅
那有没有优雅又高效的方法呢?答案是肯定有的。
假设我们的 assets
文件夹是这样的:
1 | - assets |
我们在 images
的文件夹中加入 index.js
,并添加以下代码:
1 | export default { |
然后在 assets
文件夹在添加 index.js
,代码:
1 | import images from './images'; |
这是,文件目录应该是这样的:
1 | - assets |
使用起来也很简单,我们回到 App.vue
在 Vue
的实例 created
时:
1 | // App.vue <script> |
1 | <!-- App.vue template --> |
关于 webpack 如何处理静态资源路径,这里有个中文文档
使用动态 polyfill
polyfill
为不支持ES2015+的老旧浏览器提供了一个完整的 ES2015+ 环境,这样就可以使用新的内置对象比如 Promise 或者 WeakMap, 静态方法比如 Array.from 或者 Object.assign, 实例方法比如 Array.prototype.includes 和生成器函数(提供给你使用 regenerator 插件)。为了达到这一点, polyfill 添加到了全局范围,就像原生类型比如 String 一样。
但是 polyfill
的体积通常都很大,而且几乎所有的现代浏览器都已经能运行绝大部分的ES2015+环境,polyfill
只是为那一小部分用户提供的’垫片’。
为了只在需要 polyfill
的时候加载特定的一部分,我们可以使用polyfill.io
- 引入polyfill
1 | <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script> |
- 引入特定部分的polyfill
1 | <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Map"></script> |
- polyfill.io会根据浏览器的UserAgent判断浏览器需要哪些polyfill,然后只再返回需要的那部分。
你可以使用 Chrome 和 Edge,IE 访问 https://cdn.polyfill.io/v2/polyfill.min.js 试试。
在生产环境中部署ES2015+
原文链接:【译】如何在生产环境中部署ES2015+
我们在打包时通常会了兼容老旧浏览器,使用babel将es2015+代码转译成es5(语法部分),代价就是js体积会变大不少。
如果能让浏览器自动加载他们能够识别的那部分JavaScript
岂不美哉?是的,有这种方案了。
虽然说目前并没有一个非常好的解决方案,但是我们可以通过<script type="module">
过滤出能够运行整个es2015+环境的浏览器。
能够识别出 type=module 的浏览器同样支持 async 和 await 函数, Class 类,arrow functions,fetch 、Promises、Map、Set 等更多 ES2015+ 语法。
我们可以单独打包出一份 es2015+
的代码,然后另外打包一份降级到 es5 的代码。
以下说明摘自原文[译]
- 实现方式
例如,假设你使用了 webpack 并且 JS 的入口文件是 ./path/to/main.js ,你当前的 ES5 版本的配置应该如下所示(注意,由于使用 ES5 语法书写,我给该代码包命名为 main-legacy )
1 | module.exports = { |
为了支持 ES2015+ 版本,你需要做的是生成第二个配置文件,该配置文件的使用环境是支持 <script type="module">
的浏览器, 如下面所示:
1 | module.exports = { |
接下来的步骤就是修改 HTML 代码,有条件的加载浏览器中支持 ES2015+ 的模块。你可以使用下面两个标签 <script type="module">
和 <script nomodule>
:
1 | <!-- Browsers with ES module support load this file. --> |
注意:这里唯一的问题是 Safari 10 并不支持 nomodule
属性,但是为了解决这一问题,你可以在使用 <script nomodule>
标签前,在 HTML 中使用内联JavaScript代码片段(注意:这个插件已经安装在 Safari11 版本中了)。
添加骨架屏
参考链接:为vue项目添加骨架屏
具体用法和相关概念可以查看上面列出的链接
优化webpack打包配置
禁用 sourceMap
找到build/index.js
修改 build
对象的 productionSourceMap
1 | module.exports = { |
禁用 sourceMap
可以有效减小体积,但是线上出问题时没有办法定位问题所在的代码。是否禁用看需求。
升级到webpack4, 使用 SplitChunksPlugin
webpack4默认使用 SplitChunksPlugin
分割代码,比起 CommonChunksPlugin
,SplitChunksPlugin
采用不同的方法分割代码,默认规则是:
- 模块被重复引用或者来自node_modules中的模块
- 在压缩前最小为30kb
- 在按需加载时,请求数量小于等于5
- 在初始化加载时,请求数量小于等于3
小于30kb的模块不值得再单独发送一次请求,在很小的模块的前提下,相比与多次打包,减少请求次数成本要低。
自定义配置参考:webpack4——SplitChunksPlugin使用指南
1 | new webpack.optimize.SplitChunksPlugin({ |
此外,需要注意的是,如果用到了 html-webpack-plugin
, 则需要安装 html-webpack-plugin@next
。
其他优化
- 网上有不少资料都会提到DllPlugin 和 DllReferencePlugin,使用cdn等手段进行优化打包,但是这些方法其实都只是加快build的速度,对Vue的首屏加载性能没有起到作用。
结论
- 配合vue-router使用异步加载
- 框架的按需加载
- 取消外链css
- 页面加载期间的loading动画
prerender-spa-plugin
预渲染首屏- 本地静态资源(图片等)的预加载
- 使用动态
polyfill
- 在生产环境中部署ES2015+
- 添加骨架屏
- 打包时禁用
sourceMap
- 升级到webpack4, 使用 SplitChunksPlugin