Principle of webPack asynchronous loading

This article is featured on Github github.com/Michael-lzg…

Webpack Ensure, known to some as asynchronous loading and others as code cutting, simply exports a js module to a.js file, and when using that module, creates a script object and adds it to the Document. head object. The browser will automatically make a request to the JS file for us, and then write a callback function to let the requested JS file do some business operations.

For example

Requirements: Main. js relies on two JS files: a. js is executed after clicking the aBtn button, and B. JS is executed after clicking the bBtn button.

Webpack.config.js, let’s first write the code for the WebPack-packaged configuration

const path = require('path') // Path processing module
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // Introduce the CleanWebpackPlugin plugin

module.exports = {
  entry: {
    index: path.join(__dirname, '/src/main.js'),},output: {
    path: path.join(__dirname, '/dist'),
    filename: 'index.js',},plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '/index.html'),}),new CleanWebpackPlugin(), // The name of the folder to clean],}Copy the code

The index. HTML code is as follows

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>webpack</title>
  </head>
  <body>
    <div id="app">
      <button id="aBtn">Button A</button>
      <button id="bBtn">Button B</button>
    </div>
  </body>
</html>
Copy the code

The entry file main.js is as follows

import A from './A'
import B from './B'

document.getElementById('aBtn').onclick = function () {
  alert(A)
}

document.getElementById('bBtn').onclick = function () {
  alert(B)
}
Copy the code

The codes of A.js and B. JS are as follows

// A.js
const A = 'hello A'
module.exports = A

// B.js
const B = 'hello B'
module.exports = B
Copy the code

At this point, we NPM run build the project and only two files are packaged

  • index.html
  • index.js

As you can see, webPack has packaged the two files that main.js depends on into the same JS file and introduced them in index.html. However, both A.JS and B.JS are executed by clicking the corresponding button. If the user does not click the corresponding button, and the two files are relatively large, will the js file loaded by default on the home page be too large, resulting in a slow rendering of the home page? Is it possible to load the dependent file when the user clicks the button?

Webpack.ensure solves this problem.

Require. Ensure Asynchronous loading

Let’s change main.js to be loaded asynchronously

document.getElementById('aBtn').onclick = function () {
  // Load A asynchronously
  require.ensure([], function () {
    let A = require('./A.js')
    alert(A)
  })
}

document.getElementById('bBtn').onclick = function () {
  // Load b asynchronously
  require.ensure([], function () {
    let B = require('./B.js')
    alert(B)
  })
}
Copy the code

At this point, we packaging again, and found that 1.index.js and 2.index.js are two more files. When we open the page, we only introduce the index.js file. When we click button A, we introduce the 1.index.js file, and when we click button B, we introduce the 2.index.js file. This satisfies our need to load on demand.

Ensure this function is a code separation line, indicating that the require in the callback is the one we want to separate out, i.e. Require (‘./ a.js ‘), which separates the a.js into a separate WebPack JS file. The syntax is as follows

require.ensure(dependencies: String[].callback: function(require), chunkName: String)
Copy the code

We open the 1.index.js file and find its code as follows

; (window.webpackJsonp = window.webpackJsonp || []).push([
  [1], [,function (o, n) {
      o.exports = 'hello A'},]])Copy the code

As you can see from the above code:

  1. Asynchronously loaded code is stored in a global filewebpackJsonpIn the.
  2. webpackJsonp.pushThe two parameters are respectively the id of the module to be installed in the file to be loaded asynchronously and the list of modules to be installed in the file to be loaded asynchronously.
  3. When certain conditions are met, the code in the specific module is executed.

Import () is loaded on demand

Webpack4 official documents provide modules to be cut and loaded on demand. Combined with es6’s import() method of loading on demand, the volume of home page packages can be reduced and the request speed of home page can be accelerated. Only other modules can load corresponding JS when needed.

The syntax for import() is simple. This function takes only one argument, the address of the referenced package, and uses a Promise-style callback to get the package loaded. All modules import() in the code are typed into a single package and placed in the chunk storage directory. When the browser runs to this line of code, the resource is automatically requested and loaded asynchronously.

Let’s change the above code to import().

document.getElementById('aBtn').onclick = function () {
  // Load A asynchronously
  import('./A').then((data) = > {
    alert(data.A)
  })
}

document.getElementById('bBtn').onclick = function () {
  // Load b asynchronously
  import('./B').then((data) = > {
    alert(data.B)
  })
}
Copy the code

The file that comes out of the package is the same as the webpack.ensure method.

The route was loaded lazily. Procedure

Why do you need lazy loading?

For a single-page application like Vue, if there is no lazy route loading, the file packaged with WebPack will be very large, resulting in too much content to be loaded when entering the home page, resulting in a long period of white screen. If lazy route loading is used, the page can be divided and the page can be loaded only when needed. It can effectively share the loading pressure of the home page and reduce the loading time of the home page.

There are three methods for lazy vUE route loading

  • Vue Asynchronous component
  • The ES6import()
  • The webpackrequire.ensure()

Vue Asynchronous component

This approach uses the resolve asynchronous mechanism and uses require instead of import for on-demand loading

export default new Router({
  routes: [{path: '/home'.', component: (resolve) => require(['@/components/home'], resolve), }, { path: '/about', '.component: (resolve) = > require(['@/components/about'], resolve),
    },
  ],
})
Copy the code

require.ensure

This pattern can be used to unpack js with webpackChunkName in the parameter.

export default new Router({
  routes: [{path: '/home'.component: (resolve) = > require.ensure([], () = > resolve(require('@/components/home')), 'home'),}, {path: '/about'.component: (resolve) = > require.ensure([], () = > resolve(require('@/components/about')), 'about'),},],})Copy the code

ES6 import ()

Vue-router provides a method on the website, which is also understood to work through the Promise’s resolve mechanism. Because the Promise function returns a Promise for the resolve component itself, we can use import to import the component.

export default new Router({
  routes: [{path: '/home'.component: () = > import('@/components/home'),}, {path: '/about'.component: () = > import('@/components/home'),},],})Copy the code

Webpack subcontracting strategy

In the process of webPack packaging, it is often the case that single vvendor. Js and app.js files are large, which is the first file loaded by the web page. This will make the loading time too long, which will make the white screen time too long, and affect the user experience. So we need to have a reasonable subcontracting strategy.

CommonsChunkPlugin

Before the Webapck4.x release, we used CommonsChunkPlugin for separation

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor'.minChunks: function (module, count) {
      return (
        module.resource &&
        /\.js$/.test(module.resource) &&
        module.resource.indexOf(path.join(__dirname, './node_modules'= = =))0)}}),new webpack.optimize.CommonsChunkPlugin({
    name: 'common'.chunks: 'initial'.minChunks: 2,}),]Copy the code

Let’s separate the following documents and pack them separately

  • node_modulesModule in the folder
  • There are 3 entranceschunkShared modules

optimization.splitChunks

The biggest change in WebPack 4 is the abolition of CommonsChunkPlugin and the introduction of optimisation. splitChunks. If your mode is production, then webpack4 will automatically open Code Splitting.

Its built-in code splitting strategy looks like this:

  • Whether the new chunk is shared or fromnode_modulesThe module
  • Whether the new chunk size is greater than 30kb before compression
  • The number of concurrent requests to load the chunk on demand is less than or equal to five
  • The number of concurrent requests on the initial page load is less than or equal to three

Although Code Splitting is automatically opened in webpack4, with the project getting bigger and bigger, it often doesn’t meet our needs and we need personalized optimization.

Examples of application

Let’s first find a project with a large optimization space to operate. This is a background management system project, most of the content is developed by 3-4 front-end, usually the development cycle is short, and most people do not have the awareness of optimization, just write good business code to complete the requirements, a long day, resulting in a large package of files, greatly affecting the performance.

We first use Webpack-bundle-Analyzer to analyze the bundled module dependencies and file size to determine the direction of optimization.

Then let’s take a look at the js file that is packaged

When I looked at these two images, I was devastated. The slots are as follows

  • After packaging, generate multiple js files of nearly 1M, many of themvendor.jsThe home page must load a large file
  • xlsx.jsThere is no need to use such a plugin. A better way to export Excel would be to return the file stream format to the front end for processing
  • echartiviewThe file is too large, the method introduced by CDN should be used

After the banter, we’ll get down to business. Because there are so many slots, we are better able to verify the feasibility of our optimization method.

Pull away from Echart and iview

Since the echart and iView files are too large, we use WebPack4’s optimization. SplitChunks to separate them into separate files. (In order to better display the optimization, we will remove xlsx.js first)

Vue.config. js is modified as follows:

chainWebpack: config= > {
    config.optimization.splitChunks({
      chunks: 'all'.cacheGroups: {
        vendors: {
          name: 'chunk-vendors'.test: /[\\/]node_modules[\\/]/,
          priority: 10.chunks: 'initial'
        },
        iview: {
          name: 'chunk-iview'.priority: 20.test: /[\\/]node_modules[\\/]_? iview(.*)/
        },
        echarts: {
          name: 'chunk-echarts'.priority: 20.test: /[\\/]node_modules[\\/]_? echarts(.*)/
        },
        commons: {
          name: 'chunk-commons'.minChunks: 2.priority: 5.chunks: 'initial'.reuseExistingChunk: true}}})},Copy the code

At this point, we use Webpack-bundle-Analyzer to analyze

The packaged JS file

As you can see here, we have successfully isolated echart and iView, and vvendor. Js has been reduced in size accordingly. In addition, we can continue to pull out more third-party modules.

CDN way

Although the third-party module is isolated, it still needs to load such a file of several hundred KB when loading the home page or corresponding route, which is not good for performance optimization. In this case, we can use CDN to introduce such a plug-in or UI component library.

  1. inindex.htmlIntroduce the corresponding CDN link
<head>
  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/styles/iview.css" />
</head>
<body>
  <div id="app"></div>
  <script src="https://cdn.bootcss.com/vue/2.6.8/vue.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/iview/3.5.4/iview.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/xlsx.mini.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/cpexcel.min.js"></script>
</body>
Copy the code
  1. vue.config.jsconfigurationexternals
configureWebpack: (config) = > {
  config.externals = {
    vue: 'Vue'.xlsx: 'XLSX'.iview: 'iView'.iView: 'ViewUI',}}Copy the code
  1. Delete the previous import mode and uninstall the corresponding NPM dependency packages
npm uninstall vue iview echarts xlsx --save
Copy the code

At this point we’re going to look at what happens when you pack

The packaged JS file

well done ! At this time, there is basically no large file packaged out, and the vvendor. Js required for home page loading is only dozens of KB. Moreover, we can further optimize by introducing some modules of vUE family bucket through CDN, such as Vue-router, VUex, axios, etc. The performance of the page, especially the home page loading, is greatly optimized.

Recommend the article

Talk about data state management and implementing a simple version of VUEX
Summary of 18 WebPack plugins, there’s always something you want!
Build a Vue-CLI4 + WebPack Mobile Framework (out of the box)
Build from zero to optimize a scaffolding similar to VUe-CLI
Encapsulate a TOAST and Dialog component and publish it to NPM
Build a WebPack project from scratch
Summarize a few ways to optimize WebPack packaging
Summarize the advanced application of VUE knowledge system
Summarize the practical skills of vUE knowledge system
Summarize the basic introduction of VUE knowledge system
Summary of mobile terminal H5 development commonly used skills (full of dry goods oh!)