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.