Officially, Webpack is a static module wrapper for modern JavaScript applications. It unifies the various modularization schemes in the development process of JavaScript, and we can think of Webpack as a unified solution for JavaScript modularization.

If you are not familiar with the various modular schemes of JavaScript, you are advised to read the history of JavaScript modules.

In the process of compiling the source code in Webpack, there is a module packaging work. In fact, Webpack starts from the entry file, analyzes the dependencies of each module in the source code step by step, and enters the final package file.

Let’s take a closer look at the module packaging mechanism of Webpack using CommonJS and ES6 as examples.

The following simple configuration of webpack.config.js is used in the following examples.

// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  mode:'development'
};
Copy the code

CommonJS

In CommonJS module mechanism, use require import module, module.exports/exports module.

The module file bar.js and the entry file index.js are as follows:

// src/bar.js
var num = 1;
function addNum(){
  num++;
}

module.exports = { 
  num: num, 
  addNum: addNum
}
Copy the code
// src/index.js
var bar = require('./bar.js'); console.log(bar.num) // 1 bar.addNum(); console.log(bar.num); / / 1Copy the code

After running the webpack command, we can get the final output file bundle.js. The content of the generated bundle.js file is actually a self-executing function, and its parameter modules object contains the entry file and various dependent modules, as follows:

{
 "./src/bar.js": (function (module, exports) {}),
 "./src/index.js": (function (module, exports, __webpack_require__) {})
}
Copy the code

In modules objects, each property is called a file path string, and the value of the property is a function that contains the content of the file source code, and the import and output functions in the source code are customized.

The key is the __webpack_require__ method:

var installedModules = {};
// 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;
}
Copy the code

The __webpack_require__ method takes a module’s unique moduleId as an argument and does the following:

  1. Determine whether the module exists in the cache object, if so, return the corresponding module in the cache object, if not, define the module object and store it in the cache object;
  2. Executes functions of the corresponding module in the Modules object (where the module’s output is stored in module.exports);
  3. Exports returns the module module.exports;

As a result, the resulting self-executing function returns a reference to the entry file, in addition to the definitions of some variables and methods:

return __webpack_require__(__webpack_require__.s = "./src/index.js");
Copy the code

The __webpack_require__ function is then executed as expected, passing in the moduleId as./ SRC /index.js, so the corresponding attribute function in the Modules object is executed:

// modules./ SRC /index.jsfunction(module, exports, __webpack_require__) {
  eval("// src/index.js\nvar bar = __webpack_require__(/*! ./bar.js */ \"./src/bar.js\"); \n\nconsole.log(bar.num) // 1\nbar.addNum(); \nconsole.log(bar.num); // 1\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code

As you can see, in this function, the custom __webpack_require__ method replaces the CommonJS require method by importing the bar.js module and executing the corresponding code.

/ SRC /bar.js attribute value functions are introduced, and the code inside the function is executed:

// modules./ SRC /bar.jsfunction(module, exports) {
  eval("var num = 1; \nfunction addNum() {\n num++; \n}\n\nmodule.exports = {\n num: num,\n addNum: addNum\n}\n\n//# sourceURL=webpack:///./src/bar.js?");
})
Copy the code

Module. exports does not belong to the CommonJS module syntax, but is an attribute of the module object passed in by the function. This module is a custom module object in Webpack and has the following form:

var module = installedModules[moduleId] = {
  i: moduleId,
  l: false,
  exports: {}
};
Copy the code

This is how the output of module content is implemented in Webpack.

Now that Webpack has implemented CommonJS module import output, let’s dig into one of the CommonJS module output features: The CommonJS module output is a cache of values, there is no dynamic update.

In this example, we can print num in the index.js file, then call the addNum method, and then print num. Because the addNum method changes the value of num in bar.js, the output in bar.js is a shallow copy.

So, when executing the attribute value function corresponding to./ SRC /bar.js, you can find the following code:

module.exports = {\n  num: num,\n  addNum: addNum\n}
Copy the code

This also shows that the output of modules in Webpack still follows the characteristics of CommonJS module output, which is implemented as a shallow copy (note that this is compared to the export output feature in ES6 below).

ES6

In the ES6 modularity scheme, import is used to import modules and export or export default is used to export modules.

export

// src/bar.js
var num = 1;
function addNum(){
  num++;
}

export { num, addNum }
Copy the code
// src/index.js
import { num, addNum } from './bar.js'; console.log(num) // 1 addNum(); console.log(num); / / 2Copy the code

If you run the webpack command, it will output a bundle.js file, which is also a self-executing function in the bundle.js file, which is basically the same as the CommonJS module package file, except that the corresponding attribute function in modules object is different.

The output features of the ES6 module mechanism are as follows: The interface output by the export statement is dynamically bound to its corresponding value.

In modules,./ SRC /index.js corresponds to the property values as follows:

// modules./ SRC /index.jsfunction (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _bar_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bar.js */ \"./src/bar.js\"); \n\n\nconsole.log(_bar_js__WEBPACK_IMPORTED_MODULE_0__[\"num\"]) // 1\nObject(_bar_js__WEBPACK_IMPORTED_MODULE_0__[\"addNum\"])(); \nconsole.log(_bar_js__WEBPACK_IMPORTED_MODULE_0__[\"num\"]); // 2\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code

./ SRC /bar.js corresponding attribute value function is:

// modules./ SRC /bar.jsfunction (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  eval("__webpack_require__.r(__webpack_exports__); \n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"num\", function() { return num; }); \n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"addNum\", function() { return addNum; }); \nvar num = 1; \nfunction addNum() {\n num++; \n}\n\n\n\n//# sourceURL=webpack:///./src/bar.js?");
})
Copy the code

Module. exports is not implemented directly as a shallow copy, but as a __webpack_require__.d method:

__webpack_require__.d = function (exports, name, getter) {
  if(! __webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable:true, get: getter }); }};Copy the code

This method implements access to imported variables directly by defining getters for object attributes, still internal variables of the original module.

The reason why getters are defined but not setters is that in the ES6 module, the variables entered by the import command are read-only because they are essentially input interfaces. If the import pair variable is an object, overwriting the attributes of the variable is allowed.

export default

// src/bar.js
var num = 1;
function getNum() {return num;
}

export default getNum
Copy the code
// src/index.js
import getNum from './bar.js';

console.log(getNum())  // 1
Copy the code

Do the same with the webpack command to get bundle.js.

In modules,./ SRC /index.js corresponds to the property values as follows:

// modules./ SRC /index.jsfunction (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _bar_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bar.js */ \"./src/bar.js\"); \n\n\nconsole.log(Object(_bar_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()) // 1\n\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code

In modules,./ SRC /bar.js corresponds to the property values as follows:

// modules./ SRC /bar.jsfunction (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  eval("__webpack_require__.r(__webpack_exports__); \nvar num = 1; \nfunction getNum(){\n return num; \n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (getNum); \n\n//# sourceURL=webpack:///./src/bar.js?");
})
Copy the code

Export. Default is a shallow copy of CommonJS, stored in the default property of module.exports. In entry files it is accessed through the default property of the object.


Reference article:

Principles of Webpack organization module – Basics