Current mainstream module specifications

  • UMD
  • CommonJs
  • es6 module

Umd module (General purpose module)

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

If you see this code in the header of a JS file, this file is using the UMD specification and it’s actually a combination of amd + CommonJS + global variables and this code is a judgment on the current runtime environment, if it’s a Node environment it’s using the CommonJS specification, With UMD, our code runs on both Nodes and browsers, so now most of the front-end libraries are packaged using the UMD specification

CommonJs

The Nodejs environment uses a module system based on the CommonJs specification, which we now refer to as the Node module system

Module export

Keywords: Module. exports exports

// foo.js

// Export one by one
module.exports.age = 1
module.exports.foo = function(){}
exports.a = 'hello'

// Export as a whole
module.exports = { age: 1.a: 'hello'.foo:function(){}}// Whole exports cannot be used with 'exports.' Exports cannot be used with imports
exports = { age: 1.a: 'hello'.foo:function(){}}Copy the code

Exports = module.exports before the module starts. After that, exports loses its reference to module.exports and becomes a local variable in a module

Module import

Key words: require

const foo = require('./foo.js')
console.log(foo.age) / / 1
Copy the code

Module import rules:

Call require() for files in SRC /app/index.js

./moduleAStart of relative path

Find the sibling directory without specifying the suffix: SRC /app/

  • src/app/moduleANo file name extension as perjavascriptparsing
  • src/app/moduleA.jsJs file according tojavascriptparsing
  • src/app/moduleA.jsonThe JSON file followsjsonparsing
  • src/app/moduleA.nodeThe node file compiles the plug-in module dlopen as loaded

If no moduleA file is found in the same directory, the SRC /app/moduleA directory will be found

  • src/app/moduleA/package.jsonCheck whether the directory existspackage.jsonFiles, if any are foundmainField definition file returns ifmainField indicates file does not exist ormainThe field does not exist orpackage.jsonFile does not exist to execute down
  • src/app/moduleA/index.js
  • src/app/moduleA/index.json
  • src/app/moduleA/index.node

The end of the

/module/moduleAStart of absolute path

Find the rule directly in the /module/moduleA directory as above

reactNo path start

If the path does not start with a path, it is considered to import a package. If the path does not start with a path, it is considered to import a core module. If the path does not start with a path, it will import a core module first

  • /src/app/node_modules/Find rules as above to importreactAs an exampleJson ->react. Node ->react directory ->react package.json -> index. index.json -> index.node If you don’t find the parent directorynode_modulesIn looking for
  • /src/node_modules/
  • /node_modules/

Until finally can’t find the end

require wrapper

Node modules can actually be understood as code wrapped in a simple require demo in a function wrapper:

function wrapper (script) {
    return '(function (exports, require, module, __filename, __dirname) {' + 
        script +
     '\n})'
}

function require(id) {
 var cachedModule = Module._cache[id];
  if(cachedModule){
    return cachedModule.exports;
  }
  
  const module = { exports: {}}// Add the reference to the cache
  Module._cache[id] = module

  // Not eval
  eval(wrapper('module.exports = "123"'(a))module.exports, require.module.'filename'.'dirname')


  return module.exports
}
Copy the code

From the above code we can know:

  • The module calls the acquired object after executing it only oncemodule.exportsIt’s all cache even this onejsNot finished yet (because the module was added to the cache before it was executed)
  • Module export isreturnAnd this variable is essentially thetaa = bThe assignmentThe same,Basic types ofThe exported isvalue.Reference typesThe exported isRefer to the address
  • exportsmodule.exportsHolds the same reference because the last exported ismodule.exportsAnd so onexportsPerforming an assignment causesexportsThe operation is no longermodule.exportsA reference to the

A circular reference

// a.js
module.exports.a = 1
var b = require('./b')
console.log(b)
module.exports.a = 2

// b.js
module.exports.b = 11
var a = require('./a')
console.log(a)
module.exports.b = 22

//main.js
var a = require('./a')
console.log(a)
Copy the code

Run this code with the require demo above to analyze each step of the process:

  1. Nodemain.js -> require(a.js), (nodeThe execution can also be understood as calling the require method, which we omitrequire(main.js)Content)
  2. Enter the require(a) method: check the cache (none) -> initialize a module -> add module to the cache -> execute the contents of module A. jsIt should be noted thatFirst,joinThe cache.After the implementationModule content)
  3. A. js: a = 1 -> b: require(b.js)(A only executes the first line)
  4. Enter the same content as 1 -> execute module B. js in require(b)
  5. B.js: line 1 b = 11 -> line 2 require(a.js)
  6. Require (a) this is the second call to require -> judge cache -> cachedModule.exports -> return to b.js(because thejsObject reference problem at this timecachedModule.exports = { a: 1 })
  7. B. js: output {a: 1} -> modify the fourth line B = 22 -> Return to A. js after execution
  8. {b: 22} -> a = 2 -> return to main.js
  9. Main.js: get a -> second line of output {a: 2} -> Execute complete

These are the general rules for parsing and running node modules

es6 module

Before ES6, javascript didn’t have its own module specification, so the community developed the CommonJs specification, and Node borrowed ideas from the CommonJs specification and developed Node module. The AMD asynchronous module is also derived from the Commonjs specification, and then has require.js running in browsers

Es6 Module Basic syntax

export

export * from 'module'; // The redirected export does not include default in module
export{ name1, name2, ... , nameN }from 'module'; // Redirect the named export
export { import1 as name1, import2 asname2, ... , nameN }from 'module'; // Redirect and rename the export

export { name1, name2, …, nameN }; // Bind the export to the previously declared variable name
export { variable1 as name1, variable2 asName2,... , nameN };// Rename the export

export let name1 = 'name1'; // Declare a named export or var, const, function, function*, class

export default expression; // Export by default
export default function () {... }Function *, class
export default function name1() {... }Function *, class
export { name1 as default. };// Rename the default export
Copy the code

exportThe rules

  • export * from ''orexport {} from '', redirection export, redirection name is not used in this module, just to build a bridge, for example: thisaCannot be used in this module
  • export {}, bound to the variable name, named export
  • export DeclarationDeclare, name the export,DeclarationIs this:var.let.const.function.function*.classA declaration of this class
  • export default AssignmentExpression, default export,AssignmentExpressionThe scope of the statement is very wide and can be roughly understood as except declarationDeclaration(There’s actually a crossover between the two),a=2.i++.i/4.a===b.obj[name].name in obj.func().new P().[1, 2, 3].function(){}And so on many

import

// Name the export module.js
let a = 1,b = 2
export { a, b }
export let c = 3

// Name the import main.js
import { a, b, c } from 'module'; // a: 1 b: 2 c: 3
import { a as newA, b, c as newC } from 'module'; // newA: 1 b: 2 newC: 3


// Export module.js by default
export default 1

// Import main.js by default
import defaultExport from 'module'; // defaultExport: 1


// Mix export module.js
let a = 1
export { a }
const b = 2
export { b }
export let c = 3
export default [1.2.3]

// Mix import main.js
import defaultExport, { a, b, c as newC} from 'module'; //defaultExport: [1, 2, 3] a: 1 b: 2 newC: 3
import defaultExport, * as name from 'module'; //defaultExport: [1, 2, 3] name: { a: 1, b: 2, c: 3 }
import * as name from 'module'; // name: { a: 1, b: 2, c: 3, default: [1, 2, 3] }


// module.js
Array.prototype.remove = function(){}

// Side effects run only one module
import 'module'; // Execute module without exporting value multiple calls to module.js only run once

// Dynamic import (asynchronous import)
var promise = import('module');
Copy the code

importThe rules

  • import { } from 'module', importmodule.jstheNamed after the export
  • import defaultExport from 'module', importmodule.jstheThe default is derived
  • import * as name from 'module'That will beThe module of jstheAll exportCombined intonameThe object,keySpecifies the name of the export, the default exportkeyfordefault
  • import 'module'Side effects, just runmoduleInstead of exporting content such as polyfill, the statement can only be executed once
  • import('module')Dynamic import returns onePromise.TC39thestage-3Phase is proposedtc39 import

ES6 moduleThe characteristics of

ES6 moduleSyntax is static

Imports are automatically promoted to the top level of the code

Export and import can only appear at the top level of the code

 //if for while, etc
{
  export let a = 1

  import defaultExport from 'module'
}

true || export let a = 1
Copy the code

The import name of import cannot be a string or in a statement, the following code is wrong

import 'defaultExport' from 'module'

let name = 'Export'
import 'default' + name from 'module'
Copy the code

Static syntax means that imports and exports can be determined at compile time, and dependencies can be found more quicklylintTools for checking module dependencies, imports and exports plus type information for static type checking

####ES6 Module export is bound ####

Modules that are imported using import run in strict mode

Variables that are imported using import are read only, so it is understood that they default to const decorations and cannot be assigned

Variables imported with import are bound to/referenced by the original variable, which can be understood as import variables are passed by reference regardless of whether they are basic types

// The base type in js is value passing
let a = 1
let b = a
b = 2
console.log(a,b) / / 1. 2

// The reference type in js is reference pass
let obj = {name:'obj'}
let obj2 = obj
obj2.name = 'obj2'
console.log(obj.name, obj2.name) // obj2 obj2


// Base types in es6 Modules are also passed by reference
// foo.js
export let a = 1
export function count(){
  a++
}

// main.js
import { a, count } from './foo'
console.log(a) / / 1
count()
console.log(a) / / 2

// Export default cannot be dynamically bound to A. This is a bit similar to CommonJs, which is a copy of the value
let a = 1;
export default a 

// Dynamic binding of default can be implemented in another way
let a = 1;
export { a as default }
export function count(){
  a++
}
// Same as main.js above
Copy the code

This code is the difference between CommonJs exported variables and ES6 exported variables

Es Module circular reference

// bar.js
import { foo } from './foo'
console.log(foo);
export let bar = 'bar'

// foo.js
import { bar } from './bar'
console.log(bar);
export let foo = 'foo'

// main.js
import { bar } from './bar'
console.log(bar)
Copy the code
  1. Go to main.js -> import bar.js
  2. Bar.js -> import foo.js
  3. Foo. Js -> import bar.js -> bar.js has been executed directly return -> output bar -> bar is not defined, bar is not defined error

We can use function to solve this problem:

// bar.js
import { foo } from './foo'
console.log(foo());
export function bar(){
  return 'bar'
}

// foo.js
import { bar } from './bar'
console.log(bar());
export function foo(){
  return 'foo'
}

// main.js
import { bar } from './bar'
console.log(bar)
Copy the code

Since function declarations are prompted at the top of the file, you can call the still-executing bar method of bar.js directly from foo.js. Do not use external variables inside the function, since variables have not been declared (let,const) and assigned (var)

CommonJs and ES6 Modules

In fact, we’ve already talked about some of the differences

  • CommonJsYou export a copy of the variable,ES6 ModuleWhat is exported is the binding of variables (export defaultIs special)
  • CommonJsIs a single value export,ES6 ModuleYou can export multiple
  • CommonJsIt’s dynamic syntax that can be written in judgment,ES6 ModuleStatic syntax can only be written at the top level
  • CommonJsthethisIs the current module,ES6 Modulethethisundefined

Confusing point

Module syntax and deconstruction

The module syntax is easily confused with the deconstruction syntax, for example:

import { a } from 'module'

const { a } = require('module')
Copy the code

You can’t say that two people are the same person if they wear the same clothes. Import /export {a} / {a, b} / {a as c}

let { a } = { a: 1 }
let { a = 2} = {}let { a: b } = { a: 1 }
let { a: b = 2. res } = {name:'a' }
let { a: b, obj: { name } } = { a: 1.obj: { name: '1'}}function foo({a: []}) {}
Copy the code

They are two very different things, one is module import and export, and the other is to get the syntactic sugar of the object

Export syntax and object property abbreviations

The following code is also confusing

let a = 1

export { a } // Export the syntax
export default { a } // Attribute shorthand exports {a: 1} objects

module.exports = { a } // Attribute shorthand exports {a: 1} objects
Copy the code

Export Default and Module. exports are similar

ES6 Module supports CommonJs

CommonJs is supported by ES6 Modules in various environments. How to use CommonJs in different environments

Module. exports is like export default so ES6 modules can be easily compatible with CommonJs. ES6 modules use CommonJs specifications in ES6 modules, and packaging tools vary from environment to environment

At present, we mostly use Webpack for project construction and packaging, because the front-end development environment is in the Node environment, and NPM packages are CommonJs specifications, so WebPack extends ES6 modules to support CommonJs. And support node import NPM package specification

If you use rollup, you need to download rollup-plugin-commonJS to support the Commonjs specification in ES Module. To import packages under node_modules, you need rollup-plugin-node-resolve

If you use Node, you can use ES6 in.mjs files and also support CommonJs to see nodeJS es-modules.md

CommonJs is not supported in the browser environment

Node differs from packaging tools webPack, rollup import CommonJs

// module.js
module.export.a = 1

// index.js webpack rollup
import * as a from './module'
console.log(a) // { a: 1, default: { a:1 } }

// index.mjs node
import * as a from './module'
console.log(a) // { default: { a:1 } }
Copy the code

Node uses module.exports as an export default package. Import defaultExport from ‘./foo’, Defaultexport.foo () import {foo} from ‘./foo’, foo()

Use the ES6 Module

You can get the code in the ES6Module Example repository and test it locally

Use in the browser

You need a Web server to access it. Double-click to run index.html locally does not execute the type=module tag. We can add module to the type attribute of the script tag and define two modules first

// index.js
import module from './module.js'
console.log(module) / / 123

// module.js
export default 123
Copy the code

Inline calls in HTML

<! -- index.html -->
<script type="module">
  import module from './module.js'
  console.log(module) / / 123
</script>
Copy the code

Referenced in HTML via script SRC

<! -- index.html -->
<script type="module" src="index.js"></script>// console 123Copy the code

Browser imports path rules

  • https://example.com/apples.mjs
  • http://example.com/apples.js
  • //example.com/bananas
  • ./strawberries.mjs.cgi
  • ../lychees
  • /limes.jsx
  • data:text/javascript,export default 'grapes';
  • blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f

Supplement:

  • The specific file cannot be found without the suffix
  • Interfaces can be modified on the back end/getjs? name=moduleThat kind of thing, but the back end returnsContent-Type: application/javascriptMake sure it returnsjsBecause the browser is based onMIME typeIdentification of the

Because the ES6 Module is not compatible with the browser, the compatibility table will not introduce the browser support, we generally do not use directly in the browser

Nodejs is used in

nodejs es-modules.md

ES Module is supported in Node V8.5.0 and above, requiring the.mjs extension

NOTE: DRAFT status does not mean ESM will be implemented in Node core. Instead that this is the standard, Should Node core decide to implement ESM. At which time this draft would be moved to ACCEPTED A DRAFT refers to the drafting stage.

// module.mjs
export default 123

// index.mjs
import module from './module.mjs'
console.log(module) / / 123
Copy the code

If you need to execute node — experimental-Modules index. MJS to start, it should prompt an ExperimentalWarning: The ESM Module loader is experimental. This feature is experimental (this tip does not affect execution) importing CommonJs in ES Module

// module.js
module.exports.a = 123 // module.exports is export default

// index.mjs
import module from './module.js'
console.log(module) // { a: 123 }

import * as module from './module.js'
console.log(module) // { get default: { a: 123 } }

import { default as module } from './module.js';
console.log(module) // { a: 123 }

import module from 'module'; // Import NPM package import rules are similar to require
Copy the code

Module extension.js,.mjs belongs to es Module, import form can only import file extension.mjs, module is not defined in.mjs. Module. exports, exports returns an error

The CommonJs es Module can only be imported dynamically/asynchronously using import()

// es.mjs
let foo = {name: 'foo'};
export default foo;

export let a = 1

// cjs
import('./es').then((res) = >{
  console.log(res) // { get default: {name: 'foo'}, a: 1 }
});
Copy the code

Webpack used in

Webpack2 supports es Module by default, CommonJs by default, NPM package import by default

Use the rollup

Rollup focuses on ES Modules, which can be packaged as a mainstream module specification. Note the difference with WebPack. We can use Commonjs syntax in WEBPack JS, but rollup does not support it. Rollup requires plugin support, including loading the node_modules package form ‘react’

It can be seen that ES Module is not compatible with the browser and Node, and the experimental function is mostly used in the packaging tool

Tree-shaking

Tree shaking shaking is shaking the dead leaves from a Tree, Tree-shaking was first proposed by Rollup and later supported by WebPack 2. It’s all about static analysis based on the es Module feature

rollup

The following code is packaged using rollup:

// module.js
export let foo = 'foo'
export let bar = 'bar'

// index.js
import { foo } from './module'
console.log(foo) // foo
Copy the code

Running online we can modify examples and export a variety of specifications

Packing results:

let foo = 'foo';

console.log(foo); // foo
Copy the code

We can see that the rollup package is very neat and removes the unused bar support for tree-shaking the specification importing CommonJs:

// index.js
import { a } from './module'
console.log(a) / / 1

// module.js
module.exports.a = 1
module.exports.b = 2
Copy the code

Package as ES Module

var a_1 = 2;

console.log(a_1);
Copy the code

You can see that the unused B is removed

webpack

Let’s take a look at webPack support

// src/module.js
export function foo(){ return 'foo' }
export function bar(){ return 'bar' }

// src/index.js
import { foo } from './module'
console.log(foo())
Copy the code

After executing NPX webpack -p (we use webpack 4,0 configuration, -p to enable automatic compression in build mode), we did not find bar in the package file, and bar was deleted. We modified the above example:

// src/module.js
module.exports.foo = function (){ return 'foo' }
module.exports.bar = function (){ return 'bar' }

// src/index.js
import { foo } from './module'
console.log(foo())
Copy the code

Webpack does not support tree-shaking for CommonJs

pkg.module

Webpack doesn’t support Commonjs tree-shaking, but now NPM packages are Commonjs compliant. What if? If I send a new package it is the ES Module specification, but if the code is running in the Node environment, it will report an error without being packaged

There is a load on demand solution

Full path import, import specific files:

// src/index.js
import remove from 'lodash/remove'
import add from 'lodash/add'

console.log(remove(), add())
Copy the code

You can also use plugins such as babel-plugin-lodash, &lodash-webpack-plugin

But we can’t just send out a library and write our own plugins

Module-es module pkG. module {package.json} {CommonJs -> CommonJs}

Pkg. module is supported by both Webpack and rollup

Webpack can recognize our ES Module by adding the Module field, but there is still a problem with Babel

Node_modules are excluded when we use Babel, so the PKg. module is just an ES6 module that must be compiled into ES5 code because Babel won’t compile it for us. Our package must be ES5 code with the ES6 Module specification

If you use presets-env, set “presets”: [[“env”, {“modules”:false}] not to convert es module to CommonJs because it will convert our code to CommonJs

webpackrollupThe difference between

  • webpackExport not supportedes6 moduleThe specification,rollupSupports exportes6 module
  • webpackThe packaged code is too redundant to look at directly,rollupThe packaged code is clean and readable, like source code
  • webpackYou can do code segmentation, static resource processing,HRM.rollupFocus on thees module.tree-shakingMore powerful, leaner

If you are developing an application, you can use Webpack, because you can do code splitting, static resources, HRM, plug-ins. If you are developing libraries like Vue, React, etc., rollup is better, because you can make your code lean, no redundant code, faster execution, export multiple module syntax

conclusion

Commonjs and ES6 Modules, import and export syntax rules, path parsing rules, differences between them, confusion, differences in different environments, use in different environments, tree-shaking, webpack. Hopefully, after reading this article, you will have a better understanding of the modularity of the front end

Refer to the link

  • ECMAScript® 2015 Language Specification sec-imports/sec-exports
  • MDN import
  • github nodejs lib/module
  • github nodejs node-eps/002-es-modules
  • nodejs docs modules
  • Understanding ECMAScript 6
  • ECMAScript introduction to 6
  • es6-modules-final