模块解析

当在程序中导入了一个模块时,编译器会去查找并读取导入模块的定义,我们将该过程叫作模块解析。模块解析的过程受以下因素影响:

  • 相对模块导入与非相对模块导入。

  • 模块解析策略。

  • 模块解析编译选项。

本节将先介绍如何判断相对模块导入与非相对模块导入,然后介绍 TypeScript 中的两种模块解析策略是如何解析相对模块导入和非相对模块导入的,最后还会介绍一些与模块解析相关的编译选项,这些编译选项能够配置模块解析的具体行为。

相对模块导入

在模块导入语句中,若模块名以下列符号开始,那么它就是相对模块导入。

  • /

  • ./

  • ../

/ 表示系统根目录,示例如下:

import { a } from '/mod';

此例中,导入了系统根目录下的 mod 模块。

./ 表示当前目录,示例如下:

import { a } from './mod';

此例中,导入了当前目录下的 mod 模块。

../ 表示上一级目录,示例如下:

import { a } from '../mod';

此例中,导入了上一级目录下的 mod 模块。

在解析相对模块导入语句中的模块名时,将参照当前模块文件所在的目录位置。

例如,有如下目录结构的工程:

C:\
|-- app
|   |-- foo
|   |   |-- a.ts
|   |   `-- b.ts
|   `-- bar
|       `-- c.ts
`-- d.ts

a.ts 模块中,可以使用如下相对模块导入语句来导入 b.ts 模块:

import b from './b';

b.ts 模块中,可以使用如下相对模块导入语句来导入 c.ts 模块:

import c from '../bar/c';

c.ts 模块中,可以使用如下相对模块导入语句来导入系统根目录下的 d.ts 模块:

import d from '/d';

非相对模块导入

在模块导入语句中,若模块名不是以 /./../ 符号开始,那么它就是非相对模块导入。例如,下面的两个导入语句都是非相对模块导入:

import { Observable } from 'rxjs';

import { Component } from '@angular/core';

模块解析策略

TypeScript 提供了两种模块解析策略,分别是:

  • Classic 策略。

  • Node 策略。

模块解析策略可以使用 --moduleResolution 编译选项来指定,示例如下:

tsc --moduleResolution Classic

关于 tsc 命令的详细介绍请参考 8.1 节。

模块解析策略也可以在 tsconfig.json 配置文件中使用 moduleResolution 属性来设置,示例如下:

{
    "compilerOptions": {
        "moduleResolution": "Node"
    }
}

关于 tsconfig.json 的详细介绍请参考 8.3 节。

当没有设置模块的解析策略时,默认的模块解析策略与 --module 编译选项的值有关。--module 编译选项用来设置编译生成的 JavaScript 代码使用的模块格式。关于 --module 编译选项的详细介绍请参考 7.6.8 节。

--module 编译选项的值为 CommonJS,则默认的模块解析策略为 Node。示例如下:

tsc --module CommonJS

这等同于:

tsc --module CommonJS --moduleResolution Node

--module 编译选项的值不为 CommonJS,则默认的模块解析策略为 Classic。示例如下:

tsc --module ES6

这等同于:

tsc --module ES6 --moduleResolution Classic

模块解析策略之Classic

Classic 模块解析策略是 TypeScript 最早提供的模块解析策略,它尝试将模块名视为一个文件进行解析。

解析相对模块导入

Classic 模块解析策略下,相对模块导入的解析过程包含以下两个阶段:

  1. 将导入的模块名视为文件,并在指定目录中查找 TypeScript 文件。

  2. 将导入的模块名视为文件,并在指定目录中查找 JavaScript 文件。

假设有如下目录结构的工程:

C:\app
`-- a.ts

a.ts 模块文件中使用相对模块导入语句导入了模块 b。示例如下:

import * as B from './b';

Classic 模块解析策略下,模块 b 的解析过程如下:

  1. 查找 C:/app/b.ts

  2. 查找 C:/app/b.tsx

  3. 查找 C:/app/b.d.ts

  4. 查找 C:/app/b.js

  5. 查找 C:/app/b.jsx

在查找模块文件的过程中,一旦找到匹配的文件,就会停止搜索。

解析非相对模块导入

Classic 模块解析策略下,非相对模块导入的解析过程包含以下三个阶段:

  1. 将导入的模块名视为文件,从当前目录开始向上遍历至系统根目录,并查找 TypeScript 文件。

  2. 将导入的模块名视为安装的声明文件,从当前目录开始向上遍历至系统根目录,并在每一级目录下的 node_modules/@types 文件夹中查找安装的声明文件。

  3. 将导入的模块名视为文件,从当前目录开始向上遍历至系统根目录,并查找 JavaScript 文件。

Classic 模块解析策略下,非相对模块导入的解析与相对模块导入的解析相比较有以下几点不同:

  • 解析相对模块导入时只会查找指定的一个目录;解析非相对模块导入时会向上遍历整个目录树。

  • 解析非相对模块导入比解析相对模块导入多了一步,即在每一层目录的 node_modules/@types 文件夹中查找是否安装了要导入的声明文件。

例如,有如下目录结构的工程:

C:\app
`-- a.ts

a.ts 模块文件中使用非相对模块导入语句导入了模块 b。示例如下:

import * as B from 'b';

下面分别介绍在 Classic 模块解析策略下,模块 b 的解析过程。

第一阶段,遍历目录树并查找 TypeScript 文件。具体步骤如下:

  1. 查找文件 C:/app/b.ts

  2. 查找文件 C:/app/b.tsx

  3. 查找文件 C:/app/b.d.ts

  4. 查找文件 C:/b.ts

  5. 查找文件 C:/b.tsx

  6. 查找文件 C:/b.d.ts

第二阶段,遍历目录树并在每一级目录下查找是否在 node_modules/@types 文件夹中安装了要导入的声明文件。具体步骤如下:

  1. 查找文件 C:/app/node_modules/@types/b.d.ts。

  2. 如果 C:/app/node_modules/@types/b/package.json 文件存在,且包含了 typings 属性或 types 属性(假设属性值为 typings.d.ts),那么:

    • ① 查找文件 C:/app/node_modules/@types/b/typings.d.ts。

    • ② 查找文件 C:/app/node_modules/@types/b/typings.d.ts.ts(注意,尝试添加 .ts 文件扩展名)。

    • ③ 查找文件 C:/app/node_modules/@types/b/typings.d.ts.tsx(注意,尝试添加 .tsx 文件扩展名)。

    • ④ 查找文件 C:/app/node_modules/@types/b/typings.d.ts.d.ts(注意,尝试添加 .d.ts 文件扩展名)。

    • ⑤ 若存在目录 C:/app/node_modules/@types/b/typings.d.ts/,则:

      1. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.ts。

      2. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.tsx。

      3. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.d.ts。

  3. 查找文件 C:/app/node_modules/@types/b/index.d.ts。

  4. 查找文件 C:/node_modules/@types/b.d.ts(注意,第 4~6 步与第 1~3 步的流程相同,区别是在上一级目录 C:/ 中继续搜索)。

  5. 如果文件 C:/node_modules/@types/b/package.json 存在,且包含了 typings 属性或 types 属性(假设属性值为 ./typings.d.ts),那么:

    • ① 查找文件 C:/node_modules/@types/b/typings.d.ts。

    • ② 查找文件 C:/node_modules/@types/b/typings.d.ts.ts(注意,尝试添加 .ts 文件扩展名)。

    • ③ 查找文件 C:/node_modules/@types/b/typings.d.ts.tsx(注意,尝试添加 .tsx 文件扩展名)。

    • ④ 查找文件 C:/node_modules/@types/b/typings.d.ts.d.ts(注意,尝试添加 .d.ts 文件扩展名)。

    • ⑤ 若存在目录 C:/node_modules/@types/b/typings.d.ts/,则:

      1. 查找文件 C:/node_modules/@types/b/typings.d.ts/index.ts。

      2. 查找文件 C:/node_modules/@types/b/typings.d.ts/index.tsx。

      3. 查找文件 C:/node_modules/@types/b/typings.d.ts/index.d.ts。

  6. 查找文件 C:/node_modules/@types/b/index.d.ts。

第三阶段,遍历目录树并查找 JavaScript 文件。具体步骤如下:

  1. 查找文件 C:/app/b.js。

  2. 查找文件 C:/app/b.jsx。

  3. 查找文件 C:/b.js。

  4. 查找文件 C:/b.jsx。

在查找模块文件的过程中,一旦找到匹配的文件,就会停止搜索。

模块解析策略之Node

Node 模块解析策略是 TypeScript 1.6 版本中引入的,它因模仿了 Node.js 的模块解析策略而得名。在实际工程中,我们可能更想要使用 Node 模块解析策略,因为它的功能更加丰富。

解析相对模块导入

Node 模块解析策略下,相对模块导入的解析过程包含以下几个阶段:

  1. 将导入的模块名视为文件,并在指定目录中查找 TypeScript 文件。

  2. 将导入的模块名视为目录,并在该目录中查找 package.json 文件,然后解析 package.json 文件中的 typings 属性和 types 属性。

  3. 将导入的模块名视为文件,并在指定目录中查找 JavaScript 文件。

  4. 将导入的模块名视为目录,并在该目录中查找 package.json 文件,然后解析 package.json 文件中的 main 属性。

假设有如下目录结构的工程:

C:\app
`-- a.ts

a.ts 模块文件中使用相对模块导入语句导入了模块 b。示例如下:

import * as B from './b';

下面分别介绍在 Node 模块解析策略下,模块 b 的解析过程。

第一阶段,将导入的模块名视为文件,并在指定目录中依次查找 TypeScript 文件。具体步骤如下:

  1. 查找文件 C:/app/b.ts。

  2. 查找文件 C:/app/b.tsx。

  3. 查找文件 C:/app/b.d.ts。

第二阶段,将导入的模块名视为目录,并在该目录中查找 package.json 文件,然后解析 package.json 文件中的 typings 属性和 types 属性。具体步骤如下:

  1. 如果 C:/app/b/package.json 文件存在,且包含了 typings 属性或 types 属性(假设属性值为 typings.d.ts),那么:

    • ① 查找文件 C:/app/b/typings.d.ts。

    • ② 查找文件 C:/app/b/typings.d.ts.ts(注意,尝试添加 .ts 文件扩展名)。

    • ③ 查找文件 C:/app/b/typings.d.ts.tsx(注意,尝试添加 .tsx 文件扩展名)。

    • ④ 查找文件 C:/app/b/typings.d.ts.d.ts(注意,尝试添加 .d.ts 文件扩展名)。

    • ⑤ 如果存在目录 C:/app/b/typings.d.ts/,那么:

      1. 查找文件 C:/app/b/typings.d.ts/index.ts。

      2. 查找文件 C:/app/b/typings.d.ts/index.tsx。

      3. 查找文件 C:/app/b/typings.d.ts/index.d.ts。

  2. 查找文件 C:/app/b/index.ts。

  3. 查找文件 C:/app/b/index.tsx。

  4. 查找文件 C:/app/b/index.d.ts。

第三阶段,将导入的模块名视为文件,并在指定目录中依次查找 JavaScript 文件。具体步骤如下:

  1. 查找文件 C:/app/b.js。

  2. 查找文件 C:/app/b.jsx。

第四阶段,将导入的模块名视为目录,并在该目录中查找 package.json 文件,然后解析 package.json 文件中的 main 属性。具体步骤如下:

  1. 如果 C:/app/b/package.json 文件存在,且包含了 main 属性(假设属性值为 main.js),那么:

    • ① 查找文件 C:/app/b/main.js。

    • ② 查找文件 C:/app/b/main.js.js(注意,尝试添加 .js 文件扩展名)。

    • ③ 查找文件 C:/app/b/main.js.jsx(注意,尝试添加 .jsx 文件扩展名)。

    • ④ 查找文件 C:/app/b/main.js(注意,尝试删除文件扩展名后再添加 .js 文件扩展名)。

    • ⑤ 查找文件 C:/app/b/main.jsx(注意,尝试删除文件扩展名后再添加 .jsx 文件扩展名)。

    • ⑥ 如果存在目录 C:/app/b/main.js/,那么:

      1. 查找文件 C:/app/b/main.js/index.js。

      2. 查找文件 C:/app/b/main.js/index.jsx。

  2. 查找文件 C:/app/b/index.js

  3. 查找文件 C:/app/b/index.jsx

在查找模块文件的过程中,一旦找到匹配的文件,就会停止搜索。

解析非相对模块导入

Node 模块解析策略下,非相对模块导入的解析过程包含以下几个阶段:

  1. 将导入的模块名视为文件,并在当前目录下的 node_modules 文件夹中查找 TypeScript 文件。

  2. 将导入的模块名视为目录,并在当前目录下的 node_modules 文件夹中查找给定目录下的 package.json 文件,然后解析 package.json 文件中的 typings 属性和 types 属性。

  3. 将导入的模块名视为安装的声明文件,并在当前目录下的 node_modules/@types 文件夹中查找安装的声明文件。

  4. 重复第1~3步的查找过程,从当前目录开始向上遍历至系统根目录。

  5. 将导入的模块名视为文件,并在当前目录下的 node_modules 文件夹中查找 JavaScript 文件。

  6. 将导入的模块名视为目录,并在当前目录下的 node_modules 文件夹中查找给定目录下的 package.json 文件,然后解析 package.json 文件中的 main 属性。

  7. 重复第5~6步的查找过程,从当前目录开始向上遍历至系统根目录。

假设有如下目录结构的工程:

C:\app
`-- a.ts

a.ts 模块文件中使用非相对模块导入语句导入了模块 b。示例如下:

import * as B from 'b';

下面分别介绍在 Node 模块解析策略下,模块 b 的解析过程。

第一阶段,将导入的模块名视为文件,并在当前目录下的 node_modules 文件夹中查找 TypeScript 文件。具体步骤如下:

  1. 查找文件 C:/app/node_modules/b.ts。

  2. 查找文件 C:/app/node_modules/b.tsx。

  3. 查找文件 C:/app/node_modules/b.d.ts。

第二阶段,将导入的模块名视为目录,并在当前目录下的 node_modules 文件夹中查找给定目录下的 package.json文件,然后解析 package.json 文件中的 typings 属性和 types 属性。具体步骤如下:

  1. 如果 C:/app/node_modules/b/package.json 文件存在,且包含了 typings 属性或 types 属性(假设属性值为 typings.d.ts),那么:

    • ① 查找文件 C:/app/node_modules/b/typings.d.ts。

    • ② 查找文件 C:/app/node_modules/b/typings.d.ts.ts(注意,尝试添加 .ts 文件扩展名)。

    • ③ 查找文件 C:/app/node_modules/b/typings.d.ts.tsx(注意,尝试添加 .tsx 文件扩展名)。

    • ④ 查找文件 C:/app/node_modules/b/typings.d.ts.d.ts(注意,尝试添加 .d.ts 文件扩展名)。

    • ⑤ 若存在目录 C:/app/node_modules/b/typings.d.ts/,则:

      1. 查找文件 C:/app/node_modules/b/typings.d.ts/index.ts。

      2. 查找文件 C:/app/node_modules/b/typings.d.ts/index.tsx。

      3. 查找文件 C:/app/node_modules/b/typings.d.ts/index.d.ts。

  2. 查找文件 C:/app/node_modules/b/index.ts。

  3. 查找文件 C:/app/node_modules/b/index.tsx。

  4. 查找文件 C:/app/node_modules/b/index.d.ts。

第三阶段,将导入的模块名视为安装的声明文件,并在当前目录下的 node_modules/@types 文件夹中查找安装的声明文件。具体步骤如下:

  1. 查找文件 C:/app/node_modules/@types/b.d.ts。

  2. 如果 C:/app/node_modules/@types/b/package.json 文件存在,且包含了 typings 属性或 types 属性(假设属性值为 typings.d.ts),那么:

    • ① 查找文件 C:/app/node_modules/@types/b/typings.d.ts。

    • ② 查找文件 C:/app/node_modules/@types/b/typings.d.ts.ts(注意,尝试添加 .ts 文件扩展名)。

    • ③ 查找文件 C:/app/node_modules/@types/b/typings.d.ts.tsx(注意,尝试添加 .tsx 文件扩名)。

    • ④ 查找文件 C:/app/node_modules/@types/b/typings.d.ts.d.ts(注意,尝试添加 .d.ts 文件扩展名)。

    • ⑤ 如果存在目录 C:/app/node_modules/@types/b/typings.d.ts/,那么:

      1. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.ts。

      2. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.tsx。

      3. 查找文件 C:/app/node_modules/@types/b/typings.d.ts/index.d.ts。

  3. 查找文件 C:/app/node_modules/@types/b/index.d.ts。

第四阶段,重复第一阶段至第三阶段的查找步骤,只不过是在上一级目录 C:/ 下继续搜索。

第五阶段,将导入的模块名视为文件,并在当前目录中的 node_modules 文件夹下查找 JavaScript 文件:

  1. 查找文件 C:/app/node_modules/b.js。

  2. 查找文件 C:/app/node_modules/b.jsx。

第六阶段,将导入的模块名视为目录,并在当前目录下的 node_modules 文件夹中查找给定目录下的 package.json 文件,然后解析 package.json 文件中的 main 属性。具体步骤如下:

  1. 如果 C:/app/node_modules/b/package.json 文件存在,且包含了 main 属性(假设属性值为 main.js),那么:

    • ① 查找文件 C:/app/node_modules/b/main.js。

    • ② 查找文件 C:/app/node_modules/b/main.js.js(注意,尝试添加 .js 文件扩展名)。

    • ③ 查找文件 C:/app/node_modules/b/main.js.jsx(注意,尝试添加 .jsx 文件扩展名)。

    • ④ 查找文件 C:/app/node_modules/b/main.js(注意,尝试删除文件扩展名后再添加 .js 文件扩展名)。

    • ⑤ 查找文件 C:/app/node_modules/b/main.jsx(注意,尝试删除文件扩展名后再添加 .jsx 文件扩展名)。

    • ⑥ 如果存在目录 C:/app/node_modules/b/main.js/,那么:

      1. 查找文件 C:/app/node_modules/b/main.js/index.js。

      2. 查找文件 C:/app/node_modules/b/main.js/index.jsx。

  2. 查找文件 C:/app/node_modules/b/index.js。

  3. 查找文件 C:/app/node_modules/b/index.jsx。

第七阶段,重复第五阶段至第六阶段的查找步骤,只不过是在上一级目录 C:/ 下继续搜索。

在查找模块文件的过程中,一旦找到匹配的文件,就会停止搜索。

--baseUrl

--baseUrl 编译选项用来设置非相对模块导入的基准路径。在解析相对模块导入时,将不受 --baseUrl 编译选项值的影响。

设置—​baseUrl

该编译选项既可以在命令行上指定,也可以在 tsconfig.json 配置文件中进行设置。

在命令行上使用 --baseUrl 编译选项,示例如下:

tsc --baseUrl ./

此例中,将 --baseUrl 编译选项的值设置为当前目录 ./,参照的是执行 tsc 命令时所在的目录。

tsconfig.json 配置文件中使用 baseUrl 属性来设置 --baseUrl 编译选项,示例如下:

{
    "compilerOptions": {
        "baseUrl": "./"
    }
}

此例中,将 baseUrl 设置为当前目录 ./,参照的是 tsconfig.json 配置文件所在的目录。

解析—​baseUrl

当设置了 --baseUrl 编译选项时,非相对模块导入的解析过程包含以下几个阶段:

  1. 根据 --baseUrl 的值和导入的模块名,计算出导入模块的路径。

  2. 将导入的模块名视为文件,并查找 TypeScript 文件。

  3. 将导入的模块名视为目录,在该目录中查找 package.json 文件,然后解析 package.json 文件中的 typings 属性和 types 属性。

  4. 将导入的模块名视为目录,在该目录中查找 package.json 文件,然后解析 package.json 文件中的 main 属性。

  5. 忽略 --baseUrl 的设置并回退到使用 Classic 模块解析策略或 Node 模块解析策略来解析模块。

当设置了 --baseUrl 编译选项时,相对模块导入的解析过程不受影响,将使用设置的 Classic 模块解析策略或 Node 模块解析策略来解析模块。

假设有如下目录结构的工程:

C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

tsconfig.json 文件的内容如下:

{
    "compilerOptions": {
        "baseUrl": "./"
    }
}

此例中,将 --baseUrl 编译选项的值设置为 tsconfig.json 配置文件所在的目录,即 C:\app

a.ts 文件的内容如下:

import * as B from 'bar/b';

在解析非相对模块导入 'bar/b' 时,编译器会使用 --baseUrl 编译选项设置的基准路径 C:\app 计算出目标路径 C:\app\bar\b,然后根据上文列出的具体步骤去解析该模块。因此,最终能够成功地将模块 'bar/b' 解析为 C:\app\bar\b.ts

paths

paths 编译选项用来设置模块名和模块路径的映射,用于设置非相对模块导入的规则。

设置paths

paths 编译选项只能在 tsconfig.json 配置文件中设置,不支持在命令行上使用。由于 paths 是基于 --baseUrl 进行解析的,所以必须同时设置 --baseUrlpaths 编译选项。

假设有如下目录结构的工程:

C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

tsconfig.json 文件的内容如下:

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "b": ["bar/b"]
        }
    }
}

此例中的 paths 设置会将对模块 b 的非相对模块导入映射到 C:\app\bar\b 路径。

a.ts 文件的内容如下:

import * as B from 'b';

编译器在解析非相对模块导入 b 时,发现存在匹配的 paths 路径映射,因此会使用路径映射中的地址 C:\app\bar\b 作为模块路径去解析模块 b。

使用通配符

在设置 paths 时,还可以使用通配符 *,它能够匹配任意路径。

假设有如下目录结构的工程:

C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

tsconfig.json 文件的内容如下:

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

此例中的 paths 设置会将对模块 @bar/…​ 的导入映射到 C:\app\bar... 路径下。两个星号通配符代表相同的路径。

a.ts 文件的内容如下:

import * as B from '@bar/b';

编译器在解析非相对模块导入 '@bar/b' 时,发现存在匹配的 paths 路径映射,因此会使用路径映射后的地址 C:\app\bar\b 作为模块路径去解析模块 b。

使用场景

在大型工程中,程序会被拆分到不同的目录中,并且在每个目录下还会具体划分出子目录,这就可能导致出现如下模块导入语句:

import { logger } from '../../../../core/logger/logger';

此例中,先向上切换若干层目录找到 core 目录,然后再切换到 logger 目录。该代码的缺点是在编写时需要花费额外的精力来确定切换目录的层数并且很容易出错。就算一些代码编辑器能够帮助用户自动添加模块导入语句,该代码仍然具有较差的可读性。这种情况下使用 paths 设置能够很好地缓解这个问题。

假设有如下目录结构的工程:

C:\app
|-- src
|   |-- a
|   |   `-- b
|   |       `-- c
|   |           `-- c.ts
|   `-- core
|       `-- logger
|           `-- logger.ts
`-- tsconfig.json

如果没有配置 paths 编译选项,那么在 c.ts 中需要使用如下方式导入 logger.ts 模块:

import { logger } from "../../../core/logger/logger";

下面我们在 tsconfig.json 文件中配置 paths--baseUrl 编译选项。示例如下:

{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "@core/*": ["core/*"]
        }
    }
}

然后,就可以像下面这样在 c.ts 模块文件中使用路径映射来导入 logger.ts 模块:

import { logger } from '@core/logger/logger';

rootDirs

rootDirs 编译选项能够使用不同的目录创建出一个虚拟目录,在使用时就好像这些目录被合并成了一个目录一样。在解析相对模块导入时,编译器会在 rootDirs 编译选项构建出来的虚拟目录中进行搜索。

rootDirs 编译选项需要在 tsconfig.json 配置文件中设置,它的值是由路径构成的数组。

假设有如下目录结构的工程:

C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

tsconfig.json 文件的内容如下:

{
    "compilerOptions": {
        "rootDirs": ["bar", "foo"]
    }
}

此例中的 rootDirs 创建了一个虚拟目录,它包含了 C:\app\barC:\app\foo 目录下的内容。

a.ts 文件的内容如下:

import * as B from './b';

编译器在解析相对模块导入 './b' 时,将会同时查找 C:\app\bar 目录和 C:\app\foo 目录。

导入外部模块声明

Classic 模块解析策略和 Node 模块解析策略中,编译器都是在尝试查找一个与导入模块相匹配的文件。但如果最终未能找到这样的模块文件并且导入语句是非相对模块导入,那么编译器将继续在外部模块声明中查找导入的模块。关于外部模块声明的详细介绍请参考 7.7.2 节。

例如,有如下目录结构的工程:

C:\app
|-- foo
|   |---a.ts
|   `-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare module 'mod' {
    export function add(x: number, y: number): number;
}

a.ts 文件中,可以使用非相对模块导入语句来导入外部模块 mod。示例如下:

import * as Mod from 'mod';

Mod.add(1, 2);

注意,在 a.ts 文件中无法使用相对模块导入来导入外部模块 mod,示例如下:

import * as M1 from './mod';
//                  ~~~~~~~
//                  错误:无法找到模块'./mod'

import * as M2 from './typings';
//                  ~~~~~~~~~~~
//                  错误:typings.d.ts不是一个模块

--traceResolution

在启用了 --traceResolution 编译选项后,编译器会打印出模块解析的具体步骤。不论是在学习 TypeScript 语言的过程中还是在调试代码的过程中,都可以通过启用该选项来了解编译器解析模块时的具体行为。随着 TypeScript 版本的更新,模块解析算法也许会有所变化,而 --traceResolution 的输出结果能够真实反映当前使用的 TypeScript 版本中的模块解析算法。

该编译选项可以在命令行上指定,也可以在 tsconfig.json 配置文件中设置。

在命令行上使用 --traceResolution 编译选项,示例如下:

tsc --traceResolution

tsconfig.json 配置文件中使用 traceResolution 属性来设置,示例如下:

{
    "compilerOptions": {
        "traceResolution": true
    }
}

例如,有如下目录结构的工程:

C:\app
`-- a.ts

a.ts 文件中使用相对模块导入语句导入了模块 b。示例如下:

import * as B from './b';

C:\app 目录下执行 tsc 命令并使用 --traceResolution 编译选项来打印模块解析过程。示例如下:

tsc a.ts --moduleResolution classic --traceResolution

tsc 命令的执行结果如下:

======== Resolving module './b' from 'C:/app/a.ts'. ========
Explicitly specified module resolution kind: 'Classic'.
File 'C:/app/b.ts' does not exist.
File 'C:/app/b.tsx' does not exist.
File 'C:/app/b.d.ts' does not exist.
File 'C:/app/b.js' does not exist.
File 'C:/app/b.jsx' does not exist.
======== Module name './b' was not resolved. ========

该输出结果的第 1 行显示了正在解析的模块名;第 2 行显示了使用的模块解析策略为 Classic;第 3~7 行显示了模块解析的具体步骤;第 8 行显示了模块解析的结果,此例中未能成功解析模块 './b'