webpack4. Lazy loading of X source code analysis

This paper analyzes the source code after construction according to the principle of Webpack lazy load construction and loading.

I preparation

First, after init, create a simple basic configuration of webpack, and create two js files (a main entry file and a non main entry file) and an html file in the src directory, package json,webpack.config.js common part code is as follows:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <button id="oBtn">Button loading</button>
    </body>
</html>
//entrance js file
let oBtn=document.getElementById('oBtn')
oBtn.addEventListener('click',function(){
    import (/*webpackChunkName:"index1"*/'./index1.js').then((index1)=>{
        console.log(index1)
    })    
})
//Non entry file
module.exports="I'm Zhang San"
//package.json
{
  "name": "mywebpack",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "html-webpack-plugin": "4.5.0",
    "webpack": "4.44.2",
    "webpack-cli": "3.3.12"
  }
}
//webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  devtool: 'none',
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: path.resolve('dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}

II Perform packaging operations

yarn webpack performs the operation and generates build.xml after packaging js. index1. built. js,index. html,

From the code of js entry above, we can analyze that import (/*webpackChunkName:"index1"*/'./index1.js') can realize the specified lazy loading operation.

The following is the packaged source code index html,built. js,index1. built. js

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <button id="oBtn">Button loading</button>
    <script src="built.js"></script></body>
</html>
 //built.js
 (function(modules) { // webpackBootstrap
     // install a JSONP callback for chunk loading
     function webpackJsonpCallback(data) {
         var chunkIds = data[0];
         var moreModules = data[1];
         // add "moreModules" to the modules object,
         // then flag all "chunkIds" as loaded and fire callback
         var moduleId, chunkId, i = 0, resolves = [];
         for(;i < chunkIds.length; i++) {
             chunkId = chunkIds[i];
             if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                 resolves.push(installedChunks[chunkId][0]);
             }
             installedChunks[chunkId] = 0;
         }
         for(moduleId in moreModules) {
             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                 modules[moduleId] = moreModules[moduleId];
             }
         }
         if(parentJsonpFunction) parentJsonpFunction(data);
         while(resolves.length) {
             resolves.shift()();
         }
     };
     // The module cache
     var installedModules = {};
     // object to store loaded and loading chunks
     // undefined = chunk not loaded, null = chunk preloaded/prefetched
     // Promise = chunk loading, 0 = chunk loaded
     var installedChunks = {
         "main": 0
     };
     // script path function
     function jsonpScriptSrc(chunkId) {
         return __webpack_require__.p + "" + chunkId + ".built.js"
     }
     // The require function
     function __webpack_require__(moduleId) {
         // Check if module is in cache
         if(installedModules[moduleId]) {
             return installedModules[moduleId].exports;
         }
         // Create a new module (and put it into the cache)
         var module = installedModules[moduleId] = {
             i: moduleId,
             l: false,
             exports: {}
         };
         // Execute the module function
         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
         // Flag the module as loaded
         module.l = true;
         // Return the exports of the module
         return module.exports;
     }
     // This file contains only the entry chunk.
     // The chunk loading function for additional chunks
     __webpack_require__.e = function requireEnsure(chunkId) {
         var promises = [];
         // JSONP chunk loading for javascript
         var installedChunkData = installedChunks[chunkId];
         if(installedChunkData !== 0) { // 0 means "already installed".
             // a Promise means "currently loading".
             if(installedChunkData) {
                 promises.push(installedChunkData[2]);
             } else {
                 // setup Promise in chunk cache
                 var promise = new Promise(function(resolve, reject) {
                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
                 });
                 promises.push(installedChunkData[2] = promise);
                 // start chunk loading
                 var script = document.createElement('script');
                 var onScriptComplete;
                 script.charset = 'utf-8';
                 script.timeout = 120;
                 if (__webpack_require__.nc) {
                     script.setAttribute("nonce", __webpack_require__.nc);
                 }
                 script.src = jsonpScriptSrc(chunkId);
                 // create error before stack unwound to get useful stacktrace later
                 var error = new Error();
                 onScriptComplete = function (event) {
                     // avoid mem leaks in IE.
                     script.onerror = script.onload = null;
                     clearTimeout(timeout);
                     var chunk = installedChunks[chunkId];
                     if(chunk !== 0) {
                         if(chunk) {
                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                             var realSrc = event && event.target && event.target.src;
                             error.message = 'Loading chunk ' + chunkId + 
                  ' failed.\n(' + errorType + ': ' + realSrc + ')'; error.name = 'ChunkLoadError'; error.type = errorType; error.request = realSrc; chunk[1](error); } installedChunks[chunkId] = undefined; } }; var timeout = setTimeout(function(){ onScriptComplete({ type: 'timeout', target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; document.head.appendChild(script); } } return Promise.all(promises); }; // expose the modules object (__webpack_modules__) __webpack_require__.m = modules; // expose the module cache __webpack_require__.c = installedModules; // define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } }; // define __esModule on exports __webpack_require__.r = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); }; // create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require __webpack_require__.t = function(value, mode) { if(mode & 1) value = __webpack_require__(value); if(mode & 8) return value; if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; var ns = Object.create(null); __webpack_require__.r(ns); Object.defineProperty(ns, 'default', { enumerable: true, value: value }); if(mode & 2 && typeof value != 'string')
      for(var key in value) __webpack_require__.d(ns, key, function(key) {
       return value[key];
}.bind(null, key)); return ns; }; // getDefaultExport function for compatibility with non-harmony modules __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; }; // Object.prototype.hasOwnProperty.call __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; __webpack_require__.p = ""; __webpack_require__.oe = function(err) { console.error(err); throw err; }; var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback; jsonpArray = jsonpArray.slice(); for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); var parentJsonpFunction = oldJsonpFunction; return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) /************************************************************************/ ({ "./src/index.js": (function(module, exports, __webpack_require__) { //entrance js file let oBtn=document.getElementById('oBtn') oBtn.addEventListener('click',function(){ __webpack_require__.e(/*! import() | index1 */ "index1") .then(__webpack_require__.t.bind(null, /*! ./index1.js */ "./src/index1.js", 7)) .then((index1)=>{ console.log(index1) }) }) }) });
//index1.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["index1"],{ "./src/index1.js": (function(module, exports) { module.exports="I'm Zhang San" }) }]);

III Code block parsing after packaging

__ webpack_require__.e) realize jsonp loading content, and use promise to realize asynchronous loading operation

__webpack_require__.e = function requireEnsure(chunkId) {
         var promises = [];
         //judge installedChunks Whether there is a corresponding id Value of
         var installedChunkData = installedChunks[chunkId];
      //0 Indicates that it has been loaded promise Indicates loading undefined Indicates that it is not loaded
if(installedChunkData !== 0) { // a Promise means "currently loading". if(installedChunkData) {
          //Push the complete promise into the array promises.push(installedChunkData[
2]); } else { // Creation does not exist promise var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });
          //Push the complete promise into the array promises.push(installedChunkData[
2] = promise); // establish script label var script = document.createElement('script'); var onScriptComplete; script.charset = 'utf-8'; script.timeout = 120; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); }
          //Set src script.src
= jsonpScriptSrc(chunkId); var error = new Error(); onScriptComplete = function (event) { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { var errorType = event && (event.type === 'load' ? 'missing' : event.type); var realSrc = event && event.target && event.target.src; error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; error.name = 'ChunkLoadError'; error.type = errorType; error.request = realSrc; chunk[1](error); } installedChunks[chunkId] = undefined; } }; var timeout = setTimeout(function(){ onScriptComplete({ type: 'timeout', target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; document.head.appendChild(script); } } return Promise.all(promises); };

This method mainly judges whether the module corresponding to chunkId has been loaded. If it has been loaded, it will not be reloaded;

If the module has not been loaded, but the module is in the process of being loaded, it will not be loaded again, and the promise of the loaded module will be returned directly.

If the module has not been loaded and is not in the loading process, create a promise and store the array composed of resolve, reject and promise in the installedChunks cache object attribute mentioned above. Then create a script tag to load the corresponding file. The loading timeout is 2 minutes. If script

If the file fails to load, trigger reject (corresponding to the source code: chunk[1](error), chunk[1] is the second element reject of the array cached above), and set the value of the corresponding key in the installedChunks cache object to undefined to indicate that it has not been loaded.

//Define variable storage array window Is there anything in it webpackJsonp Method of,If not set to[]array
     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    //oldJsonpFunction Save old jsonpArray
     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    //Redefine push method
     jsonpArray.push = webpackJsonpCallback;
     jsonpArray = jsonpArray.slice();
     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
     var parentJsonpFunction = oldJsonpFunction;

The above code mainly defines a global variable jsonpArray, and redefines the push method. The amount of change is an array. The original push method of the array variable is copied as webpackJsonpCallback method. This method is a core method of lazy load implementation, and its function is to call the custom push method for the sub module

(webpackJsonpCallback) is added.

//The push method is actually the webpackJsonpCallback method
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["index1"],{
    "./src/index1.js":
    (function(module, exports) {
        module.exports="I'm Zhang San"
    })
}]);

Analysis of webpackJsonpCallback function

function webpackJsonpCallback(data) {
        //Get the module to be loaded id
         var chunkIds = data[0];
        //Get the module dependencies that need to be dynamically loaded
         var moreModules = data[1];
         var moduleId, chunkId, i = 0, resolves = [];
        //Circular judgment chunkIds Has the corresponding module content in been loaded
         for(;i < chunkIds.length; i++) {
             chunkId = chunkIds[i];
            //judge installedChunks[chunkId]Does it exist,also installedChunks There are in the object properties of chunkId Value of (loading).)
             if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                //hold resolve Putting into the array indicates that the loading is complete
                 resolves.push(installedChunks[chunkId][0]);
             }
            //take chunkId Setting the corresponding value to 0 indicates that it has been loaded
             installedChunks[chunkId] = 0;
         }
        //Merge modules
         for(moduleId in moreModules) {
             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                 modules[moduleId] = moreModules[moduleId];
             }
         }
        //Load subsequent content
         if(parentJsonpFunction) parentJsonpFunction(data);
        //If there is a call resolves Method to perform subsequent then operation
         while(resolves.length) {
             resolves.shift()();
         }
     };

Define the webpackJsonpCallback implementation: merge the module definitions, change the promise state, and execute subsequent actions,

Parameter data is an array. There are two elements: the first element is an array composed of chunkids of all modules in the lazy loading file; The second parameter is an object. The attribute and value of the object are the moduleId and module code function of the module to be loaded,

Functions of this function:

(1) chunkId in traversal parameters:

Judge the attribute value of the corresponding chunkId in the installedChunks cache variable: if it is true, it indicates that the module is loading, because from the above analysis, only one case of installedChunks[chunkId] is true, that is, when the corresponding module is loading, the promise created by the loading module will be combined

An array [resolve, reject, project] is assigned to installedChunks[chunkId]. Store resolve in the resolves variable. Set the corresponding chunkId in installedChunks to 0, and the module is identified as loaded.

(2) Module attributes in traversal parameters

Store the module code function in modules, which is the entry file build Parameters of self executing function in JS.

(3) Call parentJsonpFunction (native push method) to load the subsequent contents of the module

(4) Traverse all, resolve, resolve

__ webpack_require__.t method analysis

__webpack_require__.t = function(value, mode) {
         if(mode & 1) value = __webpack_require__(value);
         if(mode & 8) return value;
         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
         var ns = Object.create(null);
         __webpack_require__.r(ns);
         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
         if(mode & 2 && typeof value != 'string') 
        for(var key in value) 
        __webpack_require__.d(ns, key, function(key) {
            return value[key]; 
        }.bind(null, key));
         return ns;
     };

__ webpack_require__.t method is the last process of lazy loading and exporting pages. It mainly does these things

*1 receives two parameters, one is value, which is generally used to represent the id of the loaded module, and the second value mode is a binary value

*2. The first thing to do inside the T method is to call the custom require method to load the module export corresponding to value and re assign it to value

*3 when the value value is obtained, the remaining 84 2 ns are processed for the current content and then returned for use

*4 when mode & 8 is established, it will be returned directly (for example, 0111 1000 is not established) (commonjs specification)

*5 when mode & 4 is true, return value directly (esmodule)

*6 if none of the above conditions holds, continue to process value and define an ns {}

*6-1 if the obtained value is a content that can be used directly, such as a string, mount it to the default attribute of ns

*6-2 if it is not a simple data type, call d method to add getter attribute, and the external can be through NS Name gets the value

/**********************************************************************************************************************************************************************************************************************************************************************************/

Happiness is very simple, that is, flowers in spring, shade in summer, wild fruits in autumn and flying snow in winter.

 

 

 

 

 

 

 

 

 

 

 

 

Posted by UQKdk on Sun, 17 Apr 2022 14:54:35 +0930