preface

The modularized implementation of WebPack was mentioned earlier in the Front End Vision front End Engineering Preexistence article, but it was not extended in detail at the time. Let’s recall the question: how does WebPack achieve modularity in a browser that doesn’t support modularity? With this in mind, I will introduce webPack’s modular implementation mechanism today.

Reference: Ruan Yifeng’s introduction to modularity

What is modularity?

Modularity is the process of dividing a system into modules, layer by layer, from top to bottom, to solve a complex problem. Divide a large project into small modules, each module independent of each other, but also interdependent. Comparing a complex system to a car, the engine, chassis, body, electrical equipment, etc., can be compared to modules, each of which is responsible for independent functions but interdependent on each other.

Why does the front-end need modularity?

This question is addressed in the article “Front-end Vision” : The preexistence of front-end engineering. Because JavaScript is an interpreted scripting language, it started out handling some form submission logic and some simple interaction logic for web pages, and there was no need for modularity in the front end. With the rise of Ajax and Node, the front-end takes on more and more complex tasks in a Web application. The increasing proportion of the front end in the business also means more code, so engineering and modularity at the code level is a must in front end development.

Initial modular implementation

1. Original mode

function module1() {
  var status = 0;
  // ...
}

function module2() {
  var status = 0;
  // ...
}
Copy the code

In the original mode of modularity, a function is a module, and you can call the function directly when you use it. With such a modular implementation, all functions (modules) are exposed to the global environment, and if the development is multi-party, the global environment name conflict can be quite annoying.

2. Object writing

var module = { status: 0, module1: function() { var status = 0; / /... }, module2: function() { var status = 0; / /... }}Copy the code

Object writing is modularized. You can obtain the elements inside the module by using module.status. However, with such a modular implementation, the external can change its internal state: module.status = 1.

3. Perform function writing immediately

var module = (function() { var status = 0; function add() { status++; } function sub() { status--; } function val() { return status; } return { val, add, sub } })() console.log(module.val()); // 0 module.add(); console.log(module.val()); // 1 module.sub(); console.log(module.val()); / / 0Copy the code

Returning a Module object through an instant-execute function is done through closures, which you can see here if you don’t already know how. In this way, its internal status variable cannot be changed from the outside, only through its internal exposure of add, sub, and other functions to modify its internal variables. What if a module is so large that it needs to be broken up into smaller modules, or if one module inherits from another?

4. Zoom in mode

var module = (function() {
  var status = 0;

  function add() {
    status++;
  }
  
  function sub() {
    status--;
  }

  function val() {
    return status;
  }

  return {
    val,
    add,
    sub
  }
})()

var module2 = (function(mod){
  mod.more = function() {
    // ...
  }

  return mod;
})(module)

console.log(module2)
Copy the code

Module2 exposes a Module interface, accepts another module as an input, inherits the other module’s properties and methods, and adds its own methods. Console print:

Here is the basic introduction to the original modularization implementation mechanism: using closures to build a local scope (function scope) that is used as a module to implement the modularization mechanism. However, this is only a special means in the absence of a modular specification, so later proposed CommonJS, AMD and other modular solutions, and finally in ES6 esModule. Let’s take a look at what these modular solutions really are.

CommonJS modular solution

In 2009, NodeJS was introduced as a JavaScript server environment that, unlike the browser, had to have a modular specification. The need to manipulate files, interact with the operating system, and interact with other programs needs to be extracted as separate modules. Node’s modularity specification is based on the CommonJS specification.

CommonJS module import

The CommonJS specification, which specifies that each.js file is a module, uses the require method to import modules:

const a = require('./b.js');
Copy the code

Export of CommonJS module

The CommonJS specification states that within each module, the module variable represents the current module. This variable is an object whose exports property is the external interface. If you load a module, you load the module.exports property of that module.

const a = require('./b.js');

const status = 0;

module.exports = {
  status
}
Copy the code

Webpack’s implementation of CommonJS

Because modularity is not supported by lower browser versions, in today’s front-end engineering, we need tools to help us convert our modular code to run on lower browser versions (currently Google and other higher browser versions already support es6Module). Let’s look at how WebPack implements the CommonJS specification.

First, let’s start a new webpack-demo project by executing the following command.

mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
Copy the code

Then create a SRC folder in your project and create two files: index.js and module1.js:

Index. Js:

const module1 = require("./module1");

const num = module1.status + 12;

module.exports = {
  num,
};
Copy the code

Module1. Js:

const status = 0;

module.exports = {
  status,
};

Copy the code

Then we configure webPack again, creating a new webpack.config.js file in the root directory:

webpack.config.js

var path = require("path");

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

It is important to configure mode: “development” here, because we need to see what is packaged, otherwise WebPack will compress the code for us.

So far everything is ready, let’s run the webpack command from the root directory:

The specific implementation

You can see that a dist folder is generated with an index.js file in it, so let’s take a look at what it is. I’ll leave out most of the comments here because the packaged code looks very rough and messy. Let’s look at the code in three parts:

The first part

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the first part of the * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / var __webpack_modules__ = { "./src/index.js": ( module, __unused_webpack_exports, __webpack_require__) => { eval( 'const module1 = __webpack_require__(/*! ./module1 */ "./src/module1.js"); \r\n\r\nconst num = module1.status + 12; \r\n\r\nmodule.exports = {\r\n num,\r\n}; \r\n\n\n//# sourceURL=webpack://webpack-demo/./src/index.js? '); }, "./src/module1.js": (module) => { eval( "const status = 0; \r\n\r\nmodule.exports = {\r\n status,\r\n}; \r\n\n\n//# sourceURL=webpack://webpack-demo/./src/module1.js?" ); }};Copy the code

First, we can see that it is a self-executing function. We can see that there is an __webpack_modules__ object, which is a collection of all the modules we wrote (index.js and module1.js above). The object’s key is the reference path and its value is a function that takes module, __unused_webpack_exports, and __webpack_require__. The body of the function is an eval function that executes the code we wrote. In fact, our JS file is processed by WebPack and packaged with a layer of functions:

We now know that WebPack will package all the JS files we write into a function, so that our JS files are in the scope of a function and don’t pollute the global environment. After packaging, it is assigned to a __webpack_modules__ object to bring in all modules. So what are these entries? Let’s keep looking:

The second part

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the second part of * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / var __webpack_module_cache__ = cache object {}; // Export function - returns the module in the cache object if it has already been cached. Otherwise, execute the module code, store it in the cache and return function __webpack_require__(moduleId) {// Check whether the module's cache exists in the cache, Var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule ! == undefined) { return cachedModule.exports; Var module = (__webpack_module_cache__[moduleId] = {exports: {},}); var module = (__webpack_module_cache__[moduleId] = {exports: {},}); // Execute the module code __webpack_modules__[moduleId](module, module.exports, __webpack_require__) if there is no cache; // exports; }Copy the code

The first is a __webpack_module_cache__, which is a cache object. We’ll see what it does next.

Next up is a __webpack_require__ function, which is the require function as one of the three incoming parameters. Its specific function is: If so, use the cache directly. If not, call the module from the __webpack_modules__ collection in part 1 and save a copy to the cache. Finally, the export of the required module is returned. The first two arguments are the reference address of the cached object to which we assign a value in the module’s last module.exports, which is recorded by the cached object.

We now know that all modules we write, if called by require, will be copied and stored in the cache, and that module will always be called in the cache. It is worth noting that WebPack will not introduce our module into the __webpack_modules__ collection if it is not already referenced.

The third part

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * in the third section * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / var __webpack_exports__ = __webpack_require__("./src/index.js");Copy the code

The final statement is to execute the entire code logic by executing the require function once, calling the entry file that we configured in the configuration file.

conclusion

Webpack’s implementation of CommonJS is based on function scope. It takes the module code we write (js file) and packages a layer of functions. It implements CommonJS require with its own __webpack_require__ method. When a module is referenced, the module’s module.exports is placed in a cache object, and the next reference reads the module directly from the cache. As soon as it starts loading the entry functions, it puts all the modules used into the cache at the beginning, which is why webPack takes so long to compile and pack. Because WebPack has chosen to load all the modules at the beginning, if the project is too large, the more modules you load, the longer it will take.

We already know the CommonJS implementation, whose require does not execute the following statement until the module has been loaded, unless it has been cached. So its import is synchronous, which is not appropriate in a browser environment, so a modular specification for asynchronous loading has come in.

AMD modular solution

AMD is short for “Asynchronous Module Definition”, which means “Asynchronous Module Definition”. It loads modules asynchronously and does not affect the execution of statements following the module. All statements that depend on this module are defined in a callback function that will not run until the load is complete.

AMD module import and export

// Define ("module1", function(m) {}); / / load the require (". / module1 "], function (module1) {});Copy the code

I’m not going to go into AMD’s modular solution here, but if you’re interested, you can click here

ES6 modular solution

ES6 adds a modular approach, which is probably the one we’re most familiar with. Let’s first look at the import and export of the ES6Module module.

ES6Module import

import module1 from "./module1";
Copy the code

The export ES6Module

function add(a, b) {
  return a + b;
}

export default {
  add,
};
Copy the code

Webpack implementation of ES6Module

This modularity scheme is now supported in higher versions of Google’s browser, and can be implemented by adding type=”module” to the script tag, which will not be expanded here. However, many browsers do not support the modular approach of ES6, so we still need tools like WebPack to implement it. Webpack supports a variety of modularity solutions to convert modular code from ES6 into modular code that is compatible with lower browser versions. Let’s see how this works. If you are interested, you can check out the official WebPack documentation

The specific implementation

Let’s change the two files in the above example to ES6Module

Index. Js:

import module1 from "./module1";

module1.add(1, 2);
Copy the code

Module1. Js:

function add(a, b) {
  return a + b;
}

export default {
  add,
};

Copy the code

We then run the webpack command to package it, and you can see that dist generates another index.js file. As you can see, in terms of the components of index.js packaged with CommonJS, it has one more component than the one, two and three:

/* WebPack/Runtime /define property getters * {// define getter functions for harmony exports __webpack_require__. D = (exports,) definition) => { for(var key in definition) { if(__webpack_require__.o(definition, key) && ! __webpack_require__.o(exports, key)) { Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); }}}; }) (); / * webpack/runtime/hasOwnProperty shorthand * / / / this function is used to judge whether there is a property of objects (() = > {__webpack_require__. O = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) })(); /* webpack/runtime/make Namespace object */ // / this function is used to mark an __esModule attribute (() => {// define) to the module.exports object of the ES6 module __esModule on exports __webpack_require__.r = (exports) => { if(typeof Symbol ! == 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); }; }) ();Copy the code

Let’s look at the extra part and see how it’s used:

  1. __webpack_require__.d: this is a core function for exporting objectsmodule.exportsExtended attributes are used to assign values to exported objects.
  2. __webpack_require__.o: This function determines whether an attribute exists in an object.
  3. __webpack_require__.r: this function is used to flag the module.exports object to an ES6 module__esModuleProperties.

Let’s take a look at the packaged code. Let’s take a look at index.js before and after compilation:

Take a look at module1.js before and after compilation:

conclusion

So we can see very clearly what this addition does. Webpack converts ES6Module modularity in the same way as CommonJS, but identifies which modules belong to the ES6Module.

conclusion

Front-end modularity is supported to varying degrees across browsers, and WebPack provides a modular solution for older browsers. In the process of our work and study, we should know not only the how, but also the why. I hope my article is helpful to you, if you feel harvested, please click a like to support it, thank you for watching.