TypeScript——模塊解析(2)

路徑映射

有時(shí)模塊不是直接放在baseUrl下面。 比如,充分 "jquery"模塊地導(dǎo)入,在運(yùn)行時(shí)可能被解釋為"node_modules/jquery/dist/jquery.slim.min.js"。 加載器使用映射配置來將模塊名映射到運(yùn)行時(shí)的文件,查看 RequireJs documentation和SystemJS documentation。

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

{

? "compilerOptions": {

? ? "baseUrl": ".", // This must be specified if "paths" is.

? ? "paths": {

? ? ? "jquery": ["node_modules/jquery/dist/jquery"] // 此處映射是相對(duì)于"baseUrl"

? ? }

? }

}

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

通過"paths"我們還可以指定復(fù)雜的映射,包括指定多個(gè)回退位置。 假設(shè)在一個(gè)工程配置里,有一些模塊位于一處,而其它的則在另個(gè)的位置。 構(gòu)建過程會(huì)將它們集中至一處。 工程結(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)入會(huì)在以下兩個(gè)位置查找:

"*": 表示名字不發(fā)生改變,所以映射為<moduleName> => <baseUrl>/<moduleName>

"generated/*"表示模塊名添加了“generated”前綴,所以映射為<moduleName> => <baseUrl>/generated/<moduleName>

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

導(dǎo)入'folder1/file2'

匹配'*'模式且通配符捕獲到整個(gè)名字。

嘗試列表里的第一個(gè)替換:'*' -> folder1/file2。

替換結(jié)果為非相對(duì)名 - 與baseUrl合并 -> projectRoot/folder1/file2.ts。

文件存在。完成。

導(dǎo)入'folder2/file3'

匹配'*'模式且通配符捕獲到整個(gè)名字。

嘗試列表里的第一個(gè)替換:'*' -> folder2/file3。

替換結(jié)果為非相對(duì)名 - 與baseUrl合并 -> projectRoot/folder2/file3.ts。

文件不存在,跳到第二個(gè)替換。

第二個(gè)替換:'generated/*' -> generated/folder2/file3。

替換結(jié)果為非相對(duì)名 - 與baseUrl合并 -> projectRoot/generated/folder2/file3.ts。

文件存在。完成。

利用rootDirs指定虛擬目錄

有時(shí)多個(gè)目錄下的工程源文件在編譯時(shí)會(huì)進(jìn)行合并放在某個(gè)輸出目錄下。 這可以看做一些源目錄創(chuàng)建了一個(gè)“虛擬”目錄。

利用rootDirs,可以告訴編譯器生成這個(gè)虛擬目錄的roots; 因此編譯器可以在“虛擬”目錄下解析相對(duì)模塊導(dǎo)入,就 好像它們被合并在了一起一樣。

比如,有下面的工程結(jié)構(gòu):

src

└── views

? ? └── view1.ts (imports './template1')

? ? └── view2.ts

generated

└── templates

? ? ? ? └── views

? ? ? ? ? ? └── template1.ts (imports './view2')

src/views里的文件是用于控制UI的用戶代碼。 generated/templates是UI模版,在構(gòu)建時(shí)通過模版生成器自動(dòng)生成。 構(gòu)建中的一步會(huì)將 /src/views和/generated/templates/views的輸出拷貝到同一個(gè)目錄下。 在運(yùn)行時(shí),視圖可以假設(shè)它的模版與它同在一個(gè)目錄下,因此可以使用相對(duì)導(dǎo)入 "./template"。

可以使用"rootDirs"來告訴編譯器。 "rootDirs"指定了一個(gè)roots列表,列表里的內(nèi)容會(huì)在運(yùn)行時(shí)被合并。 因此,針對(duì)這個(gè)例子, tsconfig.json如下:

{

? "compilerOptions": {

? ? "rootDirs": [

? ? ? "src/views",

? ? ? "generated/templates/views"

? ? ]

? }

}

每當(dāng)編譯器在某一rootDirs的子目錄下發(fā)現(xiàn)了相對(duì)模塊導(dǎo)入,它就會(huì)嘗試從每一個(gè)rootDirs中導(dǎo)入。

rootDirs的靈活性不僅僅局限于其指定了要在邏輯上合并的物理目錄列表。它提供的數(shù)組可以包含任意數(shù)量的任何名字的目錄,不論它們是否存在。這允許編譯器以類型安全的方式處理復(fù)雜捆綁(bundles)和運(yùn)行時(shí)的特性,比如條件引入和工程特定的加載器插件。

設(shè)想這樣一個(gè)國(guó)際化的場(chǎng)景,構(gòu)建工具自動(dòng)插入特定的路徑記號(hào)來生成針對(duì)不同區(qū)域的捆綁,比如將#{locale}做為相對(duì)模塊路徑./#{locale}/messages的一部分。在這個(gè)假定的設(shè)置下,工具會(huì)枚舉支持的區(qū)域,將抽像的路徑映射成./zh/messages,./de/messages等。

假設(shè)每個(gè)模塊都會(huì)導(dǎo)出一個(gè)字符串的數(shù)組。比如./zh/messages可能包含:

export default [

? ? "您好嗎",

? ? "很高興認(rèn)識(shí)你"

];

利用rootDirs我們可以讓編譯器了解這個(gè)映射關(guān)系,從而也允許編譯器能夠安全地解析./#{locale}/messages,就算這個(gè)目錄永遠(yuǎn)都不存在。比如,使用下面的tsconfig.json:

{

? "compilerOptions": {

? ? "rootDirs": [

? ? ? "src/zh",

? ? ? "src/de",

? ? ? "src/#{locale}"

? ? ]

? }

}

編譯器現(xiàn)在可以將import messages from './#{locale}/messages'解析為import messages from './zh/messages'用做工具支持的目的,并允許在開發(fā)時(shí)不必了解區(qū)域信息。

跟蹤模塊解析

如之前討論,編譯器在解析模塊時(shí)可能訪問當(dāng)前文件夾外的文件。 這會(huì)導(dǎo)致很難診斷模塊為什么沒有被解析,或解析到了錯(cuò)誤的位置。 通過 --traceResolution啟用編譯器的模塊解析跟蹤,它會(huì)告訴我們?cè)谀K解析過程中發(fā)生了什么。

假設(shè)我們有一個(gè)使用了typescript模塊的簡(jiǎn)單應(yīng)用。 app.ts里有一個(gè)這樣的導(dǎo)入import * as ts from "typescript"。

│? tsconfig.json

├───node_modules

│? └───typescript

│? ? ? └───lib

│? ? ? ? ? ? ? typescript.d.ts

└───src

? ? ? ? app.ts

使用--traceResolution調(diào)用編譯器。

tsc --traceResolution

輸出結(jié)果如下:

======== 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'. ========

需要留意的地方

導(dǎo)入的名字及位置

======== Resolving module 'typescript' from 'src/app.ts'. ========

編譯器使用的策略

Module resolution kind is not specified, using 'NodeJs'.

從npm加載types

'package.json' has 'types' field './lib/typescript.d.ts' that references 'node_modules/typescript/lib/typescript.d.ts'.

最終結(jié)果

======== Module name 'typescript' was successfully resolved to 'node_modules/typescript/lib/typescript.d.ts'. ========

使用--noResolve

正常來講編譯器會(huì)在開始編譯之前解析模塊導(dǎo)入。 每當(dāng)它成功地解析了對(duì)一個(gè)文件 import,這個(gè)文件被會(huì)加到一個(gè)文件列表里,以供編譯器稍后處理。

--noResolve編譯選項(xiàng)告訴編譯器不要添加任何不是在命令行上傳入的文件到編譯列表。 編譯器仍然會(huì)嘗試解析模塊,但是只要沒有指定這個(gè)文件,那么它就不會(huì)被包含在內(nèi)。

比如

app.ts

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)樗诿钚猩现付恕?/p>

找不到moduleB,因?yàn)闆]有在命令行上傳遞。

常見問題

為什么在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="..." />指令的文件。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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