This modularity will be illustrated from the following points

Why modules are needed

CommonJs modular characteristics and implementation

The disadvantages of CommonJs

Less important AMD and CMD

ES6 modular

Why modularity is needed

In my opinion, there are several important reasons for modularity:

  1. The decoupling. Before modularization, business logic was highly coupled. Not easy to optimize code. For example, we compare a project to a robot. If there is no modularity, the client may need a robot with no arms. At this point, it can only be redeveloped. But if we make the arms, legs, and other parts into parts, we can put together what the customer needs.
  2. Avoid naming conflicts. Before modularization, all code was initialized in the same context, and it was easy to have naming conflicts when multiple people were collaborating.
  3. Independent and easy to maintain. Just like the computer network hierarchy, we don’t care how other modules’ code is implemented, we just need to maintain the import and output of our own modules. When a better solution emerges for the problem we are trying to solve, we simply change our own module without telling anyone else. For example, BEFORE ES6, I wrote an Ajax request, but due to the limitations of the technology at the time, I could only execute the successful request in the form of a callback. But after ES6, I had Promise, so I upgraded my module, but since the method names I provided to the outside didn’t change, it didn’t affect others. They only need to maintain their own module references to the output.
CommonJs modular characteristics and implementation

CommonJs is introduced by require and exposed by module.exports.

The main features of CommonJs are as follows:

  1. All files are modules, which means all modules are instances of a Module.

    const moduleParentCache = new SafeWeakMap();
    function Module(id = ' ', parent) { / / source location in / / source locations in: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
      this.id = id;
      this.path = path.dirname(id);
      this.exports = {}; // When instantiated, this is assigned to module, so module.exports, and this.exports is assigned to a variable exports. Exports === module.exports
      moduleParentCache.set(this, parent); / / cache
      updateChildren(parent, this.false); // Update the child node
      this.filename = null; // File name
      this.loaded = false; // Whether it has been loaded
      this.children = []; / / child nodes
    }
    Copy the code

  2. Caching takes precedence. That is, a file is loaded only when it is first referenced

    Module._load = function(request, parent, isMain) {/ / source location in: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
     / /... Eliminate irrelevant code
      const filename = relativeResolveCache[relResolveCacheIdentifier];
       if(filename ! = =undefined) { // If the file exists
         const cachedModule = Module._cache[filename]; // module. _cache stores cached files
         if(cachedModule ! = =undefined) { // If there is a cache
           updateChildren(parent, cachedModule, true);
           if(! cachedModule.loaded)// There is a cache but it is not loaded
             return getExportsForCircularRequire(cachedModule); / / load
           return cachedModule.exports; // returns the module's exports, that is, module.exports
         }
         deleterelativeResolveCache[relResolveCacheIdentifier]; }}/ /... Eliminate irrelevant code
    Module._cache[filename] = module; // Save the current module to the cache directory.
     if(parent ! = =undefined) {
       relativeResolveCache[relResolveCacheIdentifier] = filename;
     }
    / /... Eliminate irrelevant code
    }
    Copy the code
  3. Load synchronously at runtime. Test is only loaded when require(‘./test.js’) is executed, and undefined until then. Nodejs uses Chrome DevTools to debug –inspect-brk

  4. Exoprts or exports. This is because Node defines an exports when compiling your code, points to the memory address of the exports of the Module instance, and assigns this of the Module instance to Module. We saw in the first article that all Module instances have an exports attribute.

    Module.prototype._compile = function(content, filename) { / / source location in: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
    	// Discard useless code
      let result;
      const exports = this.exports; // This refers to an instance of Module.
      const thisValue = exports;
      const module = this; // Assign this to module
      if (requireDepth === 0) statCache = new SafeMap();
      if (inspectorWrapper) {
        result = inspectorWrapper(compiledWrapper, thisValue, exports.require.module, filename, dirname);
      } else {
         // ReflectApply is reflect.apply (), which is similar to the function.prototype.apply () method in ES5: Call a method and explicitly specify the this variable and arguments list, which can be arrays, or array-like objects.
        // reflect. apply(target, thisArgument, argumentsList) target thisArgument taeget when called, bind the this argumentsList entry argument
        result = ReflectApply(compiledWrapper, thisValue,
                              [exports.require.module, filename, dirname]);
      }
      hasLoadedAnyUserCJSModule = true;
      if (requireDepth === 0) statCache = null;
      return result;
    };
    Copy the code
  5. All code runs in the module scope. Does not contaminate the global scope. So all he needs to do before compiling is wrap up the module scope. As we know, JS only has block-level scope after ES6, and before ES6, only global scope and function scope. Without closures, the scope inside a function can be equivalent to a block-level scope. So it wraps a function around the file you write before compiling.

let wrap = function(script) { / / source location in: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
  return Module.wrapper[0] + script + Module.wrapper[1];
};

const wrapper = [
  '(function (exports, require, module, __filename, __dirname) { '.'\n}); ',];Copy the code

  1. The output is a copy of the value, not a copy of the reference.

    // a.js
    var x = 10;
    const arr = [];
    function changeX() {
      x = 20;
    },
    function pushData(item) {
      arr.push(item);
    },
    module.exports = {
      x,
      arr,
    };
    // b.js
    const a = require('./a.js');
    console.log(a);
    Copy the code

    Value copying means that the module I have referenced is not affected by changes to the module’s own internal values.

    As shown above. I a. JS exposed x outwards, and I introduced in B. js. After THE introduction of A. js by B.js, there may be some operations in A. js that change the value of x in A. Js, as shown in the changeX method. At this time, it will only affect the value of x inside the MODULE of A. js, not the value of x in the module of A that has been introduced in B.js.

    However, there is a problem that if it is exposed as a reference type, such as arR in A. js, if it changes, it will affect the value of ARR in a module that has been introduced by b.js. So isn’t there a conflict with value copying? The reason for this problem is that: Exposed are shallow, knowledge copy the address on the stack, and didn’t go to copy the data on the heap, because the base type is on the stack, so it will not be affected, but a reference type, knowledge stored on the stack pointer, the pointer to the detailed data in the heap memory, when the data is changed, because memory is a point to the same heap memory, So it’s going to be affected. For information about stacks and storage, see the JS series on data types, how to determine and where to store them

The disadvantages of CommonJs
  1. In the third point we described above, we can see that CommonJs is loaded at runtime and is loaded synchronously, which blocks subsequent execution. This results in CommonJs not being used on the client. The reason: If CommonJs works on the server, all files are on the server disk, and when synchronization is performed, the time we need to wait is the time the disk reads the file, which is very fast. But if you work on the client side, first of all, when loaded into a module, I need to go to the server requests the file back, assuming the server bandwidth of 1 m, your file size is 1 m, it needs time to request it back for a long time, this time, he blocked the execution, will cause the bad time is too long.

  2. Circular reference problem.

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

    As we explained in CommonJs modularity features and implementation # 2, cache first. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = The cache is preferred, but the console.log(b) and console.log(a) files will not be executed because the cached file is incomplete.

Less important AMD and CMD

Before ES6, CommonJs was not available on the client, so it spawned a variety of front-end modular solutions. Two of the most important ones were AMD, which used define(ID? , dependencies? < span style = “box-count: inherit! Important; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;” Introduce by require

define("module"["other1"."other1"].function(m1, m2) {
  // ... do something
  return something;
});
require(["module".".. /file"].function(module, file) { / *... * / });
Copy the code

CMD is very similar to the AMD implementation, except that CMD advocates a dependency on post-loading, loading at runtime, rather than performing a callback after loading.

define(function(require.exports.module) {
  var$=require('something');
  exports.something = ... ;module.exports = ... ; })Copy the code
ES6 modular

For ES6 modularization, you can import XXX from XXX or import {XXX} from XXX and export it by export or export default.

ES6 modular, JS native support, do not need to install the rest of the dependency can be used directly.

ES6 modularity differs from CommonJs in that:

  1. ES6 can be used on servers as well as clients

  2. ES6 module reference analysis occurs during compilation, which is divided into the following types

    If not packaged with webpack third-party plug-ins, it is analyzed at execution context creation time. To learn more about the execution context, go to: Dig into the EXECUTION context of JS. As you can see from the figure below, there is already a module called byeL in scope before we start executing import {test as byeL}. This is also known as compile-time loading, where the execution context is created and initialized as the code block enters the execution stack, and an import, if any, is parsed first in the execution context. This is why our import XXXX from XXX cannot be written in a judgment statement or function. If written in a judgment statement or function, analysis will be loaded at runtime, which is contrary to the ES6 specification.

    If packaged with a third party such as WebPack, it converts the source code into ES5 CommoJs mode when WebPack compiles it. One of the drawbacks of CommonJs mentioned earlier is synchronous loading. You may think that converting to ES5 CommonJs mode will have to wait. That’s not true, it’s now a single page, and WebPack will pack all the files into the same file (if you don’t set the package to different files). So all the script files are all loaded back on the first request. The specific escape is as follows:

    Sample code:

    // a.js
    export const byeL = 123;
     //b.js
    import { byeL } from './a.js';
    Copy the code

    Escape:

    (function (modules) { // webpackBootstrap
      // The module cache
      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;
      }
    
    
      // Load entry module and return exports
      return __webpack_require__(__webpack_require__.s = 1);
    })
      / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
      ([
        /* 0 */
        (function (module, __webpack_exports__, __webpack_require__) { // This is a function that needs to be wrapped when CommobJs is compiled.
          "use strict";
          const byeL = 123;
          __webpack_exports__["byeL"] = i;
    
        }),
        / * 1 * /
        (function (module, __webpack_exports__, __webpack_require__) {
    
          "use strict";
          Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
          var __WEBPACK_IMPORTED_MODULE_0__a__ = __webpack_require__(0);
          console.log(__WEBPACK_IMPORTED_MODULE_0__a__["a" /* byeL */], __WEBPACK_IMPORTED_MODULE_0__a__["b" /* j */")}) ");Copy the code
  3. ES6 outputs references that, when changed in the referenced module, affect the values in the referenced module.

As can be seen from the figure, the Module actually stores an object, which stores variables exported by the Module, etc. This object exists on the heap, which causes that when other places change the data on the heap, other modules will also be aware of the change.