“这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战”
模块解析是编译器找出导入内容所指向地方的过程。以 import { a } from "moduleA" 为例,为例检查对象 a 的使用情况,编译器必须知道 a 的含义,并且需要检查 moduleA。
此时,编译器会问 “moduleA” 的类型是什么?当然,这个问题听起来似乎很简单,moduleA 在ts/tsx 文件中被定义,或者依赖于 .d.ts 文件。
首先,编译器会尝试定义一个可以代表被导入模块的文件。编译器会在如下两种策略中选择一种去定位文件:Classic 、Node。这两种策略会告诉编译器在哪里寻找 moduleA。
如果这两种都不起作用并且如果模块名称 non-relative ,编译器会尝试定位 ambient module declaration。
最终,如果不能解析模块,会打印错误。
Relative vs. Non-relative module imports
Relative: 以 / 活 ./ 或 ../ 开始的模块。并不能解析 ambient module declaration。
non-relative: "jquer",“@angular/core”等形式。可以解析 ambient module declaration
模块解析策略
如上所述,解析策略有两种。没有特殊指定是,对于commonjs模块,Node 是默认的策略;其他模块都是 Classic策略。可以通过--moduleResolution 选项来指定策略。
Classic
过去是TS 的默认解析策略。现在是一种向下兼容的策略。
对于 relative module :
在/root/src/folder/A.ts 文件中 import { b } from './moduleB' 的查找策略:
-
/root/src/folder/moduleB.ts
-
/root/src/folder/moduleB.d.ts
对于 non-relative module:
在/root/src/folder/A.ts 文件中 import { b } from '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是如何解析模块的。
relative module:var x = require("./moduleB")
1. /root/src/moduleB.js 是否存在
2. 文件夹 /root/src/moduleB 是否存在package.json,如果有就读取其中的 main 字段所指向的文件
3. 文件夹 /root/src/moduleB 中是否存在 index.js
non-relative module:var x = require("moduleB")
递归地从当前文件夹的 node_modules 往上查找
-
/root/src/node_modules/moduleB.js
-
/root/src/node_modules/moduleB/package.json
(if it specifies a"main"
property) -
/root/src/node_modules/moduleB/index.js
-
/root/node_modules/moduleB.js
-
/root/node_modules/moduleB/package.json
(if it specifies a"main"
property) -
/root/node_modules/moduleB/index.js
-
/node_modules/moduleB.js
-
/node_modules/moduleB/package.json
(if it specifies a"main"
property) -
/node_modules/moduleB/index.js
现在了解一下 TS 是如何加载模块的。
realtive module:文件查找顺序 ts -> tsx -> .d.ts -> packages.json -> index.ts -> index.tsx -> index.d.ts。其中 package.json 中会查找 types 字段对应的值。
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
(if it specifies a"types"
property)/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
non-relative module:
与 node 相同,也是递归查找。不过多了tsx文件 与 @types 文件夹查找。
其中 ts -> tsx -> d.ts -> packages.json ( types ) -> @types/ .d.ts -> index.ts -> index.tsx -> index.d.ts。
-
/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
(if it specifies a"types"
property) -
/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
(if it specifies a"types"
property) -
/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
(if it specifies a"types"
property) -
/node_modules/@types/moduleB.d.ts
-
/node_modules/moduleB/index.ts
-
/node_modules/moduleB/index.tsx
-
/node_modules/moduleB/index.d.ts
额外的模块解析参数 Additional module resolution flags
Base URL
通知编译器去哪里寻找模块。所有的 non-relative 模块都会根据 baseUrl 读取。而 relative module 不会受影响。
-> 命令行参数:如果值是相对路径,那么就是当前文件夹
-> tsconfig 配置:如果值是相对路径,那么就是 tsconfig 配置文件的相对路径
Path mapping
可以将模块名映射到指定的文件/文件夹。
注意:paths 的值是相对于 baseUrl 的。所以如果指定了 path,baseUrl 也应该指定。
{
"compilerOptions":{
"baseUrl": ".",
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"]
}
}
}
paths 值也可以是一个数组,作为查找时候的降级方案。
projectRoot
├── folder1
│ ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│ └── file2.ts
├── generated
│ ├── folder1
│ └── folder2
│ └── file3.ts
└── tsconfig.json
{ "compilerOptions": { "baseUrl": ".", "paths": { "*": ["*", "generated/*"] } }}
虚拟目录 rootDirs
有时候会从多目录中读取文件,最终将他们编译成一份文件。这时候就可以使用rootDirs将他们“整合“到同一个虚拟的根目录下面。
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
{ "compilerOptions": { "rootDirs": ["src/views", "generated/templates/views"] }}
除此之外,rootDirs 有更强大的功能。
比如在国际化的时候,需要打包成不同的语言。引用路径中包含特殊字符 ./#{locale}/messages
在开发的时候可以进行如下配置
{ "compilerOptions": { "rootDirs": ["src/zh", "src/de", "src/#{locale}"] }}
在导入的时候,会把 "src/#{locale}" -> 解析成 ”src/zh“。
Tracing module resolution
如下指令,可以输出 TS 解析模块的顺序
tsc --traceResolution
使用 --noResolve
通常情况下,编译器在编译前会解析所有导入的模块。每次成功解析一个 import ,被解析的文件就会被添加到集合内部。稍后进行处理。
-- noResolve 编译选项让编译器不要 ”添加“ 任何没在命令行指定的模块文件。
app.ts
import * as A from "moduleA"; // OK, 'moduleA' passed on the command-lineimport * as B from "moduleB"; // Error TS2307: Cannot find module 'moduleB'.
tsc app.ts moduleA.ts --noResolve