模塊解析是編譯器用來肯定導入所指內容的過程。考慮像 import { a } from "moduleA"; 這樣的導入語句。爲了檢查 a 的任何使用,編譯器須要確切地知道它表明什麼,而且須要檢查它的定義 moduleA。node
此時,編譯器會問「moduleA 的形狀是什麼?」雖然這聽起來很簡單,但 moduleA 能夠在您本身的 .ts/.tsx 文件之一中定義,或者在您的代碼所依賴的 .d.ts 中定義。jquery
首先,編譯器會嘗試定位一個表明導入模塊的文件。爲此,編譯器遵循兩種不一樣策略之一:classical 或 Node。這些策略告訴編譯器去哪裏尋找 moduleA。算法
若是這不起做用而且模塊名稱是非相關的(在「moduleA」的狀況下,它是),那麼編譯器將嘗試定位一個環境模塊聲明。接下來咱們將介紹非相對導入。sql
最後,若是編譯器沒法解析模塊,它將記錄一個錯誤。在這種狀況下,錯誤相似於錯誤 TS2307:找不到模塊 'moduleA'。typescript
根據模塊引用是相對的仍是非相對的,模塊導入的解析方式不一樣。npm
相對導入是以 /、./ 或 ../ 開頭的導入。 一些例子包括:json
任何其餘導入都被認爲是非相關的。 一些例子包括:app
相對導入是相對於導入文件解析的,沒法解析爲環境模塊聲明。 您應該對本身的模塊使用相對導入,以保證在運行時保持其相對位置。函數
有兩種可能的模塊解析策略:Node 和 Classic。 您可使用 --moduleResolution 標誌來指定模塊解析策略。 若是未指定,則 --module commonjs 默認爲 Node,不然默認爲 Classic(包括 --module 設置爲 amd、system、umd、es201五、esnext 等時)。
注意:Node 模塊解析是 TypeScript 社區中最經常使用的,推薦用於大多數項目。 若是您在 TypeScript 中遇到導入和導出的解析問題,請嘗試設置 moduleResolution: "node" 以查看它是否解決了問題。
這曾經是 TypeScript 的默認解析策略。 現在,這種策略主要是爲了向後兼容。
相對導入將相對於導入文件進行解析。 所以,在源文件 /root/src/folder/A.ts 中 import { b } from "./moduleB" 將致使如下查找:
然而,對於非相關模塊導入,編譯器從包含導入文件的目錄開始沿着目錄樹向上走,試圖找到匹配的定義文件。
例如:
在源文件 /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.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 按如下順序解析該導入:
可是,對非相關模塊名稱的解析以不一樣的方式執行。 Node 將在名爲 node_modules 的特殊文件夾中查找您的模塊。 node_modules 文件夾能夠與當前文件位於同一級別,也能夠在目錄鏈中的更高級別。 Node 將沿着目錄鏈向上遍歷,查看每一個 node_modules,直到找到您嘗試加載的模塊。
按照咱們上面的例子,考慮 /root/src/moduleA.js 是否使用非相對路徑並導入 var x = require("moduleB");。而後,Node 會嘗試將 moduleB 解析爲每一個位置,直到一個位置正常工做。
(1) /root/src/node_modules/moduleB.js
(2) /root/src/node_modules/moduleB/package.json(若是它指定了「main」屬性)
(3) /root/src/node_modules/moduleB/index.js
(4) /root/node_modules/moduleB.js
(5) /root/node_modules/moduleB/package.json(若是它指定了「main」屬性)
(6) /root/node_modules/moduleB/index.js
(7) /node_modules/moduleB.js
(8) /node_modules/moduleB/package.json(若是它指定了「main」屬性)
(9) /node_modules/moduleB/index.js
請注意,Node.js 在步驟 (4) 和 (7) 中跳轉了一個目錄。
您能夠在 Node.js 文檔中閱讀有關從 node_modules 加載模塊的更多信息。
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":
(1)/root/src/moduleB.ts
(2)/root/src/moduleB.tsx
(3)/root/src/moduleB.d.ts
(4)/root/src/moduleB/package.json(若是它指定了「types」屬性)
(5)/root/src/moduleB/index.ts
(6)/root/src/moduleB/index.tsx
(7)/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(若是它指定了「types」屬性) /root/src/node_modules/@types/moduleB.d.ts /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(若是它指定了「types」屬性) /root/node_modules/@types/moduleB.d.ts /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(若是它指定了「types」屬性) /node_modules/@types/moduleB.d.ts /node_modules/moduleB/index.ts /node_modules/moduleB/index.tsx /node_modules/moduleB/index.d.ts
不要被這裏的步驟數嚇倒——TypeScript 仍然只在步驟 (9) 和 (17) 中兩次跳轉目錄。這實際上並不比 Node.js 自己所作的更復雜。
項目源佈局有時與輸出佈局不匹配。 一般一組構建步驟會生成最終輸出。 其中包括將 .ts 文件編譯爲 .js,以及將依賴項從不一樣的源位置複製到單個輸出位置。 最終結果是模塊在運行時的名稱可能與包含其定義的源文件的名稱不一樣。 或者最終輸出中的模塊路徑可能在編譯時與其對應的源文件路徑不匹配。
TypeScript 編譯器有一組額外的標誌來通知編譯器預期發生在源上的轉換以生成最終輸出。
須要注意的是,編譯器不會執行任何這些轉換; 它只是使用這些信息來指導將模塊導入解析到其定義文件的過程。
在使用 AMD 模塊加載器的應用程序中,使用 baseUrl 是一種常見作法,其中模塊在運行時「部署」到單個文件夾。 這些模塊的源代碼能夠位於不一樣的目錄中,可是構建腳本會將它們放在一塊兒。
設置 baseUrl 通知編譯器在哪裏能夠找到模塊。 假定全部具備非相對名稱的模塊導入都與 baseUrl 相關。
baseUrl 的值肯定爲:
(1)baseUrl 命令行參數的值(若是給定的路徑是相對的,則根據當前目錄計算)
(2)'tsconfig.json' 中 baseUrl 屬性的值(若是給定的路徑是相對的,則根據 'tsconfig.json' 的位置計算)
請注意,相對模塊導入不會受到設置 baseUrl 的影響,由於它們老是相對於它們的導入文件進行解析。
您能夠在 RequireJS 和 SystemJS 文檔中找到有關 baseUrl 的更多文檔。
有時模塊並不直接位於 baseUrl 下。例如,對模塊「jquery」的導入將在運行時轉換爲「node_modules/jquery/dist/jquery.slim.min.js」。加載器使用映射配置在運行時將模塊名稱映射到文件,請參閱 RequireJs 文檔和 SystemJS 文檔。
TypeScript 編譯器支持使用 tsconfig.json 文件中的「paths」屬性聲明此類映射。 如下是如何爲 jquery 指定「paths」屬性的示例。
{ "compilerOptions": { "baseUrl": ".", // This must be specified if "paths" is. "paths": { "jquery": ["node_modules/jquery/dist/jquery"] // This mapping is relative to "baseUrl" } } }
如前所述,編譯器在解析模塊時能夠訪問當前文件夾以外的文件。 在診斷模塊未解析的緣由或解析爲不正確的定義時,這可能很困難。 使用 --traceResolution 啓用編譯器模塊解析跟蹤能夠深刻了解模塊解析過程當中發生的狀況。
假設咱們有一個使用 typescript 模塊的示例應用程序。
app.ts has an import like import * as ts from "typescript".
│ tsconfig.json ├───node_modules │ └───typescript │ └───lib │ typescript.d.ts └───src app.ts
使用以下命令行編譯:
tsc --traceResolution
結果:
======== Resolving module 'typescript' from 'src/app.ts'. ======== Module resolution kind is not specified, using 'NodeJs'. Loading module 'typescript' from 'node_modules' folder. File 'src/node_modules/typescript.ts' does not exist. File 'src/node_modules/typescript.tsx' does not exist. File 'src/node_modules/typescript.d.ts' does not exist. File 'src/node_modules/typescript/package.json' does not exist. File 'node_modules/typescript.ts' does not exist. File 'node_modules/typescript.tsx' does not exist. File 'node_modules/typescript.d.ts' does not exist. Found 'package.json' at 'node_modules/typescript/package.json'. 'package.json' has 'types' field './lib/typescript.d.ts' that references 'node_modules/typescript/lib/typescript.d.ts'. File 'node_modules/typescript/lib/typescript.d.ts' exist - use it as a module resolution result. ======== Module name 'typescript' was successfully resolved to 'node_modules/typescript/lib/typescript.d.ts'. ========
觸發模塊解析的源代碼位置:
======== Resolving module ‘typescript’ from ‘src/app.ts’. ========
模塊解析策略:
Module resolution kind is not specified, using ‘NodeJs’.
Loading of types from npm packages:
‘package.json’ has ‘types’ field ‘./lib/typescript.d.ts’ that references ‘node_modules/typescript/lib/typescript.d.ts’.
最後成功解析的輸出:
======== Module name ‘typescript’ was successfully resolved to ‘node_modules/typescript/lib/typescript.d.ts’. ========