This is the first day of my participation in Gwen Challenge

Modern JavaScript development, of course, suffers from large amounts of code and extensive use of third-party libraries. The solution to this problem usually involves breaking the code into many pieces and then connecting them together in some way.

Prior to the ECMAScript 6 module specification, there was an urgent need for module behavior, even though browsers didn’t support it natively. ECMAScript also does not support modules, so libraries or code libraries that wish to use module patterns must “fake” module-like behavior based on JavaScript’s syntactic and lexical features.

Because JavaScript is an interpreted language for asynchronous loading, the various module implementations that are widely used also take on different shapes. These different shapes lead to different results, but in the end they all implement the classic modular pattern

Development history of front-end module system

1. Stage 1 – How files are divided

We started with modularity based on file partitioning, the original modular system of the Web.

This is done by placing each function and its associated state data separately into a separate JS file, with the convention that each file is a separate module.

Use a module to introduce the module to the page, one script tag for each module, and then directly call the members (variables/functions) in the module.

The directory structure

├─ class-1 exercises ─ Module-a.├ ─ module-B.├ ─ index.htmlCopy the code

module-a.js

// module-a.js
function foo() {
  console.log("moduleA#foo");
}
Copy the code

module-b.js

// module-b.js
var data = "something";
Copy the code

index.html

<! DOCTYPEhtml>
<html>
  <head>
      
    <meta charset="UTF-8" />
      
    <title>Stage 1</title>
  </head>
  <body>
      
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
      
    <script>
      // Use global members directly
      foo(); // There may be a naming conflict
      console.log(data);
      data = "other"; // Data may be modified
    </script>
  </body>
</html>
Copy the code

Disadvantages:

  • Modules work directly in the global scope, and a large number of module members pollute the global scope.
  • There is no private space, and all module members can be accessed or modified outside the module.
  • Once the number of modules increases, naming conflicts tend to occur.
  • Unable to manage dependencies between modules
  • It is also difficult to identify which module each member belongs to during maintenance.

In short, this primitive “modular” approach to implementation relies entirely on conventions, which can be very unreliable as projects get bigger, so we need to fix as many of the problems as possible.

2. Stage 2 — Namespace mode

The convention is that each module exposes only one global object to which all module members are mounted,

By “wrapping” each module into a global object,

This approach is like adding “namespaces” to the members of a module, so we also call it the namespace approach.

module-a.js

// module-a.js
window.moduleA = {
  method1: function () {
    console.log("moduleA#method1"); }};Copy the code

module-b.js

// module-b.js
window.moduleB = {
  data'something'
  method1function ({
    console.log('moduleB#method1')}}Copy the code

index.html

<! DOCTYPEhtml>
<html>
  <head>
      
    <meta charset="UTF-8" />
      
    <title>Stage 2</title>
  </head>
  <body>
      
    <script src="module-a.js"></script>
      
    <script src="module-b.js"></script>
      
    <script>
      moduleA.method1();
      moduleB.method1(); // Module members can be modified
      moduleA.data = "foo";
    </script>
  </body>
</html>
Copy the code

This approach to namespaces only solves the problem of naming conflicts, but other problems remain.

Stage 3 — IIFE

To provide the necessary encapsulation in accordance with the module pattern, modules prior to ES6 sometimes encapsulated the module definition in an anonymous closure using Function scope and Immediately Invoked Function Expression (IIFE)

Private space is implemented in this way.

This is done by placing each module member in a private scope created by a function that executes immediately, and by hanging onto global objects for members that need to be exposed externally.

module-a.js

// module-a.js
(function () {
  var name = "module-a";
  function method1() {
    console.log(name + "#method1");
  }
  window.moduleA = {
    method1: method1, }; }) ();Copy the code

module-b.js

// module-b.js
(function () {
  var name = "module-b";
  function method1() {
    console.log(name + "#method1");
  }
  window.moduleB = {
    method1: method1, }; }) ();Copy the code

index.html

<! DOCTYPEhtml>
<html>
  <head>
      
    <meta charset="UTF-8" />
      
    <title>Stage 2</title>
  </head>
  <body>
      
    <script src="module-a.js"></script>
      
    <script src="module-b.js"></script>
      
    <script>
      moduleA.method1();
      moduleB.method1(); // Module members can be modified
      moduleA.data = "foo";
    </script>
  </body>
</html>
Copy the code

This approach introduces the concept of private members, which can only be accessed through closures within module members, but it only solves the problem

  • Global scope contamination
  • Naming conflicts

Other problems remain

4. Stage 4-Iife depends on parameters

In addition to IIFE, we can use IIFE parameters as dependency declarations, which makes the dependencies between each module more obvious.

module-a.js

// module-a.js
(function ($) {
  // Clearly indicate the module's dependencies with parameters
  var name = "module-a";
  function method1() {
    console.log(name + "#method1");
    $("body").animate({ margin: "200px" });
    return "123456";
  }

  window.moduleA = {
    method1,
  };
})(jQuery);
Copy the code

module-b.js

// module-a.js
(function ($, moduleA) {
  // Clearly indicate the module's dependencies with parameters
  var name = "module-b";
  function method1() {
    console.log(name + "#method1");
    $("body").animate({ margin: "200px" });
  }
  function method2() {
    // console.log(name + '#method2');
    console.log(moduleA.method1());
  }
  window.moduleB = {
    method1,
    method2,
  };
})(jQuery, moduleA);
Copy the code

index.html

<! DOCTYPEhtml>
<html>
  <head>
      
    <title>Evolution</title>
  </head>
  <body>
      
    <script src="https://unpkg.com/jquery"></script>
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
      // moduleA.method1();
      moduleB.method2();
    </script>
  </body>
</html>
Copy the code

But it was only solved

  • Global scope contamination
  • Naming conflicts
  • Unable to manage dependencies between modules The problem.

Module loading problem

The above four phases are ways that early developers landed on modularity without tools or specifications, and they did solve many of the problems of implementing modularity in the front end, but there were still some unanswered questions.

<! DOCTYPEhtml>

<html>
  <head>
      
    <title>Evolution</title>
  </head>

  <body>
      
    <script src="https://unpkg.com/jquery"></script>

      
    <script src="module-a.js"></script>

      
    <script src="module-b.js"></script>

      
    <script>
      moduleA.method1();

      moduleB.method1();
    </script>
  </body>
</html>
Copy the code

The most obvious problem is module loading.

In these ways, although the organization of module code is solved, the problem of module loading is ignored. We are directly introduced these modules in the page through script tags, which means that the loading of modules is not controlled by the code, and it will be very troublesome to maintain over a long time.

Think about it. If your code needs a module, forgetting to include it in your HTML, or removing the use of a module from your code, and forgetting to remove references to that module, can cause a lot of problems and unnecessary trouble.

Ideally, a JS entry file would be introduced into the page, and the rest of the modules used could be controlled by code and loaded as needed.

The appearance of a module loader

 CommonJS

It’s the module specification that node.js follows. According to the convention, a file is a module, and each module has its own scope.

Exports members via module.exports and loads modules via the require function. However, if we try to use the specification directly on the browser side, some new problems arise.

Node.js module loading mechanism: CommonJS convention is to load modules synchronously

Since the Node.js execution mechanism loads modules at startup and only uses modules during execution, this approach is not a problem.

However, if you want to use synchronous loading mode on the browser side, it will cause a large number of synchronous mode requests, resulting in low application efficiency

Summary:

  • Implemented by Node.js.
  • Used on the server during module installation
  • There is noruntime/asyncThe module
  • throughrequireThe import module
  • throughmodule.exportsExport module
  • When you import, you get an object
  • Cannot use Tree shaking because when youimportYou get an object
  • Because you get an object, property look-up is done at run time and cannot be statically analyzed
  • You always get a copy of the object, so the module itself ** doesn’t change in real time
  • Circular dependency management is not elegant
  • Grammar is simple

Asynchronous Module Definition (AMD) Asynchronous Module Definition

CommonJS targets a server-side environment and can load all modules into memory at once,

AMD (Asynchronous Module Definition) module-definition systems target the browser execution environment, which requires consideration of network latency.

AMD’s general strategy is to let modules declare their own dependencies, and the module system running in the browser acquires dependencies on demand and executes modules that depend on them as soon as the dependencies are loaded.

The core of AMD module implementation is to wrap module definitions in functions. This prevents declarations of global variables and allows the loader library to control when modules are loaded. Wrapping functions also facilitate portability of module code, since all module code inside wrapping functions uses native JavaScript constructs. The wrapper module functions are parameters to global define, which are defined by the IMPLEMENTATION of the AMD loader library.

AMD modules can specify their dependencies using string identifiers, and the AMD loader calls the module factory function as soon as all dependency modules are loaded. Unlike CommonJS, AMD supports optionally specifying string identifiers for modules

26 define('moduleA', ['moduleB'], function(moduleB) {
   return {
    stuff: moduleB.doStuff();
}; });
Copy the code

AMD also supports require and Exports objects through which commonJs-style modules can be defined inside AMD module factory functions. This allows them to be requested as modules, but the AMD loader recognizes them as native AMD constructs rather than module definitions:

define("moduleA"["require"."exports"].function (require.exports) {
  var moduleB = require("moduleB");
  exports.stuff = moduleB.doStuff();
});
Copy the code

Dynamic dependencies are also supported in this way:

define("moduleA"["require"].function (require) {
  if (condition) {
    var moduleB = require("moduleB"); }});Copy the code

The asynchronous module definition specification. At the same time, a very famous library called require.js was released, which, in addition to implementing the AMD modularization specification, is itself a very powerful module loader.

It is relatively complex to use, and when the module division in the project is too detailed, there will be too many requests for JS files on the same page, which leads to a decrease in efficiency

Summary:

  • Implemented by RequireJS
  • Used when you load modules asynchronously in a client (browser) environment
  • throughrequireTo realize the import
  • Complicated grammar

Universal Module Definition (UMD) Universal Module Definition

In order to unify the CommonJS and AMD ecosystems, the Universal Module Definition (UMD) specification was created.

UMD can be used to create module code that can be used by both systems.

In essence, modules defined by UMD detect which module system to use at startup, configure it appropriately, and wrap all the logic in a function expression (IIFE) that is invoked immediately.

While this combination is not perfect, it is sufficient for both ecosystems to coexist in many scenarios.

Here is an example containing only one dependent UMD module definition (source: UMD repository on GitHub):

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    / / AMD. Register as an anonymous module
    define(["moduleB"], factory);
  } else if (typeof module= = ="object" && module.exports) {
    / / the Node. Strict CommonJS is not supported
    Module. Exports is supported by Node
    // Use in CommonJS environment
    module.exports = factory(require(" moduleB "));
  } else {
    // Browser global context (root is window) root.returnexports = Factory (root.moduleb);
  }
})(this.function (moduleB) {
  // Use moduleB in some way
  // Export the return value as the module
  // This example returns an object
  // But modules can also return functions as export values return {};
});
Copy the code

This pattern has variations that support strict CommonJS and browser global context. This wrapper function should not be expected to be written by hand; it should be generated automatically by the build tool. Developers only need to focus on the internalization of modules, not the boilerplate code.


Summary:

  • Combination of CommonJS + AMD (i.e., CommonJS syntax + AMD asynchronous loading)
  • Can be used in AMD/CommonJS environments
  • UMD also supports global variable definitions. Thus, UMD modules can work on both clients and servers.

ECMAScript modules

Summary:

  • Used on the server/client

  • Supports Runtime/static loading of modules

  • When you import, get the binding value (the actual value)

  • Import using import and export using export

  • Static analyzing

  • Tree Shaking is possible because ES6 supports static analysis

  • Always get the actual value so that you can change the module itself in real time

  • Better loop dependency management than CommonJS

Standard specification for modularity

While all of the approaches and standards described above are modularized, there are still some issues that developers find difficult to accept.

With the development of technology, the standard of JavaScript is gradually becoming perfect. It can be said that today’s front-end modularization has developed very mature, and the best practices of the front-end modularization specification are basically unified.

  • In the Node.js environment, we follow the CommonJS specification to organize modules.
  • In the browser environment, we follow the ES Modules specification.

With the development of technology, the standards of JavaScript are gradually improving, and the best practices of front-end modular specifications are basically unified.

As stated in the latest Node.js proposal, the Node environment will also move toward the ES Modules specification, which means that you should focus on the ES Modules specification.

Since CommonJS is a built-in module system, there are no environmental support issues when using it in node.js environments. You simply follow the standard and use require and Module directly.

CommJs module specification

  • JavaScript community initiated, applied and promoted in Node.js
  • This later affects browser-side JavaScript as well
    • eg: webpack

exports

// lib.js
exports.hello = "hello";

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

exports.obj = { name: "a".age: 20 };

setTimeout(() = > {
  console.log("============exports========================");
  console.log(exports);
  console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
}, 3000);
Copy the code
// index.js

const lib = require("./lib.js");

console.log("=============== can get things from lib =====================");
console.log(lib);

lib.modify = "modify";
console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
Copy the code

The exported object is the same reference as the external variable -lib. Because the external variable -lib can modify the contents of lib.js

module.exports

// lib.js
exports.hello = "hello";

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

exports.obj = { name: "a".age: 20 };

module.exports = function min(a, b) {
  return a - b;
};

setTimeout(() = > {
  console.log(
    "= = = = = = = = = = = = exports = = = still can print out the contents of the original = = = = = = = = = = = = = = = = = = = = =",);console.log(exports);
  console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
}, 3000);
Copy the code
// index.js

const lib = require("./lib.js");

console.log("=============== prints only min function =====================");
// Only the min function is printed
console.log(lib);
console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
Copy the code

Module. Exports’ ‘exports**

  • module.exportsoverwriteexports, butexportsThere are still references to
  • External references are usedmodule.exportsThe content of the

How does this work? Welcome to the comments section

ES Module Module specification

One of the biggest improvements in ES6 is the introduction of module specifications. This specification simplifies all previous module loaders, and native viewer support means that loaders and other preprocessing are no longer necessary. In many ways, the ES6 module system is the best combination of AMD and CommonJS.

Here is a code example

You first need to start a server to use ES Modules properly in HTML, so we use http-server

$ npm i -y

$ npm install http-server

$Add the code for the following two files$ http-server
Copy the code

module-a.js

function methodA() {
  return "123456";
}

export { methodA };
Copy the code

index.html

<! DOCTYPEhtml>
<html>
  <head>
    <title>Evolution</title>
  </head>
  <body>
      
    <script type="module">
      import { methodA } from "./module-a.js";

      console.log(methodA());
    </script>
  </body>
</html>
Copy the code

Please refer to the details

  • Module loading implementation – Ruan Yifeng
  • MDN official details
  • ECMAScript official details

summary

  • Module export:export ,export default
  • Module import: static import –importDynamic import –import()

conclusion

Ok, JavaScript module system shallow to parse here, let’s briefly summarize the main content of the article:

  • Firstly, we briefly analyze the development process of front-end modularization

    • The age
      • File partition mode
      • Namespace mode
      • An anonymous way to execute a function immediately
    • Module loader era
      • CommonJS
      • AMD
      • UMD
    • Modular specification standards era
      • CommonJS
      • ES Modules
  • We then looked at the common use of module loaders and modular specification standards

This is the main content of this article, the article is very simple, welcome to see the officer to leave your opinion in the comment section!

Feel the harvest of the students welcome to like, pay attention to a wave!