A big factory interview question

Interviewer:

What do import moduleName from ‘xxModule’ and import(‘xxModule’) end up with after webpack compilation and packaging? How does it work in the browser?

Job seeker, answer:

B: well… Sorry, I only know that import can be used to load modules, and the second import is usually used for lazy loading, but the rest is unknown

These two statements should be seen a lot, not to mention the first import, which can be seen everywhere in front end projects nowadays. The second import can be seen in places that require lazy loading, such as the lazy loading configuration of vue-Router, but it seems that no one has ever looked too deeply into this.

A prelude to

Import is a method of loading modules provided by ES Module, which is also supported by mainstream browsers. For example, the popular Vite is implemented by using the browser’s ability to support import natively. Of course, it also has a koA server implementation.

We all know thatwebpackThe general packaging process is as follows:

  • mergewebpack.config.jsAnd command line parameters to form the final configuration
  • Parse the configuration and getentryThe entrance
  • Read the contents of the entry file, pass@babel/parseConvert the entry content (code) toast
  • through@babel/traversetraverseastGet the module’s individual dependencies
  • through@babel/core(The actual conversion work is done by@babel/preset-envTo accomplish) willastConverted toes5 code
  • Take all dependencies of all modules and convert them toes5

As you can see from the above, there will be no import statements in the final code, because ES5 does not have import statements. So where do we find the answers? There are two places, the WebPack source and the packaged file, which is simpler and more straightforward for today’s question.

project

Now let’s build a test project

Initialize the project

mkdir webpack-bundle-analysis && cd webpack-bundle-analysis && npm init -y && npm i webpack webpack-cli -D
Copy the code

webpack.config.js

const path = require('path')

module.exports = {
  entry: './src/index.js'.// To facilitate analysis of the packaged code, select development mode
  mode: 'development'.output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'main.js'}}Copy the code

Write code, / SRC

/src/index.js

/** * import file, introduce the print method, and execute * define a button method, add a button to the page, and set an onclick event for the button, which dynamically import a file */
import { print } from './num.js'

print()

function button () {
  const button = document.createElement('button')
  const text = document.createTextNode('click me')
  button.appendChild(text)
  button.onclick = e= > import('./info.js').then(res= > {
    console.log(res.log)
  })
  return button
}

document.body.appendChild(button())

Copy the code

/src/num.js

import { tmpPrint } from './tmp.js'
export function print () {
  tmpPrint() 
  console.log('I'm a print method for num. Js')}Copy the code

/src/tmp.js

export function tmpPrint () {
  console.log('tmp.js print')}Copy the code

/src/info.js

export const log = "log info"

Copy the code

packaging

Project root directory execution

npx webpack
Copy the code

This is the file name of the output we configured in webpack.config.js, but what about 0.main.js? What’s this? We haven’t configured it, so we can think about it, and then we’ll figure it out in the code

Template file

Create a new /dist/index.html file and introduce the packaged main.js

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script src = "./main.js"></script>
</body>
</html>

Copy the code

Open index.html in your browser

Network

Console

Elements

After the index.html is loaded, the resources are loaded and the code is executed. You can see that the synchronous logic in the code is executed. Next, the asynchronous logic (click the button)

Network

Console

Elements

If you look at the message, what happens when you click the button? On the surface it looks like this:

Click the button to dynamically add a script tag to the HTML, introduce a file (0.main.js), and then send two HTTP requests for resource loading, and output a log in the console after successful loading.

Import (‘xxModule ‘), which provides a lazy loading mechanism, dynamically adds script ‘tags to HTML, then loads resources and executes them.

Well, the phenomenon we have also looked at, next we go to the source code to find the answer

Source code analysis

Let’s take apart the packaged code step by step

First, we fold the packaged code as follows

(function (modules) {
  // xxxx({})// xxx
})

Copy the code

Does this code look familiar? It’s a self-executing function

Function parameters

(function (modules) {
  // xxxx({})/ / SRC/index. Js module
    "./src/index.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        var _num_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/num.js");
        Object(_num_js__WEBPACK_IMPORTED_MODULE_0__["print") ()function button() {
          const button = document.createElement('button')
          const text = document.createTextNode('click me')
          button.appendChild(text)
          button.onclick = e= > __webpack_require__.e(0)
            .then(__webpack_require__.bind(null."./src/info.js"))
            .then(res= > {
              console.log(res.log)
            })
          return button
        }
        document.body.appendChild(button())
        //# sourceURL=webpack:///./src/index.js?" );
      }),

	/ /. / SRC/num. Js module
    "./src/num.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, "print".function () { return print; });
        var _tmp_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/tmp.js");
        function print() {
          Object(_tmp_js__WEBPACK_IMPORTED_MODULE_0__["tmpPrint") ()console.log('I'm a print method for num. Js')}//# sourceURL=webpack:///./src/num.js?" );
      }),

    / / / SRC/TMP. Js module
    "./src/tmp.js":
      (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        // eval("__webpack_require__.r(__webpack_exports__); \n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"tmpPrint\", function() { return tmpPrint; }); \nfunction tmpPrint () {\n console.log('tmp.js print')\n}\n\n//# sourceURL=webpack:///./src/tmp.js?" );
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(
          __webpack_exports__,
          "tmpPrint".function () {
            return tmpPrint;
          });
        function tmpPrint() {
          console.log('tmp.js print')}//# sourceURL=webpack:///./src/tmp.js?" );})})Copy the code

If this sounds familiar, think back to the packaging process for WebPack. Webpack turns all import moduleName from ‘xxModule’ into a Map object with key as the file path and value as an executable function. The function content is actually the exported content of the module, and of course the module itself is treated by WebPack, and so on.

We can find modules and their contents in our code from the packaged Map object, but there is also a lot of code that we did not write, such as __webpack_require__. What is this? There are actually some webPack custom methods that we’ll read on

The body of the function

The following content is the complete code after packaging, with certain formatting and detailed annotations in key places. When reading, search for “entry position” and start to read step by step. If there is any place that is difficult to understand, you can coordinate with single step debugging

/** * modules = { * './src/index.js': function () {}, * './src/num.js': function () {}, * './src/tmp.js': function () {} * } */
(function (modules) { // webpackBootstrap
  /** * install a JSONP callback for chunk loading /** * install a JSONP callback for chunk loading@param {*} data = [
   *  [chunkId],
   *  {
   *    './src/info.js': function () {}
   *  }
   * ]
   */
  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]);
      }
      // Change the loading state of the module to 0, indicating that the loading is complete
      installedChunks[chunkId] = 0;
    }
    for (moduleId in moreModules) {
      if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
        // Execute module codemodules[moduleId] = moreModules[moduleId]; }}if (parentJsonpFunction) parentJsonpFunction(data);

    while(resolves.length) { resolves.shift()(); }};// The module cache is similar to The commonJS require cache, except that The key is a relative path
  var installedModules = {};


  /** * define chunk loading condition, For example, main = 0 is loaded * 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 returns the path of the chunk to be loaded dynamically
  function jsonpScriptSrc(chunkId) {
    return __webpack_require__.p + "" + chunkId + ".main.js"
  }

  /** * The require function receives a moduleId, which is a relative path to The module, then checks The cache (if there is no cache), and then executes The module code. Module. exports * loads the specified module and executes it. Returns the execution result (module.exports) * * __webpack_require__(__webpack_require__. S = "./ SRC /index.js") */
  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) ** / example * module = installedModules['./ SRC /index.js'] = {* I: './src/index.js', * l: false, * exports: {} * } */
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false.exports: {}};/** * Execute the module function * modules['./src/index.js'] is a 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

    // Retrieve the module from the cache
    var installedChunkData = installedChunks[chunkId];
    
    // 0 means "already installed".
    if(installedChunkData ! = =0) { 
      
      // The module is not 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, create script element
        var script = document.createElement('script');
        var onScriptComplete;

        script.charset = 'utf-8';
        // Set the timeout period
        script.timeout = 120;
        if (__webpack_require__.nc) {
          script.setAttribute("nonce", __webpack_require__.nc);
        }
        // script.src = __webpack_public_path__ + chunkId + main.js, that is, the module path
        script.src = jsonpScriptSrc(chunkId);

        // create error before stack unwound to get useful stacktrace later
        var error = new Error(a);// Load the result handler
        onScriptComplete = function (event) {
          // avoid mem leaks in IE.
          script.onerror = script.onload = null;
          clearTimeout(timeout);
          var chunk = installedChunks[chunkId];
          if(chunk ! = =0) {
            // The chunk state is not 0, indicating a loading problem
            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; }};// The timeout timer is executed after the timeout
        var timeout = setTimeout(function () {
          onScriptComplete({ type: 'timeout'.target: script });
        }, 120000);
        // Load error or successfully loaded handler
        script.onerror = script.onload = onScriptComplete;
        // Add the script tag to the end of the head tag
        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
   * @param {*} exports = {}
   * @param {*} Name = Module name *@param {*} Getter => module function * * defines a key value on the exports object, where key is the moduleName and value is the module's executable function. * exports = {* moduleName: module function * } */
  __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
   * @param {*} exports = {}
   * 
   * exports = {
   *  __esModule: true
   * }
   */
  __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 && typeofvalue ! ='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_public_path__
  __webpack_require__.p = "";

  // on error function for async loading
  __webpack_require__.oe = function (err) { console.error(err); throw err; };
  
  /** * Asynchronously loaded resource items are stored via global properties. If the property value is not empty when the package file is loaded for the first time, resources are already loaded. * Synchronize these resources to the installedChunks object to avoid resource reloading. Of course, this is also the reason why all sub-applications in the micro-application framework single-SPA must export the * package name. Otherwise, once the asynchronous module with the same name exists, the subsequent module with the same name will not be loaded, and the resource displayed is the first loaded module with the same name. * JS global scope contamination * * Had mentioned in webpack website * https://webpack.docschina.org/configuration/output/#outputjsonpfunction * /
  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;


  Load entry module and return exports */
  return __webpack_require__(__webpack_require__.s = "./src/index.js"); ({})// All import moduleName from 'xxModule' in the code becomes the following Map object

    / / / SRC/index. Js module
    "./src/index.js":
      / * * *@param module = {
       *  i: './src/index.js',
       *  l: false,
       *  exports: {}
       * 
       * @param __webpack_exports__ = module.exports = {}
       * 
       * @param __webpack_require__ => a custom require function that loads the specified module and executes the module code, returning the result * */
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        /** * * define __esModule on exports * __webpack_exports = module.exports = { * __esModule: true * } */
        __webpack_require__.r(__webpack_exports__);
        // Load the./ SRC /num. Js module
        var _num_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/num.js");
        Object(_num_js__WEBPACK_IMPORTED_MODULE_0__["print") ()function button() {
          const button = document.createElement('button')
          const text = document.createTextNode('click me')
          button.appendChild(text)
          /** * Asynchronous execution */
          button.onclick = e= > __webpack_require__.e(0)
            => window["webpackJsonp"]. Push = window["webpackJsonp"]. Push = function (data) {}
            .then(__webpack_require__.bind(null."./src/info.js"))
            .then(res= > {
              console.log(res.log)
            })
          return button
        }
        document.body.appendChild(button())
        //# sourceURL=webpack:///./src/index.js?" );
      }),

    / / / SRC/num. Js module
    "./src/num.js":
       / * * *@param module = {
       *  i: './src/num.js',
       *  l: false,
       *  exports: {}
       * 
       * @param __webpack_exports__ = module.exports = {}
       * 
       * @param __webpack_require__ => a custom require function that loads the specified module and executes the module code, returning the result * */
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
         /** * * define __esModule on exports * __webpack_exports = module.exports = { * __esModule: true * } */
        __webpack_require__.r(__webpack_exports__);
        /** * module.exports = { * __esModule: true, * print * } */
        __webpack_require__.d(__webpack_exports__, "print".function () { return print; });
        // Load the./ SRC /tmp.js module
        var _tmp_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/tmp.js");
        function print() {
          Object(_tmp_js__WEBPACK_IMPORTED_MODULE_0__["tmpPrint") ()console.log('I'm a print method for num. Js')}//# sourceURL=webpack:///./src/num.js?" );
      }),

    / / / SRC/TMP. Js module
    "./src/tmp.js":
      / * * *@param module = {
       *  i: './src/num.js',
       *  l: false,
       *  exports: {}
       * 
       * @param __webpack_exports__ = module.exports = {}
       * 
       * @param __webpack_require__ => a custom require function that loads the specified module and executes the module code, returning the result * */
      (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
         /** * * define __esModule on exports * __webpack_exports = module.exports = { * __esModule: true * } */
        __webpack_require__.r(__webpack_exports__);
        /** * module.exports = { * __esModule: true, * tmpPrint * } */
        __webpack_require__.d(__webpack_exports__, "tmpPrint".function () { return tmpPrint; });
        function tmpPrint() {
          console.log('tmp.js print')}//# sourceURL=webpack:///./src/tmp.js?" );})});Copy the code

conclusion

After learning the above content, compared to the initial question, the answer is clear.

Interviewer:

What do import moduleName from ‘xxModule’ and import(‘xxModule’) end up with after webpack compilation and packaging? How does it work in the browser?

Job seeker, answer:

Import is packaged by Webpack into some Map objects, key is the module path, value is the module executable function;

After the code is loaded into the browser, it is executed from the entry module. During the execution process, the most important function is the __webpack_require__ function defined by Webpack, which is responsible for the actual module loading and executing the module content, and returning the execution result. In fact, it is to read the Map object, and then execute the corresponding function.

Of course, the asynchronous method (import(‘xxModule’)) is more special, it will be a separate package, using dynamic loading mode, detailed process: When the user triggers the loading action, a script tag will be dynamically created in the head tag, and then an HTTP request will be sent to load the module. After the module is loaded, the code will be automatically executed. The main work is to change the state of the module in the cache, and the other is to execute the module code.

That’s the full answer to the interview question.

github