Analysis of webpack output file

Time:2021-2-23

Packing principle

  • In short, it is to generate ast syntax tree, and generate corresponding JS code according to the syntax tree
  • Here we only analyze the results of the packaged output, and analyze what webpack has done to our code from the results

analysis

// main.js
//Import through commonjs specification
const show = require('./show.js');
//Execute the show function
show('Webpack');
  • Dependent files
// show.js
//Operate the DOM element to display the content on the web page
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

//Export show function through commonjs specification
module.exports = show;
  • Package results
// bundle.js
(
    //Webbackbootstrap startup function
    //Modules is an array of all modules. Each element in the array is a function
    function (modules) {
        //All the installed modules are stored here
        //The function is to cache the loaded modules in memory to improve performance
        var installedModules = {};

        //To load a module in the array, moduleid is the index of the module to be loaded in the array
        //Role and Node.js  Similar to the require statement in
        function __webpack_require__(moduleId) {
            //If the module to be loaded has been loaded, it will be returned directly from the memory cache
            if (installedModules[moduleId]) {
                return installedModules[moduleId].exports;
            }

            //If there is no module to load in the cache, create a new module and store it in the cache
            var module = installedModules[moduleId] = {
                //Index of module in array
                i: moduleId,
                //Has the module been loaded
                l: false,
                //The exported value of the module
                exports: {}
            };

            //Get the function corresponding to the module whose index is moduleid from modules
            //Call the function again, and pass in the required parameters of the function
            modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
            //Mark this module as loaded
            module.l = true;
            //Returns the exported value of this module
            return module.exports;
        }

        //The public path in the webpack configuration is used to load the separated asynchronous code
        __webpack_require__.p = "";

        //Use__ webpack_ require__  To load the module with index 0 and return the exported content of the module
        //The module with index 0 is main.js  The corresponding file is the execution entry module
        // __ webpack_ require__ . s means the index corresponding to the startup module
        return __webpack_require__(__webpack_require__.s = 0);

    })(

    //All modules are stored in an array, and the modules are distinguished and located according to the index of each module in the array
    [
        /* 0 */
        (function (module, exports, __webpack_require__) {
            //Through__ webpack_ require__  Import the show function in the specification, show.js  The corresponding module index is 1
            const show = __webpack_require__(1);
            //Execute the show function
            show('Webpack');
        }),
        /* 1 */
        (function (module, exports) {
            function show(content) {
                window.document.getElementById('app').innerText = 'Hello,' + content;
            }
            //Export show function through commonjs specification
            module.exports = show;
        })
    ]
);

//The above seemingly complex code is actually an immediate execution function, which can be abbreviated as follows:
(function(modules) {

  //Simulate require statement
  function __webpack_require__() {
  }

  //Execute the 0 th module in all modules array
  __webpack_require__(0);

}) ([/ * array of all modules * /])
  • You can see thatbundle.jsIs a self executing function, the input parameter ismain.jsandshow.jsAfter the transformation of the code block formed by the array
  • It runs in the self executing function__webpack_require__In this function, the input parameter is 0. 0 is actually the corresponding input parameter in the code block array, representing the first code block
  • Let’s see__webpack_require__Function, the first execution is the cache judgment, through themoduleIdJudge whether it has been loaded before. If it has been loaded, return the direct loading result directlyexportsmouduleIdIt is the index of different code modules in the input parameter group
  • If it has not been loaded, create a new object. The most important thing is theexportsAttribute, which stores the things exported by the corresponding module after loading the module
  • And then use thisexportsExecute the corresponding code block as the context, and pass the parameters as the newly created module, the exports in the module, and the__webpack_require__The method itself
  • And then you seemain.jsThe requirement in is transformed into__webpack_require____webpack_require__(1)Represents loading the second code block
  • In the second code block, the show method is defined, and then show will act as the module.exports The export of, that is, the assignment toinstalledModules[0].module.exportsIn other words, this export has been cached. Next time it is used in other places, it will be exported directly
  • This is the general packaging idea of webpack, which transforms each individual module into an array as an input parameter, passes it to the self executing function, and maintains ainstalledModulesRecord the loaded module, use the index in the module array as the key value, and exports record the exported object

Load on demand

  • Because single page applications also have the concept of routing, before switching to the corresponding routing, you may not want the browser to download JS of this part of the page, so as to improve the speed of opening the home page, which involves a lazy loading, that is, on-demand loading
  • Webpack is loaded on demand throughimport(XXX)Implemented, import () is a proposal, and webpack supports it
//Asynchronous loading show.js
import(/* webpackChunkName: 'show' */ './show').then((module) => {
  //Execute the show function
  const show = module.default;
  show('Webpack');
});
  • By packaging in this way, we can find that the final packaged file is divided into two parts,bundle.jsandshow.xxx.js
  • among/* webpackChunkName: 'show' */It is specially annotated to webpack to specify the name of the package to be loaded on demand. At the same time, remember to configure thechunkFilename: '[name].[hash].js'Otherwise, the designation will not take effect
  • Let’s look at the entry file first. Hide all the functions that are not used for the time being as follows:
(function (modules) {
  //Webpackjsonp is used to install modules from asynchronously loaded files
  window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
    //... omitted first
  };

  //Cache installed modules
  var installedModules = {};

  //Store the loading status of each chunk;
  //The key is the ID of the chunk, and a value of 0 indicates that it has been loaded successfully
  var installedChunks = {
    1: 0
  };

  //Simulate the require statement, which is consistent with the above
  function __webpack_require__(moduleId) {
    //... omit the same as above
  }

  //It is used to load the file corresponding to the chunk that is split and needs to be loaded asynchronously
  __webpack_require__.e = function requireEnsure(chunkId) {
    //... omitted first
  };

  //Load and execute the entry module, as described above
  return __webpack_require__(__webpack_require__.s = 0);
})
(
  //Store all modules that have not been loaded asynchronously and are loaded with the entry file
  [
    //  main.js  Corresponding module
    (function (module, exports, __webpack_require__) {
      //Through__ webpack_ require__ . e to load asynchronously show.js  Corresponding chunk
      __webpack_require__.e('show').then(__webpack_require__.bind(null, 'show')).then((show) => {
        //Execute the show function
        show('Webpack');
      });
    })
  ]
);
  • You can see thatimport(xxx).thenIt was replaced by__webpack_require__.e(0).then__webpack_require__.e(0)A promise is returned
  • The first then is equivalent to execution__webpack_require__(1)But it is obvious that there is only one element in the input parameter group of the self executing function, and there is no [1]. When was this [1] inserted
  • to glance at__webpack_require__.eThe realization of
__webpack_require__.e = function requireEnsure(chunkId) {
    //Get the loading status of the chunk corresponding to the chunk ID from the installedchunks defined above
    var installedChunkData = installedChunks[chunkId];
    //If the loading status is 0, it means that the chunk has been loaded successfully, and directly returns to resolve promise
    if (installedChunkData === 0) {
      return new Promise(function (resolve) {
        resolve();
      });
    }

    //Installedchunk data is not empty and not 0, indicating that the chunk is being loaded on the network
    if (installedChunkData) {
      //Returns the promise object stored in the installedchunk data array
      return installedChunkData[2];
    }

    //If the installedchunk data is empty, it means that the chunk has not been loaded. Load the corresponding file of the chunk
    var promise = new Promise(function (resolve, reject) {
      installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
    installedChunkData[2] = promise;

    //Through DOM operation, insert a script tag into HTML head to load the JavaScript file corresponding to chunk asynchronously
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.charset = 'utf-8';
    script.async = true;
    script.timeout = 120000;

    //The path of the file is composed of the configured publicpath and chunkid
    script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";

    //Set the maximum timeout for asynchronous loading
    var timeout = setTimeout(onScriptComplete, 120000);
    script.onerror = script.onload = onScriptComplete;

    //Callback when script loading and execution complete
    function onScriptComplete() {
      //Prevent memory leaks
      script.onerror = script.onload = null;
      clearTimeout(timeout);

      //To check whether the chunk corresponding to the chunk ID is successfully installed, it will exist in installedchunks only when the installation is successful
      var chunk = installedChunks[chunkId];
      if (chunk !== 0) {
        if (chunk) {
          chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
        }
        installedChunks[chunkId] = undefined;
      }
    };
    head.appendChild(script);

    return promise;
  };
  • First, judge whether the chunk ID has been loaded. If so, return a resolve promise directly
  • If it is neither empty nor 0, it means that it is being loadedinstalledChunks[chunkId]It’s an array that holds[resovle, reject], which is assigned when the network request is initiated
  • If the above two judgments are not hit, it means that it has not been loaded. Next, we will start to construct the loading method, mainly in the form of jsonp
  • First, create a new promise, andinstalledChunks[chunkId]Assign a value and save the project and its resolve and reject in it, which is why the above can be judgedinstalledChunks[chunkId]If it is neither empty nor 0, it is in the middle of a request. The third value of the array, the new promise, is returned directly, so that subsequent operations can register callbacks on this promise
  • Then, the following method is to construct a script tag and insert it into the head to ensure that the code can be downloaded immediately. At the same time, it defines the callback when the code is executed. It judges that the code has been loaded. If the loading is successful, it clears the listening. If the loading fails, it throws an exception
  • Finally, the promise is returned for external registration callback
  • Here, the code loaded through jsonp is another file separated from the packageshow.xx.jsThat is, it is loaded asynchronouslyshow.jsRelated code
webpackJsonp(
  //The ID of the module stored in other files
  ['show'],
  //Modules included in this document
  {//  show.js  Corresponding module
    show: (function (module, exports) {
      function show(content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
      }

      module.exports = show;
    })
  }
);
  • Next, let’s see how the webbackjsonp method is defined
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
    //Add moremodules to the modules object
    //Mark all modules corresponding to chunkids as loaded successfully 
    var moduleId, chunkId, i = 0, resolves = [], result;
    
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      if (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];
        }
    }
    
    while (resolves.length) {
      resolves.shift()();
    }
  };
  • chunkIdsIt represents the ID name of the file, because when loading dynamically, it uses the dynamically loaded file name to form a script tag for downloading. This ID is passed in here to trigger the subsequent resolve of promise, mark the module and be loaded
  • moreModulesIs the corresponding code module set
  • executeModules It is the index of the module to be executed after loading
  • First, traverseinstalledChunksAs mentioned earlierinstalledChunks[chunkId]When downloading through the network, three values are given back to represent its corresponding promise. Here, the first resolve is taken out and saved. At the same time, the load flag is set to 0, indicating that it has been loaded
  • Then traverse the dynamically loaded module and insert the code block into the modules array
  • Finally, execute the previously saved resolve function to trigger the__webpack_require__.e(0).thenImplementation of
  • In this way, the dynamically loaded code is downloaded by constructing jsonp, and the corresponding code is transferred to thebundle.jsIn the then function__webpack_require__Execution module, cache output
  • Here, in order to facilitate understanding, we have to make some adjustments to the code. The real output can be viewed through the specific packaging output. Here, we only describe the specific packaging idea