Since the birth of JavaScript language, the road of module standardization has been moving forward in twists and turns. A variety of solutions emerged in the front-end community: AMD, CMD, CommonJS, etc., and then ECMA added module function ESM (also known as ES6 Module because it was introduced in ES2015 version) at the level of JavaScript language standard.

This article will comb the development course of the whole front-end modularization, briefly describe its upgrade to beat strange road.

An overview of the

Modular concept

Encapsulate a complex program into several blocks (files) according to certain rules (specifications) and combine them together; The internal data and implementation of a block are private, exposing only interfaces (methods) to communicate with other external modules.

The above content gives several key words of module: split, composition, private domain, external interface. Unfortunately, until THE release of ES6, JavaScript didn’t have an officially certified module system that could take a large program and break it into small interdependent files and put it back together in a simple way. This is not the fault of JavaScript designers, who originally developed JavaScript for the simple purpose of allowing browsers to simply interact with user input, clicks, and so on.

In 1994, Netscape released version 0.9 of its Navigator browser. It was the first full-fledged web browser in history, and it was a hit. However, this version of the browser can only be used for browsing and does not have the ability to interact with visitors. . Netscape desperately needed a web scripting language that would allow browsers to interact with the web.

However, with the continuous expansion of JavaScript application scenarios, more and more complex interactive operations are bound to bring about a double increase in code complexity and volume. JavaScript urgently needs an effective modularization scheme to cope with the increasingly complex programming environment.

Modularization effect

In the actual development process, often encounter variables, functions, objects and other name conflicts, so easy to cause logical conflicts, but also cause global variables are polluted; At the same time, the program complex need to write a lot of code, but also to introduce a lot of class libraries, so a little attention is easy to cause file dependency confusion; Secondly, for some functions are actually a lot of repeated, can be packaged into a reusable code unit; The above problems and requirements rely on modularity to solve them. In short, modules can:

  • Avoid contaminating global variables
  • Easy to write and maintain code
  • Helps encapsulate reusable logic

Other languages have this functionality, such as Ruby’s require, Python’s import, and even CSS has @import, but JavaScript has no support for any of this, which is a huge barrier to developing large, complex projects.

Prior to ES6, there were several module loading schemes developed by the community. The main ones were CommonJS and AMD/CMD. The former is used for servers and the latter for browsers. ES6 in the language standard level, the realization of module function, and the implementation is quite simple, can completely replace CommonJS and AMD/CMD specifications, become a common browser and server module solution.

Initial modularity

1. General writing (global functions and variables)

Putting different functions or variables together is a simple module, which has obvious disadvantages, such as easy contamination of variables, poor encapsulation, and basically zero reusability. Early scripts written with inline script tags on HTML pages can be thought of as a visual representation of normal writing:

<! -- Page embedded script -->
<script type="application/javascript">
  // module code
</script>

<! -- External script -->
<script type="application/javascript" src="./module1.js">
</script>
Copy the code

2. Object encapsulation

Put the entire module in an object and call the properties or methods of the object directly for external access. Although this method solves the problem of variable conflict, it is easy to be arbitrarily modified by external:

var utils = {
  request() {
    console.log(window.utils); }}/ / when used
utils.request();
window.utils.request();
Copy the code

3. Anonymous instant function mode

In the browser environment, variables declared in the global scope are global variables. Global variables have many problems such as naming conflicts, memory usage that cannot be reclaimed, and low code readability. The anonymous Execute now function (IIFE) appears:

var say = {name: 'hello world'};
(function (say) {
  var say = {name: 'hahaha'};
  console.log('say.name = ',say.name); } ());console.log('say.name = ', say.name)
Copy the code

After using the above writing method, the SAY variable in the anonymous function and the external SAY variable will not interfere with each other under different scopes. Anonymous functions basically solve the problem of function contamination and variable modification, which is the cornerstone of the JavaScript modularity specification!

Modular specification

Enter the main topic of this article: the JavaScript modularity specification. Most JavaScript runtime environments now have their own modular specifications, based on the way anonymous functions call themselves, and to enhance code dependencies. It can be divided into four categories: AMD, CMD, CommonJS and ESM.

AMD

Facing a modular solution, the first thing to understand is: 1. How to export the module interface; 2. How to import module interfaces? AMD stands for “Asynchronous Module Definition”. It loads modules asynchronously without affecting the execution of subsequent statements. All statements that depend on this module are defined in a callback function that will not run until the load is complete.

The Asynchronous Module Definition Specification (AMD) lays down rules for defining modules so that modules and their dependencies can be loaded asynchronously. This is appropriate for an environment where browsers load modules asynchronously (which can cause performance, availability, debugging, and cross-domain access issues).

The sequence diagram of module invocation is as follows:

AMD grammar:

/ / ts statement
/ * * *@param {string} Id Module name *@param {string[]} Array * of modules that the dependencies module depends on@param {function} The factory module initializes the function or object to be executed *@return {any} Module export interface */
function define(id? , dependencies? , factory) :any// Syntax templatedefine([module name], [dependent module],function(){
  name: 'vey-module-1',
  getName: function() {return name
  }
  return {getName: getName}
}/ / instancedefine(['m1'].function(m1){
  name: 'vey-module-2'.function show() {
    console.log(name, m1.getName())
  }
  return { show }
})
Copy the code

RequireJS

AMD is an asynchronous module specification, and RequireJS is an implementation of the AMD specification.

Special note: there was RequireJS first, then AMD specification, with the promotion and popularity of RequireJS, AMD specification was created.

Next, we refactor the demo wrapped with the above objects using RequireJS.

// utils.js defines the module
define(function(config) {
  var utils = {
    request() {
      console.log(window.utils); }};return utils;
});

// main.js references the module
require(['./utils'].function(utils) {
  utils.request();
});
Copy the code
<! -- index.html -->
<body>
  <! -- require. Js library -->
  <script src="./lib/require.js"></script>
  <! -- import main.js -->
  <script src="./main.js"></script>
</body>
</html>
Copy the code

It can be seen that with RequireJS, each file can be managed as a module, and the communication mode is also in the form of modules, which can not only clearly manage module dependencies, but also avoid declaring global variables.

The main characteristics

  • keywords: asynchronous,defineDefine the interface,requireIntroduction of interface, full load;
  • AMD’s core implementation is throughdefineTo define the module, and then passrequireTo load modules;
  • AMD is a dependency front, that is, whether you use it or not, as long as you set the dependency, it will load the full load;
  • Dynamically load modules at runtime.

CMD

CMD, like AMD, is an asynchronous modular specification that is used in a browser environment. CMD (Common Module Definition) is closer to CommonJS Modules/1.1 and Node Modules specifications. A Module is a file. It advocates relying on proximity, loading whenever required, and implementing lazy loading (lazy loading) while retaining all the features of the AMD specification. There’s no global require, every API is pure and simple. The syntax template is:

define(factory);
Copy the code

Define is a global function that defines a module.

define(factory)

Define takes the factory argument, which can be a function, an object or a string. When factory is an object or string, the interface representing the module is that object or string. For example, you can define a JSON data module as follows:

define({ "foo": "bar" });
Copy the code

Template modules can also be defined as strings:

define('I am a template. My name is {{name}}.');
Copy the code

When factory is a function, it represents a module constructor. Execute this constructor to get the interface that the module provides externally. The factory method is passed three arguments by default: require, exports, and module:

Define (function(require, exports, module) {// module code});Copy the code

The method of relying on nearby writing advocated by CMD supports lazy loading of modules. Of course, although it is lazy loading, it is also lazy loading under the premise of full loading.

Function (utils) {utils. Request (); function(utils) {utils. }); Var utils = require('./utils'); utils.request(); });Copy the code

The main characteristics

  • keywords: asynchronous, lazy loading,defineDefine the interface,requireIntroduction of interface, full load;
  • CMD is created in the process of promotion and popularization of SeaJS;
  • CMD is post-dependent and allows lazy loading, which is the biggest difference from AMD.

With the emergence of ES6 module specification, AMD/CMD will eventually become a thing of the past, the use of AMD and CMD in mainstream projects is becoming less and less, we have a general understanding of AMD and CMD, not too much to say here. But there is no doubt that the emergence of AMD/CMD is an important step in the process of front-end modularization.

CommonJS

In 2009, American programmer Ryan Dahl created the Node.js project to use the javascript language for server-side programming.

As mentioned earlier, AMD and CMD are mainly used on the browser side. With the birth of Node, the server side modular specification CommonJS was created. This marks the official birth of “Javascript modular programming “. In fact, in the browser environment, the lack of modules is not particularly problematic, since web applications are of limited complexity; But on the server side, there must be a concept of modules, so it involves interacting with the operating system and other applications, without which you simply can’t program properly!

Node.js module system is implemented by referring to CommonJS specification. In CommonJS, there is a global method require() for loading modules. CommonJS: utils. Js: main.js: CommonJS: utils.

// utils.js
var utils = {
  request() {
    console.log(window.utils); }};module.exports = utils;
Copy the code

You can then call the methods provided by the module:

// main.js
var utils = require('./utils');
utils.request();
Copy the code

The module exports and exports

What is the difference between exporting modules from node applications that use module.exports and exporting from node applications? The CommonJS specification only defined exports, but exports had the problem of being rewritten and lost, so module.exports was created and called CommonJS2. In the CommonJS specification, each JS file is a module, and each module has a global Module object. The module’s exports property is used to export interfaces, and external modules import the current module using the module object. This is all node does based on the CommonJS2 specification.

// hello.js
var s = 'hello world! '
module.exports = s;
console.log(module);
Copy the code

Print result:

When other modules import this module:

// main.js
var hello = require('./hello.js'); // hello = hello world!
Copy the code

When writing this in hello.js:

// hello.js var s = 'hello world! ' exports = s;Copy the code

Module. exports of the hello. Js module will be overridden to an empty object:

// main.js
var hello = require('./hello.js'); // a --> {}
Copy the code

Exports: module. Exports: module. Exports: exports

var module = { exports: {} } var exports = module.exports; console.log(module.exports === exports); // true var s = 'hello world! ' exports = s; Console. log(module.exports === exports); // module.exports is not affected console.log(module.exports === exports); // falseCopy the code

The code above perfectly simulates why exports were rewritten:

  1. During module initialization,exportsandmodule.exportsIt points to the same block of memoryexports === module.exports;
  2. whenexportsAfter being reassigned, it repoints to the new memory address, thus cutting off the connection with the original memory address, so there isexports ! == module.exports

So, the exports specification is used in:

// hello.js exports.s = 'hello world! '; // main.js var hello = require('./hello.js'); console.log(hello.s); // hello world!Copy the code

Prohibit direct use of exports = XXX module in CommonJ!!

CommonJS is often confused with CommonJS2, and is often referred to as CommonJS2.

CommonJS with AMD

Similarities:

  • Both CommonJS and AMD are run time loaded, in other words: dependencies between modules are determined at run time.

Differences between the two:

  • CommonJS is the server side module specification, AMD is the browser side module specification.
  • The CommonJS loading module issynchronousOf, namely executevar hello = require('./hello.js');After the hello.js file is loaded, the following code is executed. AMD load modules areasynchronousAfter all dependencies are loaded, the code is executed as a callback function.

The main characteristics

  • Key words: synchronization, internal variable private,module.exports,exports,require;
  • Used in node environment, does not support browser environment, because browser does not havemodule.exports.require.globalFour environment variables;
  • useexports\module.exportsExport the module usingrequire()Conduct import modules.
  • The module is synchronously loaded, that is, only after the loading is complete, the following operations can be performed;
  • The module is cached after the first execution, and only returns cached results if it is reloaded. If it wants to execute again, the cache can be cleared.
  • CommonJS output is a copy of the value (i.erequireThe value returned is a copy of the output value, and changes within the module do not affect this value.

ESM

ESM is ES6 Module. Before ES6, the JavaScript community developed some module loading schemes, the most important of which are CommonJS and AMD. The former is used for servers and the latter for browsers. ES6 in the language standard level, the realization of module function, and the implementation is quite simple, can completely replace CommonJS and AMD specifications, become a common browser and server module solution. The ES6 module is designed to be as static as possible, so that the module dependencies, as well as the input and output variables, can be determined at compile time. Both CommonJS and AMD/CMD modules can only determine these things at run time. For example, a CommonJS module is an object, and you have to look for object properties when you enter it. The ESM specification directives are simple, with only three: import, export, and export default. Export \export default is used to export module interfaces, and import is used to import module interfaces.

export

Export Export interface can be exported in the following ways:

// config.js
// Export directly
export const hello = 'hello world! ';
export const api = `${prefix}/api`;

// Set export
const hello = 'hello world! ';
const api = `${prefix}/api`;
export {
  hello,
  api,
}
Copy the code

The above two export methods are written differently, but the result is the same, both export Hello and API separately.

// foo.js export default function foo() {} export {foo as default}Copy the code

Export default is used to export the default interface of the module, which is equivalent to exporting an interface named default. The AS keyword used with export is used to rename the interface when exporting the interface.

Short for import and export (export directly at the same time as import) :

export { api } from './config.js'; // import {API} from './config.js'; export { api }Copy the code

import

According to the export mode, there are corresponding import modes:

import { api, hello } from './config.js';

// The 'as' keyword used with' import 'is used to rename the imported interface.
import { api as myApi } from './config.js';
Copy the code

Overall import:

import * as config from './config.js';
const api = config.api;
Copy the code

For modules exported by export Default:

import foo from './foo.js'; Import {default as foo} from './foo.js';Copy the code

In addition to importing specified methods and objects, import can also import a module as a whole without specifying its contents:

import from './config.js'
import from './foo.js'
Copy the code

The import() directive returns a Promise object containing the imported module object:

function foo() {
  import('./config.js')
    .then(({ default }) = > {
        default(a); }); }Copy the code

The ESM and CommonJS

Before discussing both, one fact must be made clear: ES6 modules are completely different from CommonJS modules. There are three important differences.

  1. The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
    • The CommonJS module outputs a copy of the value, meaning that once a value is output, changes within the module do not affect that value.
    • ESM operates differently from CommonJS. The JS engine encountered a module load command while statically analyzing the scriptimport, a read-only reference is generated. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference. In other words, ESMimportIt’s kind of like a symbolic link on Unix, where the original value changes,importThe loaded value will also change. Therefore, the ESM is a dynamic reference and does not cache values. Variables in a module are bound to the module in which they reside.
  2. The CommonJS module is run time loaded, and the ES6 module is compile time output interface.
    • Runtime loading: CommonJS modules are objects; That is, the entire module is loaded on input, an object is generated, and methods are read from that object. This loading is called “runtime loading.”
    • Load at compile time: THE ESM is not an object, but passesexportThe command explicitly specifies the code to output,importIs in the form of a static command. In theimportInstead of loading the entire module, you can specify that an output value should be loaded, which is called “compile-time loading.”
  • CommonJS modulerequire()Synchronous loading module, ES6 moduleimportCommands are loaded asynchronously, with a separate module-dependent parsing phase.

The second difference is because CommonJS loads an object (that is, the module.exports property) that is generated only after the script runs. The ESM is not an object, and its external interface is a static definition that is generated during the code static parsing phase. See ES6 module vs. CommonJS module for a concrete example of these three differences.

conclusion

Javascript programs are small — in the early days, they were mostly used to perform stand-alone scripting tasks that provided some interaction where your Web page needed it, so you generally didn’t need much scripting. Over the years, we now have complex programs that run lots of Javascript scripts, and some that are used in other environments (such as Node.js).

Therefore, it is necessary to start thinking about providing a mechanism for breaking uP JavaScript programs into separate modules that can be imported on demand. The JavaScript community is ahead of the standards committee in this regard.

Node.js uses CommonJS, as well as many Javascript libraries and frameworks that have started using modules from AMD’s other module systems such as RequireJS, and more recently Webpack and Babel.

These modular specifications are widely popular and used in the community. It wasn’t until ES6 that JavaScript finally supported module functionality natively at the language level. This is definitely good news for developers – browsers can optimize module loading, making it more efficient than using libraries: libraries often require extra client-side processing.

At present, with the promotion of ESM, AMD\CMD has gradually withdrawn from the historical stage, we just need to understand. CommonJS and ESM are often used in daily development, but many people only know it and do not know why. I hope that through this article, we can have a clear and complete understanding of the way of JS modularization.

reference

Developer.mozilla.org/zh-CN/docs/… Nodejs. Cn/API/modules… Requirejs.org/ github.com/seajs/seajs es6.ruanyifeng.com/#docs/modul…