65.9K
CodeProject 正在变化。 阅读更多。
Home

理解(所有)JavaScript 模块格式和工具

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2020年4月16日

CPOL

9分钟阅读

viewsIcon

20897

JavaScript模块所有主流技术的集锦教程。

目录

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的兼容性,来自此链接

import compatibility

export compatibility

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.jssrc下所有文件的打包文件)
    • src
      • amdDependencyModule1.js
      • commonJSDependencyModule2.js
      • esCounterModule.js
      • index.js
    • webpack.config.js

由于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

现在,所有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多个系统/格式。

  1. IIFE模块: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. System模块:SystemJS模块
  9. Webpack模块:CJS、AMD、ES模块的转译和打包
  10. Babel模块:ES模块的转译
  11. TypeScript模块和命名空间

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

历史

  • 2020年4月16日:初始版本
© . All rights reserved.