JavaScript Modularization Syntax

Today, when I was making up the test, I encountered the problem of not being able to identify the import. After being solved by babel’s dynamic import plugin, I had a little doubt about the keywords that are often used at ordinary times, import, export, exports, and require, so I sorted out. The evolution process and relationship between them.

I mainly understand this problem through these three blogs combined with my usual actual development experience:

JavaScript模块化演进历史

require和import的区别

import, require, export, module.exports混合使用详解

History

To sum up, the Modularization of js has probably gone through three important stages. The first stage is that there is no Modularization at all, and all variables and methods are global. The second stage is the various ways proposed by js developers in the community to implement Modularization, including the original CommonJS, RequireJS with AMD as the core, and SeaJS with CMD as the core. The third stage is the esModule specification launched by es6, which is import and export. The second stage is actually not the specification of the js syntax itself, but a module encapsulation method proposed by the developers themselves. The third way is the new syntax of es6, which is not well supported in most cases. Generally, it will be converted by babel into the require and exports syntax of CommonJS, which is why we sometimes have it in development. The reason why various keywords can be mixed, you think you are using esmodule, but in fact it has been converted by babel into the old syntax of es5.

In the first stage, all variables are global, so there is no concept of modularization at all.

In the second stage, the first is the various module encapsulation methods proposed by developers, such as namespaces, which can solve the problem of global variables everywhere, such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// greeting.js
var app = {};
app.helloInLang = {
en: 'Hello world!',
is: 'Hello world!',
en: 'Hello world!'
};
app.writeHello = function (lang) {
document.write(helloInLang[lang]);
}

// third_party_script.js
function writeHello() {
document.write('The script is broken');
}

However, there is no privacy at all. All properties are exposed to the global object, which can be accessed and manipulated everywhere

So someone combined the immediate execution of function and closure to solve the problem of private variables, such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var greeting = (function() {
var module = {};
var helloInLang = {
en: 'Hello world!',
is: 'Hello world!',
en: 'Hello world!',
};

module.getHello = function(lang) {
return helloInLang[lang];
};

module.writeHello = function(lang) {
document.write(module.getHello(lang));
};

return module;
})();

Immediately executing the function itself has its own independent scope, where declared variables only fall under that scope, and private variables can only be accessed elsewhere through the method returned by immediately executing the function.

Although the above two methods solve the problem of a little module, but still can not be a good relationship between the management module, this point until the arrival of Node.js, CommonJS specification landing can be solved to a certain extent.

CommonJS is a synchronous solution that considers Node.js running at the server level, mainly by’require ‘to load dependencies, and by’exports’ or’module.exports’ to expose interfaces or data.

Because the server through the’require 'loading resources are directly read the file, so the time required in the middle can be ignored, but in the browser that needs to rely on HTTP to obtain resources, the time required for the acquisition of resources is uncertain, which leads to the need to use asynchronous mechanism, on behalf of the main two:

  • Based on AMD The RequireJS
  • Based on CMD Of SeaJS

They are in the browser to achieve the “define”, “require” and “module” of the core functions, although the two goals are the same, but the implementation of the way or ideas, or some differences, AMD biased dependent on the front, CMD biased to use the idea of running, resulting in dependencies loading and running time points will be different, about the comparison of the two, there are many on the Internet, here recommended a few for reference only:

1
2
3
4
5
6
7
8
9
10
11
12
13
// CMD
define(function (require) {
Var a = require ('./a '); // <- to load and run module a
Var b = require ('./b '); // <- to load and run module b
// more code ..
})
// AMD
define(
['./a', './b '], // <- predeclaration, i.e. modules a and b are loaded and run before the subject runs
function (a, b) {
// more code ..
}
)

Through examples, you can see that in addition to the differences in grammar, the main differences between the two are:

When to load and run dependencies?

This is also one of the main reasons for questioning AMD in the CommonJS community. Many people think that it destroys the specification. In contrast, the CMD model simply removes the outer wrapper of’define ', which is the standard CommonJS implementation, so CMD is the closest to CommonJS. Asynchronous Modularization scheme.

Then came the formal definition of modules in ES6

In June 2015, ** ECMAScript2015 **, which is ** ES6 **, was released. JavaScript finally implemented the module function at the level of language standards, so that the dependencies of the module can be determined during compilation, as well as its input and output variables. Unlike CommonJS, AMD and the like, which need to be determined at runtime (for example, tools such as FIS can only preprocess dependencies, which are essentially parsed at runtime), it has become a common module solution for browsers and servers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lib/greeting.js
const helloInLang = {
en: 'Hello world!',
is: 'Hello world!',
en: 'Hello world!'
};

export const getHello = (lang) => (
helloInLang[lang];
);

export const sayHello = (lang) => {
console.log(getHello(lang));
};

// hello.js
import { sayHello } from './lib/greeting';

sayHello('ru');

Unlike CommonJS, which uses the require () method to load modules, in ES6, the import command can specify the interface exposed by the export command in the loading module (do not specify the specific interface, load the export default by default), and what is not specified will not be loaded, so the loading of the module will be completed during compile. This loading method is called ** compile-time loading ** or ** static loading **.

CommonJS 'require () ’ method is only loaded at runtime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lib/greeting.js
const helloInLang = {
en: 'Hello world!',
is: 'Hello world!',
en: 'Hello world!'
};
const getHello = function (lang) {
return helloInLang[lang];
};

exports.getHello = getHello;
exports.sayHello = function (lang) {
console.log(getHello(lang))
};

// hello.js
const sayHello = require('./lib/greeting').sayHello;

sayHello('ru');

It can be seen that in CommonJS, the entire module is introduced as an object, and then a property on this object is obtained.

Aspects of use

require/exports

The usage of require/exports can only be written in the following three simple ways:

1
2
3
const fs = require('fs')
exports.fs = fs
module.exports = fs

There are various ways to write import/export:

1
2
3
4
5
6
7
8
9
10
11
12
import fs from 'fs'
import {default as fs} from 'fs'
import * as fs from 'fs'
import {readFile} from 'fs'
import {readFile as read} from 'fs'
import fs, {readFile} from 'fs'

export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'

require/exports

The form looks varied, but in essence:

  1. CommonJS or ES6 Module output can be seen as an object with multiple properties or methods;
  2. default is a unique keyword of ES6 Module, export default fs outputs the default interface object, import fs from’fs’ can directly import this object;
  3. The properties or methods of imported modules in ES6 Module are strongly bound, including basic types; while CommonJS is ordinary value passing or reference passing.

1, 2 is relatively easy to understand, 3 need to see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// counter.js
exports.count = 0
setTimeout(function () {
console.log('increase count to', ++exports.count, 'in counter.js after 500ms')
}, 500)

// commonjs.js
const {count} = require('./counter')
setTimeout(function () {
console.log('read count after 1000ms in commonjs is', count)
}, 1000)

//es6.js
import {count} from './counter'
setTimeout(function () {
console.log('read count after 1000ms in es6 is', count)
}, 1000)

Run commonjs.js and es6.js separately:

1
2
3
4
5
6
test node commonjs.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in commonjs is 0
test babel-node es6.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in es6 is 1

The above is just a summary of my three blogs and my own understanding. For a detailed explanation, you can see the above three blogs and realize it yourself to have a better understanding.