Vue工程打包优化
FridaS opened this issue · comments
Vue工程打包优化
一.前言
本文以考拉社区后台工程(kaola-communityms-web)为例进行分析。
首先介绍一个可视化打包分析工具——webpack-bundle-analyzer。
kaola-communityms-web已经做了如下配置:
// webapp/config/index.js
module.exports = {
build: {
//...
bundleAnalyzerReport: process.env.npm_config_report
},
}
// webapp/build/webpack.prod.conf.js
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
所以添加指令:
// package.json
{
// ...
"scripts":{
// ...
"analyz": "NODE_ENV=production npm_config_report=true npm run build"
}
}
执行指令npm run analyz
即可启动webpack-bundle-analyzer。
其他工具如 webpack analyse、webpack chart等不再做详细介绍。
二.为什么优化、有什么优化点
webpack打包可以总结为:
- 对于单入口文件,每个入口文件把自己所依赖的资源全部打包到一起,即使一个资源循环加载,也只会打包一份;
- 对于多入口文件,分别独立执行单个入口的情况,每个入口文件各不相干。
所以对于kaola-communityms-web(vue-cli构建,单入口),在默认情况下,执行npm run build
会把所有js代码打包成3个文件
- static/js/vendor.[hash].js //公用模块,该工程配置为/node_modules下的文件
- static/js/app.[hash].js //所有引用到的组件js代码
- static/js/manifest.[hash].js //chunks清单
其中app.[hash].js文件在页面很多时会很大、加载缓慢(特别是在加载一些只在特定环境下才会使用到的阻塞的代码的时候),所以需要把大的文件拆分成多个小块。另外并不是每个页面都需要所有的资源,如果能只加载当前页面所需资源,那页面加载速度将得到有效提升。
对kaola-communityms-web进行本地打包,打包情况如下所示:
可以看到vue-highcharts.js过大,考虑到只有一个页面用到了vue-highcharts,可以把它单独打个包;element-ui中的方法并不是所有页面都有用到,可以考虑按需加载,或单独打包;在static/js/vendor.[hash].js和static/js/app.[hash].js中均打包了element-ui,需要移除重复打包;一些没有用到的库可以移除掉。
所以可以优化点的有:
- 大文件拆分
- 按需加载
- 个别大而使用率不高的资源剥离处理
- 处理重复加载的模块
三.如何优化
-
Vue懒加载——路由懒加载
把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应组件。结合Vue的异步组件和Webpack的代码分割功能(Code splitting),可以轻松实现路由组件的懒加载;另外,在Webpack 2中,我们可以使用动态import语法来定义代码分块点。结合以上两点,定义一个能够被Webpack自动代码分割的异步组件即:const Foo = () => import('./Foo.vue')
在路由配置中:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] })
如果想把某个路由下的所有组件都打包在同个异步块(chunk)中,只需要使用命名chunk(一个特殊的注释语法)来提供chunk name:
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue') const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack会将任何一个异步模块与相同的块名称组合到相同的异步块中,即上述三个组件Foo、Bar、Baz将被打包到同一个chunk中。
-
Vue懒加载——组件懒加载:只下载当前页面需要执行的代码、下载之后缓存起来以供再次渲染,这里用到了vue的异步组件。
new Vue({ // ... components: { 'AsyncCmp': () => import('./AsyncCmp') } })
注:按需加载(一个模块打包一个文件),需要配置webpack的output:
output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') //每个模块都会在打包目录下生成相应的js/[id].[chunkhash].js文件 }
这里有两个问题需要额外提下:
- 如果在两个异步加载的页面中分别同步与异步加载同一个组件,会造成资源重复加载。解决方案:团队约定都使用异步加载组件。
- 在异步加载页面中嵌入异步加载组件对页面渲染会产生影响。解决方案:对页面结构进行合理的设计,尽量避免首次加载闪屏现象。
-
以外源性脚本引入某些不需要改动的模块(webpack的externals):webpack externals配置选项提供了从输出的bundle中排除依赖的方法,它可以防止把某些
import
的包打包到bundle中,在运行时(runtime)才去从外部获取这些扩展依赖,比如从CND引入jQuery,而不是把它打包://index.html <script src="https://code.jquery.com/jquery-3.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous"> </script> //webpack.config.js externals: { jquery: 'jQuery' } // my-component.vue import $ from 'jquery'; $('.my-element').animate(...);//可以正常运行
又比如项目开发中常用到的
moment
等比较大的模块,如果必须引入的话,可以考虑外部引入,再借助externals
予以指定, webpack可以处理使之不参与打包,而依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。 -
webpack插件:webpack提供了一个插件
DllPlugin
,用于将不常变动的js单独打包。DllPlugin 和 DllReferencePlugin 提供了以大幅度提高构建时间性能的方式拆分软件包的方法。其中原理是,将特定的第三方NPM包模块提前构建,然后通过页面引入。这不仅能够使得 vendor 文件大幅度减小,同时,也极大地提高了构建速度。对于kaola-communityms-web,先创建webpack.dll.conf.js、配置需要单独打包的模块,在webpack.prod.conf.js中添加DllReferencePlugin插件配置,在webpack.base.conf.js中添加externals,删掉代码中对这些模块的多余的引用,执行指令即可。// webpack.dll.conf.js // ... module.exports = { // 你想要打包的模块的数组 entry: { vuecharts:['vue2-highcharts'], vendor: ['vue', 'lodash', 'vuex', 'axios', 'vue-router', 'element-ui','moment'] }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, '.', '[name]-manifest.json'), name: '[name]_library', context: __dirname }) // ... ] // ... } // webpack.prod.conf.js // ... plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: merge(require('./vuecharts-manifest.json'),require('./vendor-manifest.json')) }) // ... ] // webpack.base.conf.js // ... externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'elemenct-ui': 'ELEMENT', 'lodash':'lodash', 'vuex':'vuex', 'axios':'axios', 'vue-highcharts':'vue2-highcharts', 'moment':'moment' } // 添加指令 // package.json "dll": "webpack --config ./build/webpack.dll.conf.js"
执行
npm run dll
后生成两个manifest配置文件(vendor-manifest.json、vuecharts-manifest.json)和两个打包文件:
鉴于篇幅,具体用法可见:webpack.dll.conf.js、externals&Dll Reference。
happypack可以加速代码构建。
webpack提供的UglifyJS插件由于采用单线程压缩,速度很慢,webpack-parallel-uglify-plugin插件可以并行运行UglifyJS插件,可以有效减少构建时间(UglifyJsPlugin 不仅可以将未使用的 exports 清除,还能去掉很多不必要的代码,如无用的条件代码、未使用的变量、不可达代码等)。 -
移除组件中没有用到的引入;
-
不生成.map文件
// config/index.js module.exports = { build: { // ... productionSourceMap: false }
-
另外:webpack3新功能Scope Hoisting(作用域提升),只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快。参见 Webpack 3 的新功能:Scope Hoisting。
四.效果
可以看到优化后打包时间从29418ms减少到了11998ms,打包资源大小从1.53MB减少到了367.87KB,并且实现了按需加载(没有加载所有资源)。
五.结束语
性能优化范围广、方法多,还有很多可以提升打包速度、打包文件大小、首屏加载等性能问题的方法,本文只是对部分方法进行学习总结,还需要不断学习、研究更多方案。
六.参考文献
- https://segmentfault.com/q/1010000012211052/a-1020000012211401
- https://segmentfault.com/a/1190000008377195
- http://blog.csdn.net/qq_16559905/article/details/78551719
- hehongwei44/my-blog#203
- https://router.vuejs.org/zh-cn/advanced/lazy-loading.html
- https://router.vuejs.org/en/advanced/lazy-loading.html
- https://segmentfault.com/a/1190000011519350
- https://alexjoverm.github.io/2017/07/16/Lazy-load-in-Vue-using-Webpack-s-code-splitting/
- https://zhuanlan.zhihu.com/p/29433875
- https://www.zhihu.com/question/41147233
- https://cn.vuejs.org/v2/guide/components.html#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6
- http://blog.csdn.net/qq_27626333/article/details/76228578
- http://zakwu.me/2016/09/18/vue-routerpei-zhi/
- https://kinm.github.io/2017/08/24/VUE%E7%BB%84%E4%BB%B6%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5%EF%BC%88%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD%E3%80%81%E8%B7%AF%E7%94%B1%E6%87%92%E5%8A%A0%E8%BD%BD%EF%BC%89/
- eyasliu/blog#8
- https://www.cnblogs.com/zhanyishu/p/6587571.html
- https://www.jianshu.com/p/171e8e529f35
- https://jeffjade.com/2017/08/06/124-webpack-packge-optimization-for-volume/
- http://imweb.io/topic/597f47c790ccc00402bb1820
- https://segmentfault.com/a/1190000012220132
- https://doc.webpack-china.org/configuration/externals/
- https://juejin.im/post/5a337a1f6fb9a0452b4949e0
by Frida
不同的异步组件中引入的相同组件的脚本还是重复打包的,这个问题没有解决。
where can i see your codes?
不同的异步组件中引入的相同组件的脚本还是重复打包的,这个问题没有解决。
我也遇到了这个问题,网上找到的解决方案尝试了下都不行,请问你解决了吗