這節(jié)假設(shè)你已經(jīng)了解了模塊的一些基本知識(shí) 請(qǐng)閱讀 模塊文檔了解更多信息。
模塊解析就是指編譯器所要依據(jù)的一個(gè)流程,用它來(lái)找出某個(gè)導(dǎo)入操作所引用的具體值。 假設(shè)有一個(gè)導(dǎo)入語(yǔ)句import { a } from "moduleA"
; 為了去檢查任何對(duì) a
的使用,編譯器需要準(zhǔn)確的知道它表示什么,并且會(huì)需要檢查它的定義moduleA
。
這時(shí)候,編譯器會(huì)想知道“moduleA
的shape是怎樣的?” 這聽上去很簡(jiǎn)單, moduleA
可能在你寫的某個(gè).ts
/.tsx
文件里或者在你的代碼所依賴的.d.ts
里。
首先,編譯器會(huì)嘗試定位表示導(dǎo)入模塊的文件。 編譯會(huì)遵循下列二種策略之一: Classic或Node。 這些策略會(huì)告訴編譯器到 哪里去查找moduleA
。
如果它們失敗了并且如果模塊名是非相對(duì)的(且是在"moduleA"
的情況下),編譯器會(huì)嘗試定位一個(gè)外部模塊聲明。 我們接下來(lái)會(huì)講到非相對(duì)導(dǎo)入。
最后,如果編譯器還是不能解析這個(gè)模塊,它會(huì)記錄一個(gè)錯(cuò)誤。 在這種情況下,錯(cuò)誤可能為 error TS2307: Cannot find module 'moduleA'.
根據(jù)模塊引用是相對(duì)的還是非相對(duì)的,模塊導(dǎo)入會(huì)以不同的方式解析。
相對(duì)導(dǎo)入是以/
,./
或../
開頭的。 下面是一些例子:
import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";
所有其它形式的導(dǎo)入被當(dāng)作非相對(duì)的。 下面是一些例子:
import * as $ from "jQuery";
import { Component } from "angular2/core";
相對(duì)導(dǎo)入解析時(shí)是相對(duì)于導(dǎo)入它的文件來(lái)的,并且不能解析為一個(gè)外部模塊聲明。 你應(yīng)該為你自己寫的模塊使用相對(duì)導(dǎo)入,這樣能確保它們?cè)谶\(yùn)行時(shí)的相對(duì)位置。
共有兩種可用的模塊解析策略:Node和Classic。 你可以使用 --moduleResolution
標(biāo)記為指定使用哪個(gè)。 默認(rèn)值為 Node。
這種策略以前是TypeScript默認(rèn)的解析策略。 現(xiàn)在,它存在的理由主要是為了向后兼容。
相對(duì)導(dǎo)入的模塊是相對(duì)于導(dǎo)入它的文件進(jìn)行解析的。 因此 /root/src/folder/A.ts
文件里的import { b } from "./moduleB"
會(huì)使用下面的查找流程:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
對(duì)于非相對(duì)模塊的導(dǎo)入,編譯器則會(huì)從包含導(dǎo)入文件的目錄開始依次向上級(jí)目錄遍歷,嘗試定位匹配的聲明文件。
比如:
有一個(gè)對(duì)moduleB
的非相對(duì)導(dǎo)入import { b } from "moduleB"
,它是在/root/src/folder/A.ts
文件里,會(huì)以如下的方式來(lái)定位"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
這個(gè)解析策略試圖在運(yùn)行時(shí)模仿Node.js模塊解析機(jī)制。 完整的Node.js解析算法可以在 Node.js module documentation找到。
為了理解TypeScript編譯依照的解析步驟,先弄明白Node.js模塊是非常重要的。 通常,在Node.js里導(dǎo)入是通過(guò)require
函數(shù)調(diào)用進(jìn)行的。 Node.js會(huì)根據(jù) require
的是相對(duì)路徑還是非相對(duì)路徑做出不同的行為。
相對(duì)路徑很簡(jiǎn)單。 例如,假設(shè)有一個(gè)文件路徑為 /root/src/moduleA.js
,包含了一個(gè)導(dǎo)入var x = require("./moduleB");
Node.js以下面的順序解析這個(gè)導(dǎo)入:
將/root/src/moduleB.js
視為文件,檢查是否存在。
將/root/src/moduleB
視為目錄,檢查是否它包含package.json
文件并且其指定了一個(gè)"main"
模塊。 在我們的例子里,如果Node.js發(fā)現(xiàn)文件 /root/src/moduleB/package.json
包含了{ "main": "lib/mainModule.js" }
,那么Node.js會(huì)引用/root/src/moduleB/lib/mainModule.js
。
將/root/src/moduleB
視為目錄,檢查它是否包含index.js
文件。 這個(gè)文件會(huì)被隱式地當(dāng)作那個(gè)文件夾下的"main"模塊。
你可以閱讀Node.js文檔了解更多詳細(xì)信息:file modules 和 folder modules。
但是,非相對(duì)模塊名的解析是個(gè)完全不同的過(guò)程。 Node會(huì)在一個(gè)特殊的文件夾 node_modules
里查找你的模塊。node_modules
可能與當(dāng)前文件在同一級(jí)目錄下,或者在上層目錄里。 Node會(huì)向上級(jí)目錄遍歷,查找每個(gè)node_modules
直到它找到要加載的模塊。
還是用上面例子,但假設(shè)/root/src/moduleA.js
里使用的是非相對(duì)路徑導(dǎo)入var x = require("moduleB");
。 Node則會(huì)以下面的順序去解析 moduleB
,直到有一個(gè)匹配上。
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
(如果指定了"main"
屬性)/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
(如果指定了"main"
屬性)/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
(如果指定了"main"
屬性)/node_modules/moduleB/index.js
注意Node.js在步驟(4)和(7)會(huì)向上跳一級(jí)目錄。
你可以閱讀Node.js文檔了解更多詳細(xì)信息:loading modules from node_modules
。
TypeScript是模仿Node.js運(yùn)行時(shí)的解析策略來(lái)在編譯階段定位模塊定義文件。 因此,TypeScript在Node解析邏輯基礎(chǔ)上增加了TypeScript源文件的擴(kuò)展名( .ts
,.tsx
和.d.ts
)。 同時(shí),TypeScript在 package.json
里使用字段"typings"
來(lái)表示類似"main"
的意義 - 編譯器會(huì)使用它來(lái)找到要使用的"main"定義文件。
比如,有一個(gè)導(dǎo)入語(yǔ)句import { b } from "./moduleB"
在/root/src/moduleA.ts
里,會(huì)以下面的流程來(lái)定位"./moduleB"
:
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
(如果指定了"typings"
屬性)/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
回想一下Node.js先查找moduleB.js
文件,然后是合適的package.json
,再之后是index.js
。
類似地,非相對(duì)的導(dǎo)入會(huì)遵循Node.js的解析邏輯,首先查找文件,然后是合適的文件夾。 因此/src/moduleA.ts
文件里的import { b } from "moduleB"
會(huì)以下面的查找順序解析:
/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
(如果指定了"typings"
屬性)/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
(如果指定了"typings"
屬性)/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
(如果指定了"typings"
屬性)/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
不要被這里步驟的數(shù)量嚇到 - TypeScript只是在步驟(8)和(15)向上跳了兩次目錄。 這并不比Node.js里的流程復(fù)雜。
--noResolve
正常來(lái)講編譯器會(huì)在開始編譯之前解析模塊導(dǎo)入。 每當(dāng)它成功地解析了對(duì)一個(gè)文件 import
,這個(gè)文件被會(huì)加到一個(gè)文件列表里,以供編譯器稍后處理。
--noResolve
編譯選項(xiàng)告訴編譯器不要添加任何不是在命令行上傳入的文件到編譯列表。 編譯器仍然會(huì)嘗試解析模塊,但是只要沒(méi)有指定這個(gè)文件,那么它就不會(huì)被包含在內(nèi)。
比如
import * as A from "moduleA" // OK, moduleA passed on the command-line
import * as B from "moduleB" // Error TS2307: Cannot find module 'moduleB'.
tsc app.ts moduleA.ts --noResolve
使用--noResolve
編譯app.ts
:
moduleA
,因?yàn)樗诿钚猩现付恕?/li>moduleB
,因?yàn)闆](méi)有在命令行上傳遞。exclude
列表里的模塊還會(huì)被編譯器使用tsconfig.json
將文件夾轉(zhuǎn)變一個(gè)“工程” 如果不指定任何 “exclude”
或“files”
,文件夾里的所有文件包括tsconfig.json
和所有的子目錄都會(huì)在編譯列表里。 如果你想利用 “exclude”
排除某些文件,甚至你想指定所有要編譯的文件列表,請(qǐng)使用“files”
。
有些是被tsconfig.json
自動(dòng)加入的。 它不會(huì)涉及到上面討論的模塊解析。 如果編譯器識(shí)別出一個(gè)文件是模塊導(dǎo)入目標(biāo),它就會(huì)加到編譯列表里,不管它是否被排除了。
因此,要從編譯列表中排除一個(gè)文件,你需要在排除它的同時(shí),還要排除所有對(duì)它進(jìn)行import
或使用了/// <reference path="..." />
指令的文件。
更多建議: