模組

JavaScript 長期以來都有不同的方式來處理模組化程式碼。TypeScript 自 2012 年推出以來,已實作對許多這些格式的支援,但隨著時間的推移,社群和 JavaScript 規格已收斂到稱為 ES 模組(或 ES6 模組)的格式。您可能知道它是 import/export 語法。

ES 模組於 2015 年新增至 JavaScript 規格,並於 2020 年獲得大多數網路瀏覽器和 JavaScript 執行時間的廣泛支援。

為了專注,手冊將涵蓋 ES 模組及其廣受歡迎的前身 CommonJS module.exports = 語法,您可以在 模組 的參考部分中找到有關其他模組模式的資訊。

JavaScript 模組的定義方式

在 TypeScript 中,就像在 ECMAScript 2015 中一樣,任何包含頂層 importexport 的檔案都被視為模組。

相反地,沒有任何頂層匯入或匯出宣告的檔案會被視為腳本,其內容可於全域範圍內使用(因此模組也可以使用)。

模組會在它們自己的範圍內執行,而不是在全域範圍內執行。這表示在模組中宣告的變數、函式、類別等在模組外不可見,除非它們使用其中一種匯出形式明確地匯出。相反地,要使用從不同模組匯出的變數、函式、類別、介面等,必須使用其中一種匯入形式匯入。

非模組

在我們開始之前,了解 TypeScript 認為什麼是模組非常重要。JavaScript 規範宣告任何沒有 import 宣告、export 或頂層 await 的 JavaScript 檔案都應該視為腳本,而非模組。

在腳本檔案內,變數和類型宣告為在共用全域範圍內,並且假設您會使用 outFile 編譯器選項將多個輸入檔案合併成一個輸出檔案,或在 HTML 中使用多個 <script> 標籤載入這些檔案(順序正確!)。

如果您有一個目前沒有任何 importexport 的檔案,但您想要將其視為模組,請新增一行

ts
export {};
Try

這將會將檔案變更為一個不輸出任何內容的模組。此語法不論您的模組目標為何,都能運作。

TypeScript 中的模組

其他閱讀資料
急躁的 JS(模組)
MDN:JavaScript 模組

在 TypeScript 中撰寫基於模組的程式碼時,有三個主要事項需要考量

  • 語法:我想使用什麼語法來匯入和匯出項目?
  • 模組解析:模組名稱(或路徑)與磁碟上的檔案之間的關係為何?
  • 模組輸出目標:我發出的 JavaScript 模組應該是什麼樣子?

ES 模組語法

檔案可透過 export default 宣告主要匯出

ts
// @filename: hello.ts
export default function helloWorld() {
console.log("Hello, world!");
}
Try

這會透過以下方式匯入

ts
import helloWorld from "./hello.js";
helloWorld();
Try

除了預設匯出之外,您還可以透過 export 匯出多個變數和函式,方法是省略 default

ts
// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
 
export class RandomNumberGenerator {}
 
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
Try

這些可以在另一個檔案中透過 import 語法使用

ts
import { pi, phi, absolute } from "./maths.js";
 
console.log(pi);
const absPhi = absolute(phi);
const absPhi: number
Try

其他匯入語法

匯入可以使用 import {old as new} 格式重新命名

ts
import { pi as π } from "./maths.js";
 
console.log(π);
(alias) var π: number import π
Try

您可以將上述語法混合搭配到單一 import

ts
// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}
 
// @filename: app.ts
import RandomNumberGenerator, { pi as π } from "./maths.js";
 
RandomNumberGenerator;
(alias) class RandomNumberGenerator import RandomNumberGenerator
 
console.log(π);
(alias) const π: 3.14 import π
Try

您可以使用 * as name 將所有匯出的物件放入單一命名空間中

ts
// @filename: app.ts
import * as math from "./maths.js";
 
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
const positivePhi: number
Try

您可以透過 import "./file" 匯入檔案,將任何變數納入目前的模組中

ts
// @filename: app.ts
import "./maths.js";
 
console.log("3.14");
Try

在此情況下,import 什麼都不做。不過,maths.ts 中的所有程式碼都已評估,這可能會觸發影響其他物件的副作用。

TypeScript 特定的 ES 模組語法

類型可以使用與 JavaScript 值相同的語法匯出和匯入

ts
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
 
export interface Dog {
breeds: string[];
yearOfBirth: number;
}
 
// @filename: app.ts
import { Cat, Dog } from "./animal.js";
type Animals = Cat | Dog;
Try

TypeScript 已擴充 import 語法,新增兩個用於宣告類型匯入的概念

import type

這是一個匯入陳述式,只能匯入類型

ts
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
'createCatName' cannot be used as a value because it was imported using 'import type'.1361'createCatName' cannot be used as a value because it was imported using 'import type'.
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
 
// @filename: valid.ts
import type { Cat, Dog } from "./animal.js";
export type Animals = Cat | Dog;
 
// @filename: app.ts
import type { createCatName } from "./animal.js";
const name = createCatName();
Try
內嵌 type 匯入

TypeScript 4.5 也允許個別匯入加上 type 前綴,以表示匯入的參考為類型

ts
// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";
 
export type Animals = Cat | Dog;
const name = createCatName();
Try

這些功能加起來,讓非 TypeScript 轉譯器(例如 Babel、swc 或 esbuild)知道哪些匯入可以安全移除。

具備 CommonJS 行為的 ES 模組語法

TypeScript 具有 ES 模組語法,直接對應到 CommonJS 和 AMD 的 require。使用 ES 模組的匯入在大部分情況下與這些環境中的 require 相同,但此語法可確保 TypeScript 檔案與 CommonJS 輸出之間有 1 對 1 的對應關係

ts
import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");
Try

您可以在 模組參考頁面 中深入了解此語法。

CommonJS 語法

CommonJS 是 npm 上大多數模組所提供的格式。即使您使用上述 ES 模組語法撰寫,了解 CommonJS 語法如何運作有助於您更輕鬆地除錯。

匯出

識別碼會透過設定名為 module 的全域變數上的 exports 屬性來匯出。

ts
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
 
module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
Try

然後,這些檔案可以透過 require 陳述式匯入

ts
const maths = require("./maths");
maths.pi;
any
Try

或者,您可以使用 JavaScript 中的解構功能簡化一點

ts
const { squareTwo } = require("./maths");
squareTwo;
const squareTwo: any
Try

CommonJS 和 ES 模組互通

CommonJS 和 ES 模組在預設匯入和模組命名空間物件匯入的區別上,功能不匹配。TypeScript 有個編譯器標記,可使用 esModuleInterop 減少兩組不同限制之間的摩擦。

TypeScript 的模組解析選項

模組解析是從 importrequire 陳述式中取得字串,並決定該字串所指的文件。

TypeScript 包含兩種解析策略:傳統和 Node。傳統是預設值,當編譯器選項 module 不是 commonjs 時,會包含傳統策略以維持向後相容性。Node 策略複製了 Node.js 在 CommonJS 模式下的運作方式,並針對 .ts.d.ts 進行額外檢查。

有許多 TSConfig 標記會影響 TypeScript 中的模組策略: moduleResolutionbaseUrlpathsrootDirs

有關這些策略如何運作的完整詳細資料,您可以參閱模組解析度參考頁面。

TypeScript 的模組輸出選項

有兩個選項會影響發出的 JavaScript 輸出

  • target,決定哪些 JS 功能會降級(轉換為在較舊的 JavaScript 執行環境中執行),哪些會保持不變
  • module,決定使用哪些程式碼讓模組彼此互動

您使用的target由您預期執行 TypeScript 程式碼的 JavaScript 執行環境中的可用功能決定。這可能是:您支援的最舊網路瀏覽器、您預期執行的 Node.js 的最低版本,或來自您的執行環境的獨特限制(例如 Electron)。

模組之間的所有通訊都透過模組載入器進行,編譯器選項module決定使用哪一個。在執行階段,模組載入器負責在執行模組之前找到並執行模組的所有相依性。

例如,以下是使用 ES 模組語法的 TypeScript 檔案,展示了幾個不同的 module 選項

ts
import { valueOfPi } from "./constants.js";
 
export const twoPi = valueOfPi * 2;
Try

ES2020

ts
import { valueOfPi } from "./constants.js";
export const twoPi = valueOfPi * 2;
 
Try

CommonJS

ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;
 
Try

UMD

ts
(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", "./constants.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;
});
 
Try

請注意,ES2020 與原始的 index.ts 實際上相同。

您可以在 module 的 TSConfig 參考 中看到所有可用的選項及其發出的 JavaScript 程式碼的外觀。

TypeScript 命名空間

TypeScript 有其自己的模組格式,稱為 命名空間,它早於 ES 模組標準。此語法有許多有用的功能,可用於建立複雜的定義檔案,而且在 DefinitelyTyped 中仍然活躍使用。雖然尚未棄用,但命名空間中的大部分功能都存在於 ES 模組中,我們建議您使用它來與 JavaScript 的方向一致。您可以在 命名空間參考頁面 中瞭解更多關於命名空間的資訊。

TypeScript 文件是一個開源專案。透過 傳送 Pull Request ❤ 來幫助我們改善這些頁面。

此頁面的貢獻者
RCRyan Cavanaugh (52)
OTOrta Therox (7)
MMyo (3)
ABAndrew Branch (2)
HAHossein Ahmadian-Yazdi (2)
18+

最後更新:2024 年 3 月 21 日