TypeScript基礎入門之模組解析(一)
模組解析
本節假設有關模組的一些基本知識。有關更多資訊,請參閱模組文件。
模組解析是編譯器用來確定匯入所引用內容的過程。 考慮一個匯入語句,如import { a } from "moduleA"; 為了檢查a的任何使用,編譯器需要確切地知道它代表什麼,並且需要檢查它的定義moduleA。
此時,編譯器將詢問"moduleA的型別是什麼?“雖然這聽起來很簡單,但是moduleA可以在您自己的.ts/.tsx檔案中定義,或者在您的程式碼所依賴的.d.ts中定義。
首先,編譯器將嘗試查詢表示匯入模組的檔案。 為此,編譯器遵循兩種不同策略之一:Classic或Node。 這些策略告訴編譯器在哪裡尋找moduleA。
如果這不起作用並且模組名稱是非相對的(並且在"moduleA"的情況下,則是),則編譯器將嘗試查詢環境模組宣告。 接下來我們將介紹非相對進口。
最後,如果編譯器無法解析模組,它將記錄錯誤。 在這種情況下,錯誤就像error TS2307: Cannot find module 'moduleA'
相對與非相對模組匯入
根據模組引用是相對引用還是非相對引用,模組匯入的解析方式不同。
相對匯入是以/、./或../開頭的匯入。 一些例子包括:
- import Entry from "./components/Entry";
- import { DefaultHeaders } from "../constants/http";
- import "/mod";
任何其他import都被視為非親屬。 一些例子包括:
- import * as $ from "jquery";
- import { Component } from "@angular/core";
相對匯入是相對於匯入檔案解析的,無法解析為環境模組宣告。 您應該為自己的模組使用相對匯入,這些模組可以保證在執行時保持其相對位置。
可以相對於baseUrl或通過路徑對映解析非相對匯入,我們將在下面介紹。 他們還可以解析為環境模組宣告。 匯入任何外部依賴項時,請使用非相對路徑。
模組解決策略
有兩種可能的模組解析策略:Node和Classic。 您可以使用--moduleResolution標誌指定模組解析策略。 如果未指定,則預設為Classic for ```--module AMD | System | ES2015```或其他Node。
Classic 策略
這曾經是TypeScript的預設解析策略。 如今,這種策略主要用於向後相容。
將相對於匯入檔案解析相對匯入。 因此,從原始檔/root/src/folder/A.ts中的import { b } from "./moduleB"將導致以下查詢:
- /root/src/folder/moduleB.ts
- /root/src/folder/moduleB.d.ts
但是,對於非相對模組匯入,編譯器會從包含匯入檔案的目錄開始遍歷目錄樹,嘗試查詢匹配的定義檔案。
例如:
在原始檔/root/src/folder/A.ts中對moduleB進行非相對匯入(例如import { b } from "moduleB")將導致嘗試以下位置來定位"moduleB":
- /root/src/folder/moduleB.ts
- /root/src/folder/moduleB.d.ts
- /root/src/moduleB.ts
- /root/src/moduleB.d.ts
- /root/moduleB.ts
- /root/moduleB.d.ts
- /moduleB.ts
- /moduleB.d.ts
Node 策略
他的解析策略試圖在執行時模仿Node.js模組解析機制。Node.js模組文件中概述了完整的Node.js解析演算法。
Node.js如何解析模組
要了解TS編譯器將遵循的步驟,重要的是要闡明Node.js模組。 傳統上,Node.js中的匯入是通過呼叫名為require的函式來執行的。 Node.js採取的行為將根據require是否給定相對路徑或非相對路徑而有所不同。
相對路徑相當簡單。 例如,讓我們考慮位於/root/src/moduleA.js的檔案,其中包含import var x = require("./ moduleB"); Node.js按以下順序解析匯入:
1. 詢問名為/root/src/moduleB.js的檔案(如果存在)。 2. 詢問資料夾/root/src/moduleB是否包含名為package.json的檔案,該檔案指定了"main"模組。在我們的示例中,如果Node.js找到包含{"main": "lib/mainModule.js"}的檔案/root/src/moduleB/package.json,那麼Node.js將引用/root/src/moduleB/lib/mainModule.js。 3. 詢問資料夾/root/src/moduleB是否包含名為index.js的檔案。該檔案被隱含地視為該資料夾的"main"模組。
您可以在Node.js文件中瞭解有關檔案模組和資料夾模組的更多資訊。
但是,非相對模組名稱的解析以不同方式執行。 Node將在名為node_modules的特殊資料夾中查詢模組。 node_modules資料夾可以與當前檔案位於同一級別,或者在目錄鏈中位於更高級別。 Node將走向目錄鏈,檢視每個node_modules,直到找到您嘗試載入的模組。
按照上面的示例,考慮是否/root/src/moduleA.js使用非相對路徑並且匯入var x = require("moduleB");然後,Node會嘗試將moduleB解析到每個位置,直到一個工作。
- /root/src/node_modules/moduleB.js
- /root/src/node_modules/moduleB/package.json (if it specifies a "main" property)
- /root/src/node_modules/moduleB/index.js
- /root/node_modules/moduleB.js
- /root/node_modules/moduleB/package.json (if it specifies a "main" property)
- /root/node_modules/moduleB/index.js
- /node_modules/moduleB.js
- /node_modules/moduleB/package.json (if it specifies a "main" property)
- /node_modules/moduleB/index.js
請注意,Node.js在步驟(4)和(7)中跳過了一個目錄。
您可以在Node.js文件中閱讀有關從node_modules載入模組的過程的更多資訊。
TypeScript如何解析模組
TypeScript將模仿Node.js執行時解析策略,以便在編譯時定位模組的定義檔案。 為此,TypeScript通過Node的解析邏輯覆蓋TypeScript原始檔副檔名(.ts、.tsx和.d.ts)。 TypeScript還將使用package.json中名為"types"的欄位來映象"main"的目的 - 編譯器將使用它來查詢要查詢的"main"定義檔案。
例如,/root/src/moduleA.ts中的import { b } from "./moduleB"等匯入語句將導致嘗試以下位置來定位"./moduleB":
- /root/src/moduleB.ts
- /root/src/moduleB.tsx
- /root/src/moduleB.d.ts
- /root/src/moduleB/package.json (if it specifies a "types" property)
- /root/src/moduleB/index.ts
- /root/src/moduleB/index.tsx
- /root/src/moduleB/index.d.ts
回想一下,Node.js查詢名為moduleB.js的檔案,然後查詢適用的package.json,然後查詢index.js。
類似地,非相對匯入將遵循Node.js解析邏輯,首先查詢檔案,然後查詢適用的資料夾。 因此,從原始檔/root/src/moduleA.ts中的import { b } from "moduleB"將導致以下查詢:
- /root/src/node_modules/moduleB.ts
- /root/src/node_modules/moduleB.tsx
- /root/src/node_modules/moduleB.d.ts
- /root/src/node_modules/moduleB/package.json (if it specifies a "types" property)
- /root/src/node_modules/moduleB/index.ts
- /root/src/node_modules/moduleB/index.tsx
- /root/src/node_modules/moduleB/index.d.ts
- /root/node_modules/moduleB.ts
- /root/node_modules/moduleB.tsx
- /root/node_modules/moduleB.d.ts
- /root/node_modules/moduleB/package.json (if it specifies a "types" property)
- /root/node_modules/moduleB/index.ts
- /root/node_modules/moduleB/index.tsx
- /root/node_modules/moduleB/index.d.ts
- /node_modules/moduleB.ts
- /node_modules/moduleB.tsx
- /node_modules/moduleB.d.ts
- /node_modules/moduleB/package.json (if it specifies a "types" property)
- /node_modules/moduleB/index.ts
- /node_modules/moduleB/index.tsx
- /node_modules/moduleB/index.d.ts
不要被這裡的步驟數嚇倒 - TypeScript仍然只在步驟(8)和(15)兩次跳過目錄。 這實際上並不比Node.js本身正在做的複雜。
附加模組解析度標誌
專案源佈局有時與輸出的佈局不匹配。 通常,一組構建步驟會導致生成最終輸出。 這些包括將.ts檔案編譯為.js,以及將不同源位置的依賴項複製到單個輸出位置。 最終結果是執行時的模組可能具有與包含其定義的原始檔不同的名稱。 或者,在編譯時,最終輸出中的模組路徑可能與其對應的原始檔路徑不匹配。
TypeScript編譯器具有一組附加標誌,用於通知編譯器預期發生在源上的轉換以生成最終輸出。
重要的是要注意編譯器不會執行任何這些轉換; 它只是使用這些資訊來指導將模組匯入解析到其定義檔案的過程。
未完待續...