理解(所有)JavaScript 模块格式和工具
JavaScript模块所有主流技术的集锦教程。
目录
- IIFE模块:JavaScript模块模式
- 揭示模块:JavaScript揭示模块模式
- CJS模块:CommonJS模块,或Node.js模块
- AMD模块:异步模块定义,或RequireJS模块
- UMD模块:通用模块定义,或UmdJS模块
- ES模块:ECMAScript 2015,或ES6模块
- ES动态模块:ECMAScript 2020,或ES11动态模块
- System模块:SystemJS模块
- Webpack模块:从CJS、AMD、ES模块打包
- Babel模块:从ES模块转译
- TypeScript模块:转译为CJS、AMD、ES、System模块
- 结论
- 历史
IIFE模块:JavaScript模块模式
在浏览器中,定义一个JavaScript变量相当于定义一个全局变量,这会导致当前网页加载的所有JavaScript文件之间发生污染。
// 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的占位符。只引入了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)就遵循了这种模式。(jQuery的最新版本遵循UMD模块,稍后将在本文中介绍。)
揭示模块: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提供了module
变量和exports
变量。并提供了一个require
函数来加载和使用模块。以下代码以CommonJS语法定义了计数器模块:
// 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
};
以下示例使用了计数器模块:
// Use CommonJS module.
const { increase, reset } = require("./commonJSCounterModule");
increase();
reset();
// Or equivalently:
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(异步模块定义)是一种定义和使用模块的模式。它由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
};
});
它还提供了一个require
函数来使用模块:
// Use AMD module.
require(["amdCounterModule"], amdCounterModule => {
amdCounterModule.increase();
amdCounterModule.reset();
});
AMD的require
函数与CommonJS的require
函数完全不同。AMD的require
接受要使用的模块名称,并将模块传递给函数参数。
动态加载
AMD的define
函数有另一个重载。它接受一个回调函数,并将一个类似CommonJS的require
函数传递给该回调。在回调函数内部,可以调用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模块
上面的define
函数重载也可以将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模块:通用模块定义,或UmdJS模块
UMD(通用模块定义)是一组巧妙的模式,可使您的代码文件在多个环境中工作。
UMD同时支持AMD(RequireJS)和原生浏览器
例如,以下是一种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
对象的属性)。
UMD同时支持AMD(RequireJS)和CommonJS(Node.js)
以下是另一种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);
再次,不要害怕。它只是另一个IIFE。当调用匿名函数时,其参数将被评估。参数评估会检测环境(检查CommonJS/Node.js的module
变量和exports
变量,以及AMD/RequireJS的define
函数)。
- 如果环境是CommonJS/Node.js,匿名函数的参数是一个手动创建的
define
函数。 - 如果环境是AMD/RequireJS,匿名函数的参数就是AMD的
define
函数。因此,当匿名函数执行时,保证有一个可用的define
函数。在匿名函数内部,它只是调用define
函数来创建模块。
ES模块:ECMAScript 2015,或ES6模块
在经历了所有模块的混乱之后,2015年,JavaScript的规范版本6引入了一种不同的模块语法。该规范称为ECMAScript 2015(ES2015),或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();
})();
以下是import
/动态import
/export
的兼容性,来自此链接:
System模块:SystemJS模块
SystemJS是一个库,可以为旧版ES启用ES模块语法。例如,以下模块是用ES 6语法定义的:
// 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库APISystem.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
});
}
};
});
这样,import
/export
的新ES6语法就消失了。旧的API调用语法肯定有效。这种转译可以自动完成,使用Webpack、TypeScript等工具,这些工具将在本文稍后介绍。
动态模块加载
SystemJS还为动态导入提供了导入函数。
// Use SystemJS module with promise APIs.
System.import("./esCounterModule.js").then(dynamicESCounterModule => {
dynamicESCounterModule.increase();
dynamicESCounterModule.reset();
});
Webpack模块:从CJS、AMD、ES模块打包
Webpack是一个模块打包器。它将组合的CommonJS模块、AMD模块和ES模块转译为单一的Harmony模块模式,并将所有代码打包到一个文件中。例如,以下三个文件用三种不同的语法定义了三个模块:
// 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可以将所有上述文件(即使它们使用3种不同的模块系统)打包到一个名为main.js的文件中。
- 根
- dist
- main.js(src下所有文件的打包文件)
- src
- amdDependencyModule1.js
- commonJSDependencyModule2.js
- esCounterModule.js
- index.js
- webpack.config.js
- dist
由于Webpack基于Node.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'),
},
};
现在运行以下命令来转译和打包所有四个文件(这些文件采用不同的语法):
npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js
结果是,Webpack生成了打包文件main.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。所有四个文件的代码都被转译成一个数组中的四个函数。然后将该数组作为参数传递给匿名函数。
Babel模块:从ES模块转译
Babel是另一个转译器,用于将ES6+的JavaScript代码转换为旧语法的代码,以支持旧环境(如旧浏览器)。上面使用ES6 import/export语法的计数器模块可以转换为以下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中使用了计数器模块的代码:
// 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配置babel.config.json中。
{
"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
结果是:
- 根
- 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
- lib
现在,所有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、System模块
TypeScript支持所有JavaScript语法,包括ES6模块语法。当TypeScript进行转译时,ES模块代码可以保留为ES6,或者根据tsconfig.json中指定的转译器选项,转译为其他格式,包括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支持的ES模块语法称为外部模块。
内部模块和命名空间
TypeScript还具有module
关键字和namespace
关键字https://typescript.net.cn/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.");
};
}
它们都转译为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;
}
}
转译与子模块和子命名空间相同。
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模块:JavaScript模块模式
- 揭示模块:JavaScript揭示模块模式
- CJS模块:CommonJS模块,或Node.js模块
- AMD模块:异步模块定义,或RequireJS模块
- UMD模块:通用模块定义,或UmdJS模块
- ES模块:ECMAScript 2015,或ES6模块
- ES动态模块:ECMAScript 2020,或ES11动态模块
- System模块:SystemJS模块
- Webpack模块:CJS、AMD、ES模块的转译和打包
- Babel模块:ES模块的转译
- TypeScript模块和命名空间
幸运的是,现在JavaScript拥有标准的内置语言功能来实现模块,并且Node.js和所有最新的现代浏览器都支持它。对于旧环境,您仍然可以使用新的ES模块语法进行编码,然后使用Webpack/Babel/SystemJS/TypeScript将其转译为旧的或兼容的语法。
历史
- 2020年4月16日:初始版本