前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue打包优化之code spliting

Vue打包优化之code spliting

作者头像
QQ音乐技术团队
发布2023-03-01 15:10:32
2.1K0
发布2023-03-01 15:10:32
举报
文章被收录于专栏:QQ音乐技术团队的专栏

前言

在http1的时代,比较常见的一种性能优化就是合并http的请求数量,通常我们会把许多js代码合并在一起,但是如果一个js包体积特别大的话对于性能提升来说就有点矫枉过正了。而如果我们对所有的代码进行合理的拆分,将首屏和非首屏的代码进行剥离,将业务代码和基础库代码进行拆分,在需要某段代码的时候再加载它,下次若再需要用则从缓存中读取,一来可以更好地使用浏览器缓存,再者就是可以提高首屏加载速度,很好提升用户的体验。

核心思想

业务代码和基础库的分离

这个其实很好理解,业务代码通常更新迭代很频繁,而基础库通常更新缓慢,这里做拆分的话可以充分利用浏览器缓存来加载基础库代码。

按需异步加载

这个主要解决首屏请求大小的问题,我们在访问首屏的时候只需要加载首屏所需的逻辑,而不是加载所有路由的代码。

实战

最近,采用vuetify改造了一个内部系统,一开始用了最常用的webpack配置,功能很快开发了,可是一打包,发现效果不是很明显,打出很多大包。

这里我们看下打包分布,这里使用的是 webpack-bundle-analyzer,可以很清晰的看到 vue 和 vuetify等模块都有出现 被重复打包的情况。

CommonChunkPlugin

ventor入口这里我们发现并没有筛选出所有引用的node_module下的模块 ,比如axios ,所以导致打包到了app.js里了,这里我们做下分离

代码语言:javascript
复制
entry: {    vendor: ['vue', 'vue-router', 'vuetify', 'axios'],    app: './src/main.js'  },

那这里又出现个问题了,我不可能手动去手动录入模块,这时我们可能需要 自动化分离 ventor,这里我们需要引入 minChunks,在配置中我们就可以对所有mode_module下所引用的模块进行打包 修改配置如下

代码语言:javascript
复制
entry: {    //vendor: ['vue', 'vue-router', 'vuetify', 'axios'], //删除    app: './src/main.js'  }new webpack.optimize.CommonsChunkPlugin({        name: 'vendor',        minChunks: ({ resource }) => (          resource &&          resource.indexOf('node_modules') >= 0 &&          resource.match(/\.js$/)        ) }),

经过上面几步的优化,我们再看看文件分布,会发现node_module下的模块都收归到了vendor下了。

这里我们可以得到一个经验,就是在一个项目中可以专门针对nodemodule下的模块进行打包优化。但是这里细心的你可能发现codemirror组件不也是nodemodule中的么,但为啥没被打包进去反而重复打包到其他单页面了呢,其实这里是因为在commonChunk中使用name属性其实也就意味着只会沿着entry入口去找寻所依赖的包,由于我们的组件采用的是异步加载,故这里就不会去打包了,我们做个实验验证下,现在我们去掉dbmanage和system页面的路由懒加载改为直接引入

代码语言:javascript
复制
// const dbmanage = () => import(/* webpackChunkName: "dbmanage" */'../views/dbmanage.vue')// const system = () => import(/* webpackChunkName: "system" */'../views/system.vue')import dbmanage from '../views/dbmanage.vue'import system from '../views/system.vue'

这时我们重新打包可以发现,codemirror被打包进来了,那么问题来了,这样子好么?

async

上面的问题答案是肯定的,不可以的,很明显ventor是我们的入口代码即首屏,我们完全没有必要去加载这个codemirror组件,我们先把刚才的路由修改恢复回去。但是这时又有了新问题,我们的codemirror被同时打包进了两个单页面,并且还有些自己封装的components,例如MTable或是MDataTable等也出现了重复打包。并且codemirror特别大,同时加载到两个单页面也会造成很大的性能问题,简单说就是,我们在访问第一个单页面加载了codemirror之后,在第二个页面其实就不应该再加载了。 要解决这个问题,这里我们可以使用 CommonsChunkPlugin 的 async 并在 minChunnks 里的count方法来判断数量,只要是 重用次数 超过两个包括两个的异步加载模块(即 import () 产生的chunk )我们都认为是 可以 打成公共的 ,这里我们增加一项配置。

代码语言:javascript
复制
new webpack.optimize.CommonsChunkPlugin({  async: 'used-twice',  minChunks: (module, count) => (    count >= 2  ),})

再次打包,我们发现所有服用的组件被重新打到了 0.used-twice-app.js中了,这样各个单页面大小也有所下降,平均小了近10k左右

可是,这里我们发现vuetify.js和vuetify.css实在太庞大了,导致我们的打包的代码很大,这里,我们考虑把它提取出来,这里为了避免重复打包,需要使用external,并将vue以及vuetify的代码采用cdn读取的方式,首先修改index.html

代码语言:javascript
复制
css引入<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css"><link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet">js引入<script src="https://unpkg.com/vue/dist/vue.js"></script><script src="https://unpkg.com/vuetify/dist/vuetify.js"></script>//去掉main.js中之前对vuetifycss的引入//import 'vuetify/dist/vuetify.css'

再修改webpack配置,新增externals

代码语言:javascript
复制
externals: {    'vue':'Vue',    "vuetify":"Vuetify"  }

再重新打包,可以看到vue相关的代码已经没有了,目前也只有used-twice-app.js比较大了,app.js缩小了近200kb。

但是新问题又来了,codemirror很大,而used-twice又是首屏需要的,这个打包在首屏肯定不是很好,这里我们要将system和dbmanage页面的codemirror组件改为异步加载,单独打包,修改如下:

代码语言:javascript
复制
// import MCode from "../component/MCode.vue"; //注释掉components: {      MDialog,      MCode: () => import(/* webpackChunkName: "MCode" */'../component/MCode.vue') },

重新打包下,可以看到 codemirror被抽离了,首屏代码进一步得到了减少,used-twice-app.js代码缩小了近150k。

做了上面这么多的优化之后,业务测的js基本都被拆到了50kb一下(忽略map文件),算是优化成功了。

总结

简单来讲,就是通过三个步骤来优化: 1. 利用commonChunkPlugin的minChunks属性来分离基础库(node_module)代码和业务代码并针对多次复用的模块进行单独打包;

2. 利用webpack的externals属性从打包的代码中抽离出vue以及vuetify代码;

3. 利用()=>import方式异步加载方式抽离非首屏代码。

这里最后贴一下优化后的webpack配置,大家一起交流学习下哈。

代码语言:javascript
复制
const path = require('path')const webpack = require('webpack')const CleanWebpackPlugin = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;const generateHtml = new HtmlWebpackPlugin({  title: '逍遥系统',  template: './src/index.html',  minify: {    removeComments: true  }})module.exports = {  entry: {    app: './src/main.js'  },  output: {    path: path.resolve(__dirname, './dist'),    filename: '[name].[hash].js',    chunkFilename:'[id].[name].[chunkhash].js'  },  resolve: {    extensions: ['.js', '.vue'],    alias: {      'vue$': 'vue/dist/vue.esm.js',      'public': path.resolve(__dirname, './public')    }  },  externals: {    'vue':'Vue',    "vuetify":"Vuetify"  },  module: {    rules: [      {        test: /\.vue$/,        loader: 'vue-loader',        options: {          loaders: {          }          // other vue-loader options go here        }      },      {        test: /\.js$/,        loader: 'babel-loader',        exclude: /node_modules/      },      {        test: /\.(png|jpg|gif|svg)$/,        loader: 'file-loader',        options: {          objectAssign: 'Object.assign'        }      },      {        test: /\.css$/,        loader: ['style-loader', 'css-loader']      },      {        test: /\.styl$/,        loader: ['style-loader', 'css-loader', 'stylus-loader']      }    ]  },  devServer: {    historyApiFallback: true,    noInfo: true  },  performance: {    hints: false  },  devtool: '#eval-source-map',  plugins: [      new CleanWebpackPlugin(['dist']),      generateHtml  ]}if (process.env.NODE_ENV === 'production') {  module.exports.devtool = '#source-map'  module.exports.plugins = (module.exports.plugins || []).concat([    new BundleAnalyzerPlugin(),    new webpack.optimize.CommonsChunkPlugin({      name: 'ventor',      minChunks: ({ resource }) => (        resource &&        resource.indexOf('node_modules') >= 0 &&        resource.match(/\.js$/)      )    }),    new webpack.optimize.CommonsChunkPlugin({      async: 'used-twice',      minChunks: (module, count) => (        count >= 2      ),    }),    new webpack.DefinePlugin({      'process.env': {        NODE_ENV: '"production"'      }    }),    new webpack.optimize.UglifyJsPlugin({      sourceMap: true,      compress: {        warnings: false      }    }),    new webpack.LoaderOptionsPlugin({      minimize: true    })  ])}

QQ音乐团队诚聘测试、研发。有意者请发送简历至tmezp@tencent.com,请注明来自公众号,我们将优先拜读。

参考资料:

  1. Webpack 大法之 Code Splitting:https://zhuanlan.zhihu.com/p/26710831
  2. vue+webpack实现异步组件加载:http://blog.csdn.net/weixin_36094484/article/details/74555017
  3. VUE2组件懒加载浅析:https://www.cnblogs.com/zhanyishu/p/6587571.html
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯音乐技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 核心思想
    • 业务代码和基础库的分离
      • 按需异步加载
      • 实战
      • CommonChunkPlugin
      • async
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档