與 JavaScript 模組相關的所有知識點
JavaScript語言最初是為簡單的表單操作而發明的,沒有諸如模組或名稱空間之類的內建功能。多年以來發明瞭大量的術語、模式、庫、語法和工具來模組化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 工具等所有這些模式。
IIFE 模組:JavaScript 模組模式
在瀏覽器中,定義 JavaScript 變數就是定義全域性變數,這會導致當前網頁所載入的全部 JavaScript 檔案之間的汙染:
// Define global variables.
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
// Use global var iables.
increase();
reset();
為了避免全域性汙染,可以用匿名函式來包裝程式碼:
(() => {
let count = 0;
// ...
});
顯然,這樣就不再有任何全域性變數。但是定義函式不會在函式內部執行程式碼。
IIFE:立即呼叫的函式表示式
為了執行函式f中的程式碼,語法是將函式呼叫()作為f()。為了在匿名函式(() => {})中執行程式碼,可以將相同的函式呼叫語法()用作(() => {}):
(() => {
let count = 0;
// ...
})();
這稱為 IIFE(立即呼叫的函式表示式)。因此,可以通過以下方式定義基本模組:
// Define IIFE module.
const iifeCounterModule = (() => {
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0;
console.log("Count is reset.");
}
};
})();
// Use IIFE module.
iifeCounterModule.increase();
iifeCounterModule.reset();
它將模組程式碼包裝在 IIFE 中,返回一個物件,這個物件是匯出的 API 的佔位符。僅引入 1 個全域性變數,這是模式名稱。之後模組名可用於呼叫匯出的模組 API。這稱為 JavaScript 的模組模式。
混合匯入
定義模組時,可能需要一些依賴關係。使用 IIFE 模組模式,其他所有模組都是全域性變數。它們可以在匿名函式內部直接訪問,也可以通過匿名函式的引數進行傳遞:
// Define IIFE module with dependencies.
const iifeCounterModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0;
console.log("Count is reset.");
}
};
})(dependencyModule1, dependencyModule2);
一些流行庫(如 jQuery)的早期版本遵循這種模式。
revealing module:JavaScript 揭示模組模式
揭示模組模式由 Christian Heilmann 命名。此模式也是 IIFE,但它強調將所有 API 定義為匿名函式內的區域性變數:
// Define revealing module.
const revealingCounterModule = (() => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
return {
increase,
reset
};
})();
// Use revealing module.
revealingCounterModule.increase();
revealingCounterModule.reset();
用這種語法,當 API 需要相互呼叫時,將會變得更加容易。
CJS 模組:CommonJS 模組或 Node.js 模組
CommonJS(最初名為 ServerJS)是定義和使用模組的模式。它由 Node.js 實現。預設情況下,每個 .js 檔案都是 CommonJS 模組。為模組提供了暴露 API 的模組變數和匯出變數。並且提供了一個 require 函式來使用模組。以下程式碼以 CommonJS 語法定義了counter模組:
// Define CommonJS module: commonJSCounterModule.js.
const dependencyModule1 = require("./dependencyModule1");
const dependencyModule2 = require("./dependencyModule2");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
exports.increase = increase;
exports.reset = reset;
// Or equivalently:
module.exports = {
increase,
reset
};
以下例子使用了counter模組:
// Use CommonJS module.
const { increase, reset } = require("./commonJSCounterModule");
increase();
reset();
// Or equivelently:
const commonJSCounterModule = require("./commonJSCounterModule");
commonJSCounterModule.increase();
commonJSCounterModule.reset();
在執行時,Node.js 通過將檔案內的程式碼包裝到一個函式中,然後通過引數傳遞exports變數、module變數和require函式來實現這一目的。
// Define CommonJS module: wrapped commonJSCounterModule.js.
(function (exports, require, module, __filename, __dirname) {
const dependencyModule1 = require("./dependencyModule1");
const dependencyModule2 = require("./dependencyModule2");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
module.exports = {
increase,
reset
};
return module.exports;
}).call(thisValue, exports, require, module, filename, dirname);
// Use CommonJS module.
(function (exports, require, module, __filename, __dirname) {
const commonJSCounterModule = require("./commonJSCounterModule");
commonJSCounterModule.increase();
commonJSCounterModule.reset();
}).call(thisValue, exports, require, module, filename, dirname);
AMD 模組:非同步模組定義或 RequireJS 模組
AMD(Asynchronous Module Definitionhttps://github.com/amdjs/amdj...)是一種定義和使用模組的模式。它由 RequireJS 庫(https://requirejs.org/)實現。 AMD 提供了一個定義模組的定義函式,該函式接受模組名稱、依賴模組的名稱以及工廠函式:
// Define AMD module.
define("amdCounterModule", ["dependencyModule1", "dependencyModule2"], (dependencyModule1, dependencyModule2) => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
return {
increase,
reset
};
});
它還提供了 require 函式來使用模組:
// Use AMD module.
require(["amdCounterModule"], amdCounterModule => {
amdCounterModule.increase();
amdCounterModule.reset();
});
AMD 的require函式與 CommonJS 的require函式完全不同。 AMD 的require接受要使用的模組的名稱,並將模組傳遞給函式引數。
動態載入
AMD 的require函式還有另一個過載。它接受一個回撥函式,並將類似 CommonJS 的require函式傳遞給該回調。所以可以通過呼叫require來載入 AMD 模組:
// Use dynamic AMD module.
define(require => {
const dynamicDependencyModule1 = require("dependencyModule1");
const dynamicDependencyModule2 = require("dependencyModule2");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
return {
increase,
reset
};
});
來自 CommonJS 模組的 AMD 模組
上面的define函式有一個過載,它可以傳遞require函式,並將變數和模組變數匯出到回撥中,以便 CommonJS 程式碼可以在其內部工作:
// Define AMD module with CommonJS code.
define((require, exports, module) => {
// CommonJS code.
const dependencyModule1 = require("dependencyModule1");
const dependencyModule2 = require("dependencyModule2");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
exports.increase = increase;
exports.reset = reset;
});
// Use AMD module with CommonJS code.
define(require => {
// CommonJS code.
const counterModule = require("amdCounterModule");
counterModule.increase();
counterModule.reset();
});
UMD 模組:通用模組定義或 UmdJS 模組
UMD(Universal Module Definition,https://github.com/umdjs/umd)是一組棘手的模式,可以使你的程式碼檔案在多種環境中工作。
適用於 AMD(RequireJS)和本機瀏覽器的 UMD
例如以下是一種 UMD 模式,能夠使模組定義可用於 AMD(RequireJS)和本機瀏覽器:
// Define UMD module for both AMD and browser.
((root, factory) => {
// Detects AMD/RequireJS"s define function.
if (typeof define === "function" && define.amd) {
// Is AMD/RequireJS. Call factory with AMD/RequireJS"s define function.
define("umdCounterModule", ["deependencyModule1", "dependencyModule2"], factory);
} else {
// Is Browser. Directly call factory.
// Imported dependencies are global variables(properties of window object).
// Exported module is also a global variable(property of window object)
root.umdCounterModule = factory(root.deependencyModule1, root.dependencyModule2);
}
})(typeof self !== "undefined" ? self : this, (deependencyModule1, dependencyModule2) => {
// Module code goes here.
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
return {
increase,
reset
};
});
它比較複雜,但仍然只是 IIFE。匿名函式會檢測是否存在 AMD 的define函式,如果存在,請使用 AMD 的define函式呼叫模組工廠。如果不是,它將直接呼叫模組工廠。目前,root引數實際上是瀏覽器的window物件。它從全域性變數(window物件的屬性)獲取依賴項模組。當factory返回模組時,返回的模組也被分配給一個全域性變數(window物件的屬性)。
適用於 AMD(RequireJS)和 CommonJS(Node.js)的 UMD
以下是使模組定義與 AMD(RequireJS)和 CommonJS(Node.js)一起工作的另一種 UMD 模式:
(define => define((require, exports, module) => {
// Module code goes here.
const dependencyModule1 = require("dependencyModule1");
const dependencyModule2 = require("dependencyModule2");
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
module.export = {
increase,
reset
};
}))(// Detects module variable and exports variable of CommonJS/Node.js.
// Also detect the define function of AMD/RequireJS.
typeof module === "object" && module.exports && typeof define !== "function"
? // Is CommonJS/Node.js. Manually create a define function.
factory => module.exports = factory(require, exports, module)
: // Is AMD/RequireJS. Directly use its define function.
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:
// Define ES module: esCounterModule.js or esCounterModule.mjs.
import dependencyModule1 from "./dependencyModule1.mjs";
import dependencyModule2 from "./dependencyModule2.mjs";
let count = 0;
// Named export:
export const increase = () => ++count;
export const reset = () => {
count = 0;
console.log("Count is reset.");
};
// Or default export:
export default {
increase,
reset
};
要在瀏覽器中使用此模組檔案,請新增<script>標籤並指定它為模組:<script type="module" src="esCounterModule.js"></script>。要在 Node.js 中使用此模組檔案,請將其副檔名.js改為.mjs。
// Use ES module.
// Browser: <script type="module" src="esCounterModule.js"></script> or inline.
// Server: esCounterModule.mjs
// Import from named export.
import { increase, reset } from "./esCounterModule.mjs";
increase();
reset();
// Or import from default export:
import esCounterModule from "./esCounterModule.mjs";
esCounterModule.increase();
esCounterModule.reset();
對於瀏覽器,可以將<script>的nomodule屬性用於後備:
<script nomodule>
alert("Not supported.");
</script>
ES 動態模組:ECMAScript 2020 或 ES11 動態模組
在 2020 年,最新的 JavaScript 規範第 11 版引入了內建函式import以動態使用 ES 模組。import函式返回一個promise,因此可以通過其then方法呼叫該模組:
// Use dynamic ES module with promise APIs, import from named export:
import("./esCounterModule.js").then(({ increase, reset }) => {
increase();
reset();
});
// Or import from default export:
import("./esCounterModule.js").then(dynamicESCounterModule => {
dynamicESCounterModule.increase();
dynamicESCounterModule.reset();
});
通過返回一個promise,顯然 import 函式也可以與await關鍵字一起使用:
// Use dynamic ES module with async/await.
(async () => {
// Import from named export:
const { increase, reset } = await import("./esCounterModule.js");
increase();
reset();
// Or import from default export:
const dynamicESCounterModule = await import("./esCounterModule.js");
dynamicESCounterModule.increase();
dynamicESCounterModule.reset();
})();
以下是來自https://developer.mozilla.org...的匯入、動態匯入、匯出的相容性列表:
系統模組:SystemJS 模組
SystemJS 是一個庫,可以為較舊的 ES5 啟用 ES6 模組語法。例如以下模組以 ES6 語法定義:
// Define ES module.
import dependencyModule1 from "./dependencyModule1.js";
import dependencyModule2 from "./dependencyModule2.js";
dependencyModule1.api1();
dependencyModule2.api2();
let count = 0;
// Named export:
export const increase = function () { return ++count };
export const reset = function () {
count = 0;
console.log("Count is reset.");
};
// Or default export:
export default {
increase,
reset
}
如果當前執行時(例如舊的瀏覽器)不支援 ES6 語法,則以上程式碼將無法正常工作。 SystemJS 可以將模組定義轉換為對庫 API 的呼叫——System.register:
// Define SystemJS module.
System.register(["./dependencyModule1.js", "./dependencyModule2.js"], function (exports_1, context_1) {
"use strict";
var dependencyModule1_js_1, dependencyModule2_js_1, count, increase, reset;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (dependencyModule1_js_1_1) {
dependencyModule1_js_1 = dependencyModule1_js_1_1;
},
function (dependencyModule2_js_1_1) {
dependencyModule2_js_1 = dependencyModule2_js_1_1;
}
],
execute: function () {
dependencyModule1_js_1.default.api1();
dependencyModule2_js_1.default.api2();
count = 0;
// Named export:
exports_1("increase", increase = function () { return ++count };
exports_1("reset", reset = function () {
count = 0;
console.log("Count is reset.");
};);
// Or default export:
exports_1("default", {
increase,
reset
});
}
};
});
這樣新的 ES6 語法 import/export 就消失了。
動態模組載入
SystemJS 還提供了用於動態匯入的import函式:
// Use SystemJS module with promise APIs.
System.import("./esCounterModule.js").then(dynamicESCounterModule => {
dynamicESCounterModule.increase();
dynamicESCounterModule.reset();
});
Webpack 模組:來自 CJS,AMD,ES 模組的捆綁包
Webpack 是模組的捆綁器。它使用將組合的 CommonJS 模組、AMD 模組和 ES 模組轉換為和諧模組模式,並將所有程式碼捆綁到一個檔案中。例如以下 3 個檔案中用 3 種不同的語法定義了 3 個模組:
// Define AMD module: amdDependencyModule1.js
define("amdDependencyModule1", () => {
const api1 = () => { };
return {
api1
};
});
// Define CommonJS module: commonJSDependencyModule2.js
const dependencyModule1 = require("./amdDependencyModule1");
const api2 = () => dependencyModule1.api1();
exports.api2 = api2;
// Define ES module: esCounterModule.js.
import dependencyModule1 from "./amdDependencyModule1";
import dependencyModule2 from "./commonJSDependencyModule2";
dependencyModule1.api1();
dependencyModule2.api2();
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
export default {
increase,
reset
}
以下檔案使用了 counter 模組:
// Use ES module: index.js
import counterModule from "./esCounterModule";
counterModule.increase();
counterModule.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中:
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: "none", // Do not optimize or minimize the code for readability.
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
現在執行以下命令以不同的語法轉換和捆綁 4 個檔案:
npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js
重新格式化了以下捆綁檔案main.js,並重命名了變數以提高可讀性:
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function require(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, require);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
require.m = modules;
// expose the module cache
require.c = installedModules;
// define getter function for harmony exports
require.d = function (exports, name, getter) {
if (!require.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
require.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
require.t = function (value, mode) {
if (mode & 1) value = require(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
require.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string') for (var key in value) require.d(ns, key, function (key) { return value[key]; }.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
require.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
require.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
require.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
require.p = "";
// Load entry module and return exports
return require(require.s = 0);
})([
function (module, exports, require) {
"use strict";
require.r(exports);
// Use ES module: index.js.
var esCounterModule = require(1);
esCounterModule["default"].increase();
esCounterModule["default"].reset();
},
function (module, exports, require) {
"use strict";
require.r(exports);
// Define ES module: esCounterModule.js.
var amdDependencyModule1 = require.n(require(2));
var commonJSDependencyModule2 = require.n(require(3));
amdDependencyModule1.a.api1();
commonJSDependencyModule2.a.api2();
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
exports["default"] = {
increase,
reset
};
},
function (module, exports, require) {
var result;
!(result = (() => {
// Define AMD module: amdDependencyModule1.js
const api1 = () => { };
return {
api1
};
}).call(exports, require, exports, module),
result !== undefined && (module.exports = result));
},
function (module, exports, require) {
// Define CommonJS module: commonJSDependencyModule2.js
const dependencyModule1 = require(2);
const api2 = () => dependencyModule1.api1();
exports.api2 = api2;
}
]);
同樣,它只是一個 IIFE。所有 4 個檔案的程式碼都轉換為 4 個函式中的程式碼。並且這 4 個函式作為引數傳遞給匿名函式。
Babel 模組:從 ES 模組轉換
Babel 是另一個為舊版環境(如舊版瀏覽器)把 ES6 + JavaScript 程式碼轉換為舊版語法的編譯器。可以將 ES6 import/export 語法中的上述 counter 模組轉換為以下替換了新語法的 babel 模組:
// Babel.
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
// Define ES module: esCounterModule.js.
var dependencyModule1 = _interopRequireDefault(require("./amdDependencyModule1"));
var dependencyModule2 = _interopRequireDefault(require("./commonJSDependencyModule2"));
dependencyModule1["default"].api1();
dependencyModule2["default"].api2();
var count = 0;
var increase = function () { return ++count; };
var reset = function () {
count = 0;
console.log("Count is reset.");
};
exports["default"] = {
increase: increase,
reset: reset
};
這是index.js中使用counter模組的程式碼:
// Babel.
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
// Use ES module: index.js
var esCounterModule = _interopRequireDefault(require("./esCounterModule.js"));
esCounterModule["default"].increase();
esCounterModule["default"].reset();
這是預設的轉譯。 Babel 還可以與其他工具一起使用。
Babel 與 SystemJS
SystemJS 可以用作 Babel 的外掛:
npm install --save-dev @babel/plugin-transform-modules-systemjs
並將其新增到 Babel 配置中:
{
"plugins": ["@babel/plugin-transform-modules-systemjs"],
"presets": [
[
"@babel/env",
{
"targets": {
"ie": "11"
}
}
]
]
}
現在 Babel 可以與 SystemJS 一起使用以轉換 CommonJS/Node.js 模組、AMD/RequireJS 模組和 ES 模組:
npx 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 語法:
// Transpile AMD/RequireJS module definition to SystemJS syntax: lib/amdDependencyModule1.js.
System.register([], function (_export, _context) {
"use strict";
return {
setters: [],
execute: function () {
// Define AMD module: src/amdDependencyModule1.js
define("amdDependencyModule1", () => {
const api1 = () => { };
return {
api1
};
});
}
};
});
// Transpile CommonJS/Node.js module definition to SystemJS syntax: lib/commonJSDependencyModule2.js.
System.register([], function (_export, _context) {
"use strict";
var dependencyModule1, api2;
return {
setters: [],
execute: function () {
// Define CommonJS module: src/commonJSDependencyModule2.js
dependencyModule1 = require("./amdDependencyModule1");
api2 = () => dependencyModule1.api1();
exports.api2 = api2;
}
};
});
// Transpile ES module definition to SystemJS syntax: lib/esCounterModule.js.
System.register(["./amdDependencyModule1", "./commonJSDependencyModule2"], function (_export, _context) {
"use strict";
var dependencyModule1, dependencyModule2, count, increase, reset;
return {
setters: [function (_amdDependencyModule) {
dependencyModule1 = _amdDependencyModule.default;
}, function (_commonJSDependencyModule) {
dependencyModule2 = _commonJSDependencyModule.default;
}],
execute: function () {
// Define ES module: src/esCounterModule.js.
dependencyModule1.api1();
dependencyModule2.api2();
count = 0;
increase = () => ++count;
reset = () => {
count = 0;
console.log("Count is reset.");
};
_export("default", {
increase,
reset
});
}
};
});
// Transpile ES module usage to SystemJS syntax: lib/index.js.
System.register(["./esCounterModule"], function (_export, _context) {
"use strict";
var esCounterModule;
return {
setters: [function (_esCounterModuleJs) {
esCounterModule = _esCounterModuleJs.default;
}],
execute: function () {
// Use ES module: src/index.js
esCounterModule.increase();
esCounterModule.reset();
}
};
});
TypeScript模組:轉換為CJS、AMD、ES、系統模組
TypeScript 支援 ES 模組語法(https://www.typescriptlang.or...),根據 tsconfig.json 中指定的 transpiler 選項,可以將其保留為 ES6 或轉換為其他格式,包括 CommonJS/Node.js、AMD/RequireJS、UMD/UmdJS 或 System/SystemJS:
{
"compilerOptions": {
"module": "ES2020", // None, CommonJS, AMD, System, UMD, ES6, ES2015, ES2020, ESNext.
}
}
例如:
// TypeScript and ES module.
// With compilerOptions: { module: "ES6" }. Transpile to ES module with the same import/export syntax.
import dependencyModule from "./dependencyModule";
dependencyModule.api();
let count = 0;
export const increase = function () { return ++count };
// With compilerOptions: { module: "CommonJS" }. Transpile to CommonJS/Node.js module:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var dependencyModule_1 = __importDefault(require("./dependencyModule"));
dependencyModule_1["default"].api();
var count = 0;
exports.increase = function () { return ++count; };
// With compilerOptions: { module: "AMD" }. Transpile to AMD/RequireJS module:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
define(["require", "exports", "./dependencyModule"], function (require, exports, dependencyModule_1) {
"use strict";
exports.__esModule = true;
dependencyModule_1 = __importDefault(dependencyModule_1);
dependencyModule_1["default"].api();
var count = 0;
exports.increase = function () { return ++count; };
});
// With compilerOptions: { module: "UMD" }. Transpile to UMD/UmdJS module:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./dependencyModule"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
var dependencyModule_1 = __importDefault(require("./dependencyModule"));
dependencyModule_1["default"].api();
var count = 0;
exports.increase = function () { return ++count; };
});
// With compilerOptions: { module: "System" }. Transpile to System/SystemJS module:
System.register(["./dependencyModule"], function (exports_1, context_1) {
"use strict";
var dependencyModule_1, count, increase;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (dependencyModule_1_1) {
dependencyModule_1 = dependencyModule_1_1;
}
],
execute: function () {
dependencyModule_1["default"].api();
count = 0;
exports_1("increase", increase = function () { return ++count; });
}
};
});
這在 TypeScript 中稱為外部模組。
內部模組和名稱空間
TypeScript還具有一個module關鍵字和一個namespace關鍵字。它們被稱為內部模組:
module Counter {
let count = 0;
export const increase = () => ++count;
export const reset = () => {
count = 0;
console.log("Count is reset.");
};
}
namespace Counter {
let count = 0;
export const increase = () => ++count;
export const reset = () => {
count = 0;
console.log("Count is reset.");
};
}
它們都被轉換為 JavaScript 物件:
var Counter;
(function (Counter) {
var count = 0;
Counter.increase = function () { return ++count; };
Counter.reset = function () {
count = 0;
console.log("Count is reset.");
};
})(Counter || (Counter = {}));
通過支援.分隔符,TypeScript 模組和名稱空間可以有多個級別:
module Counter.Sub {
let count = 0;
export const increase = () => ++count;
}
namespace Counter.Sub {
let count = 0;
export const increase = () => ++count;
}
它們被轉換為物件的屬性:
var Counter;
(function (Counter) {
var Sub;
(function (Sub) {
var count = 0;
Sub.increase = function () { return ++count; };
})(Sub = Counter.Sub || (Counter.Sub = {}));
})(Counter|| (Counter = {}));
TypeScript 模組和名稱空間也可以在export語句中使用:
module Counter {
let count = 0;
export module Sub {
export const increase = () => ++count;
}
}
module Counter {
let count = 0;
export namespace Sub {
export const increase = () => ++count;
}
}
編譯後 sub 模組和 sub 名稱空間相同:
var Counter;
(function (Counter) {
var count = 0;
var Sub;
(function (Sub) {
Sub.increase = function () { return ++count; };
})(Sub = Counter.Sub || (Counter.Sub = {}));
})(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 轉換為較舊或相容的語法。