Front-end Module standards CommonJS, ES6 Module, AMD, UMD

Modules are to programs what cells are to living organisms, building blocks with specific functions. Different modules are responsible for different tasks, and they are linked together in some way to keep the program running properly.

CommonJS and ES6 modules, AMD and UMD are introduced in the following

CommonJS

CommonJS is a series of standards, including modules, files, IO and console, proposed by the JavaScript community in 2009. A part of CommonJS standard has been adopted in the node.js implementation and some adjustments have been made to it. CommonJS is now referred to as the version in Node.js, not its original definition.

The difference between CommonJS and ES6 Modules for front-end modularity

1. The module

CommonJS specifies that each file is a module. The biggest difference between inserting a JavaScript file directly into a page with aScript tag and encapsulating it as a CommonJS module is that the top-level scope of the former is the global scope. Variable and function declarations will pollute the global environment. The latter will form a scope belonging to the module itself, all variables and functions can only be accessed by itself, is invisible to the outside world. Look at the following example:

// a.js
var name = 'a'

// index.js
var name = 'index'
require('./a.js')
console.log(name) // index
// Each module has its own scope and does not interfere with each other
Copy the code

2. The export

Exports are the only way a module can expose itself. In CommonJS, you can export the contents of modules through module.exports, for example:

module.exports = {
    name: 'aa'.add: function(a, b) {
        return a + b
    }
}
Copy the code

The CommonJS module has a module object that holds information about the current module. The following objects are defined at the beginning of each module:

var module= {... }// Module logic
module.exports = {... }Copy the code

Module. exports specifies what the module should expose. In the above code we exported an object containing the name and add attributes. For ease of writing, CommonJS also supports another simplified export method — using exports directly.

exports.name = 'aa'
exports.add = function(a, b) {
    return a + b
}
Copy the code

This code is not any different from the module.exports code above. The underlying mechanism is that exports points to module.exports, which is an empty object when initialized. CommonJS adds the following code at the beginning of each module by default:

var module = {
    exports: {}}var exports = module.exports
Copy the code

Thus, assigning a value to exports.add is equivalent to adding an attribute to the module.exports object. One thing to note when using exports is that you should not assign a value to exports directly, otherwise it will become invalid. Such as:

exports = {
    name: 'aa'
}
Copy the code

Exports is assigned to a new object, but module.exports is still empty, so the name attribute is not exported.

Module. Exports/exports Because it might get covered. Module. exports and exports statements should be placed at the end of modules to improve readability.

3. Import

Use require to import modules in CommonJS, for example:

// Import module A in index.js and call its add function.

// a.js
module.exports = {
    add: function (a, b) { return a + b }
}

// index.js
const fn = require('./a.js')
const sum = fn.add(2.3)
console.log(sum) / / 5
Copy the code

There are two situations when we require a module:

  • The require module is loaded for the first time. The module is executed first, then the content is exported.
  • The require module has been loaded. The module’s code is not executed again, but directly exports the result of the last execution.

As mentioned earlier, modules have a Module object to store information about them, and this object has a property loaded to record whether the module has been loaded. It defaults to false and is set to true when the module is loaded and executed for the first time. If module.loaded is found to be true when the module is loaded again, the module code will not be executed again.

Sometimes when we load a module and don’t need to fetch its exported content, we just want to do something by executing it, such as hanging its interface on a global object, we just use require.

require('./task.js')
Copy the code

In addition, the require function can accept expressions, with which we can dynamically specify the module load path.

const moduleNames = ['foo.js'.'bar.js']
moduleNames.forEach(name= > {
    require('/' + name)
})
Copy the code

ES6 Module

When Brendan Eich, the father of JavaScript, first designed the language, the concept of modules was not included. Due to increasing engineering needs, several modular standards have sprung up in the JavaScript community for development using modularity, including CommonJS. It was not until June 2015, when ES6 (ECMAScript 6.0) was officially released by the TC39 Standards Committee, that the JavaScript language had the feature of modules. In the following example, we have rewritten a.js and index.js using ES6.

The difference between CommonJS and ES6 Modules for front-end modularity

// a.js
export default {
    name: 'aa'.add: function (a, b) { return a + b }
}

// index.js
import fn from './a.js'
const sum = fn.add(2.3)
console.log(sum) / / 5
Copy the code

ES6 Module also treats each file as a Module, and each Module has its own scope. The difference is the import and export statements.

  • Import and export have also been added as reserved keywords in ES6 (module in CommonJS is not a keyword)

ES6 Modules automatically adopt strict mode,

  • This is optional in ES5 (ECMAScript 5.0). Previously we could control strict mode by choosing whether or not to add “Use strict” at the beginning of a file. In the ES6 Module, strict mode is used regardless of whether or not “usestrict” is at the beginning. Be aware of this if you are rewriting a CommonJS Module or any code that is not in strict mode to ES6 Module.

1. Export

Use the export command in ES6 Module to export modules. Export has two forms:

  • Named after the export
  • The default is derived

A module can have multiple named exports. It can be written in two different ways:

/ / write 1
export const name = 'aa'
export const add = function(){}

/ / write 2
const name = 'aa'
const add = function(){}
export { name, add }

// When using named exports, variables can be renamed using the AS keyword. Such as
export { name, add as getSum }
Copy the code

Unlike named exports, there can only be one default export for a module

export defalut { // There cannot be two export defalut
    name: 'aa'.add: function(){}}Copy the code

Export default can be understood as output of a variable named default. Therefore, it is not necessary to declare variables like named export, but directly export values.

// Export a string
export defalut 'aaa'
/ / derived class
export defalut class {... }// Export the anonymous function
export defalut function(){}
Copy the code

2. The import

ES6 Modules use the import syntax to import modules. First let’s see how to load a module with a named export. Here’s an example:

// a.js
const name = 'aa'
const add = function(a, b){ return a + b}
export { name, add }

// index.js
import { name, add } from './a.js'
add(2.3)

// Similar to naming exports, we can rename imported variables using the as keyword
import { name, add as getSum } from './a.js'
getSum(2.3)

// We can also import variables as a whole. Such as:
import * as cal from './a.js'
cal.getSum(2.3)
Copy the code

When loading a module with a named export, import is followed by curly braces to enclose the imported variable names, which need to be exactly the same as the exported variable names. The effect of importing variables is that they are declared in the current scope (name and add) and cannot be changed, meaning that all imported variables are read-only.

Other types of modules

The CommonJS and ES6 modules we’ve covered so far are the main ones, but other types of modules are likely to be encountered during development. Some standards, such as AMD and UMD, currently use fewer scenarios, but still need to know how to deal with such modules when they are encountered.

1. Modular files

A modular file is a file that does not follow any of the module standards. If you are maintaining a project from a few years ago, there is a good chance that there will be modular files, most commonly jQuery and its various plug-ins introduced in the Script tag.

How do you package such files using Webpack? In fact, as long as direct introduction, such as:

import './jquery.min.js'
Copy the code

This code will execute jquery.min.js directly. Libraries like jquery generally bind their interfaces globally, so the end result is the same whether they are imported from script tags or as Webpack packages.

However, if we introduce a modular file that exposes its interface in the form of implicit global variable declarations, problems can occur. Such as:

// Expose the interface by declaring variables in the top-level scope
var calculator = {
    // ...
}
Copy the code

Because Webpack wraps a layer of function scope for each file to avoid global contamination, the code above will not be able to hang the Calculator object globally, so this implicit declaration of global variables requires special care.

2. AMD

AMD stands for Asynchronous Module Definition, a standard developed by the JavaScript community that focuses on supporting browser-side modularity. As the name suggests, the biggest difference between CommonJS and ES6 Modules is that it loads modules asynchronously. The following example shows how to define an AMD module.

define('getSum'['calculator'].function(math) {
    return function(a, b) {
        console.log('sum' + calculator.add(a, b))
    }
})
Copy the code

Modules are defined using the define function in AMD, which accepts three parameters.

  • The first parameter is the id of the current module, which is equivalent to the module name.
  • The second parameter is the dependency of the current module. For example, the getSum module defined above needs to introduce the Calculator module as a dependency.
  • The third parameter describes the exported value of the module, which can be a function or an object. If it is a function, it exports the return value of the function; If it is an object, export the object itself directly.

Similar to CommonJS, AMD uses the require function to load modules, but asynchronously.

require(['getSum'].function(getSum) {
    getSum(2.3)})Copy the code

The first argument to require specifies the module to load, and the second argument is the callback function to execute when the load is complete.

The advantage of defining modules in AMD’s form is that module loading is non-obstructive and does not stop when the REQUIRE function is executed to execute the loaded module, but instead continues to execute the code after require, so that module loading does not block the browser.

While AMD’s design philosophy is good, the downsides are:

  • The syntax is much more verbose than the synchronously loaded module standard.
  • In addition, asynchronous loading is not as clear as synchronous loading and is prone to callback hell.

It is used less and less in practical applications today, and most developers still prefer CommonJS or ES6 Modules.

3. UMD

We’ve looked at a number of Module forms, CommonJS, ES6 Module, AMD, and modular files, and in many cases projects will use two or more of these forms. Sometimes developers of a library or framework need to consider supporting various forms of modules if the target audience is large enough.

Strictly speaking, UMD is not a module standard, but rather a collection of module forms. UMD stands for Universal Module Definition, which is the Universal Module standard. Its goal was to enable a Module to run in a variety of environments, whether CommonJS, AMD, or modular (at the time, ES6 Modules were not introduced).

Look at the following example:

// calculator
(function(global, main) {
    // Take different export methods according to the current environment
    if (typeof define === 'function' && defind.amd) {
        // AMD
        define(...)
    } else if (typeof exports= = ='object') {
        // CommonJS
        module.exports = ...
    } else {
        global.add = ...
    }
})(this.function() {
    // Define the module body
    return {...}
})
Copy the code

As you can see, UMD basically determines which module environment you are in based on the values in the current global object. If the current environment is AMD, export as AMD. The current environment is CommonJS, so export as CommonJS.

Note that UMD modules are usually the first to determine whether there are define functions in the AMD environment, and modules defined by AMD cannot be properly imported using CommonJS or ES6 modules.

  • In Webpack, because it supports both AMD and CommonJS, it is possible that all modules in the project are CommonJS, but UMD standard finds that there is an AMD environment and uses AMD export mode, which will cause module import error.
    • When we need to do this, we can change the order of judgments in the UMD module so that they are exported as CommonJS.

Refer to Webpack Practice: Getting Started, Advancing and Tuning (Ju Yuhao)

The difference between CommonJS and ES6 Modules for front-end modularity