原文地址 What Are CJS, AMD, UMD, ESM, System, and IIFE?
古代 Javascript 我的项目须要用打包工具来将小段代码编译成库或者应用程序那种更大更简单的货色。风行的打包器有 webpack、Rollup、Parcel、RequireJS 和 Browserify。它们将 JavaScript 代码转换为能够作为一个 bundle 加载的模块。
一个 bundle 能够用不同的格局打包。这篇文章中,咱们将展现 CJS, AMD, UMD, ESM, System 和 IIFE 格局的打包示例。
打包工具和格局
上面是一个规范的 HTML 文件,第 5 行引入了一个款式文件,第 6 行引入了一个 JS 文件:
<!DOCTYPE html>
<html lang="en">
<head>
...
<link rel="stylesheet" href="style.css" />
<script src="src/index.js"></script>
</head>
<body>
...
</body>
</html>
简略状况下能够将所有的 JavaScript 代码放到一个文件中。随着我的项目的增大,咱们须要将代码拆分成具备独立命名空间的独立模块。除了更好的构造,模块化还带来了封装的性能、依赖治理和复用性。
这就是打包工具呈现的起因。它须要将小块的 JavaScript 代码,样式表和图片编译成更大更简单的像库或者应用程序一样的货色。
打包工具是如何将打包的代码格式化为输入?有很多的抉择,上面是 Rollup 定义的一些格局:
cjs
(CommonJS) — 实用于 Node 和其余打包工具(别名:commonjs)。amd
(Asynchronous Module Definition,异步模块化定义) — 与 RequireJS 等模块加载工具一起应用。umd
(Universal Module Definition,通用模块化定义) —amd
,cjs
和iife
蕴含在一个文件中。es
— 将 bundle 保留为 ES 模块文件。实用于其余打包工具,在古代浏览器中用<script type=module>
标签引入(别名:ems
,module
)。system
— SystemJS 加载器的原生格局(别名:systemjs
)。iife
—<script>
标签引入的自执行函数。如果你想为你的利用创立一个包,你须要用到的可能就是这种。
在这篇文章中,咱们将通过示例解释这些格局。
示例
上面是一个打包四个文件的示例:
index.js
increase.js
decrease.js
other.js
入口文件是 index.js
:
/**
* This is the main file
*/
import {increase} from './increase';
import {decrease} from './decrease';
import others, {e} from './others';
function multiply(total, value) {return total * value;}
function divide(total, value) {return total / value;}
export function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
4-6 行中,index.js
明确地列出了从 increase.js
,decrease.js
和 other.js
中引入的内容。
这是引入的 increase.js
:
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
export const increase = (total, value) => total + value;
这是引入的 decrease.js
:
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
export const decrease = (total, value) => total - value;
这是引入的 other.js
:
export default {
a: 1,
b: 2,
c: () => 3,};
export const d = 4;
export const e = 5;
在这个示例中,other.js
中的 const d
和 index.js
中的 function divide()
未被应用。index.js
中的 function power()
尽管也未用到但被导出了。
ES2015/ES6 引入了动态 import
和 export
,容许动态分析器在不运行代码的状况下构建残缺的依赖关系树。此外,这为 tree shaking(摇树)优化奠定了根底。依据维基百科:
Tree shaking 从入口开始,只蕴含可能被执行的函数,删除包中未应用的函数。
因为这个示例是用 ES2015 编写的,且带有特定的导入(不是 import *
),所以摇树过程会删除所有生成格局中的 const d
和 function divide()
。因为导出的函数可能会被应用,function power()
得以保留。
CommonJS(CJS)
CJS 实用于浏览器之外的 Node 和其余生态系统。它在服务端被宽泛应用。CJS 能够通过应用 require()
函数和 module.exports
来辨认。require()
是一个可用于从另一个模块导入 symbols 到以后作用域的函数。module.exports
是以后模块在另一个模块中引入时返回的对象。
CJS 模块的设计思考到了服务器开发。这个 API 天生是同步的。换言之,在源文件中按 require 的程序刹时加载模块。
因为 CJS 是同步的且不能被浏览器辨认,CJS 模块不能在浏览器端应用,除非它被转译器打包。像 Babel 和 Traceur 那样的转译器,是一种帮忙咱们在新版 JavaScript 中编码的工具。如果环境原生不反对新版本的 JavaScript,转译器将它们编译成反对的 JS 版本。
上面是 Rollup 生成的 CJS 格局的文件:
'use strict';
Object.defineProperty(exports, '__esModule', { value: true});
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
exports.power = power;
咱们在浏览器端执行这个文件,它会报错,报错信息是 exports is not defined
(第 3 行)。
这个谬误能够通过 index.html
中增加以下代码来修复:
<script>
const exports = {};
</script>
异步模块定义(AMD)
AMD 脱胎于 CJS,反对异步模块加载。AMD 和 CJS 的次要区别在于它是否反对异步模块加载。RequireJS 应用 AMD 在浏览器端工作。
据 维基百科:
AMD 提供了一些 CJS 类似的个性。它容许在代码中应用相似的
exports
和require()
接口,只管它本人的define()
接口更根底更受欢迎。
上面是 Rollup 生成的 AMD 格局的文件:
define(['exports'], function (exports) { 'use strict';
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
exports.power = power;
Object.defineProperty(exports, '__esModule', { value: true});
});
咱们在浏览器上执行这个文件,它会报错,报错信息是 define is not a function
(第 1 行)。
这个报错能通过在 index.html
中引入 require.js
修复。
<script src=”https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
通用模块定义(UMD)
UMD 被设计用于任何中央 — 包含服务端和浏览器端。它试图兼容目前最风行的 script 加载器(如 RequireJS)。在许多状况下,它应用 AMD 作为根底,且兼容 CJS。然而兼容减少了一些复杂度,使得读写变得更加艰难。
上面是 Rollup 生成的 UMD 格局的文件:
(function (global, factory) {typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.example = {}));
}(this, (function (exports) { 'use strict';
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
exports.power = power;
Object.defineProperty(exports, '__esModule', { value: true});
})));
此代码能够在浏览器中工作。
ES2015 模块(ESM)
动态 import
指令可用于将模块引入以后作用域。与 require
和 define
不同,这个指令只能放在文件的顶部。动静 import()
目前处于 TC39 流程的第 4 阶段。
另一方面,export
指令可用于显式地将对象公开。
上面是 Rollup 生成的 ESM 格局的文件:
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
export {power};
咱们在浏览器上运行这个文件,它会报错,报错信息是 Uncaught SyntaxError: Unexpected token 'export'
(第 45 行)。
能够通过设置 script
标签的 type
为 module
来修复这个报错:
<script type=”module”src=”dist/bundle.js”></script>
零碎模块
SystemJs 是一个通用的模块加载器,反对 CJS,AMD 和 ESM 模块。Rollup 能够将代码打包成 SystemJS 的原生格局。
上面是 Rollup 生成的 System 格局的文件:
System.register([], (function (exports) {
'use strict';
return {execute: (function () {exports('power', power);
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
})
};
}));
咱们在浏览器运行这个文件,它会报错,报错信息是 System is not defined
(第 1 行)。
装置 system.js
:
npm install --save-dev systemjs
在 index.html
中引入 system.js
能够解决这个问题:
<script src="node_modules/systemjs/dist/s.min.js"></script>
立刻执行的函数表达式(IIFE)模块
正如模块名所示,IIFE 是一个适宜用 <script>
标签引入的自执行函数。咱们能够用这种格局为利用创立一个包。它帮忙咱们将内容放到命名空间中,防止变量抵触并使代码公有。
上面是 Rollup 生成的 IIFE 格局的文件:
var example = (function (exports) {
'use strict';
/**
* Increase the current total value
* @param {number} total The current total value
* @param {number} value The new value to be added
* @returns {number} The new total value
*/
const increase = (total, value) => total + value;
/**
* Decrease the current total value
* @param {number} total The current total value
* @param {number} value The new value to be subtracted
* @returns {number} The new total value
*/
const decrease = (total, value) => total - value;
var others = {
a: 1,
b: 2,
c: () => 3,};
const e = 5;
/**
* This is the main file
*/
function multiply(total, value) {return total * value;}
function power(total, value) {return total ** value;}
let total = others.a;
total = increase(total, 10);
total = increase(total, 20);
total = decrease(total, 5);
total = multiply(total, e);
console.log(`Total is ${total}`);
exports.power = power;
Object.defineProperty(exports, '__esModule', { value: true});
return exports;
}({}));
此代码能够在浏览器中运行。
生成多种格局
rollup.config.js
是 Rollup 的配置文件。它是可选的,但功能强大,不便,因而举荐应用。
上面是咱们用来一次生成多个输入格局的输入配置:
output: [
{
file: 'dist/bundle.amd.js',
format: 'amd',
sourcemap: false,
},
{
file: 'dist/bundle.cjs.js',
format: 'cjs',
sourcemap: false,
},
{
file: 'dist/bundle.umd.js',
format: 'umd',
name: 'example',
sourcemap: false,
},
{
file: 'dist/bundle.es.js',
format: 'es',
sourcemap: false,
},
{
file: 'dist/bundle.iife.js',
format: 'iife',
name: 'example',
sourcemap: false,
},
{
file: 'dist/bundle.system.js',
format: 'system',
sourcemap: false,
},
]
论断
明天咱们探讨了 JavaScript 模块化。在下一篇文章中,咱们将逐渐介绍如何用 Rollup 打包 JavaScript 我的项目。
请继续关注!
注:感激 Daria Mehra 审阅这篇文章。
文章中的代码参考