Vue SPA 项目webpack打包优化指南
栏目: JavaScript · 发布时间: 6年前
内容简介:最近一个小伙伴问我他们公司的Vue后台项目怎么首次加载要十多秒太慢了,有什么能优化的,于是乎我打开了他们的网站,发现主要耗时在加载vendor.js文件这个文件高达2M,于是乎我就拿来他们的代码看看,进行了一番折腾。最终还是取得了不错的效果。对于网页性能,如何提升加载速度、等原理以及操作,在修言 大佬 这本本文将主要从
最近一个小伙伴问我他们公司的Vue后台项目怎么首次加载要十多秒太慢了,有什么能优化的,于是乎我打开了他们的网站,发现主要耗时在加载vendor.js文件这个文件高达2M,于是乎我就拿来他们的代码看看,进行了一番折腾。最终还是取得了不错的效果。
优化思路
对于网页性能,如何提升加载速度、等原理以及操作,在修言 大佬 这本 《前端性能优化原理与实践》 书中介绍的很详细,有兴趣的小伙伴可以去看看。
本文将主要从 webpack
打包的角度进行一些首屏加载速度的优化,以及打包速度的优化的实践
优化成效
我选取的是一个用vue-cli2.0+版本构建的 Vue
+ Vuex
+ Vue-router
+ axios
+ elment-ui
的一个后台系统项目进行测试,大概有20个异步加载路由页面。
我们将优化分成了 3 个主要的角度,每一个角度优化后进行速度打包速度的测试,打包构建花费的时间列在下面:
-
优化resolve.modules、配置装载机的 include & exclude、使用webpack-parallel-uglify-plugin 压缩代码
-
配置 externals 使库文件采用cdn加载
-
webpack DllPlugin、webpack DllReferencePlugin 分离框架库文件
次数\打包耗时(s) | 原始配置用时 | 优化步骤1 | 优化步骤2 | 优化步骤3 |
---|---|---|---|---|
1 | 24.86 | == 23.86 == | 11.22 | 13.92 |
2 | 23.52 | 14.51 | 11.04 | 12.63 |
3 | 25.49 | 14.04 | 11.29 | 13.19 |
4 | 24.84 | 14.56 | 11.25 | 13.14 |
5 | 24.60 | 15.44 | 11.86 | 14 |
由此可看出,还是能达到显著的提升了10多s左右效果。具体时间,当然跟你的项目又关系。接下来,我们将介绍如何具体操作。
优化步骤
1. 通过基本的webpack插件来加速打包
我们首先通过修改基本的 webpack
配置的方式提升打包速率
1.优化resolve.modules
原理:
-
webpack 的 resolve.modules 是用来配置模块库(即 node_modules)所在的位置。当 js 里出现 import 'vue' 这样不是相对、也不是绝对路径的写法时,它便会到 node_modules 目录下去找。
-
在默认配置下,webpack 会采用向上递归搜索的方式去寻找。但通常项目目录里只有一个 node_modules,且是在项目根目录。为了减少搜索范围,可我们以直接写明 node_modules 的全路径
所以平时在写 import
导入模块的时候引入指向的是具体的哪个文件,也对打包速度的提升又一定的影响
操作:
打开 build/webpack.base.conf.js
文件,添加如下 modules
代码块:
module.exports = { resolve: { ... modules: [ resolve('src'), resolve('node_modules') ], ... }, 复制代码
2.配置loader的 include & exclude
原理:
-
webpack
的loaders
里的每个子项都可以有 include 和 exclude 属性:
- include:导入的文件将由加载程序转换的路径或文件数组(把要处理的目录包括进来)
- exclude:不能满足的条件(排除不处理的目录)
- 我们可以使用 include 更精确地指定要处理的目录,这可以减少不必要的遍历,从而减少性能损失。
- 同时使用 exclude 对于已经明确知道的,不需要处理的目录,予以排除,从而进一步提升性能。
操作:
打开 build/webpack.base.conf.js
文件,添加如下 include
, exclude
配置:
module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig, include: [resolve('src')], // 添加配置 exclude: /node_modules\/(?!(autotrack|dom-utils))|vendor\.dll\.js/ // 添加配置 }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')], // 添加配置 exclude: /node_modules/ // 添加配置 }, 复制代码
除此之外,如果我们选择开启缓存将转译结果缓存至文件系统,则至少可以将 babel-loader 的工作效率提升两倍。要做到这点,我们只需要为 loader 增加相应的参数设定:
loader: 'babel-loader?cacheDirectory=true' 复制代码
3.使用 webpack-parallel-uglify-plugin 插件来压缩代码
原理:
- 默认情况下
webpack
使用UglifyJS
插件进行代码压缩,但由于其采用单线程压缩,速度很慢。 - 我们可以改用
webpack-parallel-uglify-plugin
插件,它可以并行运行UglifyJS
插件,从而更加充分、合理的使用 CPU 资源,从而大大减少构建时间,该插件能设置缓存,大大减小构建时间。
操作: 1.安装 webpack-parallel-uglify-plugin
插件
yarn add webpack-parallel-uglify-plugin -D // or npm i webpack-parallel-uglify-plugin -D 复制代码
2.打开 build/webpack.prod.conf.js
文件,并作如下修改
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); ... // 删掉webpack提供的UglifyJS插件 //new UglifyJsPlugin({ // uglifyOptions: { // compress: { // warnings: false // } // }, // sourceMap: config.build.productionSourceMap, // parallel: true //}), // 增加 webpack-parallel-uglify-plugin来替换 new ParallelUglifyPlugin({ cacheDir: '.cache/', uglifyJS:{ output: { comments: false }, compress: { warnings: false, drop_debugger: true, // 去除生产环境的 debugger 和 console.log drop_console: true } } }), ... 复制代码
使用 HappyPack 来加速代码构建
原理:
- 由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的事情只能一件一件地做,不能多件事一起做。
- 而 HappyPack 的处理思路是:将原有的 webpack 对 loader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建。
操作:
这一步具体操作,就没贴代码了,我感觉没作用不明显,时间还加了一点点,可能是跟项目有关把,想使用的小伙伴自行百度用到自己项目里面试试。
查看效果
当你把上面这些优化都做完了,运行build的时候发现第一次所需要的构建时间跟最开始一样23s左右,稍微少了2秒(主要是优化resolve,loader等的效果)
再次build的时候时间大大减少,因为在跟目录下 .cache/
下缓存了 Uglify
相关的js多以大大提高了构建的速度。赶紧去试试把。小伙伴们。
2. 配置 externals 使库文件采用cdn加载
开头说到由于 vendor.js
过大引起的首页加载慢,但是vue打包好的 vendor.js 是由什么构成的呢?
vue-cli 生成的项目中 集成了 webpack-bundle-analyzer 依赖可视化分析工具
运行
npm run build --report 复制代码根据上图所知
vendor.js
Parsed 后为739kb,包主要包含了 像
Vue
、
Vue-router
、
elment-ui
等之类需要全局引入的库文件。这些库文件都是一些不经常变动的问题,所以我们可以考虑把他们分离出来,用cdn的方式把框架库引入。
原理:
利用 webpack
的 externals
属性 。文档
官网的解释 : 防止 将某些 import 的包(package) 打包 到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
通俗的解释:让某些资源包即使不在本地npm安装,通过 script
标签引入后也能使用
操作:
- 首先在模板文件
index.html
中添加以下内容
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>XXXX平台</title> <link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.4.1/theme-chalk/index.css"> </head> <body> <div id="app"></div> <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script> <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script> <script src="https://cdn.bootcss.com/axios/0.17.0/axios.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.4.1/index.js"></script> <!-- built files will be auto injected --> </body> </html> 复制代码
注意!版本号要与 package.json
中的版本号一致
- 修改
build/webpack.base.conf.js
module.exports = { ... externals: { 'vue': 'Vue', 'vuex': 'Vuex', 'vue-router': 'VueRouter', 'axios': 'axios', 'element-ui': 'ELEMENT' } ... } 复制代码
注意!这里 axios
变量名要使用 axios
注意!这里 element-ui
变量名要使用 ELEMENT
,因为 element-ui
的 umd
模块名是 ELEMENT
- 修改
src/router/index.js
// import Vue from 'vue' import VueRouter from 'vue-router' // 注释掉 // Vue.use(VueRouter) ... } 复制代码
- 修改
src/store/index.js
... // 注释掉 // Vue.use(Vuex) ... } 复制代码
- 修改
src/main.js
import Vue from 'vue' import App from './App.vue' import ElementUI from 'element-ui' // 注释掉 // import 'element-ui/lib/theme-chalk/index.css' // router setup import router from './router' // Vuex setup import store from './store' Vue.use(ElementUI) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, template: '<App/>', components: { App } }) 复制代码
完事
上面都配置好了后启动 npm run build
发现构建时间在11-12s左右,为什么相比较于步骤1的提升并不大呢,因为步骤1中 ParallelUglifyPlugin
在重复构建中,并没有改动代码,缓存起了重要作用
vendor
包Parsed 后只有
24KB
左右,框架文件利用cdn加速,以及浏览器缓存机制,可以显著提升首页的访问速度。我们可以把文件部署在服务器上,打开Chrome network查看具体的加载用时。
缺点
- 此方法就没办法使用
vue-devtools
谷歌调试 工具 了,毕竟直接用的线上的资源包。但是,根据环境做区分修改部分代码,就可以实现开发环境用的本地包,打包后的使用cdn资源。具体请参考这位大佬的实践 Vue SPA 首屏加载优化实践 ,可以区分环境来引入。 - 请求代价可能大于下载代价,在web优化指南中,就是尽量整合文件,减小请求数量,这样多了很多cdn资源并不一定合适。。
3. webpack DllPlugin
、 webpack DllReferencePlugin
预编译第三方库文件
既然 cdn 还是有他的弊端,那么我们为何不考虑把库文件合并呢,所以我们利用 webpack.DllPlugin
+ webpack DllReferencePlugin
+ add-asset-html-webpack-plugin
预编译并且引入
原理:
- 利用
webpack DllPlugin
插件将第三方插件单独打包出来至vendor.dll.js
- 利用
webpack DllReferencePlugin
是把这些预先编译好的模块引用起来 - 利用
add-asset-html-webpack-plugin
把vendor.dll.js
包插入html
操作:
我们还是从操作1完成后继续修改代码(cdn的相关操作代码退回)
- 在
build
文件夹中新建webpack.dll.conf.js
文件,内容如下(主要是配置下需要提前编译打包的库):
var path = require('path') var webpack = require('webpack') var context = path.join(__dirname, '..') module.exports = { entry: { vendor: [ 'vue/dist/vue.common.js', 'vuex', 'vue-router', 'axios', 'element-ui' ] }, output: { path: path.join(context, 'static/js'), // 打包后的 vendor.js放入 static/js 路径下 filename: '[name].dll.js', library: '[name]' }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new webpack.DllPlugin({ path: path.join(context, '[name].manifest.json'), name: '[name]', context: context }), // 压缩js代码 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, output: { // 删除打包后的注释 comments: false } }) ] } 复制代码
- 编辑
package.json
文件,添加一条编译命令:
"scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "lint": "eslint --ext .js,.vue src", "build": "node build/build.js", "build:dll": "webpack --config build/webpack.dll.conf.js --progress" }, 复制代码
然后命令行运行 npm run build:dll
这时,会在 static/js 里面生成 vendor.dll.js
, vendor
属性内的相关库文件就打包在内了。
- 打开
index.html
这边将vendor.dll.js
引入进来。
<body> <div id="app"></div> <script src="./static/js/vendor.dll.js"></script> </body> 复制代码
- 打开
build/webpack.base.conf.js
文件,编辑添加如下配置,作用是通过 DLLReferencePlugin 来使用 DllPlugin 生成的 DLL Bundle
const webpack = require('webpack'); module.exports = { ... plugins: [ new webpack.DllReferencePlugin({ // name参数和dllplugin里面name一致,可以不传 name: 'vendor', // dllplugin 打包输出的manifest.json manifest: require('../vendor.manifest.json'), // 和dllplugin里面的context一致 context: path.join(__dirname, '..') }) ] ... } 复制代码
- 修改
build/webpack.prod.js
注释掉CommonsChunkPlugin
相关代码,因为库文件在之前的 vendor.dll.js 中已经编译好了,不需要在编译
module.exports = { plugins: [ ... // 去掉这里的CommonsChunkPlugin // new webpack.optimize.CommonsChunkPlugin({ // name: 'vendor', // minChunks (module) { // // any required modules inside node_modules are extracted to vendor // return ( // module.resource && // /\.js$/.test(module.resource) && // module.resource.indexOf( // path.join(__dirname, '../node_modules') // ) === 0 // ) // } // }), // 去掉这里的CommonsChunkPlugin // new webpack.optimize.CommonsChunkPlugin({ // name: 'manifest', // minChunks: Infinity // }), ... ] } 复制代码
完事
至此,保存代码,进行构建,发现构建时间大概在14s左右。怎么比cdn时间还增多了呢,因为element-ui的样式文件还需要每次打包,样式不建议单独打包出来,要么也是使用cdn的方式。
最后我们还是部署到服务器上打开Chrome network查看网页具体的加载用时。
打开构建依赖图,发现vendor
文件已经不见了,不需要每次打包了,直接引入
vendor.dll.js
文件就好,这样还有一个好处:当你有多个项目的依赖相同的时候,引用同一份
dll
即可。
真的就完事儿了?大家有没有注意到 vendor.dll.js
是一个固定的文件,没有加 hash 后缀,这对缓存来说是致命的,当你升级了库或者增加了库文件,重新打包后的 还是叫做 vendor.dll.js
文件,没有破坏缓存,当用户访问时程序可能会出现问题。
有时候开发环境和测试环境可能 引入的 vendor.dll.js
路径不一样你得手动更改,也是一个问题。既然这样怎么办呢??
还好有 add-asset-html-webpack-plugin
这个插件进行依赖资源的注入,本人在实践的时候以为找到了救命稻草。可是奈何不知道是姿势不对,还是该插件已经过时未升级,程序运行时候报错,无法使用,也希望使用过的大佬,指点一下。。
结语
至此关于 Vue SPA 项目中的优化,介绍的差不多了,但是仅仅只是提供一个思路,优化并不是一成不变的,有些项目可能只需要步骤1,有些项目可能引用资源小采用cdn的方式也可以,而有些多个项目依赖都相同,就可考虑dll,当然是根据具体的场景来进行选择优化。
最终还是以部署到服务器后,清除缓存访问,后分析加载时间。毕竟加载时间比打包时间重要得多
但是,我们平时写代码的时候应该多多思考,在写代码的时候注意一些细节,也能提升不少效率和性能。
举个例子1:很多项目会用到 echarts
,我发现有小伙伴把 echarts
注入在 main.js
中,这显然是没必要的白白增大了 vendor.js
的大小,应该在仅仅需要使用的页面去引入就好,还得注意 echarts
的地图组件,是采用同步渲染,还是异步渲染好呢,还有根据窗口的 resize
,是否注意防抖和节流呢。
举个例子2:当我们使用百度地图的jssdk的时候,是在 index.html
里面通过 script
标签引入,还是在某个页面需要使用地图的时候采用异步加载的形式呢。这些都是值得我们思考的问题。
所以从每一步写代码的细节多多思考。
至此写完了,我也是抱着学习的态度,如有什么错误,请大佬们斧正,顺便请教 add-asset-html-webpack-plugin
的正确姿势。
以上所述就是小编给大家介绍的《Vue SPA 项目webpack打包优化指南》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【前端打包部署】谈一谈我在SPA项目打包=>部署的处理
- Maven多模块项目打包前的一些注意事项(打包失败)
- tar打包如何不打包某一个文件夹(排除某些文件夹)
- iOS新手用swift写一个macos打包工具 一键打包到指定位置
- Android应用签名打包
- 多渠道打包
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。