疯狂的技术宅 前端先锋
翻译:疯狂的技术宅
作者:Dixin
来源:weblogs.asp.net
正文共:4953 字
预计阅读时间:10分钟
JavaScript 语言最初是为简单的表单操作而发明的,没有诸如模块或命名空间之类的内置功能。多年以来发明了大量的术语、模式、库、语法和工具来模块化 JavaScript。本文讨论了 JavaScript 中的所有主流模块系统、格式、库和工具,包括:
- IIFE 模块:JavaScript 模块模式
- IIFE:立即调用的函数表达式
- 混合导入
- Revealing 模块:JavaScript 显示模块模式
- CJS 模块:CommonJS 模块或 Node.js 模块
- AMD 模块:异步模块定义或 RequireJS 模块
- 动态加载
- 来自 CommonJS 模块的 AMD 模块
- UMD 模块:通用模块定义或 UmdJS 模块
- 适用于AMD(RequireJS)和本机浏览器的 UMD
- 适用于AMD(RequireJS)和CommonJS(Node.js)的UMD
- ES 模块:ECMAScript 2015 或 ES6 模块
- ES 动态模块:ECMAScript 2020 或 ES11 动态模块
- 系统模块:SystemJS 模块
- 动态模块加载
- Webpack 模块:来自 CJS、AMD、ES 模块的捆绑软件
- Babel 模块:从 ES 模块转换
- Babel with SystemJS
- TypeScript 模块:转换为 CJS、AMD、ES、系统模块
- 内部模块和命名空间
- 结论
希望本文可以帮助你了解和使用 JavaScript/TypeScript 语言,RequireJS/SystemJS 库和 Webpack/Babel 工具等所有这些模式。
在浏览器中,定义 JavaScript 变量就是定义全局变量,这会导致当前网页所加载的全部 JavaScript 文件之间的污染:
1// Define global variables. 2let count = 0; 3const increase = () => ++count; 4const reset = () => { 5 count = 0; 6 console.log("Count is reset."); 7}; 8 9// Use global variables.10increase();11reset();
为了避免全局污染,可以用匿名函数来包装代码:
1(() => {2 let count = 0;3 // ...4});
显然,这样就不再有任何全局变量。但是定义函数不会在函数内部执行代码。
IIFE:立即调用的函数表达式为了执行函数 f 中的代码,语法是将函数调用 () 作为 f()。为了在匿名函数 (() => {}) 中执行代码,可以将相同的函数调用语法 () 用作 (() => {}):
1(() => {2 let count = 0;3 // ...4})();
这称为 IIFE(立即调用的函数表达式)。因此,可以通过以下方式定义基本模块:
1// Define IIFE module. 2const iifeCounterModule = (() => { 3 let count = 0; 4 return { 5 increase: () => ++count, 6 reset: () => { 7 count = 0; 8 console.log("Count is reset."); 9 }10 };11})();1213// Use IIFE module.14iifeCounterModule.increase();15iifeCounterModule.reset();
它将模块代码包装在 IIFE 中,返回一个对象,这个对象是导出的 API 的占位符。仅引入 1 个全局变量,这是模式名称。之后模块名可用于调用导出的模块 API。这称为 JavaScript 的模块模式。
混合导入定义模块时,可能需要一些依赖关系。使用 IIFE 模块模式,其他所有模块都是全局变量。它们可以在匿名函数内部直接访问,也可以通过匿名函数的参数进行传递:
1// Define IIFE module with dependencies. 2const iifeCounterModule = ((dependencyModule1, dependencyModule2) => { 3 let count = 0; 4 return { 5 increase: () => ++count, 6 reset: () => { 7 count = 0; 8 console.log("Count is reset."); 9 }10 };11})(dependencyModule1, dependencyModule2);
一些流行库(如 jQuery)的早期版本遵循这种模式。
revealing module:JavaScript 揭示模块模式揭示模块模式由 Christian Heilmann 命名。此模式也是 IIFE,但它强调将所有 API 定义为匿名函数内的局部变量:
1// Define revealing module. 2const revealingCounterModule = (() => { 3 let count = 0; 4 const increase = () => ++count; 5 const reset = () => { 6 count = 0; 7 console.log("Count is reset."); 8 }; 910 return {11 increase,12 reset13 };14})();1516// Use revealing module.17revealingCounterModule.increase();18revealingCounterModule.reset();
用这种语法,当 API 需要相互调用时,将会变得更加容易。
CJS 模块:CommonJS 模块或 Node.js 模块CommonJS(最初名为 ServerJS)是定义和使用模块的模式。它由 Node.js 实现。默认情况下,每个 .js 文件都是 CommonJS 模块。为模块提供了暴露 API 的模块变量和导出变量。并且提供了一个 require 函数来使用模块。以下代码以 CommonJS 语法定义了 counter 模块:
1// Define CommonJS module: commonJSCounterModule.js. 2const dependencyModule1 = require("./dependencyModule1"); 3const dependencyModule2 = require("./dependencyModule2"); 4 5let count = 0; 6const increase = () => ++count; 7const reset = () => { 8 count = 0; 9 console.log("Count is reset.");10};1112exports.increase = increase;13exports.reset = reset;14// Or equivalently:15module.exports = {16 increase,17 reset18};
以下例子使用了 counter 模块:
1// Use CommonJS module.2const { increase, reset } = require("./commonJSCounterModule");3increase();4reset();5// Or equivelently:6const commonJSCounterModule = require("./commonJSCounterModule");7commonJSCounterModule.increase();8commonJSCounterModule.reset();
在运行时,Node.js 通过将文件内的代码包装到一个函数中,然后通过参数传递 exports 变量、module 变量和 require 函数来实现这一目的。
1// Define CommonJS module: wrapped commonJSCounterModule.js. 2(function (exports, require, module, __filename, __dirname) { 3 const dependencyModule1 = require("./dependencyModule1"); 4 const dependencyModule2 = require("./dependencyModule2"); 5 6 let count = 0; 7 const increase = () => ++count; 8 const reset = () => { 9 count = 0;10 console.log("Count is reset.");11 };1213 module.exports = {14 increase,15 reset16 };1718 return module.exports;19}).call(thisValue, exports, require, module, filename, dirname);2021// Use CommonJS module.22(function (exports, require, module, __filename, __dirname) {23 const commonJSCounterModule = require("./commonJSCounterModule");24 commonJSCounterModule.increase();25 commonJSCounterModule.reset();26}).call(thisValue, exports, require, module, filename, dirname);
AMD 模块:异步模块定义或 RequireJS 模块AMD(Asynchronous Module Definition https://github.com/amdjs/amdjs-api)是一种定义和使用模块的模式。它由 RequireJS 库(https://requirejs.org/)实现。AMD 提供了一个定义模块的定义函数,该函数接受模块名称、依赖模块的名称以及工厂函数:
1// Define AMD module. 2define("amdCounterModule", ["dependencyModule1", "dependencyModule2"], (dependencyModule1, dependencyModule2) => { 3 let count = 0; 4 const increase = () => ++count; 5 const reset = () => { 6 count = 0; 7 console.log("Count is reset."); 8 }; 910 return {11 increase,12 reset13 };14});
它还提供了 require 函数来使用模块:
1// Use AMD module.2require(["amdCounterModule"], amdCounterModule => {3 amdCounterModule.increase();4 amdCounterModule.reset();5});
AMD 的 require 函数与 CommonJS 的 require 函数完全不同。AMD 的 require 接受要使用的模块的名称,并将模块传递给函数参数。
动态加载AMD 的 require 函数还有另一个重载。它接受一个回调函数,并将类似 CommonJS 的 require 函数传递给该回调。所以可以通过调用 require 来加载 AMD 模块:
1// Use dynamic AMD module. 2define(require => { 3 const dynamicDependencyModule1 = require("dependencyModule1"); 4 const dynamicDependencyModule2 = require("dependencyModule2"); 5 6 let count = 0; 7 const increase = () => ++count; 8 const reset = () => { 9 count = 0;10 console.log("Count is reset.");11 };1213 return {14 increase,15 reset16 };17});
来自 CommonJS 模块的 AMD 模块上面的 define 函数有一个重载,它可以传递 require 函数,并将变量和模块变量导出到回调中,以便 CommonJS 代码可以在其内部工作:
1// Define AMD module with CommonJS code. 2define((require, exports, module) => { 3 // CommonJS code. 4 const dependencyModule1 = require("dependencyModule1"); 5 const dependencyModule2 = require("dependencyModule2"); 6 7 let count = 0; 8 const increase = () => ++count; 9 const reset = () => {10 count = 0;11 console.log("Count is reset.");12 };1314 exports.increase = increase;15 exports.reset = reset;16});1718// Use AMD module with CommonJS code.19define(require => {20 // CommonJS code.21 const counterModule = require("amdCounterModule");22 counterModule.increase();23 counterModule.reset();24});
UMD 模块:通用模块定义或 UmdJS 模块UMD(Universal Module Definition,https://github.com/umdjs/umd)是一组棘手的模式,可以使你的代码文件在多种环境中工作。
适用于 AMD(RequireJS)和本机浏览器的 UMD例如以下是一种 UMD 模式,能够使模块定义可用于 AMD(RequireJS)和本机浏览器:
1// Define UMD module for both AMD and browser. 2((root, factory) => { 3 // Detects AMD/RequireJS"s define function. 4 if (typeof define === "function" && define.amd) { 5 // Is AMD/RequireJS. Call factory with AMD/RequireJS"s define function. 6 define("umdCounterModule", ["deependencyModule1", "dependencyModule2"], factory); 7 } else { 8 // Is Browser. Directly call factory. 9 // Imported dependencies are global variables(properties of window object).10 // Exported module is also a global variable(property of window object)11 root.umdCounterModule = factory(root.deependencyModule1, root.dependencyModule2);12 }13})(typeof self !== "undefined" ? self : this, (deependencyModule1, dependencyModule2) => {14 // Module code goes here.15 let count = 0;16 const increase = () => ++count;17 const reset = () => {18 count = 0;19 console.log("Count is reset.");20 };2122 return {23 increase,24 reset25 };26});
它比较复杂,但仍然只是 IIFE。匿名函数会检测是否存在 AMD 的 define 函数,如果存在,请使用 AMD 的define 函数调用模块工厂。如果不是,它将直接调用模块工厂。目前,root 参数实际上是浏览器的 window 对象。它从全局变量( window 对象的属性)获取依赖项模块。当 factory 返回模块时,返回的模块也被分配给一个全局变量( window 对象的属性)。
适用于 AMD(RequireJS)和 CommonJS(Node.js)的 UMD以下是使模块定义与 AMD(RequireJS)和 CommonJS(Node.js)一起工作的另一种 UMD 模式:
1(define => define((require, exports, module) => { 2 // Module code goes here. 3 const dependencyModule1 = require("dependencyModule1"); 4 const dependencyModule2 = require("dependencyModule2"); 5 6 let count = 0; 7 const increase = () => ++count; 8 const reset = () => { 9 count = 0;10 console.log("Count is reset.");11 };1213 module.export = {14 increase,15 reset16 };17}))(// Detects module variable and exports variable of CommonJS/Node.js.18 // Also detect the define function of AMD/RequireJS.19 typeof module === "object" && module.exports && typeof define !== "function"20 ? // Is CommonJS/Node.js. Manually create a define function.21 factory => module.exports = factory(require, exports, module)22 : // Is AMD/RequireJS. Directly use its define function.23 define);
别怕,这只是一个IIFE。调用IIFE时,将评估其参数。参数评估检测环境(CommonJS / Node.js的模块变量和exports 变量,以及 AMD/RequireJS 的 define 函数)。如果环境是 CommonJS/Node.js,则匿名函数的参数是手动创建的 define 函数。如果环境是 AMD/RequireJS,则匿名函数的参数就是 AMD 的 define 函数。因此,当执行匿名函数时,可以确保它具有有效的 define 函数。在匿名函数内部,它仅调用 define 函数来创建模块。
ES 模块:ECMAScript 2015 或 ES6 模块在所有模块混乱之后,JavaScript 的规范第 6 版在 2015 年定义了完全不同的模块系统和语法。该规范称为ECMAScript 2015 或 ES2015,AKA ECMAScript 6 或 ES6。主要语法是 import 关键字和 export 关键字。以下例子使用新语法演示 ES 模块的命名 import/export 和默认 import/export:
1// Define ES module: esCounterModule.js or esCounterModule.mjs. 2import dependencyModule1 from "./dependencyModule1.mjs"; 3import dependencyModule2 from "./dependencyModule2.mjs"; 4 5let count = 0; 6// Named export: 7export const increase = () => ++count; 8export const reset = () => { 9 count = 0;10 console.log("Count is reset.");11};12// Or default export:13export default {14 increase,15 reset16};
要在浏览器中使用此模块文件,请添加 <script> 标签并指定它为模块:<script type="module" src="esCounterModule.js"></script>。要在 Node.js 中使用此模块文件,请将其扩展名 .js 改为 .mjs。
1// Use ES module. 2// Browser: <script type="module" src="esCounterModule.js"></script> or inline. 3// Server: esCounterModule.mjs 4// Import from named export. 5import { increase, reset } from "./esCounterModule.mjs"; 6increase(); 7reset(); 8// Or import from default export: 9import esCounterModule from "./esCounterModule.mjs";10esCounterModule.increase();11esCounterModule.reset();
对于浏览器,可以将 <script> 的 nomodule 属性用于后备:
1<script nomodule>2 alert("Not supported.");3</script>
ES 动态模块:ECMAScript 2020 或 ES11 动态模块在 2020 年,最新的 JavaScript 规范第 11 版引入了内置函数 import 以动态使用 ES 模块。import 函数返回一个 promise,因此可以通过其 then 方法调用该模块:
1// Use dynamic ES module with promise APIs, import from named export: 2import("./esCounterModule.js").then(({ increase, reset }) => { 3 increase(); 4 reset(); 5}); 6// Or import from default export: 7import("./esCounterModule.js").then(dynamicESCounterModule => { 8 dynamicESCounterModule.increase(); 9 dynamicESCounterModule.reset();10});
通过返回一个 promise ,显然 import 函数也可以与 await 关键字一起使用:
1// Use dynamic ES module with async/await. 2(async () => { 3 4 // Import from named export: 5 const { increase, reset } = await import("./esCounterModule.js"); 6 increase(); 7 reset(); 8 9 // Or import from default export:10 const dynamicESCounterModule = await import("./esCounterModule.js");11 dynamicESCounterModule.increase();12 dynamicESCounterModule.reset();1314})();
以下是来自 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules 的导入、动态导入、导出的兼容性列表:
import 兼容性
export 兼容性
SystemJS 是一个库,可以为较旧的 ES5 启用 ES6 模块语法。例如以下模块以 ES6 语法定义:
1// Define ES module. 2import dependencyModule1 from "./dependencyModule1.js"; 3import dependencyModule2 from "./dependencyModule2.js"; 4dependencyModule1.api1(); 5dependencyModule2.api2(); 6 7let count = 0; 8// Named export: 9export const increase = function () { return ++count };10export const reset = function () {11 count = 0;12 console.log("Count is reset.");13};14// Or default export:15export default {16 increase,17 reset18}
如果当前运行时(例如旧的浏览器)不支持 ES6 语法,则以上代码将无法正常工作。SystemJS 可以将模块定义转换为对库 API 的调用——System.register:
1// Define SystemJS module. 2System.register(["./dependencyModule1.js", "./dependencyModule2.js"], function (exports_1, context_1) { 3 "use strict"; 4 var dependencyModule1_js_1, dependencyModule2_js_1, count, increase, reset; 5 var __moduleName = context_1 && context_1.id; 6 return { 7 setters: [ 8 function (dependencyModule1_js_1_1) { 9 dependencyModule1_js_1 = dependencyModule1_js_1_1;10 },11 function (dependencyModule2_js_1_1) {12 dependencyModule2_js_1 = dependencyModule2_js_1_1;13 }14 ],15 execute: function () {16 dependencyModule1_js_1.default.api1();17 dependencyModule2_js_1.default.api2();18 count = 0;19 // Named export:20 exports_1("increase", increase = function () { return ++count };21 exports_1("reset", reset = function () {22 count = 0;23 console.log("Count is reset.");24 };);25 // Or default export:26 exports_1("default", {27 increase,28 reset29 });30 }31 };32});
这样新的 ES6 语法 import/export 就消失了。
动态模块加载SystemJS 还提供了用于动态导入的 import 函数:
1// Use SystemJS module with promise APIs.2System.import("./esCounterModule.js").then(dynamicESCounterModule => {3 dynamicESCounterModule.increase();4 dynamicESCounterModule.reset();5});
Webpack 模块:来自 CJS,AMD,ES 模块的捆绑包Webpack 是模块的捆绑器。它使用将组合的 CommonJS 模块、AMD 模块和 ES 模块转换为和谐模块模式,并将所有代码捆绑到一个文件中。例如以下 3 个文件中用 3 种不同的语法定义了 3 个模块:
1// Define AMD module: amdDependencyModule1.js 2define("amdDependencyModule1", () => { 3 const api1 = () => { }; 4 return { 5 api1 6 }; 7}); 8 9// Define CommonJS module: commonJSDependencyModule2.js10const dependencyModule1 = require("./amdDependencyModule1");11const api2 = () => dependencyModule1.api1();12exports.api2 = api2;1314// Define ES module: esCounterModule.js.15import dependencyModule1 from "./amdDependencyModule1";16import dependencyModule2 from "./commonJSDependencyModule2";17dependencyModule1.api1();18dependencyModule2.api2();1920let count = 0;21const increase = () => ++count;22const reset = () => {23 count = 0;24 console.log("Count is reset.");25};2627export default {28 increase,29 reset30}
以下文件使用了 counter 模块:
1// Use ES module: index.js2import counterModule from "./esCounterModule";3counterModule.increase();4counterModule.reset();
Webpack 可以将以上所有文件打包在一起,即使它们位于 3 个不同的模块系统中,也都能打包为一个文件 main.js:
- root
dist
- main.js (捆绑 src 下的所有文件)
- src
- amdDependencyModule1.js
- commonJSDependencyModule2.js
- esCounterModule.js
- index.js
- webpack.config.js
有趣的是,Webpack 本身使用 CommonJS 模块语法。在webpack.config.js 中:
1const path = require('path'); 2 3module.exports = { 4 entry: './src/index.js', 5 mode: "none", // Do not optimize or minimize the code for readability. 6 output: { 7 filename: 'main.js', 8 path: path.resolve(__dirname, 'dist'), 9 },10};
现在运行以下命令以不同的语法转换和捆绑 4 个文件:
1npm install webpack webpack-cli --save-dev2npx webpack --config webpack.config.js
重新格式化了以下捆绑文件 main.js,并重命名了变量以提高可读性:
1(function (modules) { // webpackBootstrap 2 // The module cache 3 var installedModules = {}; 4 // The require function 5 function require(moduleId) { 6 // Check if module is in cache 7 if (installedModules[moduleId]) { 8 return installedModules[moduleId].exports; 9 10 } 11 // Create a new module (and put it into the cache) 12 var module = installedModules[moduleId] = { 13 i: moduleId, 14 l: false, 15 exports: {} 16 17 }; 18 // Execute the module function 19 modules[moduleId].call(module.exports, module, module.exports, require); 20 // Flag the module as loaded 21 module.l = true; 22 // Return the exports of the module 23 return module.exports; 24 } 25 26 // expose the modules object (__webpack_modules__) 27 require.m = modules; 28 // expose the module cache 29 require.c = installedModules; 30 // define getter function for harmony exports 31 require.d = function (exports, name, getter) { 32 if (!require.o(exports, name)) { 33 Object.defineProperty(exports, name, { enumerable: true, get: getter }); 34 35 } 36 37 }; 38 // define __esModule on exports 39 require.r = function (exports) { 40 if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { 41 Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 42 43 } 44 Object.defineProperty(exports, '__esModule', { value: true }); 45 46 }; 47 // create a fake namespace object 48 // mode & 1: value is a module id, require it 49 // mode & 2: merge all properties of value into the ns 50 // mode & 4: return value when already ns object 51 // mode & 8|1: behave like require 52 require.t = function (value, mode) { 53 if (mode & 1) value = require(value); 54 if (mode & 8) return value; 55 if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 56 var ns = Object.create(null); 57 require.r(ns); 58 Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 59 if (mode & 2 && typeof value != 'string') for (var key in value) require.d(ns, key, function (key) { return value[key]; }.bind(null, key)); 60 return ns; 61 }; 62 // getDefaultExport function for compatibility with non-harmony modules 63 require.n = function (module) { 64 var getter = module && module.__esModule ? 65 function getDefault() { return module['default']; } : 66 function getModuleExports() { return module; }; 67 require.d(getter, 'a', getter); 68 return getter; 69 }; 70 // Object.prototype.hasOwnProperty.call 71 require.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 72 // __webpack_public_path__ 73 require.p = ""; 74 // Load entry module and return exports 75 return require(require.s = 0); 76})([ 77 function (module, exports, require) { 78 "use strict"; 79 require.r(exports); 80 // Use ES module: index.js. 81 var esCounterModule = require(1); 82 esCounterModule["default"].increase(); 83 esCounterModule["default"].reset(); 84 }, 85 function (module, exports, require) { 86 "use strict"; 87 require.r(exports); 88 // Define ES module: esCounterModule.js. 89 var amdDependencyModule1 = require.n(require(2)); 90 var commonJSDependencyModule2 = require.n(require(3)); 91 amdDependencyModule1.a.api1(); 92 commonJSDependencyModule2.a.api2(); 93 94 let count = 0; 95 const increase = () => ++count; 96 const reset = () => { 97 count = 0; 98 console.log("Count is reset."); 99 };100101 exports["default"] = {102 increase,103 reset104 };105 },106 function (module, exports, require) {107 var result;108 !(result = (() => {109 // Define AMD module: amdDependencyModule1.js110 const api1 = () => { };111 return {112 api1113 };114 }).call(exports, require, exports, module),115 result !== undefined && (module.exports = result));116 },117 function (module, exports, require) {118 // Define CommonJS module: commonJSDependencyModule2.js119 const dependencyModule1 = require(2);120 const api2 = () => dependencyModule1.api1();121 exports.api2 = api2;122 }123]);
同样,它只是一个 IIFE。所有 4 个文件的代码都转换为 4 个函数中的代码。并且这 4 个函数作为参数传递给匿名函数。
Babel 模块:从 ES 模块转换
Babel 是另一个为旧版环境(如旧版浏览器)把 ES6 + JavaScript 代码转换为旧版语法的编译器。可以将 ES6 import/export 语法中的上述 counter 模块转换为以下替换了新语法的 babel 模块:
1// Babel. 2Object.defineProperty(exports, "__esModule", { 3 value: true 4}); 5exports["default"] = void 0; 6function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 7 8// Define ES module: esCounterModule.js. 9var dependencyModule1 = _interopRequireDefault(require("./amdDependencyModule1"));10var dependencyModule2 = _interopRequireDefault(require("./commonJSDependencyModule2"));11dependencyModule1["default"].api1();12dependencyModule2["default"].api2();1314var count = 0;15var increase = function () { return ++count; };16var reset = function () {17 count = 0;18 console.log("Count is reset.");19};2021exports["default"] = {22 increase: increase,23 reset: reset24};
这是 index.js 中使用 counter 模块的代码:
1// Babel.2function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }34// Use ES module: index.js5var esCounterModule = _interopRequireDefault(require("./esCounterModule.js"));6esCounterModule["default"].increase();7esCounterModule["default"].reset();
这是默认的转译。Babel 还可以与其他工具一起使用。
Babel 与 SystemJS
SystemJS 可以用作 Babel 的插件:
1npm install --save-dev @babel/plugin-transform-modules-systemjs
并将其添加到 Babel 配置中:
1{ 2 "plugins": ["@babel/plugin-transform-modules-systemjs"], 3 "presets": [ 4 [ 5 "@babel/env", 6 { 7 "targets": { 8 "ie": "11" 9 }10 }11 ]12 ]13}
现在 Babel 可以与 SystemJS 一起使用以转换 CommonJS/Node.js 模块、AMD/RequireJS 模块和 ES 模块:
1npx babel src --out-dir lib
结果是:
root
- lib
- amdDependencyModule1.js (与 SystemJS 一起编译)
- commonJSDependencyModule2.js (与 SystemJS 一起编译)
- esCounterModule.js (与 SystemJS 一起编译)
- index.js (与 SystemJS 一起编译)
- src
- amdDependencyModule1.js
- commonJSDependencyModule2.js
- esCounterModule.js
- index.js
- babel.config.json
现在所有 ADM、CommonJS 和 ES 模块语法都被转换为 SystemJS 语法:
1// Transpile AMD/RequireJS module definition to SystemJS syntax: lib/amdDependencyModule1.js. 2System.register([], function (_export, _context) { 3 "use strict"; 4 return { 5 setters: [], 6 execute: function () { 7 // Define AMD module: src/amdDependencyModule1.js 8 define("amdDependencyModule1", () => { 9 const api1 = () => { };1011 return {12 api113 };14 });15 }16 };17});1819// Transpile CommonJS/Node.js module definition to SystemJS syntax: lib/commonJSDependencyModule2.js.20System.register([], function (_export, _context) {21 "use strict";22 var dependencyModule1, api2;23 return {24 setters: [],25 execute: function () {26 // Define CommonJS module: src/commonJSDependencyModule2.js27 dependencyModule1 = require("./amdDependencyModule1");2829 api2 = () => dependencyModule1.api1();3031 exports.api2 = api2;32 }33 };34});3536// Transpile ES module definition to SystemJS syntax: lib/esCounterModule.js.37System.register(["./amdDependencyModule1", "./commonJSDependencyModule2"], function (_export, _context) {38 "use strict";39 var dependencyModule1, dependencyModule2, count, increase, reset;40 return {41 setters: [function (_amdDependencyModule) {42 dependencyModule1 = _amdDependencyModule.default;43 }, function (_commonJSDependencyModule) {44 dependencyModule2 = _commonJSDependencyModule.default;45 }],46 execute: function () {47 // Define ES module: src/esCounterModule.js.48 dependencyModule1.api1();49 dependencyModule2.api2();50 count = 0;5152 increase = () => ++count;5354 reset = () => {55 count = 0;56 console.log("Count is reset.");57 };5859 _export("default", {60 increase,61 reset62 });63 }64 };65});6667// Transpile ES module usage to SystemJS syntax: lib/index.js.68System.register(["./esCounterModule"], function (_export, _context) {69 "use strict";70 var esCounterModule;71 return {72 setters: [function (_esCounterModuleJs) {73 esCounterModule = _esCounterModuleJs.default;74 }],75 execute: function () {76 // Use ES module: src/index.js77 esCounterModule.increase();78 esCounterModule.reset();79 }80 };81});
TypeScript模块:转换为CJS、AMD、ES、系统模块TypeScript 支持 ES 模块语法(https://www.typescriptlang.org/docs/handbook/modules.html),根据 tsconfig.json 中指定的 transpiler 选项,可以将其保留为 ES6 或转换为其他格式,包括 CommonJS/Node.js、AMD/RequireJS、UMD/UmdJS 或 System/SystemJS:
1{2 "compilerOptions": {3 "module": "ES2020", // None, CommonJS, AMD, System, UMD, ES6, ES2015, ES2020, ESNext.4 }5}
例如:
1// TypeScript and ES module. 2// With compilerOptions: { module: "ES6" }. Transpile to ES module with the same import/export syntax. 3import dependencyModule from "./dependencyModule"; 4dependencyModule.api(); 5let count = 0; 6export const increase = function () { return ++count }; 7 8 9// With compilerOptions: { module: "CommonJS" }. Transpile to CommonJS/Node.js module:10var __importDefault = (this && this.__importDefault) || function (mod) {11 return (mod && mod.__esModule) ? mod : { "default": mod };12};13exports.__esModule = true;1415var dependencyModule_1 = __importDefault(require("./dependencyModule"));16dependencyModule_1["default"].api();17var count = 0;18exports.increase = function () { return ++count; };1920// With compilerOptions: { module: "AMD" }. Transpile to AMD/RequireJS module:21var __importDefault = (this && this.__importDefault) || function (mod) {22 return (mod && mod.__esModule) ? mod : { "default": mod };23};24define(["require", "exports", "./dependencyModule"], function (require, exports, dependencyModule_1) {25 "use strict";26 exports.__esModule = true;2728 dependencyModule_1 = __importDefault(dependencyModule_1);29 dependencyModule_1["default"].api();30 var count = 0;31 exports.increase = function () { return ++count; };32});3334// With compilerOptions: { module: "UMD" }. Transpile to UMD/UmdJS module:35var __importDefault = (this && this.__importDefault) || function (mod) {36 return (mod && mod.__esModule) ? mod : { "default": mod };37};38(function (factory) {39 if (typeof module === "object" && typeof module.exports === "object") {40 var v = factory(require, exports);41 if (v !== undefined) module.exports = v;42 }43 else if (typeof define === "function" && define.amd) {44 define(["require", "exports", "./dependencyModule"], factory);45 }46})(function (require, exports) {47 "use strict";48 exports.__esModule = true;4950 var dependencyModule_1 = __importDefault(require("./dependencyModule"));51 dependencyModule_1["default"].api();52 var count = 0;53 exports.increase = function () { return ++count; };54});5556// With compilerOptions: { module: "System" }. Transpile to System/SystemJS module:57System.register(["./dependencyModule"], function (exports_1, context_1) {58 "use strict";59 var dependencyModule_1, count, increase;60 var __moduleName = context_1 && context_1.id;61 return {62 setters: [63 function (dependencyModule_1_1) {64 dependencyModule_1 = dependencyModule_1_1;65 }66 ],67 execute: function () {68 dependencyModule_1["default"].api();69 count = 0;70 exports_1("increase", increase = function () { return ++count; });71 }72 };73});
这在 TypeScript 中称为外部模块。
内部模块和命名空间TypeScript还具有一个 module 关键字和一个 namespace 关键字。它们被称为内部模块:
1module Counter { 2 let count = 0; 3 export const increase = () => ++count; 4 export const reset = () => { 5 count = 0; 6 console.log("Count is reset."); 7 }; 8} 910namespace Counter {11 let count = 0;12 export const increase = () => ++count;13 export const reset = () => {14 count = 0;15 console.log("Count is reset.");16 };17}
它们都被转换为 JavaScript 对象:
1var Counter;2(function (Counter) {3 var count = 0;4 Counter.increase = function () { return ++count; };5 Counter.reset = function () {6 count = 0;7 console.log("Count is reset.");8 };9})(Counter || (Counter = {}));
通过支持 . 分隔符,TypeScript 模块和命名空间可以有多个级别:
1module Counter.Sub {2 let count = 0;3 export const increase = () => ++count;4}56namespace Counter.Sub {7 let count = 0;8 export const increase = () => ++count;9}
它们被转换为对象的属性:
1var Counter;2(function (Counter) {3 var Sub;4 (function (Sub) {5 var count = 0;6 Sub.increase = function () { return ++count; };7 })(Sub = Counter.Sub || (Counter.Sub = {}));8})(Counter|| (Counter = {}));
TypeScript 模块和命名空间也可以在 export 语句中使用:
1module Counter { 2 let count = 0; 3 export module Sub { 4 export const increase = () => ++count; 5 } 6} 7 8module Counter { 9 let count = 0;10 export namespace Sub {11 export const increase = () => ++count;12 }13}
编译后 sub 模块和 sub 命名空间相同:
1var Counter;2(function (Counter) {3 var count = 0;4 var Sub;5 (function (Sub) {6 Sub.increase = function () { return ++count; };7 })(Sub = Counter.Sub || (Counter.Sub = {}));8})(Counter || (Counter = {}));
结论欢迎使用 JavaScript,它具有如此丰富的功能——仅用于模块化/命名空间的就有 10 多种系统和格式:
- IIFE module:JavaScript 模块模式
- 揭示模块:JavaScript 揭示模块模式
- CJS模块:CommonJS 模块或 Node.js 模块
- AMD 模块:异步模块定义或 RequireJS 模块
- UMD 模块:通用模块定义或 UmdJS 模块
- ES 模块:ECMAScript 2015 或 ES6 模块
- ES 动态模块:ECMAScript 2020 或 ES11 动态模块
- 系统模块:SystemJS 模块
- Webpack 模块:CJS、AMD、ES 模块的移植和捆绑
- Babel 模块:可移植 ES 模块
- TypeScript模块 和命名空间
幸运的是,现在 JavaScript 有模块的标准内置语言功能,并且 Node.js 和所有最新的现代浏览器都支持它。对于较旧的环境,你仍然可以用新的 ES 模块语法进行编码,然后用 Webpack/Babel/SystemJS/TypeScript 转换为较旧或兼容的语法。
原文链接
https://weblogs.asp.net/dixin/understanding-all-javascript-module-formats-and-tools