Front end modularization - CommonJS,AMD,CMD,ES6

What problem does modularity solve

With the increasing size of JavaScript projects, teamwork is inevitable. In order to better manage and test the code, the concept of modularity is gradually introduced into the front end. Modularization can reduce the cost of collaborative development and reduce the amount of code. At the same time, it is also the basis of "high cohesion and low coupling".

Modularization mainly solves two problems:

  1. name conflict
  2. File dependency: for example, jquery needs to be introduced into bootstrap, and the location of jquery file must be introduced before bootstrap.js.

How did people in ancient times solve modularization

Before various modularization specifications came out, people used anonymous closure functions to solve the problem of modularization.

var num0 = 2; // Notice the semicolon here
(function () {
  var num1 = 3
  var num2 = 5 
  var add = function () {
    return num0 + num1 + num2
  }
  console.log(add()) // 10
})()

// console.log(num1) // num1 is not defined

The advantage of this is that you can use global and local variables inside functions without worrying about local variables polluting global variables. This way of wrapping anonymous functions in parentheses is also called immediate execution function (IIFE). All function internal code is in a closure. It provides privacy and state throughout the application lifecycle.

CommonJS specification

CommonJS treats each file as a module. Variables in each module are private variables by default. Define the external output interface of the current module through module.exports, and load the module through require.

(1) Usage:

circle.js

const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r

app.js

const circle = require('./circle.js')
console.log(circle.area(4))

(2) Principle: in the process of compiling js files, node will use the following function wrapper to wrap it Module wrapper:

(function (exports, require, module, __filename, __dirname) {
  const circle = require('./circle.js')
  console.log(circle.area(4))
})

This is why these variables that are not explicitly defined can be used in the node environment. Among them__ filename and__ dirname is passed in after being analyzed in the process of finding the file path. The module variable is the module object itself, and exports is an empty object initialized in the module constructor.

For more details, please refer to node modules

You can refer to when to use exports and when to use module.exports exports shutcut

(3) Advantages vs. disadvantages

CommonJS can avoid global namespace pollution and clarify the dependencies between codes. However, the module loading of CommonJS is synchronous. If a module references three other modules, the three modules need to be fully loaded before the module can run. This is not a problem on the server side (node), but it is not so efficient on the browser side. After all, reading network files takes more time than local files.

AMD

AMD The full name is Asynchronous Module Definition. The module is loaded asynchronously. The module loading does not affect the execution of the following statements, and the callback function is used to run the code after the module is loaded.

(1) Mode of use

Define a module of myModule, which depends on jQuery module:

define('myModule', ['jQuery'], function ($) {
  // $is the output module of jQuery
  $('#app').text('Hello World')
})

The first parameter represents the module id, which is an optional parameter, and the second parameter represents the module dependency, which is also an optional parameter.

Using the myModule module:

require(['myModule', function (myModule) {}])

requirejs It is an implementation of AMD specification. For detailed usage, you can view the official documents.

CMD

CMD specification comes from seajs As a whole, CMD is very similar to AMD. The differences between AMD and CMD can be seen from the similarities and differences with RequireJS]( https://github.com/seajs/seajs/issues/277)

(1) Usage:

// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // ...
  var b = require('./b')
  // Dependence can be written nearby
  b.doSomething()
  // ...
})

CMD advocates dependency proximity, which can be written into any line of your code. AMD is a dependency front. Before parsing and executing the current module, the module must indicate the module on which the current module depends.

UMD

UMD (Universal Module Definition) is not a specification, but a more general JS module solution combining AMD and CommonJS.

This is often seen when packaging modules:

output: {
  path: path.resolve(__dirname, '../dist'),
  filename: 'vue.js',
  library: 'Vue',
  libraryTarget: 'umd'
},

Indicates that the packaged module is a umd module, which can run on both the server side (node) and the browser side. Let's look at the vue packaged source code vue.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) : 
  (global.Vue = factory());
}(this, (function () { 'use strict';
// ...
})))

The code is translated as:

  1. First, judge whether it is a node environment: exports is an object and the module exists.
  2. If it is a node environment, use module.exports = factory() to export Vue (refer to it through require('vue ')).
  3. If it is not a node environment, judge whether AMD is supported: define is function and define.amd exists.
  4. If AMD is supported, define the module (referenced through require(['vue')).
  5. Otherwise, vue will be directly bound to the global variable (referenced by window.vue).

ES6

Finally, in the era of ES6, JS supports modularization from the language level, and supports native es modules from node8.5. However, there are two limitations:

  1. Module name (file name) must be mjs
  2. Add -- experimental modules to the startup parameters

If there is a.mjs, it is as follows:

export default {
  name: 'Jack'	
}

In b.mjs, you can refer to:

import a from './a.mjs'
console.log(a) // { name: 'Jack' }

Chrome 61 also supports JS module from the beginning. You only need to add type="module" to the script attribute.

<script type="module" src="module.js"></script>
<script type="module">
  import { sayHello } from './main.js'
  sayHello()
</script>

// main.js
export function sayHello () {
  console.info('Hello World')	
}

ES6 module details

ES6 module is mainly composed of two commands: export and import.

(1) export command

// Output variable
export let num = 123
export const name = 'Leo'

// Output a set of variables
let num = 123
let name = 'Leo'
export { num, name }

// Output function
export function foo (x, y) { return x ** y }

// Use alias
function a () {}
function b () {}
export {
  a as name,
  b as value
}
// When referencing, reference by alias
import { name, value } from '..'

It should be noted that the export command can only output external interfaces, and the following output methods are wrong:

// report errors
export 1
var m = 1
export m
function f () {}
export f

// Correct writing
export var m = 1
var m = 1
export { m }
export { m as n}
export function f () {}
function f () {}
export { f }

The value output by export is dynamically bound, which is different from CommonJS. CommonJS outputs the value cache and there is no dynamic update.

How to delete the node cache?

let config
setInterval(() => {
  delete require.cache[require.resolve('./config')]
  config = require('./config')
  console.log(config)
}, 3000)

The export command must be at the top level of the module. If it is within the scope of the block level, an error will be reported.

(2) import command

// The import variable is read-only
import { a } from './a.js'
a = 33 // Syntax Error : 'a' is read-only

// However, its attribute value can be modified
a.name = 'Jack'

// The import command can promote variables
foo()
import { foo } from './a.js'

// import is a static execution, and expressions and variables cannot be used
import { a + b } from './a.ls' // report errors

// report errors
let module = 'my_module'
import { foo } from module

// report errors
if (x === 1) {
  import { foo } from 'module1'
} else {
  import { foo } from 'module2'
}

// Load the entire module
import * as utils from './utils.js'

(3) export default

// a.js
export default function () {
  console.log('Hello World')	
}

// Citation a
import a from 'a.js'
a()

// b.js
export default funtion foo () {
  console.log('Hello World')	
}

// Reference b
import foo from 'b.js'
foo()

The difference between export default and the module exported by export is whether to package variables with {}.

function add (x, y) {
  return x + y	
}
export { add as default }

// quote
import { default as foo } from 'a.js'

// correct
export var a = 1

// correct
var a = 1;
export default a

// error
export default var a = 1

(4) Compound writing of export and import

export { foo, bar } from 'a.js'
// amount to
import { foo, bar } from 'a.js'
export { foo, bar }

reference material

Posted on Fri, 26 Nov 2021 07:20:02 -0500 by coollog