TypeScript Module Resolution解析過程
Module Resolution
模塊解析是編譯器用來確定導入所指內(nèi)容的過程。考慮像 import { a } from "moduleA"; 這樣的導入語句。為了檢查 a 的任何使用,編譯器需要確切地知道它代表什么,并且需要檢查它的定義 moduleA。
此時,編譯器會問“moduleA 的形狀是什么?”雖然這聽起來很簡單,但 moduleA 可以在您自己的 .ts/.tsx 文件之一中定義,或者在您的代碼所依賴的 .d.ts 中定義。
首先,編譯器會嘗試定位一個代表導入模塊的文件。為此,編譯器遵循兩種不同策略之一:classical 或 Node。這些策略告訴編譯器去哪里尋找 moduleA。
如果這不起作用并且模塊名稱是非相關(guān)的(在“moduleA”的情況下,它是),那么編譯器將嘗試定位一個環(huán)境模塊聲明。接下來我們將介紹非相對導入。
最后,如果編譯器無法解析模塊,它將記錄一個錯誤。在這種情況下,錯誤類似于錯誤 TS2307:找不到模塊 'moduleA'。
Relative vs. Non-relative module imports
根據(jù)模塊引用是相對的還是非相對的,模塊導入的解析方式不同。
相對導入是以 /、./ 或 ../ 開頭的導入。 一些例子包括:
import Entry from "./components/Entry"; import { DefaultHeaders } from "../constants/http"; import "/mod";
任何其他導入都被認為是非相關(guān)的。 一些例子包括:
import * as $ from "jquery"; import { Component } from "@angular/core";
相對導入是相對于導入文件解析的,無法解析為環(huán)境模塊聲明。 您應該對自己的模塊使用相對導入,以保證在運行時保持其相對位置。
Module Resolution Strategies
有兩種可能的模塊解析策略:Node 和 Classic。 您可以使用 --moduleResolution 標志來指定模塊解析策略。 如果未指定,則 --module commonjs 默認為 Node,否則默認為 Classic(包括 --module 設(shè)置為 amd、system、umd、es2015、esnext 等時)。
注意:Node 模塊解析是 TypeScript 社區(qū)中最常用的,推薦用于大多數(shù)項目。 如果您在 TypeScript 中遇到導入和導出的解析問題,請嘗試設(shè)置 moduleResolution: "node" 以查看它是否解決了問題。
Classical 解析策略
這曾經(jīng)是 TypeScript 的默認解析策略。 如今,這種策略主要是為了向后兼容。
相對導入將相對于導入文件進行解析。 因此,在源文件 /root/src/folder/A.ts 中 import { b } from "./moduleB" 將導致以下查找:
- /root/src/folder/moduleB.ts
- /root/src/folder/moduleB.d.ts
然而,對于非相關(guān)模塊導入,編譯器從包含導入文件的目錄開始沿著目錄樹向上走,試圖找到匹配的定義文件。
例如:
在源文件 /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 模塊非常重要。傳統(tǒng)上,Node.js 中的導入是通過調(diào)用名為 require 的函數(shù)來執(zhí)行的。 Node.js 采取的行為將根據(jù) require 是相對路徑還是非相對路徑而有所不同。
相對路徑相當簡單。例如,讓我們考慮一個位于 /root/src/moduleA.js 的文件,其中包含 import var x = require("./moduleB");
Node.js 按以下順序解析該導入:
- 詢問名為 /root/src/moduleB.js 的文件是否存在。
- 詢問文件夾 /root/src/moduleB 是否包含指定“主”模塊的名為 package.json 的文件。在我們的示例中,如果 Node.js 發(fā)現(xiàn)文件 /root/src/moduleB/package.json 包含 { "main": "lib/mainModule.js" },那么 Node.js 將引用 /root/src/moduleB/lib/mainModule.js。
- 詢問文件夾 /root/src/moduleB 是否包含名為 index.js 的文件。該文件被隱式視為該文件夾的“主”模塊。
但是,對非相關(guān)模塊名稱的解析以不同的方式執(zhí)行。 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) 中跳轉(zhuǎn)了一個目錄。
您可以在 Node.js 文檔中閱讀有關(guān)從 node_modules 加載模塊的更多信息。
How TypeScript resolves 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
不要被這里的步驟數(shù)嚇倒——TypeScript 仍然只在步驟 (9) 和 (17) 中兩次跳轉(zhuǎn)目錄。這實際上并不比 Node.js 本身所做的更復雜。
Additional module resolution flags
項目源布局有時與輸出布局不匹配。 通常一組構(gòu)建步驟會生成最終輸出。 其中包括將 .ts 文件編譯為 .js,以及將依賴項從不同的源位置復制到單個輸出位置。 最終結(jié)果是模塊在運行時的名稱可能與包含其定義的源文件的名稱不同。 或者最終輸出中的模塊路徑可能在編譯時與其對應的源文件路徑不匹配。
TypeScript 編譯器有一組額外的標志來通知編譯器預期發(fā)生在源上的轉(zhuǎn)換以生成最終輸出。
需要注意的是,編譯器不會執(zhí)行任何這些轉(zhuǎn)換; 它只是使用這些信息來指導將模塊導入解析到其定義文件的過程。
Base Url
在使用 AMD 模塊加載器的應用程序中,使用 baseUrl 是一種常見做法,其中模塊在運行時“部署”到單個文件夾。 這些模塊的源代碼可以位于不同的目錄中,但是構(gòu)建腳本會將它們放在一起。
設(shè)置 baseUrl 通知編譯器在哪里可以找到模塊。 假定所有具有非相對名稱的模塊導入都與 baseUrl 相關(guān)。
baseUrl 的值確定為:
(1)baseUrl 命令行參數(shù)的值(如果給定的路徑是相對的,則根據(jù)當前目錄計算)
(2)'tsconfig.json' 中 baseUrl 屬性的值(如果給定的路徑是相對的,則根據(jù) 'tsconfig.json' 的位置計算)
請注意,相對模塊導入不會受到設(shè)置 baseUrl 的影響,因為它們總是相對于它們的導入文件進行解析。
您可以在 RequireJS 和 SystemJS 文檔中找到有關(guān) baseUrl 的更多文檔。
path mapping
有時模塊并不直接位于 baseUrl 下。例如,對模塊“jquery”的導入將在運行時轉(zhuǎn)換為“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" } } }
如何檢查 TypeScript 模塊解析過程
如前所述,編譯器在解析模塊時可以訪問當前文件夾之外的文件。 在診斷模塊未解析的原因或解析為不正確的定義時,這可能很困難。 使用 --traceResolution 啟用編譯器模塊解析跟蹤可以深入了解模塊解析過程中發(fā)生的情況。
假設(shè)我們有一個使用 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
結(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'. ========
觸發(fā)模塊解析的源代碼位置:
======== 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’. ========
以上就是TypeScript Module Resolution解析過程的詳細內(nèi)容,更多關(guān)于TypeScript Module Resolution的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
TypeScript十大排序算法之選擇排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法之選擇排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊
這篇文章主要為大家介紹了ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊的實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06TypeScript數(shù)據(jù)結(jié)構(gòu)鏈表結(jié)構(gòu)?LinkedList教程及面試
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結(jié)構(gòu)鏈表結(jié)構(gòu)?LinkedList教程及面試,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02Webpack source map實戰(zhàn)分析詳解
這篇文章主要為大家介紹了Webpack source map示例分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12TypeScript與JavaScript的區(qū)別分析
TypeScript可以使用JavaScript中的所有代碼和編程概念,TypeScript是為了使JavaScript的開發(fā)變得更加容易而創(chuàng)建的。推薦先精通JS的的前提下再學習TS,這樣更有利于同時學習兩門語言。2022-12-12TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02