Reprinted fromWhat does WEB front-end modularity have?

preface

What’s the first thing that comes to mind when you think of front-end modularity? Webpack? ES6 Module? Anything else? Let’s take a look at the picture below.

Most of you are familiar with the words above. You may have used them, seen them, or just heard them. Can you use a picture to tease out the relationship between all the above words? When we write code on a daily basis, do we have a relationship with who?

A thousand threads

In order to better fit our daily development scenario (front and back end separation), we try to distinguish from the dimension of different platforms first, as the entry point of this article.

1. By platform

platform specification features
The browser AMD, CMD There is a network bottleneck and asynchronous loading is used
The browser CommonJS Direct operation IO, synchronous loading

As you can see, we are very violent in terms of being a browser or not. If you look at it carefully, the biggest difference between them is whether there is a bottleneck in their characteristics. For example, network performance bottlenecks, where each module request needs to initiate a network request and wait for the resource to complete the download before the next operation, the overall user experience is very bad. Based on this scenario, we simplify the distinction between synchronous and asynchronous loading dimensions.

features specification
Synchronous loading CommonJS
Asynchronous loading AMD, CMD

2. AMD, CMD two specifications

Ignoring CommonJS, let’s take a look at the AMD and CMD specifications that were once popular.

specification The constraint Magnum opus
AMD Rely on the pre – requirejs
CMD To rely on seajs

AMD and CMD provide methods to encapsulate modules, which are similar in syntax. Even RequireJS silently supports the writing of CMD in the later stage. Let’s use an example to illustrate the biggest difference between the two specifications: predependency and nearby dependency.

AMD:

// hello.js
define(function() {
    console.log('hello init');
    return {
        getMessage: function() {
            return 'hello'; }}; });// world.js
define(function() {
    console.log('world init');
});

// main define(['./hello.js', './world.js'], function(hello) { return { sayHello: function() { console.log(hello.getMessage()); }}; });

Copy the code

/ / output // hello init // world init Copy the code

CMD:

// hello.js
define(function(require, exports) {
    console.log('hello init');
    exports.getMessage = function() {
        return 'hello';
    };
});

// world.js
define(function(require, exports) {
    console.log('world init');
    exports.getMessage = function() {
        return 'world';
    };
});

// main
define(function(require) {
    var message;
    if (true) {
        message = require('./hello').getMessage();
    } else {
        message = require('./world').getMessage(); }});/ / output
// hello init
Copy the codeCopy the code

Conclusion: In CMD output, “world init” is not printed. Note, however, that CMD does not print “world init” and the world.js file is not loaded. Both AMD and CMD load all modules at page initialization, the only difference being that the nearest dependency triggers execution only when the module is required.

The implementation of RequireJS and SeaJS is not explained here. If you are interested, you can go to the official website to learn about it. After all, there are very few requirejs and SeaJS now.

3. CommonJS

Back at CommonJS, NodeJS will be familiar to those of you who have written about it. CommonJS defines a file as a module. In the Node.js implementation, we also give each file a Module object, which contains all the information describing the current module. We try to print the Module object.

// index.js
console.log(module);
Copy the code

/ / output { id: '/Users/x/Documents/code/demo/index.js'.exports: {}, parent: { module }, // The module that calls the module can look up the call chain based on this property filename: '/Users/x/Documents/code/demo/index.js'.loaded: false.children: [...]. .paths: [...]. }Copy the code

That is, in CommonJS modules are represented by objects. Let’s use the “looping load” example to further our understanding.

// a.js
exports.x = 'a1';
console.log('a.js '.require('./b.js').x);
exports.x = 'a2';

//b.js
exports.x = 'b1';
console.log('b.js '.require('./a.js').x);
exports.x = 'b2';

//main
console.log('index.js'.require('./a.js').x);

/ / output
b.js  a1
a.js  b2
index.js  a2
Copy the codeCopy the code

Our theoretical basis is the module object, according to which we carry out the following analysis.

Export. x = 'a1'; export. x = 'a1'; Add x to moduleA's exports property 3, a.js console.log('a.js', require('./b.js').x); X = 'b1'; 4, b. exports. X = 'b1'; 5, b. exports. 6, b.js executes console.log('b.js', require('./a.js').x); ModuleA ('b.js', moduleA.x) is found in memory. Console. log('b.js', moduleA.x) is found in memory. X = 'b2', exports.x = 'b2', exports.x = 'b2', exports.x = 'b2', exports.x = 'b2' For the same reason, step 3 is console.log('a.js', modulerb.x); Export. x = 'a2' export. x = 'a2' export. x = 'a2' export. js a2Copy the codeCopy the code

At this point, “The CommonJS module is an object.” That’s a concept you can understand, right?

Going back to this example, there is also a reserved word exports in this example. Exports is a reference to module.exports. An example can illustrate the relationship between the two.

const myFuns = { a: 1 };
let moduleExports = myFuns;
let myExports = moduleExports;

ModuleExports = {b: 2}; console.log(myExports); {a: 1}

Copy the code

Module. exports is a duplicate of module. Exports. The solution is to redirect myExports = modulerExports; console.log(myExports); // output {b: 2} Copy the code

4. ES6 module

Those who know about ES6 should know that web front-end modularity is not a language specification before ES6, unlike other languages such as Java and PHP, which have the concept of namespace or package. The AMD, CMD, and CommonJS specifications mentioned above are intended to be modularized based on the specification, not JavaScript syntactic support. Let’s start with a simple example of ES6 modular writing:

// a.js
export const a = 1;

// b.js export const b = 2;

Copy the code

// main import { a } from './a.js'; import { b } from './b.js'; console.log(a, b); // Output 1, 2 Copy the code

Emmmm, yes, does the export reserved word look a bit like CommonJS exports? Let’s try comparing ES6 and CommonJS from reserved words.

Reserved words CommonJS ES6
require support support
export / import Does not support support
exports / module.exports support Does not support

Well, there’s actually a distinct difference, except require can be used for both. So the question is, since both require can be used, is there any difference between the two uses of require?

Let’s start by comparing ES6 Module with CommonJS.

Output module Loading way
CommonJS Copy the value object
ES6 References (symbolic links) Static analysis

A few more novel words, let’s first introduce the difference between value copy and reference by example.

// Value copy vs reference

// CommonJS let a = 1; exports.a = a; exports.add = () => { a++; };

const { add, a } = require('./a.js'); add(); console.log(a); / / 1

// ES6 export const a = 1; export const add = () => { a++; };

Copy the code

import { a, add } from './a.js'; add(); console.log(a); / / 2 // The difference between CommonJS and ES6 is value copy and reference. Copy the code

Static parsing, what is static parsing? Unlike the CommonJS module implementation, an ES6 module is not an object, but a collection of code. That is, ES6 doesn’t need to load the entire file into an object to know what it has, as CommonJS does. Instead, the code is what it is as it goes along.

PS:

  1. Currently, the modular support for ES6 in various browsers and Node.js is actually not friendly, and more practical comrades can make their own wave if they are interested.
  2. Using the require word in ES6, the ability to static resolve is lost!

5. UMD

There is another UMD in the modularity specification that has to be mentioned. What is UMD?

UMD = AMD + CommonJS
Copy the codeCopy the code

Yes, UMD is that simple. The common scenario is when the module you encapsulate needs to adapt to different platforms (browser, Node.js). For example, you write A tool class for processing time based on Date object secondary encapsulation. You want to promote it to student A who is responsible for front-end page development and student B who is responsible for background Node.js development. Do you need to consider packaging modules that are compatible with both CommonJS and AMD protocols used by your classmates?

Second, the age of tools

1. webpack

With the rise of Webpack, AMD, CMD, CommonJS, UMD seem to be irrelevant. Because Webpack’s modularity is really strong.

In other words, if your module is written using CommonJS, AMD, or ES6 syntax, WebPack supports all of them! Let’s look at an example:

//say-amd.js
define(function() {
    'use strict';
    return {
        sayHello: (a)= > {
            console.log('say hello by AMD'); }}; });//say-commonjs.js
exports.sayHello = (a)= > {
    console.log('say hello by commonjs');
};
//say-es6.js
export const sayHello = (a)= > {
    console.log('say hello in es6');
};

//main import { sayHello as sayInAMD } from './say-amd'; import { sayHello as sayInCommonJS } from './say-commonjs'; import { sayHello as sayInES6 } from './say-es6';

Copy the code

sayInAMD(); sayInCommonJS(); sayInES6(); Copy the code

Not only that, but once WebPack has identified your module, it can be packaged and re-exported as UMD, AMD, etc. For example, as mentioned above, you need to encapsulate the Date module in UMD format. Simply add libraryTarget: ‘UMD’ to the Output of the Webpack.

2. more…

conclusion

Going back to the question we raised at the beginning, we tried to use a graph to summarize the list of modularity-related terms mentioned above.