模塊解析

模塊解析是指編譯器在查找導(dǎo)入模塊內(nèi)容時所遵循的流程。假設(shè)有一個導(dǎo)入語句 import { a } from "moduleA"; 為了去檢查任何對 a 的使用,編譯器需要準(zhǔn)確的知道它表示什么,并且需要檢查它的定義 moduleA。

這時候,編譯器會有個疑問“moduleA的結(jié)構(gòu)是怎樣的?” 這聽上去很簡單,但 moduleA可能在你寫的某個.ts/.tsx文件里或者在你的代碼所依賴的 .d.ts。

模塊解析策略

typescript 有兩種模塊解析策略: NodeClassic. 通過下面的分析你會知道我更推進(jìn)使用 Node 策略, 因為他和我們熟悉的 nodejs 加載模塊的方式是一致的, 即通過模塊名字查找模塊時是從 node_modules 文件夾內(nèi)查找.

我們可以使用 --moduleResolution 標(biāo)記來指定使用哪種模塊解析策略,取值為 Node 或者 Classic

若未指定moduleResolution(即默認(rèn)情況下),--module AMD | System | ES2015(ES6)moduleResolution 的默認(rèn)值為 Classic,其它情況時則為 Node

進(jìn)一步再來看看 module 的默認(rèn)取值情況, target === "ES6" ? "ES6" : "commonjs"--targetES6 時, module 的默認(rèn)值為 ES6,其他情況默認(rèn)值為 commonjs. module 的所有取值 "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"

再進(jìn)一步看看, target 的默認(rèn)值為 ES3, 其他取值有 "ES5", "ES6"/ "ES2015", "ES2016", "ES2017"或 "ESNext"。

建議: 顯示設(shè)置 moduleResolutionNode

Classic 策略

注意: 相對導(dǎo)入是以 /,./或../ 開頭的, 所有其它形式的導(dǎo)入被當(dāng)作非相對的.

相對路徑模塊導(dǎo)入

這種策略在以前是TypeScript默認(rèn)的解析策略。 現(xiàn)在,它存在的理由主要是為了向后兼容。

相對路徑導(dǎo)入的模塊是相對于導(dǎo)入它的文件進(jìn)行解析的。 因此 /root/src/folder/A.ts文件里的 import { b } from "./moduleB" 會使用下面的查找流程:

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts

非相對模塊導(dǎo)入

有一個對 moduleB 的非相對導(dǎo)入import { b } from "moduleB",它是在/root/src/folder/A.ts文件里,會以如下的方式來定位"moduleB"

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts
  3. /root/src/moduleB.ts
  4. /root/src/moduleB.d.ts
  5. /root/moduleB.ts
  6. /root/moduleB.d.ts
  7. /moduleB.ts
  8. /moduleB.d.ts

Node 策略(推薦)

這個解析策略試圖在運行時模仿Node.js模塊解析機制。
TypeScript是模仿Node.js運行時的解析策略來在編譯階段定位模塊定義文件。 因此,TypeScriptNode解析邏輯基礎(chǔ)上增加了TypeScript源文件的擴展名(.ts,.tsx和.d.ts)。 同時,TypeScriptpackage.json里使用字段"types"來表示類似"main"的意義。

相對模塊導(dǎo)入

比如,有一個導(dǎo)入語句 import { b } from "./moduleB"/root/src/moduleA.ts 里,會以下面的流程來定位"./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。

非相對導(dǎo)入(去node_modules) 內(nèi)查找

類似地,非相對的導(dǎo)入會遵循Node.js的解析邏輯,首先查找文件,然后是合適的文件夾。 因此 /root/src/moduleA.ts文件里的import { b } from "moduleB"會以下面的查找順序解析:

  1. /root/src/node_modules/moduleB.ts
  2. /root/src/node_modules/moduleB.tsx
  3. /root/src/node_modules/moduleB.d.ts
  4. /root/src/node_modules/moduleB/package.json (如果指定了"types"屬性)
  5. /root/src/node_modules/moduleB/index.ts
  6. /root/src/node_modules/moduleB/index.tsx
  7. /root/src/node_modules/moduleB/index.d.ts
  1. /root/node_modules/moduleB.ts
  2. /root/node_modules/moduleB.tsx
  3. /root/node_modules/moduleB.d.ts
  4. /root/node_modules/moduleB/package.json (如果指定了"types"屬性)
  5. /root/node_modules/moduleB/index.ts
  6. /root/node_modules/moduleB/index.tsx
  7. /root/node_modules/moduleB/index.d.ts
  1. /node_modules/moduleB.ts
  2. /node_modules/moduleB.tsx
  3. /node_modules/moduleB.d.ts
  4. /node_modules/moduleB/package.json (如果指定了"types"屬性)
  5. /node_modules/moduleB/index.ts
  6. /node_modules/moduleB/index.tsx
  7. /node_modules/moduleB/index.d.ts

不要被這里步驟的數(shù)量嚇到 - TypeScript只是在步驟(8)和(15)向上跳了兩次目錄。 這并不比Node.js里的流程復(fù)雜。

附加的模塊解析標(biāo)記(baseUrl與paths的配置)

有時工程源碼結(jié)構(gòu)與輸出結(jié)構(gòu)不同。 通常是要經(jīng)過一系統(tǒng)的構(gòu)建步驟最后生成輸出。 它們包括將 .ts編譯成.js,將不同位置的依賴拷貝至一個輸出位置。 最終結(jié)果就是運行時的模塊名與包含它們聲明的源文件里的模塊名不同。 或者最終輸出文件里的模塊路徑與編譯時的源文件路徑不同了。

TypeScript編譯器有一些額外的標(biāo)記用來通知編譯器在源碼編譯成最終輸出的過程中都發(fā)生了哪個轉(zhuǎn)換。

Base URL

在利用AMD模塊加載器的應(yīng)用里使用baseUrl是常見做法,它要求在運行時模塊都被放到了一個文件夾里。 這些模塊的源碼可以在不同的目錄下,但是構(gòu)建腳本會將它們集中到一起。

設(shè)置baseUrl來告訴編譯器到哪里去查找模塊。 所有非相對模塊導(dǎo)入都會被當(dāng)做相對于 baseUrl。

baseUrl的值由以下兩者之一決定:

  1. 命令行中baseUrl的值(如果給定的路徑是相對的,那么將相對于當(dāng)前路徑進(jìn)行計算)
  2. tsconfig.json 里的baseUrl屬性(如果給定的路徑是相對的,那么將相對于‘tsconfig.json’路徑進(jìn)行計算)

注意: 相對模塊的導(dǎo)入不會被設(shè)置的baseUrl所影響,因為它們總是相對于導(dǎo)入它們的文件。

路徑映射

有時模塊不是直接放在 baseUrl下面。 比如,充分 "jquery"模塊地導(dǎo)入,在運行時可能被解釋為"node_modules/jquery/dist/jquery.slim.min.js"。

TypeScript編譯器通過使用tsconfig.json文件里的"paths"來支持這樣的聲明映射。 下面是一個如何指定 jquery"paths"的例子。

{
  "compilerOptions": {
    "baseUrl": ".", // This must be specified if "paths" is.
    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery"] // 此處映射是相對于"baseUrl"
    }
  }
}

請注意"paths"是相對于"baseUrl"進(jìn)行解析。 如果 "baseUrl"被設(shè)置成了除"."外的其它值,比如tsconfig.json所在的目錄,那么映射必須要做相應(yīng)的改變。 如果你在上例中設(shè)置了 "baseUrl": "./src",那么jquery應(yīng)該映射到"../node_modules/jquery/dist/jquery"。

通過"paths"我們還可以指定復(fù)雜的映射,包括指定多個回退位置。 假設(shè)在一個工程配置里,有一些模塊位于一處,而其它的則在另個的位置。 構(gòu)建過程會將它們集中至一處。 工程結(jié)構(gòu)可能如下:

projectRoot
├── folder1
│   ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│   └── file2.ts
├── generated
│   ├── folder1
│   └── folder2
│       └── file3.ts
└── tsconfig.json

相應(yīng)的tsconfig.json文件如下:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "generated/*"
      ]
    }
  }
}

它告訴編譯器所有匹配"*"(所有的值)模式的模塊導(dǎo)入會在以下兩個位置查找:

  1. "*":表示名字不發(fā)生改變,所以映射為<moduleName> => <baseUrl>/<moduleName>
  2. "generated/*"表示模塊名添加了“generated”前綴,所以映射為<moduleName> => <baseUrl>/generated/<moduleName>

按照這個邏輯,編譯器將會如下嘗試解析這兩個導(dǎo)入:

  • 導(dǎo)入'folder1/file2'
  1. 匹配'*'模式且通配符捕獲到整個名字。
  2. 嘗試列表里的第一個替換:'*' -> folder1/file2。
  3. 替換結(jié)果為非相對名 - 與baseUrl合并 -> projectRoot/folder1/file2.ts。
  4. 文件存在。完成。
  • 導(dǎo)入'folder2/file3'
  1. 匹配'*'模式且通配符捕獲到整個名字。
  2. 嘗試列表里的第一個替換:'*' -> folder2/file3。
  3. 替換結(jié)果為非相對名 - 與baseUrl合并 -> projectRoot/folder2/file3.ts。
  4. 文件不存在,跳到第二個替換。
  5. 第二個替換:'generated/*' -> generated/folder2/file3。
  6. 替換結(jié)果為非相對名 - 與baseUrl合并 -> projectRoot/generated/folder2/file3.ts。
  7. 文件存在。完成。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容