[webpack advanced] what is the code of the packaged webpack?

Time:2021-7-22

[webpack advanced] what is the code of the packaged webpack?

webpackIt’s one of the important packaging tools we need to master at this stage, we knowwebpackThe dependency graph is recursively constructed, which contains each module of the application, and then these modules are packaged into one or more modulesbundle

thatwebpackWhat is the packaged code like? How is it that eachbundleConnected? How to deal with the relationship between modules? dynamicimport()What was it like when I was young?

In this article, let’s uncover it step by stepwebpackThe mystery of packaged code

preparation

Create a file and initialize it

mkdir learn-webpack-output
cd learn-webpack-output
npm init -y 
yarn add webpack webpack-cli -D

Create a new file in the root directorywebpack.config.jsThis iswebpackDefault profile

const path = require('path');

module.exports = {
  Mode: 'development' // can be set to production
  //Executed entry file
  entry: './src/index.js',
  output: {
    //The name of the output file
    filename: 'bundle.js',
    //All output files are placed in dist 
    path: path.resolve(__dirname, './dist')
  },
  //To make it easier to view the output
  devtool: 'cheap-source-map'
}

And then we go backpackage.jsonFile, innpm scriptAdd startup towebpackConfigured commands

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "webpack"
}

New onesrcFolder, addingindex.jsDocuments andsayHellofile

// src/index.js
import sayHello from './sayHello';

console.log(sayHello, sayHello('Gopal'));
// src/sayHello.js
function sayHello(name) {
  return `Hello ${name}`;
}

export default sayHello;

Everything’s ready. Go aheadyarn build

Analysis of the main process

Look at the output file, here do not put the specific code, a bit of space, you can clickCheck here

It’s actually an Iife

Don’t panic. Let’s split it up a little bit. In fact, the overall document is oneIIFE——Execute the function immediately.

(function(modules) { // webpackBootstrap
    // The module cache
    var installedModules = {};
    function __webpack_require__(moduleId) {
    //... omit details
    }
    //Import file
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({

 "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {}),
  "./src/sayHello.js": (function(module, __webpack_exports__, __webpack_require__) {})
});

Input parameter of functionmodulesIt’s an object, the object’skeyIt’s everyonejsThe relative path of the module,valueIt’s a function (we’ll call it below)Module function)。IIFEHuixianrequireEntrance module. That’s up there./src/index.js

//Import file
return __webpack_require__(__webpack_require__.s = "./src/index.js");

Then, the entry module will execute therequireOther modules such as./src/sayHello.js" The following is the simplified code to continuously load the dependent modules to form a dependency tree, such as the followingModule functionOther files are referenced insayHello.js

{
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) { 
    __webpack_require__.r(__webpack_exports__);
      var _sayHello__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/sayHello.js");
    console.log(_sayHello__WEBPACK_IMPORTED_MODULE_0__["default"],
    Object(_sayHello__WEBPACK_IMPORTED_MODULE_0__["default"])('Gopal'));
  })
}

Important implementation mechanism——__webpack_require__

Go hererequireThe main functions of other modules are__webpack_require__。 Next, let’s talk about it__webpack_require__This function

//Cache module usage
  var installedModules = {};
  // The require function
  //Simulation module loading, webpack implementation of require
  function __webpack_require__(moduleId) {
    // Check if module is in cache
    //Check whether the module is in the cache, if yes, get it directly from the cache
    if(installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // Create a new module (and put it into the cache)
    //If not, it will be created and put into the cache, where the key value is the module ID, which is the file path mentioned above
    var module = installedModules[moduleId] = {
      i: moduleId, // Module ID
      l: False, // is it executed
      exports: {}
    };

    // Execute the module function
    //Execute the module function and mount it to module. Exports. This points to module.exports
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    // Flag the module as loaded
    //Mark that the module has been loaded
    module.l = true;

    // Return the exports of the module
    //Module. Exports is saved as a parameter when the module is executed, and then the exposed interfaces in the module, such as functions and variables, are saved
    return module.exports;
  }

The first step,webpackHere we do a layer of optimization, through the objectinstalledModulesCache, check whether the module is in the cache, if there is, get it directly from the cache, if not, create it and put it into the cache, wherekeyThe value is the moduleId, which is the file path mentioned above

Step two, then executeModule function, willmodule, module.exports, __webpack_require__It is passed as a parameter and the function call object of the module is pointed tomodule.exportsTo ensure that thethisPoint to always point to the current module.

The third step is to return to the loaded module and call it directly.

thereforethis__webpack_require__Is to load a module and return it at the endmodule.exportsvariable

How does webpack support ESM

As you may have found, my writing above isESMFor the understanding of some modular solutions, you can see another article of mine[interview] what are CJS, AMD, UMD and ESM in JavaScript?

Let’s look backModule function

{
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) { 
    __webpack_require__.r(__webpack_exports__);
      var _sayHello__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/sayHello.js");
    console.log(_sayHello__WEBPACK_IMPORTED_MODULE_0__["default"],
    Object(_sayHello__WEBPACK_IMPORTED_MODULE_0__["default"])('Gopal'));
  })
}

Let’s see__webpack_require__.rfunction

__webpack_require__.r = function(exports) {
 object.defineProperty(exports, '__esModule', { value: true });
};

It’s for__webpack_exports__Add a property__esModule, the value istrue

Look at another one__webpack_require__.nThe realization of

// 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;
};

__webpack_require__.nIt will determine whether the module is an ES module__esModuleWhen it is true, it identifies the module as the ES module and returns it by defaultmodule.defaultOtherwise, returnmodule

Finally__webpack_require__.dThe main task is to integrate the abovegetterFunction bound to property a in exportsgetterupper

// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
    if(!__webpack_require__.o(exports, name)) {
        Object.defineProperty(exports, name, {
            configurable: false,
            enumerable: true,
            get: getter
        });
    }
};

Let’s see it for the last timesayHello.jsPackagedModule functionYou can see that the export here is __webpack_exports__["default"]In fact, it is__webpack_require__.nIn fact, it can also be seen that in factwebpackYes, we can support itCommonJSandES ModuleMixed together

 "./src/sayHello.js":
  /*! exports provided: default */
 (function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_require__.r(__webpack_exports__);
  function sayHello(name) {
    return `Hello ${name}`;
  }
  /* harmony default export */ __webpack_exports__["default"] = (sayHello);
 })

So far, we have a general ideawebpackHow does the packaged file work? Next, we analyze a special scenario of code separation – Dynamic import

Dynamic import

Code separation iswebpackIt’s one of the most striking features of the game. This feature separates code into different classesbundleThe files can then be loaded on demand or in parallel. Code separation can be used to obtain smallerbundleIf used properly, it will greatly affect the loading time.

There are several common methods of code segmentation

  • Starting point of entrance: useentryConfigure manual code separation.
  • Prevent duplication: useEntry dependenciesperhapsSplitChunksPluginDe duplication and separation.
  • Dynamic import: used to separate code by calling inline functions of a module.

In this article, we mainly look at dynamic importsrcNext, create a new fileanother.js

function Another() {
  return 'Hi, I am Another Module';
}

export { Another };

modifyindex.js

import sayHello from './sayHello';

console.log(sayHello, sayHello('Gopal'));

//For the sake of demonstration only, dynamic loading is required when conditions permit
if (true) {
  import('./Another.js').then(res => console.log(res))
}

Let’s look at the packaged content. If we ignore the. Map file, we can see that there is one more0.bundle.jsFile, which we call dynamically loadedchunkbundle.jsWe call it the Lordchunk

[webpack advanced] what is the code of the packaged webpack?

The output of the code, the mainchunkseehere, dynamically loadedchunkseehereThe following is an analysis of the two codes

Main chunk analysis

Let’s look at the Lord firstchunk

There are a lot more contents. Let’s take a closer look

First of all, we notice that the place where we import dynamically becomes the following after compilation, which looks like an asynchronous loaded function

if (true) {
  __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./Another.js */ "./src/Another.js")).then(res => console.log(res))
}

So let’s see__webpack_require__.eThe implementation of this function

__webpack_require__.e——Dynamic loading with jsonp

//Loaded chunk cache
var installedChunks = {
  "main": 0
};
// ...
__webpack_require__.e = function requireEnsure(chunkId) {
  //Promise queue, waiting for multiple asynchronous chunks to be loaded before the callback is executed
  var promises = [];

  // JSONP chunk loading for javascript
  var installedChunkData = installedChunks[chunkId];
  //0 means installed
  if(installedChunkData !== 0) { // 0 means "already installed".

    // a Promise means "currently loading".
    //If the target chunk is loading, push promise to promises array
    if(installedChunkData) {
      promises.push(installedChunkData[2]);
    } else {
      // setup Promise in chunk cache
      //Using promise to load target chunk asynchronously
      var promise = new Promise(function(resolve, reject) {
        //Set installedchunks [chunkid]
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
      });
      //I set the three states of chunk loading and store them in installedchunks to prevent the repeated loading of chunk
      // nstalledChunks[chunkId]  = [resolve, reject, promise]
      promises.push(installedChunkData[2] = promise);
      // start chunk loading
      //Using jsonp
      var head = document.getElementsByTagName('head')[0];
      var script = document.createElement('script');

      script.charset = 'utf-8';
      script.timeout = 120;

      if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
      }
      //Get the address of the target chunk__ webpack_ require__. P indicates the set publicpath, and the default is empty string
      script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
      //When the request times out, call the method directly and the time is 120 s
      var timeout = setTimeout(function(){
        onScriptComplete({ type: 'timeout', target: script });
      }, 120000);
      script.onerror = script.onload = onScriptComplete;
      //Set the callback for load completion or error
      function onScriptComplete(event) {
        // avoid mem leaks in IE.
        //Prevent ie memory leak
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];
        //If it is 0, it means it has been loaded. The main logic is the webpackjsonpcallback function
        if(chunk !== 0) {
          if(chunk) {
            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
            var realSrc = event && event.target && event.target.src;
            var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');
            error.type = errorType;
            error.request = realSrc;
            chunk[1](error);
          }
          installedChunks[chunkId] = undefined;
        }
      };
      head.appendChild(script);
    }
  }
  return Promise.all(promises);
};
  • It can be seen thatimport()Convert to analogJSONPTo load dynamically loadedchunkfile
  • set upchunkThree states of loading and bufferinginstalledChunksTo prevent repeated loading of chunks. These state changes will happen in the futurewebpackJsonpCallbackMentioned in

    //Set installedchunks [chunkid]
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
    • installedChunks[chunkId]by0, on behalf of thechunkLoading completed
    • installedChunks[chunkId]byundefined, on behalf of thechunkLoading failure, loading timeout, never loaded
    • installedChunks[chunkId]byPromiseObject representing thechunkLoading

Finish__webpack_require__.eWhat we know is that we introduce it dynamically through jsonpchunkThen how do we know the state after the import? Let’s look at asynchronous loadingchunkWhat is it like

Asynchronous chunk

//Window ["webpack jsonp"] is actually an array to which an element is added. This element is also an array. The first element of the array is chunkid, and the second object is the same as the parameter passed into Iife
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{

  /***/ "./src/Another.js":
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
  
  "use strict";
  __webpack_require__.r(__webpack_exports__);
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Another", function() { return Another; });
  function Another() {
    return 'Hi, I am Another Module';
  }
  /***/ })
  
  }]);
  //# sourceMappingURL=0.bundle.js.map

The main thing to do is to add an arraywindow['webpackJsonp']The first element of the array ischunkId, the second object, with the masterchunkThe parameters passed in Iife in are similar. The key is thiswindow['webpackJsonp']Where will it be used? Let’s go back to the LordchunkIn the middle. stayreturn __webpack_require__(__webpack_require__.s = "./src/index.js");There is still a period before entering the entrance

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
//Save the original array. Prototype. Push method
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
//The implementation of push method is modified to webbackjsonpcallback
//In this way, the window ['webbackjsonp ']. Push we execute in the asynchronous chunk is actually the webbackjsonpcallback function.
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
//Execute the webpackjsonpcallback method on the elements already in the array in turn
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;

jsonpArraynamelywindow["webpackJsonp"]When executingpushMethod, it will be executed webpackJsonpCallback, which is equivalent to a layer of hijacking, that is, this function will be called when the push operation is finished

jsonpArray.push = webpackJsonpCallback;

Webpackjsonpcallback — callback after loading dynamic chunk

Let’s take another look webpackJsonpCallbackFunction, where the input parameters are dynamically loadedchunkOfwindow['webpackJsonp']Push in the parameter.

var installedChunks = {
  "main": 0
};    

function webpackJsonpCallback(data) {
  //The first parameter in window ["webpackjsonp"] -- namely [0]
  var chunkIds = data[0];
  //For the details of the corresponding module, please refer to the second parameter in the push into window ["webpackjsonp"] in the packaged chunk module
  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];
    //So here is to find those unfinished chunks, their value or [resolve, reject, project]
    //This can be seen__ webpack_ require__. State set in e
    //Represents the running chunk, which is added to the resolutions array
    if(installedChunks[chunkId]) {
      resolves.push(installedChunks[chunkId][0]);
    }
    //Mark as finished
    installedChunks[chunkId] = 0;
  }
  //Add the modules in the asynchronous chunk to the modules array of the main chunk one by one
  for(moduleId in moreModules) {
    if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
      modules[moduleId] = moreModules[moduleId];
    }
  }
  //Parentjsonpfunction: the original array push method, adding data to the window ["webpackjsonp"] array.
  if(parentJsonpFunction) parentJsonpFunction(data);
  //Wait until the while loop is over__ webpack_ require__. The return value promise of e gets resolve
  //Execute resolve
  while(resolves.length) {
    resolves.shift()();
  }
};

When weJSONPTo load asynchronouslychunkWhen it’s done, it’s donewindow["webpackJsonp"] || []).pushThat is to saywebpackJsonpCallback。 There are mainly the following steps

  • Traverse the chunkids to be loaded, find the unfinished chunks, and add them to resolutions
for(;i < chunkIds.length; i++) {
  chunkId = chunkIds[i];
  //So here is to find those unfinished chunks, their value or [resolve, reject, project]
  //This can be seen__ webpack_ require__. State set in e
  //Represents the running chunk, which is added to the resolutions array
  if(installedChunks[chunkId]) {
    resolves.push(installedChunks[chunkId][0]);
  }
  //Mark as finished
  installedChunks[chunkId] = 0;
}
    • What is not executed here is a non-zero state, which is set to 0 after execution
    • installedChunks[chunkId][0]It’s actually resolve in the promise constructor

      // __webpack_require__.e 
      var promise = new Promise(function(resolve, reject) {
          installedChunkData = installedChunks[chunkId] = [resolve, reject];
      });
    • One by one will be asynchronouschunkInmoduleJoin masterchunkOfmodulesArray
    • Original arraypushMethod, willdatajoinwindow["webpackJsonp"]array
    • Execute eachresolvesMethod, tell__webpack_require__.eThe state of the callback function in

    We only know when this method is finishedJSONPSuccess or failure, that isscript.onload/onerrorWill be inwebpackJsonpCallbackAfter that. thereforeonload/onerrorIt’s actually for inspectionwebpackJsonpCallbackThe completion of: will there beinstalledChunksCorresponding inchunkThe value is set to 0

    Summary of dynamic import

    The general process is shown in the figure below

    [webpack advanced] what is the code of the packaged webpack?

    summary

    This article analyzes thewebpackThe output code in the case of package mainstream and dynamic loading is summarized as follows

    • The overall document is oneIIFE——Execute function immediately
    • webpackThe loaded files are cached to optimize performance
    • Mainly through__webpack_require__ To simulateimportA module and returns to the module at the endexportVariable of
    • webpackHow to support itES ModuleOf
    • Dynamic loadingimport()The implementation of is mainly to useJSONPDynamic loading module, and through thewebpackJsonpCallbackJudge the result of loading

    reference resources

    Recommended Today

    Use CSS preferences – * specification to improve the accessibility and robustness of the website

    The text will introduce several new features and functions in CSS media query: prefers-reduced-motion prefers-color-scheme prefers-contrast prefers-reduced-transparency prefers-reduced-data Making good use of them can improve the robustness and accessibility of our website! With the development of the Internet today, for our front-end, our focus should not only be whether the pages we produce can be […]