In this paper, starting from vivo Internet technology WeChat public links: https://mp.weixin.qq.com/s/15sedEuUVTsgyUm1lswrKA author: Morrain

One, foreword

In the last part of Node.js, we talked about how Node.js has become so popular because of its mature modularity implementation, which is based on the CommonJS specification. What about CommonJS?

Let’s start with the wikipedia definition:

CommonJS is a project that aims to create module conventions for JavaScript outside of the web browser. The main reason for creating this project was the lack of generally acceptable forms of JavaScript script module units at the time, which could be reused in different environments than those provided by regular web browsers running JavaScript scripts.

As we know, for a long time, there was no concept of modularity in JavaScript language. Until the birth of Node.js, JavaScript language was brought to the server, facing the file system, network, operating system and other complex business scenarios, modularity became indispensable. As a result, the Node.js and CommonJS specifications are in perfect harmony with each other.

As a result, CommonJS was originally a service for the server side, so I say CommonJS is not a front-end, but its carrier is the front-end language JavaScript, which laid a solid foundation for the popularity of back-end front-end modularity to have a profound impact. CommonJS: Not the front-end but revolutionizing the front-end!

Why modularity is needed

1. What does the front end look like without modularity

In the Web: The Road Ahead, the Road Ahead, we mentioned that JavaScript started out as a scripting language, doing simple form validation and so on. So the amount of code is small, and it is written directly to the <script> tag at first, as shown below:

// index.html
<script>
var name = 'morrain'
var age = 18
</script>Copy the code

As businesses became more complex, with the advent of Ajax, and the amount of code that could be done on the front end grew rapidly, developers began to write JavaScript into separate JS files, decoupled from HTML files. Something like this:

// index.html
<script src="./mine.js"></script>
 
// mine.js
var name = 'morrain'
var age = 18Copy the code

Later, more developers got involved and more JS files were introduced:

// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
 
// mine.js
var name = 'morrain'
var age = 18
 
// a.js
var name = 'lilei'
var age = 15
 
// b.js
var name = 'hanmeimei'
var age = 13Copy the code

It is not difficult to find that the problem has come! JavaScript did not have a module system before ES6, nor did it have the concept of enclosing scopes, so all the variables declared in the above three JS files will exist in the global scope. Different developers maintain different JS files, and it is difficult to ensure that they do not conflict with other JS files. Global variable pollution is starting to become a developer’s nightmare.

2. Modular prototypes

In order to solve the problem of global variable contamination, developers have started to use namespaces. Since names conflict, add namespaces, as follows:

// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
 
// mine.js
app.mine = {}
app.mine.name = 'morrain'
app.mine.age = 18
 
// a.js
app.moduleA = {}
app.moduleA.name = 'lilei'
app.moduleA.age = 15
 
// b.js
app.moduleB = {}
app.moduleB.name = 'hanmeimei'
app.moduleB.age = 13Copy the code

At this point, the concept of modularity has already begun to blur, albeit with namespaces. In this way, the problem of naming conflict is solved to a certain extent, and the developers of B. js module can easily pass
app.moduleA.nameTo get the name in module A, but you can also pass
app.moduleA.name= ‘rename’ to change the name of module A without module A knowing anything about it! This is obviously not allowed.

Clever developers are again taking advantage of the JavaScript language’s function scope and using closures to solve this problem.

// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
 
// mine.js
app.mine = (function(){
    var name = 'morrain'
    var age = 18
    return {
        getName: function() {return name
        }
    }
})()
 
// a.js
app.moduleA = (function(){
    var name = 'lilei'
    var age = 15
    return {
        getName: function() {return name
        }
    }
})()
 
// b.js
app.moduleB = (function(){
    var name = 'hanmeimei'
    var age = 13
    return {
        getName: function() {return name
        }
    }
})()Copy the code

Now the B.js module can pass

App.modulea.getname () is used to get the name of moduleA, but each module’s name is stored inside its own function and cannot be changed by another module. This design already has a hint of modularity in it, with each module maintaining something private inside and opening up interfaces for other modules to use, but it’s still not elegant or perfect. For example, in the above example, module B can fetch something from module A, but module A cannot fetch something from module B, because the above three modules are loaded sequentially and depend on each other. When a front-end application business becomes large enough, this dependency becomes extremely difficult to maintain.

To sum up, the front-end needs modularity, and modularity not only to deal with global variable pollution, data protection issues, but also to solve the maintenance of dependencies between modules.

Introduction to CommonJS specification

Since JavaScript needs modularity to solve the above problems, it needs a specification for modularity. CommonJS is a modularity specification that solves the above problems. A specification is a specification, just like a programming language syntax. Let’s take a look.

1, CommonJS overview

Node.js applications are made up of modules. Each file is a module and has its own scope. Variables, functions, and classes defined in one file are private and not visible to other files.

// a.js
var name = 'morrain'
var age = 18Copy the code

In the above code, a.js is a module in the node.js application. The variables named and age are private to a.js and are not accessible to other files.

The CommonJS specification also states that there are two variables within each module that can be used, require and Module.

Require is used to load a module

Module represents the current module. It is an object that holds information about the current module. Exports is a property on a module that holds the interface or variable that the module is exporting. The value of a module loaded with require is the value of that module exported with exports

// a.js
var name = 'morrain'
var age = 18
module.exports.name = name
module.exports.getAge = function() {return age
}
 
//b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge())// 18Copy the code

2, CommonJS exports

For convenience, Node.js implements the CommonJS specification by providing a private variable of exports for each module, pointing to module.exports. You can assume that Node.js adds the following line of code at the beginning of each module.

var exports = module.exportsCopy the code

So the above code could be written as follows:

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function() {return age
}Copy the code

Exports is a private local variable within a module. It only refers to module.exports, so directly assigning a value to exports has no effect.

As follows:

// a.js
var name = 'morrain'
var age = 18
exports = nameCopy the code

If a module’s external interface is a single value, it can be exported using module.exports

// a.js
var name = 'morrain'
var age = 18
module.exports = nameCopy the code

3. Require of CommonJS

The basic function of the require command is to read in and execute a JS file, and then return the exports object of the module. If the specified module is not found, an error is reported.

Node.js caches a module the first time it is loaded. If you load the module later, the module.exports property is returned directly from the cache.

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function() {return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'Copy the code

As shown above, the second time module A is required, module A is not reloaded and executed. It returns the result of the first require, which is module.exports for module A.

It is also important to note that the loading mechanism of the CommonJS module requires a copy of the exported value. That is, once a value is exported, changes inside the module cannot affect it.

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
    age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18Copy the code

4. CommonJS implementation

The CommonJS module is an object that requires, exports, and module, and a js file is a module. As follows:

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function () {
  return age
}
// b.js
var a = require('a.js')
console.log('a.name=', a.name)
console.log('a.age=', a.getAge())
 
var name = 'lilei'
var age = 15
exports.name = name
exports.getAge = function () {
  return age
}
// index.js
var b = require('b.js')
console.log('b.name=',b.name)Copy the code

If we provide parameters require, exports, and module to an instant-execution function, the module code is placed inside the instant-execution function. The exported value of the module is placed in module.exports, and the module is loaded. As follows:

(function(module, exports, require) {
    // b.js
    var a = require("a.js")
    console.log('a.name=', a.name)
    console.log('a.age=', a.getAge())
 
    var name = 'lilei'
    var age = 15
    exports.name = name
    exports.getAge = function () {
      return age
    }
 
})(module, module.exports, require)Copy the code

With this in mind, it is easy to convert project code that conforms to the CommonJS module specification into browser-supported code. Many tools do this by starting with the entry module, putting all dependent modules into their own functions, and packaging all modules into a JS file that runs in the browser. Browserify, Webpack, and so on.

Let’s take webPack as an example to see how to implement support for the CommonJS specification. When we build with WebPack, we package the file contents of each module into a JS file in the following format, which can be run directly from the browser because it is an anonymous function that executes immediately.

// bundle.js
(function(modules) {// Implementation of module management})({'a.js': function(module, exports, require) {// a.js'b.js': function(module, exports, require) {// b.js'index.js': function(module, exports, require) {// index.js}})Copy the code

Next, we need to implement the module management content according to the CommonJS specification. The CommonJS specification states that loaded modules are cached, so you need an object to cache the loaded modules, and then you need a require function to load the modules. When loading, you need to generate a module. And modules must have an exports attribute that receives module exports.

// bundle.js
(functionVar installedModules = {} /** * moduleName = @param {String} moduleName = / var require =function(moduleName) {// If it has already been loaded, return directlyif (installedModules[moduleName]) returnInstalledModules [moduleName].exports // generates a module if not loaded, Module = installedModules[moduleName] = {moduleName: moduleName, exports: Modules [moduleName].call(modules.exports, module, module.exports, require)return module.exports
  }
 
  return require('index.js') ({})'a.js': function(module, exports, require) {// a.js'b.js': function(module, exports, require) {// b.js'index.js': function(module, exports, require) {// index.js}})Copy the code

As you can see, the CommonJS core specification is met in the above implementation. It’s very simple. It’s not as hard as you think.

5. Other front-end modular schemes

We are already familiar with the CommonJS specification. The basic function of the require command is to read and execute a JS file, and then return the module’s exports. This is feasible on the server side, because the server side takes negligible time to load and execute a file. The module is loaded synchronously at run time. After the require command is executed, the file is executed and the exported value of the module is successfully obtained.

This specification is inherently inapplicable to browsers because it is synchronous. As you can imagine, every time the browser loads a file, it has to send a network request to fetch it. If the network speed is slow, it will take a long time. The browser will wait for the require to return, and it will always be stuck there, blocking the execution of the code behind it, and thus blocking the rendering of the page.

To solve this problem, a number of front-end modularization specifications have been developed, including CommonJS.

1, AMD (Asynchronous Module Definition)

Before you talk to AMD, familiarize yourself with RequireJS.

Here’s what the website says about it:

“RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.”

That translates roughly to:

RequireJS is a JS file and module loader. It works well in browsers, but it can also be used in other JS environments, such as Rhino and Node. Loading modularized scripts with RequireJS improves the speed and quality of your code.

It solves the problem that the CommonJS specification cannot be used on the browser side, and AMD is the canonical output of RequireJS for module definitions in the promotion process.

Take a look at the implementation of the AMD specification:

<script src="require.js"></script>
<script src="a.js"></script>Copy the code

The first step is to introduce the require.js tool library into the HTML file, which provides functions such as defining and loading modules. It provides a global define function for defining modules. So you can define modules using define for any file that is imported after the require.js file is imported.

define(id? , dependencies? , factory)Copy the code

Id: an optional parameter that defines the module id. If this parameter is not provided, use the js file name (without the extension). This parameter can be omitted if only one module is defined in a JS file. Dependencies: The module initializes the function or object to be executed in the module. If it is a function, it should be executed only once and the return value is the value the module exports. If it is an object, this object should be the output value of the module.

So module A can be defined like this:

// a.js
define(function(){
    var name = 'morrain'
    var age = 18
    return {
        name,
        getAge: () => age
    }
})
// b.js
define(['a.js'].function(a){
    var name = 'lilei'
    var age = 15
    console.log(a.name) // 'morrain'
    console.log(a.getAge()) // 18
    return {
        name,
        getAge: () => age
    }
})Copy the code

It loads modules asynchronously and does not affect the execution of statements following the module. All statements that depend on this module are defined in the callback function, which is not run until the load is complete.

The basic idea of RequireJS is that code is defined as a module through the define method. When the module is required, it begins to load its dependent modules, and when all dependent modules have been loaded, it executes the callback function, returning the exported values of the module. AMD is short for “Asynchronous Module Definition”, which means “Asynchronous Module Definition”.

2, CMD (Common Module Definition)

Similar to AMD, CMD is the normalized output of the module definition of sea-.js in the promotion process. Sea. Js was written by Yu Bo of Ali. It was born after RequireJS, and Yuber felt that the AMD specification was asynchronous and that the organization of modules was not natural or intuitive. So he was looking for a way to write like CommonJS. So there’s CMD.

Sea. Js website: Sea. Js

“Sea-.js seeks a simple, natural way of writing and organizing code, with the following core features:”

“Simple and friendly module definition specification: Sea-.js follows the CMD specification and can write module code just like Node.js. A natural and intuitive way to organize code: auto-load dependencies, clean configuration, and more fun to code.

Let’s look at the implementation of the CMD specification:

<script src="sea.js"></script>
<script src="a.js"></script>Copy the code

The first step is to introduce the sea-.js tool library into the HTML file, which provides functions such as defining modules and loading modules. It provides a global define function for defining modules. Therefore, after importing the sea-.js file, you can use define for any other file to define the module.

// All modules are defined by define()functionVar a = require(exports, module) {// import dependencies via require'xxx')
  var b = require('yyy'// exports. DoSomething =... Module. Exports =... }) // a.js define(function(require, exports, module){
    var name = 'morrain'
    var age = 18
 
    exports.name = name
    exports.getAge = () => age
})
// b.js
define(function(require, exports, module){
    var name = 'lilei'
    var age = 15
    var a = require('a.js')
 
    console.log(a.name) // 'morrain'
    console.log(a.getAge()) //18
 
    exports.name = name
    exports.getAge = () => age
})Copy the code

The secret of sea-.js being able to write module code in the same synchronized form as CommonsJS is: When the module b.js is required, after b.js is loaded, sea-. js will scan the code of B.js, find the keyword require, extract all dependencies, and then load them. After all dependent modules are loaded, the callback function will be executed. By the time the line require(‘a.js’) is executed, the a.js is already loaded in memory

3, ES6 Module

The previously mentioned CommonJS works on the server side, while AMD and CMD work on the browser side, but they all have one thing in common: the exported content is determined after the code is run, as you can see in the CommonJS implementation.

It is also important to note that AMD and CMD are modular loading schemes developed by the community developers, not language standards. Starting with ES6, modularity is implemented at the language standard level, and is fairly simple enough to replace CommonJS and CMD and AMD specifications as a common browser and server modular solution.

As a matter of fact, back in May 2013, Isaac Z. Schlueter, author of Node.js package manager NPM, stated that CommonJS was obsolete and that the Node.js kernel developers had decided to scrap the specification. There are two main reasons for this. One is that Node.js itself does not fully use CommonJS. For example, in CommonJS ‘exports, node.js added the “exports” attribute itself. Node.js decided to stop following CommonJS. Node.js is gradually replacing CommonJS with ES6 Module.

2017.9.12 Node.js release 8.5.0 starts to support ES6 Module. It’s just in the experimental stage. You need to add the –experimental-modules parameter.

In the 13.2.0 release of Node.js, the –experimental-modules argument is removed. This means that node.js has ES6 Module support enabled by default since v13.2.

(1) ES6 Module syntax

Two issues that must be considered in any modularity are import dependencies and export interfaces. The same is true of ES6 Module, which consists of two commands: export and import. The export command is used to export the external interface of a module. The import command is used to import the content exported by other modules.

Please refer to ruan Yifeng’s tutorial for detailed grammar. Examples are as follows:

// a.js
export const name = 'morrain'
const age = 18
export function getAge () {
    returnAge} // Equivalent to const name ='morrain'
const age = 18
function getAge() {return age
}
export {
    name,
    getAge
}Copy the code

Once a module’s external interface is defined using the export command, other JavaScript files can load the module using the import command.

// b.js
import { name as aName, getAge } from 'a.js'
export const name = 'lilei'
console.log(aName) // 'morrain'Const age = getAge() console.log(age) // 18 //'a.js'
export const name = 'lilei'
console.log(a.name) // 'morrin'
const age = a.getAge()
console.log(age) // 18Copy the code

In addition to specifying that an output value is loaded, you can also use global loading, which uses an asterisk (*) to specify an object on which all output values are loaded.

As you can see from the example above, when using the import command, the user needs to know the name of the variable to import, which can sometimes be cumbersome, so the ES6 Module provides a convenient way to use the export default command to specify the default output for the Module.

// a.js
const name = 'morrain'
const age = 18
function getAge () {
    return age
}
export default {
    name,
    getAge
}
 
// b.js
import a from 'a.js'
console.log(a.name) // 'morrin'
const age = a.getAge()
console.log(age) // 18Copy the code

Obviously, a module can only have one default output, so the export default command can only be used once. As you can see, there is no need to use braces after the import command.

In addition to the basic syntax, there are as usage, export and import compound writing, export * from ‘a’, import() dynamic loading, etc., you can learn by yourself.

The aforementioned Node.js already supports the ES6 Module by default, and browsers already fully support the ES6 Module. You can learn how Node.js and browsers use ES6 Modules.

(2) ES6 Module and CommonJS

CommonJS can only determine the interface to export at run time; the actual export is an object. The ES6 Module is designed to be as static as possible, so that the dependencies of the Module and the variables imported and exported are determined at compile time, known as “compile time load”.

Because of this, the import command has the effect of being promoted to the head of the entire module and executed first. The following code is legal because the execution of import predates the call to getAge.

// a.js
export const name = 'morrain'
const age = 18
export function getAge () {
    return age
}
 
// b.js
const age = getAge()
console.log(age) // 18
import { getAge } from 'a.js'Copy the code

Also, because THE ES6 Module is loaded at compile time, expressions and variables cannot be used because these are syntactic constructs that only produce results at run time. As follows:

// Error report import {'n' + 'ame' } from 'a.js'/ / an errorlet module = 'a.js'
import { name } from moduleCopy the code

As mentioned earlier in CommonJS require, require is a copy of the exported value. That is, once a value is exported, changes inside the module cannot affect it. Let’s see what the ES Module looks like.

Let’s review the previous example:

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
    age = a
}
 
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18Copy the code

Use the ES6 Module to implement this example:

// a.js
var name = 'morrain'
var age = 18
const setAge = a => age = a
export {
    name,
    age,
    setAge
}
 
// b.js
import * as a from 'a.js'
 
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 19Copy the code

ES6 Module is the specification for modules in ES6. ES6 is short for ECMAScript 6.0, the next generation standard for the JavaScript language. It was officially released in June 2015. As we mentioned in the first section of the Web: The Journey, the Journey, the Journey, the Journey, the Journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey, the journey.

Stay tuned for our next post on ES6+ and Babel…

6. References

  1. CommonJS specification

  2. Syntax for ES Module

  3. Loading implementation of the ES Module

  4. Front-end modular development solutions detailed

  5. Webpack Modularity principles – CommonJS

For more content, please pay attention to vivo Internet Technology wechat official account

Note: To reprint the article, please contact wechat: Labs2020.