與 JavaScript 模塊相關的全部知識點

做者:Dixin

翻譯:瘋狂的技術宅javascript

https://weblogs.asp.net/dixin...html

未經容許嚴禁轉載前端

JavaScript 語言最初是爲簡單的表單操做而發明的,沒有諸如模塊或命名空間之類的內置功能。多年以來發明瞭大量的術語、模式、庫、語法和工具來模塊化 JavaScript。本文討論了 JavaScript 中的全部主流模塊系統、格式、庫和工具,包括:java

  • JavaScript 模塊格式和工具大全webpack

    • IIFE 模塊:JavaScript 模塊模式git

      • IIFE:當即調用的函數表達式
      • 混合導入
    • Revealing 模塊:JavaScript 顯示模塊模式
    • CJS 模塊:CommonJS 模塊或 Node.js 模塊
    • AMD 模塊:異步模塊定義或 RequireJS 模塊程序員

      • 動態加載
      • 來自 CommonJS 模塊的 AMD 模塊
    • UMD 模塊:通用模塊定義或 UmdJS 模塊github

      • 適用於AMD(RequireJS)和本機瀏覽器的 UMD
      • 適用於AMD(RequireJS)和CommonJS(Node.js)的UMD
    • ES 模塊:ECMAScript 2015 或 ES6 模塊
    • ES 動態模塊:ECMAScript 2020 或 ES11 動態模塊
    • 系統模塊:SystemJS 模塊web

      • 動態模塊加載
    • Webpack 模塊:來自 CJS、AMD、ES 模塊的捆綁軟件
    • Babel 模塊:從 ES 模塊轉換面試

      • Babel with SystemJS
    • TypeScript 模塊:轉換爲 CJS、AMD、ES、系統模塊

      • 內部模塊和命名空間
    • 結論

但願本文能夠幫助你瞭解和使用 JavaScript/TypeScript 語言,RequireJS/SystemJS 庫和 Webpack/Babel 工具等全部這些模式。

IIFE 模塊:JavaScript 模塊模式

在瀏覽器中,定義 JavaScript 變量就是定義全局變量,這會致使當前網頁所加載的所有 JavaScript 文件之間的污染:

// Define global variables.
let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
    console.log("Count is reset.");
};

// Use global 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)的早期版本遵循這種模式。

revealing module:JavaScript 揭示模塊模式

揭示模塊模式由 Christian Heilmann 命名。此模式也是 IIFE,但它強調將全部 API 定義爲匿名函數內的局部變量:

// Define revealing module.
const revealingCounterModule = (() => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
})();

// Use revealing module.
revealingCounterModule.increase();
revealingCounterModule.reset();

用這種語法,當 API 須要相互調用時,將會變得更加容易。

CJS 模塊:CommonJS 模塊或 Node.js 模塊

CommonJS(最初名爲 ServerJS)是定義和使用模塊的模式。它由 Node.js 實現。默認狀況下,每一個 .js 文件都是 CommonJS 模塊。爲模塊提供了暴露 API 的模塊變量和導出變量。而且提供了一個 require 函數來使用模塊。如下代碼以 CommonJS 語法定義了 counter 模塊:

// Define CommonJS module: commonJSCounterModule.js.
const dependencyModule1 = require("./dependencyModule1");
const dependencyModule2 = require("./dependencyModule2");

let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
    console.log("Count is reset.");
};

exports.increase = increase;
exports.reset = reset;
// Or equivalently:
module.exports = {
    increase,
    reset
};

如下例子使用了 counter 模塊:

// Use CommonJS module.
const { increase, reset } = require("./commonJSCounterModule");
increase();
reset();
// Or equivelently:
const commonJSCounterModule = require("./commonJSCounterModule");
commonJSCounterModule.increase();
commonJSCounterModule.reset();

在運行時,Node.js 經過將文件內的代碼包裝到一個函數中,而後經過參數傳遞 exports 變量、module 變量和 require 函數來實現這一目的。

// Define CommonJS module: wrapped commonJSCounterModule.js.
(function (exports, require, module, __filename, __dirname) {
    const dependencyModule1 = require("./dependencyModule1");
    const dependencyModule2 = require("./dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    module.exports = {
        increase,
        reset
    };

    return module.exports;
}).call(thisValue, exports, require, module, filename, dirname);

// Use CommonJS module.
(function (exports, require, module, __filename, __dirname) {
    const commonJSCounterModule = require("./commonJSCounterModule");
    commonJSCounterModule.increase();
    commonJSCounterModule.reset();
}).call(thisValue, exports, require, module, filename, dirname);

AMD 模塊:異步模塊定義或 RequireJS 模塊

AMD(Asynchronous Module Definition https://github.com/amdjs/amdj...)是一種定義和使用模塊的模式。它由 RequireJS 庫(https://requirejs.org/)實現。 AMD 提供了一個定義模塊的定義函數,該函數接受模塊名稱、依賴模塊的名稱以及工廠函數:

// Define AMD module.
define("amdCounterModule", ["dependencyModule1", "dependencyModule2"], (dependencyModule1, dependencyModule2) => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

它還提供了 require 函數來使用模塊:

// Use AMD module.
require(["amdCounterModule"], amdCounterModule => {
    amdCounterModule.increase();
    amdCounterModule.reset();
});

AMD 的 require 函數與 CommonJS 的 require 函數徹底不一樣。 AMD 的 require 接受要使用的模塊的名稱,並將模塊傳遞給函數參數。

動態加載

AMD 的 require 函數還有另外一個重載。它接受一個回調函數,並將相似 CommonJS 的 require 函數傳遞給該回調。因此能夠經過調用 require 來加載 AMD 模塊:

// Use dynamic AMD module.
define(require => {
    const dynamicDependencyModule1 = require("dependencyModule1");
    const dynamicDependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

來自 CommonJS 模塊的 AMD 模塊

上面的 define 函數有一個重載,它能夠傳遞 require 函數,並將變量和模塊變量導出到回調中,以便 CommonJS 代碼能夠在其內部工做:

// Define AMD module with CommonJS code.
define((require, exports, module) => {
    // CommonJS code.
    const dependencyModule1 = require("dependencyModule1");
    const dependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    exports.increase = increase;
    exports.reset = reset;
});

// Use AMD module with CommonJS code.
define(require => {
    // CommonJS code.
    const counterModule = require("amdCounterModule");
    counterModule.increase();
    counterModule.reset();
});

UMD 模塊:通用模塊定義或 UmdJS 模塊

UMD(Universal Module Definition,https://github.com/umdjs/umd)是一組棘手的模式,可使你的代碼文件在多種環境中工做。

適用於 AMD(RequireJS)和本機瀏覽器的 UMD

例如如下是一種 UMD 模式,可以使模塊定義可用於 AMD(RequireJS)和本機瀏覽器:

// Define UMD module for both AMD and browser.
((root, factory) => {
    // Detects AMD/RequireJS"s define function.
    if (typeof define === "function" && define.amd) {
        // Is AMD/RequireJS. Call factory with AMD/RequireJS"s define function.
        define("umdCounterModule", ["deependencyModule1", "dependencyModule2"], factory);
    } else {
        // Is Browser. Directly call factory.
        // Imported dependencies are global variables(properties of window object).
        // Exported module is also a global variable(property of window object)
        root.umdCounterModule = factory(root.deependencyModule1, root.dependencyModule2);
    }
})(typeof self !== "undefined" ? self : this, (deependencyModule1, dependencyModule2) => {
    // Module code goes here.
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

它比較複雜,但仍然只是 IIFE。匿名函數會檢測是否存在 AMD 的 define 函數,若是存在,請使用 AMD 的define 函數調用模塊工廠。若是不是,它將直接調用模塊工廠。目前,root 參數其實是瀏覽器的 window 對象。它從全局變量( window 對象的屬性)獲取依賴項模塊。當 factory 返回模塊時,返回的模塊也被分配給一個全局變量( window 對象的屬性)。

適用於 AMD(RequireJS)和 CommonJS(Node.js)的 UMD

如下是使模塊定義與 AMD(RequireJS)和 CommonJS(Node.js)一塊兒工做的另外一種 UMD 模式:

(define => define((require, exports, module) => {
    // Module code goes here.
    const dependencyModule1 = require("dependencyModule1");
    const dependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    module.export = {
        increase,
        reset
    };
}))(// Detects module variable and exports variable of CommonJS/Node.js.
    // Also detect the define function of AMD/RequireJS.
    typeof module === "object" && module.exports && typeof define !== "function"
        ? // Is CommonJS/Node.js. Manually create a define function.
            factory => module.exports = factory(require, exports, module)
        : // Is AMD/RequireJS. Directly use its define function.
            define);

別怕,這只是一個IIFE。調用IIFE時,將評估其參數。參數評估檢測環境(CommonJS / Node.js的模塊變量和exports 變量,以及 AMD/RequireJS 的 define 函數)。若是環境是 CommonJS/Node.js,則匿名函數的參數是手動建立的 define 函數。若是環境是 AMD/RequireJS,則匿名函數的參數就是 AMD 的 define 函數。所以,當執行匿名函數時,能夠確保它具備有效的 define 函數。在匿名函數內部,它僅調用 define 函數來建立模塊。

ES 模塊:ECMAScript 2015 或 ES6 模塊

在全部模塊混亂以後,JavaScript 的規範第 6 版在 2015 年定義了徹底不一樣的模塊系統和語法。該規範稱爲ECMAScript 2015 或 ES2015,AKA ECMAScript 6 或 ES6。主要語法是 import 關鍵字和 export 關鍵字。如下例子使用新語法演示 ES 模塊的命名 import/export 和默認 import/export:

// Define ES module: esCounterModule.js or esCounterModule.mjs.
import dependencyModule1 from "./dependencyModule1.mjs";
import dependencyModule2 from "./dependencyModule2.mjs";

let count = 0;
// Named export:
export const increase = () => ++count;
export const reset = () => {
    count = 0;
    console.log("Count is reset.");
};
// Or default export:
export default {
    increase,
    reset
};

要在瀏覽器中使用此模塊文件,請添加 <script> 標籤並指定它爲模塊:<script type="module" src="esCounterModule.js"></script>。要在 Node.js 中使用此模塊文件,請將其擴展名 .js 改成 .mjs

// Use ES module.
// Browser: <script type="module" src="esCounterModule.js"></script> or inline.
// Server: esCounterModule.mjs
// Import from named export.
import { increase, reset } from "./esCounterModule.mjs";
increase();
reset();
// Or import from default export:
import esCounterModule from "./esCounterModule.mjs";
esCounterModule.increase();
esCounterModule.reset();

對於瀏覽器,能夠將 <script>nomodule 屬性用於後備:

<script nomodule>
    alert("Not supported.");
</script>

ES 動態模塊:ECMAScript 2020 或 ES11 動態模塊

在 2020 年,最新的 JavaScript 規範第 11 版引入了內置函數 import 以動態使用 ES 模塊。 import 函數返回一個 promise,所以能夠經過其 then 方法調用該模塊:

// Use dynamic ES module with promise APIs, import from named export:
import("./esCounterModule.js").then(({ increase, reset }) => {
    increase();
    reset();
});
// Or import from default export:
import("./esCounterModule.js").then(dynamicESCounterModule => {
    dynamicESCounterModule.increase();
    dynamicESCounterModule.reset();
});

經過返回一個 promise ,顯然 import 函數也能夠與 await 關鍵字一塊兒使用:

// Use dynamic ES module with async/await.
(async () => {

    // Import from named export:
    const { increase, reset } = await import("./esCounterModule.js");
    increase();
    reset();

    // Or import from default export:
    const dynamicESCounterModule = await import("./esCounterModule.js");
    dynamicESCounterModule.increase();
    dynamicESCounterModule.reset();

})();

如下是來自 https://developer.mozilla.org... 的導入、動態導入、導出的兼容性列表:

import 兼容性

export 兼容性

系統模塊:SystemJS 模塊

SystemJS 是一個庫,能夠爲較舊的 ES5 啓用 ES6 模塊語法。例如如下模塊以 ES6 語法定義:

// Define ES module.
import dependencyModule1 from "./dependencyModule1.js";
import dependencyModule2 from "./dependencyModule2.js";
dependencyModule1.api1();
dependencyModule2.api2();

let count = 0;
// Named export:
export const increase = function () { return ++count };
export const reset = function () {
    count = 0;
    console.log("Count is reset.");
};
// Or default export:
export default {
    increase,
    reset
}

若是當前運行時(例如舊的瀏覽器)不支持 ES6 語法,則以上代碼將沒法正常工做。 SystemJS 能夠將模塊定義轉換爲對庫 API 的調用——System.register

// Define SystemJS module.
System.register(["./dependencyModule1.js", "./dependencyModule2.js"], function (exports_1, context_1) {
    "use strict";
    var dependencyModule1_js_1, dependencyModule2_js_1, count, increase, reset;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [
            function (dependencyModule1_js_1_1) {
                dependencyModule1_js_1 = dependencyModule1_js_1_1;
            },
            function (dependencyModule2_js_1_1) {
                dependencyModule2_js_1 = dependencyModule2_js_1_1;
            }
        ],
        execute: function () {
            dependencyModule1_js_1.default.api1();
            dependencyModule2_js_1.default.api2();
            count = 0;
            // Named export:
            exports_1("increase", increase = function () { return ++count };
            exports_1("reset", reset = function () {
                count = 0;
                console.log("Count is reset.");
            };);
            // Or default export:
            exports_1("default", {
                increase,
                reset
            });
        }
    };
});

這樣新的 ES6 語法 import/export 就消失了。

動態模塊加載

SystemJS 還提供了用於動態導入的 import 函數:

// Use SystemJS module with promise APIs.
System.import("./esCounterModule.js").then(dynamicESCounterModule => {
    dynamicESCounterModule.increase();
    dynamicESCounterModule.reset();
});

Webpack 模塊:來自 CJS,AMD,ES 模塊的捆綁包

Webpack 是模塊的捆綁器。它使用將組合的 CommonJS 模塊、AMD 模塊和 ES 模塊轉換爲和諧模塊模式,並將全部代碼捆綁到一個文件中。例如如下 3 個文件中用 3 種不一樣的語法定義了 3 個模塊:

// Define AMD module: amdDependencyModule1.js
define("amdDependencyModule1", () => {
    const api1 = () => { };
    return {
        api1
    };
});

// Define CommonJS module: commonJSDependencyModule2.js
const dependencyModule1 = require("./amdDependencyModule1");
const api2 = () => dependencyModule1.api1();
exports.api2 = api2;

// Define ES module: esCounterModule.js.
import dependencyModule1 from "./amdDependencyModule1";
import dependencyModule2 from "./commonJSDependencyModule2";
dependencyModule1.api1();
dependencyModule2.api2();

let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
    console.log("Count is reset.");
};

export default {
    increase,
    reset
}

如下文件使用了 counter 模塊:

// Use ES module: index.js
import counterModule from "./esCounterModule";
counterModule.increase();
counterModule.reset();

Webpack 能夠將以上全部文件打包在一塊兒,即便它們位於 3 個不一樣的模塊系統中,也都能打包爲一個文件 main.js

  • root

    • dist

      • main.js (捆綁 src 下的全部文件)
    • src

      • amdDependencyModule1.js
      • commonJSDependencyModule2.js
      • esCounterModule.js
      • index.js
    • webpack.config.js

有趣的是,Webpack 自己使用 CommonJS 模塊語法。在webpack.config.js 中:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    mode: "none", // Do not optimize or minimize the code for readability.
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
};

如今運行如下命令以不一樣的語法轉換和捆綁 4 個文件:

npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js

從新格式化了如下捆綁文件 main.js,並重命名了變量以提升可讀性:

(function (modules) { // webpackBootstrap
    // The module cache
    var installedModules = {};
    // The require function
    function require(moduleId) {
        // Check if module is in cache
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;

        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}

        };
        // Execute the module function
        modules[moduleId].call(module.exports, module, module.exports, require);
        // Flag the module as loaded
        module.l = true;
        // Return the exports of the module
        return module.exports;
    }

    // expose the modules object (__webpack_modules__)
    require.m = modules;
    // expose the module cache
    require.c = installedModules;
    // define getter function for harmony exports
    require.d = function (exports, name, getter) {
        if (!require.o(exports, name)) {
            Object.defineProperty(exports, name, { enumerable: true, get: getter });

        }

    };
    // define __esModule on exports
    require.r = function (exports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });

        }
        Object.defineProperty(exports, '__esModule', { value: true });

    };
    // create a fake namespace object
    // mode & 1: value is a module id, require it
    // mode & 2: merge all properties of value into the ns
    // mode & 4: return value when already ns object
    // mode & 8|1: behave like require
    require.t = function (value, mode) {
        if (mode & 1) value = require(value);
        if (mode & 8) return value;
        if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
        var ns = Object.create(null);
        require.r(ns);
        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
        if (mode & 2 && typeof value != 'string') for (var key in value) require.d(ns, key, function (key) { return value[key]; }.bind(null, key));
        return ns;
    };
    // getDefaultExport function for compatibility with non-harmony modules
    require.n = function (module) {
        var getter = module && module.__esModule ?
            function getDefault() { return module['default']; } :
            function getModuleExports() { return module; };
        require.d(getter, 'a', getter);
        return getter;
    };
    // Object.prototype.hasOwnProperty.call
    require.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    // __webpack_public_path__
    require.p = "";
    // Load entry module and return exports
    return require(require.s = 0);
})([
    function (module, exports, require) {
        "use strict";
        require.r(exports);
        // Use ES module: index.js.
        var esCounterModule = require(1);
        esCounterModule["default"].increase();
        esCounterModule["default"].reset();
    },
    function (module, exports, require) {
        "use strict";
        require.r(exports);
        // Define ES module: esCounterModule.js.
        var amdDependencyModule1 = require.n(require(2));
        var commonJSDependencyModule2 = require.n(require(3));
        amdDependencyModule1.a.api1();
        commonJSDependencyModule2.a.api2();

        let count = 0;
        const increase = () => ++count;
        const reset = () => {
            count = 0;
            console.log("Count is reset.");
        };

        exports["default"] = {
            increase,
            reset
        };
    },
    function (module, exports, require) {
        var result;
        !(result = (() => {
            // Define AMD module: amdDependencyModule1.js
            const api1 = () => { };
            return {
                api1
            };
        }).call(exports, require, exports, module),
            result !== undefined && (module.exports = result));
    },
    function (module, exports, require) {
        // Define CommonJS module: commonJSDependencyModule2.js
        const dependencyModule1 = require(2);
        const api2 = () => dependencyModule1.api1();
        exports.api2 = api2;
    }
]);

一樣,它只是一個 IIFE。全部 4 個文件的代碼都轉換爲 4 個函數中的代碼。而且這 4 個函數做爲參數傳遞給匿名函數。

Babel 模塊:從 ES 模塊轉換

Babel 是另外一個爲舊版環境(如舊版瀏覽器)把 ES6 + JavaScript 代碼轉換爲舊版語法的編譯器。能夠將 ES6 import/export 語法中的上述 counter 模塊轉換爲如下替換了新語法的 babel 模塊:

// Babel.
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports["default"] = void 0;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

// Define ES module: esCounterModule.js.
var dependencyModule1 = _interopRequireDefault(require("./amdDependencyModule1"));
var dependencyModule2 = _interopRequireDefault(require("./commonJSDependencyModule2"));
dependencyModule1["default"].api1();
dependencyModule2["default"].api2();

var count = 0;
var increase = function () { return ++count; };
var reset = function () {
    count = 0;
    console.log("Count is reset.");
};

exports["default"] = {
    increase: increase,
    reset: reset
};

這是 index.js 中使用 counter 模塊的代碼:

// Babel.
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

// Use ES module: index.js
var esCounterModule = _interopRequireDefault(require("./esCounterModule.js"));
esCounterModule["default"].increase();
esCounterModule["default"].reset();

這是默認的轉譯。 Babel 還能夠與其餘工具一塊兒使用。

Babel 與 SystemJS

SystemJS 能夠用做 Babel 的插件:

npm install --save-dev @babel/plugin-transform-modules-systemjs

並將其添加到 Babel 配置中:

{
    "plugins": ["@babel/plugin-transform-modules-systemjs"],
    "presets": [
        [
            "@babel/env",
            {
                "targets": {
                    "ie": "11"
                }
            }
        ]
    ]
}

如今 Babel 能夠與 SystemJS 一塊兒使用以轉換 CommonJS/Node.js 模塊、AMD/RequireJS 模塊和 ES 模塊:

npx babel src --out-dir lib

結果是:

root

  • lib

    • amdDependencyModule1.js (與 SystemJS 一塊兒編譯)
    • commonJSDependencyModule2.js (與 SystemJS 一塊兒編譯)
    • esCounterModule.js (與 SystemJS 一塊兒編譯)
    • index.js (與 SystemJS 一塊兒編譯)
  • src

    • amdDependencyModule1.js
    • commonJSDependencyModule2.js
    • esCounterModule.js
    • index.js
  • babel.config.json

如今全部 ADM、CommonJS 和 ES 模塊語法都被轉換爲 SystemJS 語法:

// Transpile AMD/RequireJS module definition to SystemJS syntax: lib/amdDependencyModule1.js.
System.register([], function (_export, _context) {
    "use strict";
    return {
        setters: [],
        execute: function () {
            // Define AMD module: src/amdDependencyModule1.js
            define("amdDependencyModule1", () => {
                const api1 = () => { };

                return {
                    api1
                };
            });
        }
    };
});

// Transpile CommonJS/Node.js module definition to SystemJS syntax: lib/commonJSDependencyModule2.js.
System.register([], function (_export, _context) {
    "use strict";
    var dependencyModule1, api2;
    return {
        setters: [],
        execute: function () {
            // Define CommonJS module: src/commonJSDependencyModule2.js
            dependencyModule1 = require("./amdDependencyModule1");

            api2 = () => dependencyModule1.api1();

            exports.api2 = api2;
        }
    };
});

// Transpile ES module definition to SystemJS syntax: lib/esCounterModule.js.
System.register(["./amdDependencyModule1", "./commonJSDependencyModule2"], function (_export, _context) {
    "use strict";
    var dependencyModule1, dependencyModule2, count, increase, reset;
    return {
        setters: [function (_amdDependencyModule) {
            dependencyModule1 = _amdDependencyModule.default;
        }, function (_commonJSDependencyModule) {
            dependencyModule2 = _commonJSDependencyModule.default;
        }],
        execute: function () {
            // Define ES module: src/esCounterModule.js.
            dependencyModule1.api1();
            dependencyModule2.api2();
            count = 0;

            increase = () => ++count;

            reset = () => {
                count = 0;
                console.log("Count is reset.");
            };

            _export("default", {
                increase,
                reset
            });
        }
    };
});

// Transpile ES module usage to SystemJS syntax: lib/index.js.
System.register(["./esCounterModule"], function (_export, _context) {
    "use strict";
    var esCounterModule;
    return {
        setters: [function (_esCounterModuleJs) {
            esCounterModule = _esCounterModuleJs.default;
        }],
        execute: function () {
            // Use ES module: src/index.js
            esCounterModule.increase();
            esCounterModule.reset();
        }
    };
});

TypeScript模塊:轉換爲CJS、AMD、ES、系統模塊

TypeScript 支持 ES 模塊語法(https://www.typescriptlang.or...),根據 tsconfig.json 中指定的 transpiler 選項,能夠將其保留爲 ES6 或轉換爲其餘格式,包括 CommonJS/Node.js、AMD/RequireJS、UMD/UmdJS 或 System/SystemJS:

{
    "compilerOptions": {
        "module": "ES2020", // None, CommonJS, AMD, System, UMD, ES6, ES2015, ES2020, ESNext.
    }
}

例如:

// TypeScript and ES module.
// With compilerOptions: { module: "ES6" }. Transpile to ES module with the same import/export syntax.
import dependencyModule from "./dependencyModule";
dependencyModule.api();
let count = 0;
export const increase = function () { return ++count };


// With compilerOptions: { module: "CommonJS" }. Transpile to CommonJS/Node.js module:
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;

var dependencyModule_1 = __importDefault(require("./dependencyModule"));
dependencyModule_1["default"].api();
var count = 0;
exports.increase = function () { return ++count; };

// With compilerOptions: { module: "AMD" }. Transpile to AMD/RequireJS module:
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
define(["require", "exports", "./dependencyModule"], function (require, exports, dependencyModule_1) {
    "use strict";
    exports.__esModule = true;

    dependencyModule_1 = __importDefault(dependencyModule_1);
    dependencyModule_1["default"].api();
    var count = 0;
    exports.increase = function () { return ++count; };
});

// With compilerOptions: { module: "UMD" }. Transpile to UMD/UmdJS module:
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./dependencyModule"], factory);
    }
})(function (require, exports) {
    "use strict";
    exports.__esModule = true;

    var dependencyModule_1 = __importDefault(require("./dependencyModule"));
    dependencyModule_1["default"].api();
    var count = 0;
    exports.increase = function () { return ++count; };
});

// With compilerOptions: { module: "System" }. Transpile to System/SystemJS module:
System.register(["./dependencyModule"], function (exports_1, context_1) {
    "use strict";
    var dependencyModule_1, count, increase;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [
            function (dependencyModule_1_1) {
                dependencyModule_1 = dependencyModule_1_1;
            }
        ],
        execute: function () {
            dependencyModule_1["default"].api();
            count = 0;
            exports_1("increase", increase = function () { return ++count; });
        }
    };
});

這在 TypeScript 中稱爲外部模塊。

內部模塊和命名空間

TypeScript還具備一個 module 關鍵字和一個 namespace 關鍵字。它們被稱爲內部模塊:

module Counter {
    let count = 0;
    export const increase = () => ++count;
    export const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };
}

namespace Counter {
    let count = 0;
    export const increase = () => ++count;
    export const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };
}

它們都被轉換爲 JavaScript 對象:

var Counter;
(function (Counter) {
    var count = 0;
    Counter.increase = function () { return ++count; };
    Counter.reset = function () {
        count = 0;
        console.log("Count is reset.");
    };
})(Counter || (Counter = {}));

經過支持 . 分隔符,TypeScript 模塊和命名空間能夠有多個級別:

module Counter.Sub {
    let count = 0;
    export const increase = () => ++count;
}

namespace Counter.Sub {
    let count = 0;
    export const increase = () => ++count;
}

它們被轉換爲對象的屬性:

var Counter;
(function (Counter) {
    var Sub;
    (function (Sub) {
        var count = 0;
        Sub.increase = function () { return ++count; };
    })(Sub = Counter.Sub || (Counter.Sub = {}));
})(Counter|| (Counter = {}));

TypeScript 模塊和命名空間也能夠在 export 語句中使用:

module Counter {
    let count = 0;
    export module Sub {
        export const increase = () => ++count;
    }
}

module Counter {
    let count = 0;
    export namespace Sub {
        export const increase = () => ++count;
    }
}

編譯後 sub 模塊和 sub 命名空間相同:

var Counter;
(function (Counter) {
    var count = 0;
    var Sub;
    (function (Sub) {
        Sub.increase = function () { return ++count; };
    })(Sub = Counter.Sub || (Counter.Sub = {}));
})(Counter || (Counter = {}));

結論

歡迎使用 JavaScript,它具備如此豐富的功能——僅用於模塊化/命名空間的就有 10 多種系統和格式:

  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 轉換爲較舊或兼容的語法。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索