JS模块化
在使用JS开发现代大型项目时,模块化代码已经是一个必选项,然而,JS语言设计之初仅仅是为了做一些简单的表单提交,并没有内置模块或者命名空间的概念。经过长时间的发展,针对JS的模块化已经涌现了大量的解决方案。本文将详细讨论下目前市面上主流的JS模块化所使用到的技术。
IIFE module: JS模块化模式
在浏览器中,在全局环境定义变量将导致当前网页中所有JS文件的作用域被污染:
// Define global variables.
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
console.log("Count is reset.");
};
// Use global variables.
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。这就是JS模块化模式。
混合导入(Import mixins)
有时我们的模块需要依赖于其他的模块。而在JS模块化模式中,每个依赖模块都是一个全局变量。我们可以直接在匿名函数中使用它们,或者将依赖作为参数传入匿名中:
// 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。(新版的jQuery遵循的UMD规范,我们会在后面的部分提到)。
Revealing module(揭示模式)
揭示模式是由Christian Heilmann命名的。它也是一种立即函数调用表达式,但它更加强调将所有的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();
CJS module: CommonJS / Node.js module
CommonJS最初被叫做ServerJS,由Node.js制定并实施。默认情况下,每一个js文件都是一个CommonJS模块。每个模块通过module和exports变量来对外暴露接口,通过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 module / RequireJS module
AMD由RequireJS库制定并实施。AMD使用define函数来定义一个模块,该函数接受模块名,依赖模块名,工厂方法这三个参数:
// 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
};
});
AMD使用require函数调用模块:
// Use AMD module.
require(["amdCounterModule"], amdCounterModule => {
amdCounterModule.increase();
amdCounterModule.reset();
});
需要注意的是,AMD的require函数与CommonJS的require方法不同。AMD的require除了接受要使用的模块名外,还要将该模块作为参数传递给使用它的函数。
动态加载
AMD中的define方法还可以接受一个回调函数作为参数。该函数可以使用传入的require方法来动态加载模块:
// 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模块
上述的回调函数除了接受require参数外,还可以接受exports和module两个参数。因此,我们可以在这个回调里使用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 module / UmdJS module
UMD是为了兼容不同代码运行环境而产生的规范,大多数情况下,它使用AMD规范为基础,并通过一些特殊的语法设定使其与CommonJS规范相兼容。
兼容AMD(RequireJS)和浏览器的UMD规范
// 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
};
});
这段代码看起来很复杂,但它实质上只是一个立即执行函数。这个函数首先检查了AMD中的define函数是否存在:
- 如果存在,则使用
define函数调用模块的工厂方法 - 如果不存在,则直接调用工厂方法。此时,
root实际指向是浏览器的window对象,所有的变量都挂载到全局变量(即window对象)下,包括factory返回的模块。
兼容AMD(RequireJS)和CommonJS(Node.js)的UMD规范
下面的代码是UMD的另一个种实现,它可以兼容AMD(RequireJS)和CommonJS(Node.js):
(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);
不要害怕,这也是一个立即执行函数。这个匿名函数的参数在执行时会通过判断exports和define是否存在,来确定当前执行环境: 当前环境为CommonJS/Node.js时,匿名函数的参数就是一个手动定义的define函数 当前环境为AMD/RequireJS时,匿名函数的参数就直接是AMD中的define函数。 如此,在保证了define方法的存在后,匿名函数内部就可以直接使用define函数来创建模块。
ES module: ECMAScript 2015, or ES6 module
2015年,JavaScript规范第6版(ES5 / ES6)引入了一种新的模块定义方式。规范中主要定义了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后缀重命名为.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动态模块:ESMAScript 2020, or ES11 dynamic module
2020年,新的Javascript规范第11版新增可以动态引入ES模块import函数。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,显然我们也可以使用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/en-US/docs/Web/JavaScript/Guide/Modules)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules:]
System module / SystemJS module
SystemJS是一个允许ES module模块运行在旧版本ES环境的库。例如,下面的模块是使用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的registerAPI来定义模块:
// 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的新语法,因此可以正常在旧环境中被引用。这个转义过程可以使用Webpack,TypeScript等自动完成。
动态引入
SystemJS也支持import函数的动态引入:
// Use SystemJS module with promise APIs.
System.import("./esCounterModule.js").then(dynamicESCounterModule => {
dynamicESCounterModule.increase();
dynamicESCounterModule.reset();
});
Webpack module
Webpack简单来说是一个模块打包器。它可以将CommonJS模块,AMD模块,ES模块转义到一个通用的模块标准内,并将所有的代码打包进一个文件中。例如,我们在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
}
然后我们导入并使用该模块:
// Use ES module: index.js
import counterModule from "./esCounterModule";
counterModule.increase();
counterModule.reset();
Webpack可以将上面所有文件的代码打包进一个main.js中,加入我们的目录如下: root dist main.js (Bundle of all files under src) src amdDependencyModule1.js commonJSDependencyModule2.js esCounterModule.js index.js * webpack.config.js
Webpack本身运行在Node.js环境,因此使用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'),
},
};
接着运行命令打包上述文件:
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;
}
]);
显然,这还是一个立即执行函数。之前4个文件中的代码被转义进了一个包含4个元素的函数数组,然后这个数组作为参数被传入了匿名函数中。
Babel module
Babel是另一个可以将ES6及之后的JS代码转义为旧标准代码的工具。上面使用ES6书写的代码逻辑可以被转义为下面的Babel module:
// 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中使用这个模块的代码会被转义成为:
// 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
Babel可以使用SystemJS的插件,运行下面的命令:
npm install --save-dev @babel/plugin-transform-modules-systemjs
然后在Babel配置文件,例如babel.config.json中增加配置项:
{
"plugins": ["@babel/plugin-transform-modules-systemjs"],
"presets": [
[
"@babel/env",
{
"targets": {
"ie": "11"
}
}
]
]
}
执行命令:
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
所有的AMD,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 module
TypeScript支持所有的JS语法,包括ES6中的模块(https://www.typescriptlang.org/docs/handbook/modules.html)[https://www.typescriptlang.org/docs/handbook/modules.html]。在TypeScript编译过程中,这些ES模块可以被转义为ES6语法,也可以根据tsconfig.json中的配置转义为其他模块标准:
{
"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中支持的ES module语法被叫做外部模块。
内部模块和命名空间
TypeScript还有一个module关键字和一个namespace关键字(https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html#pitfalls-of-namespaces-and-modules)[https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html#pitfalls-of-namespaces-and-modules]。它们被叫做内部模块:
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.");
};
}
这两种都可以被转义成JS对象:
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;
}
}
转义结果类似于刚刚的分层模块:
var Counter;
(function (Counter) {
var count = 0;
var Sub;
(function (Sub) {
Sub.increase = function () { return ++count; };
})(Sub = Counter.Sub || (Counter.Sub = {}));
})(Counter || (Counter = {}));
结论
JavaScript的模块化路程充满了戏剧性,截止到目前,我们供讨论了十多个不同的规范/工具:
- IIFE module
- 揭示模块
- CJS module:CommonJS module / Node.js module
- AMD module: Asynchronous Module Definition / RequireJS module
- UMD module: Universal Module Definition / UmdJS module
- ES module: ECMAScript 2015 / ES6 module
- ES dynamic module: ECMAScript 2020 / ES11 dynamic module
- System module: SystemJS module
- Webpack module
- Babel module
- TypeScript module and namespace
如今JavaScript已经建立的语言内置模块标准,且几乎所有的现代浏览器都已经支持。对于旧的浏览器,我们也可以使用这些新标准,并通过Webpack/Babel/SystemJS/TypeScript来转义。