vlambda博客
学习文章列表

与 JavaScript 模块相关的所有知识点


每日前端夜话第312篇

翻译:疯狂的技术宅

来源:weblogs.asp.net

正文共:4953 字

预计阅读时间:10分钟

与 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 文件之间的污染:

 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})();
12
13// 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    };
9
10    return {
11        increase,
12        reset
13    };
14})();
15
16// 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};
11
12exports.increase = increase;
13exports.reset = reset;
14// Or equivalently:
15module.exports = {
16    increase,
17    reset
18};

以下例子使用了 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    };
12
13    module.exports = {
14        increase,
15        reset
16    };
17
18    return module.exports;
19}).call(thisValue, exports, requiremodule, filename, dirname);
20
21// 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, requiremodule, 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    };
9
10    return {
11        increase,
12        reset
13    };
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    };
12
13    return {
14        increase,
15        reset
16    };
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    };
13
14    exports.increase = increase;
15    exports.reset = reset;
16});
17
18// 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    };
21
22    return {
23        increase,
24        reset
25    };
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    };
12
13    module.export = {
14        increase,
15        reset
16    };
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    reset
16};

要在浏览器中使用此模块文件,请添加 <script> 标签并指定它为模块:<script type="module" ></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();
13
14})();

以下是来自 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules 的导入、动态导入、导出的兼容性列表:

与 JavaScript 模块相关的所有知识点

import 兼容性

与 JavaScript 模块相关的所有知识点

export 兼容性

系统模块:SystemJS 模块

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    reset
18}

如果当前运行时(例如旧的浏览器)不支持 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        executefunction ({
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                reset
29            });
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.js
10const dependencyModule1 = require("./amdDependencyModule1");
11const api2 = () => dependencyModule1.api1();
12exports.api2 = api2;
13
14// Define ES module: esCounterModule.js.
15import dependencyModule1 from "./amdDependencyModule1";
16import dependencyModule2 from "./commonJSDependencyModule2";
17dependencyModule1.api1();
18dependencyModule2.api2();
19
20let count = 0;
21const increase = () => ++count;
22const reset = () => {
23    count = 0;
24    console.log("Count is reset.");
25};
26
27export default {
28    increase,
29    reset
30}

以下文件使用了 counter 模块:

1// Use ES module: index.js
2import 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-dev
2npx 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            lfalse,
15            exports: {}
16
17        };
18        // Execute the module function
19        modules[moduleId].call(module.exports, modulemodule.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, { enumerabletrueget: 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', { valuetrue });
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 & 8return 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', { enumerabletruevalue: value });
59        if (mode & 2 && typeof value != 'string'for (var key in value) require.d(ns, key, function (keyreturn 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, propertyreturn 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        };
100
101        exports["default"] = {
102            increase,
103            reset
104        };
105    },
106    function (module, exports, require{
107        var result;
108        !(result = (() => {
109            // Define AMD module: amdDependencyModule1.js
110            const api1 = () => { };
111            return {
112                api1
113            };
114        }).call(exports, require, exports, module),
115            result !== undefined && (module.exports = result));
116    },
117    function (module, exports, require{
118        // Define CommonJS module: commonJSDependencyModule2.js
119        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    valuetrue
4});
5exports["default"] = void 0;
6function _interopRequireDefault(objreturn 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();
13
14var count = 0;
15var increase = function (return ++count; };
16var reset = function ({
17    count = 0;
18    console.log("Count is reset.");
19};
20
21exports["default"] = {
22    increase: increase,
23    reset: reset
24};

这是 index.js 中使用 counter 模块的代码:

1// Babel.
2function _interopRequireDefault(objreturn obj && obj.__esModule ? obj : { "default": obj }; }
3
4// Use ES module: index.js
5var 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        executefunction ({
7            // Define AMD module: src/amdDependencyModule1.js
8            define("amdDependencyModule1", () => {
9                const api1 = () => { };
10
11                return {
12                    api1
13                };
14            });
15        }
16    };
17});
18
19// 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        executefunction ({
26            // Define CommonJS module: src/commonJSDependencyModule2.js
27            dependencyModule1 = require("./amdDependencyModule1");
28
29            api2 = () => dependencyModule1.api1();
30
31            exports.api2 = api2;
32        }
33    };
34});
35
36// 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        executefunction ({
47            // Define ES module: src/esCounterModule.js.
48            dependencyModule1.api1();
49            dependencyModule2.api2();
50            count = 0;
51
52            increase = () => ++count;
53
54            reset = () => {
55                count = 0;
56                console.log("Count is reset.");
57            };
58
59            _export("default", {
60                increase,
61                reset
62            });
63        }
64    };
65});
66
67// 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        executefunction ({
76            // Use ES module: src/index.js
77            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;
14
15var dependencyModule_1 = __importDefault(require("./dependencyModule"));
16dependencyModule_1["default"].api();
17var count = 0;
18exports.increase = function (return ++count; };
19
20// 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;
27
28    dependencyModule_1 = __importDefault(dependencyModule_1);
29    dependencyModule_1["default"].api();
30    var count = 0;
31    exports.increase = function (return ++count; };
32});
33
34// 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 !== undefinedmodule.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;
49
50    var dependencyModule_1 = __importDefault(require("./dependencyModule"));
51    dependencyModule_1["default"].api();
52    var count = 0;
53    exports.increase = function (return ++count; };
54});
55
56// 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        executefunction ({
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}
9
10namespace 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}
5
6namespace 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 多种系统和格式:

  1. IIFE module:JavaScript 模块模式

  2. 揭示模块:JavaScript 揭示模块模式

  3. CJS模块:CommonJS 模块或 Node.js 模块

  4. AMD 模块:异步模块定义或 RequireJS 模块

  5. UMD 模块:通用模块定义或 UmdJS 模块

  6. ES 模块:ECMAScript 2015 或 ES6 模块

  7. ES 动态模块:ECMAScript 2020 或 ES11 动态模块

  8. 系统模块:SystemJS 模块

  9. Webpack 模块:CJS、AMD、ES 模块的移植和捆绑

  10. Babel 模块:可移植 ES 模块

  11. TypeScript模块 和命名空间

幸运的是,现在 JavaScript 有模块的标准内置语言功能,并且 Node.js 和所有最新的现代浏览器都支持它。对于较旧的环境,你仍然可以用新的 ES 模块语法进行编码,然后用 Webpack/Babel/SystemJS/TypeScript 转换为较旧或兼容的语法。


原文链接



https://weblogs.asp.net/dixin/understanding-all-javascript-module-formats-and-tools


 与 JavaScript 模块相关的所有知识点

2020年京程一灯全新课程体系推出,点击文末 阅读全文 查看。


与 JavaScript 模块相关的所有知识点

愿你在新的一年里保持技术领先,有个好前程,愿你年薪40万。我们是认真的 !

往期精彩回顾