preface

In the era of HTTP1, one of the more common performance optimizations is to merge the number of HTTP requests. Usually, we merge a lot of JS code together, but if a JS package is too large, it’s a bit of a overkill. And if we are to a reasonable resolution of all of the code, the first screen and the screen code, will take apart business code and basic library code, in need of a piece of code to load it again, next time if you need to use the read from the cache, then can better use the browser cache, moreover is the first screen can improve loading speed, good user experience of ascension.

core idea

Separation of business code from the base library

This is easy to understand. Business code is usually updated frequently, while base libraries are usually updated slowly, so you can use the browser cache to load the base code.

Load asynchronously on demand

This mainly solves the problem of the size of the first screen request. When accessing the first screen, we only need to load the logic required by the first screen, instead of loading all the routing code.

In actual combat

Recently, vuetify revamped an internal system, using the most common Webpack configuration at first. The functionality was quickly developed, but when it was packaged, it wasn’t as effective as it should have been

If we look at the packaging distribution here, using Webpack-bundle-Analyzer, we can clearly see that modules such as Vue and Vuetify are repeatedly packaged.

Here we first paste the configuration, while the analysis for a while:

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: 'Leisure system'.template: './src/index.html'.minify: {
    removeComments: true}})module.exports = {
  entry: {
    vendor: ['vue'.'vue-router'.'vuetify'].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')}},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 BundleAnalyzerPlugin(),
      new CleanWebpackPlugin(['dist']),
      generateHtml,
      new webpack.optimize.CommonsChunkPlugin({
        name: 'ventor']}}),if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'}}),new webpack.optimize.UglifyJsPlugin({
      sourceMap: true.compress: {
        warnings: false}}),new webpack.LoaderOptionsPlugin({
      minimize: true}})])Copy the code

CommonChunkPlugin

In ventor entry, we found that we did not filter out all the modules under the referenced node_module, such as Axios, so we packaged them in app.js. Here we do the separation

entry: {
    vendor: ['vue'.'vue-router'.'vuetify'.'axios'].app: './src/main.js'
  },
Copy the code

There is another problem here, I can’t manually input modules, in this case we may need to automatically detach ventor, in this case we need to introduce minChunks, in the configuration we can package all modules referenced under mode_module and modify the configuration as follows

entry: {
    / / vendor: [' vue ', 'vue - the router', 'vuetify', 'axios], / / deleted
    app: './src/main.js'
  }

new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'.minChunks: ({ resource }) = > (
          resource &&
          resource.indexOf('node_modules') > =0 &&
          resource.match(/\.js$/))}),Copy the code

After the optimization of the above steps, we will look at the file distribution, and we will find that all modules under node_module are under vendor.

A good lesson here is that you can optimize packaging for modules under node_module within a project. The codemirror component is also in node_module, but it is packaged repeatedly in other single pages. In fact, using the name attribute in commonChunk actually means only searching for the dependent package along the entry entry. Since our component adopts asynchronous loading, it will not be packaged here. Let’s do an experiment to verify. Now let’s remove the lazy loading of routes from the DBManage and System pages and import them directly

// 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'
Copy the code

Now, when we repackage, we can see that codemirror has been packaged, so the question is, is this good?

async

The answer to the above question is yes, no, obviously ventor is our entry code, that is, the first screen, we do not need to load the codemirror component, we first changed the route back, but then there was a new problem, our Codemirror was packaged into two single pages at the same time, There are also self-packaged components, such as MTable or MDataTable, that are repackaged. In addition, codemirror is extremely large, and loading the codemirror into two single pages at the same time can cause significant performance problems. Simply put, after loading the Codemirror on the first single page, we should not load the codemirror on the second page. To solve this problem, we can use CommonsChunkPlugin async and count in minChunnks to determine the amount. As long as more than two asynchronous loading modules (i.e. Chunk generated by import ()) are reused, we consider them to be common. Here we add a configuration.

new webpack.optimize.CommonsChunkPlugin({
  async: 'used-twice'.minChunks: (module, count) = > (
    count >= 2})),Copy the code

Once again, we found that all the consumed components were reloaded into 0. Used-twice-app.js, so that the size of each single page also decreased by about 10K on average

However, here we find that vuetify. Js and vuetify. CSS are so large that our packaging code is so large that we consider extracting it. You need to use external and read vue and Vuetify code as CDN, first modifying index.html

CSS is introduced into<link href='https://fonts.googleapis.com/css?family=Roboto:300, 400500700 | Material + Icons' rel="stylesheet" type="text/css">
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet">Js to introduce<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.js"></script>//import 'vuetify/dist/vuetify.css' from main.jsCopy the code

Modify the WebPack configuration and add externals

externals: {
    'vue':'Vue'."vuetify":"Vuetify"
  }
Copy the code

After repackaging, you can see that the code related to vUE is gone. Currently only used-twice-app.js is larger, and app.js has shrunk by nearly 200KB.

The codemirror component of the system and dbManage pages should be loaded asynchronously. The codemirror component of the system and DBManage pages should be loaded asynchronously.

// import MCode from ".. /component/MCode.vue"; / / comment out

components: {
      MDialog,
      MCode: (a)= > import(/* webpackChunkName: "MCode" */'.. /component/MCode.vue')},Copy the code

After repackaging, you can see that codemirror has been removed, the front screen code has been further reduced, and the used-twice-app.js code has been shrunk by nearly 150K.

After doing so many optimizations above, the js of the business test is basically reduced to 50KB (ignoring the map file), which is considered a success of optimization.

conclusion

Some of you might ask that splitting vue and Vuetify would increase the number of requests, but I’d like to add that our business has now switched to HTTP2, and because of multiplexing and browser caching, the number of requests we split is within reasonable limits.

Here is the last post of the optimized Webpack configuration, we exchange and learn next ha.

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: 'Leisure system'.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}})])Copy the code

References:

  • Code Splitting:https://zhuanlan.zhihu.com/p/26710831 Webpack solution
  • Vue + webpack asynchronous component loading: http://blog.csdn.net/weixin_36094484/article/details/74555017
  • VUE2 component lazy loading analyses: https://www.cnblogs.com/zhanyishu/p/6587571.html

The following is our QQ music front end team public number, I hope you support ha, we will try to write a good article to share with you