TypeScript——模块解析

模块解析

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

  • 相对模块导入与非相对模块导入。
  • 模块解析策略。
  • 模块解析编译选项。

1、相对模块导入

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

  • /
  • ./
  • ../
ts 复制代码
//"/"表示系统根目录,示例如下:
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"模块:

ts 复制代码
import b from './b';

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

ts 复制代码
import c from '../bar/c';

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

ts 复制代码
import d from '/d';

2、非相对模块导入

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

ts 复制代码
import { Observable } from 'rxjs';

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

3、模块解析策略

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

  • Classic策略。
  • Node策略。

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

shell 复制代码
tsc --moduleResolution Classic

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

json 复制代码
{
    "compilerOptions": {
        "moduleResolution": "Node"
    }
}

当没有设置模块的解析策略时,默认的模块解析策略与"--module"编译选项的值有关。​"--module"编译选项用来设置编译生成的JavaScript代码使用的模块格式。

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

shell 复制代码
tsc --module CommonJS

这等同于:

shell 复制代码
tsc --module CommonJS --moduleResolution Node

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

shell 复制代码
tsc --module ES6

这等同于:

shell 复制代码
tsc --module ES6 --moduleResolution Classic

4、模块解析策略之Classic

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

4.1、解析相对模块导入

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

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

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

复制代码
C:\app
`-- a.ts

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

ts 复制代码
import * as B from './b'; 

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

  • 查找"C:/app/b.ts"。
  • 查找"C:/app/b.tsx"。
  • 查找"C:/app/b.d.ts"。
  • 查找"C:/app/b.js"。
  • 查找"C:/app/b.jsx"。

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

4.2、解析非相对模块导入

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

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

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

  • 解析相对模块导入时只会查找指定的一个目录;解析非相对模块导入时会向上遍历整个目录树。
  • 解析非相对模块导入比解析相对模块导入多了一步,即在每一层目录的"node_modules/@types"文件夹中查找是否安装了要导入的声明文件。

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

复制代码
C:\app
`-- a.ts

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

ts 复制代码
import * as B from 'b'; 

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

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

  • 查找文件"C:/app/b.ts"。
  • 查找文件"C:/app/b.tsx"。
  • 查找文件"C:/app/b.d.ts"。
  • 查找文件"C:/b.ts"。
  • 查找文件"C:/b.tsx"。
  • 查找文件"C:/b.d.ts"。

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

  • 查找文件"C:/app/node_modules/@types/b.d.ts"。
  • 如果"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/",则:
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.ts"。
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.tsx"。
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.d.ts"。
  • 查找文件"C:/app/node_modules/@types/b/index.d.ts"。
  • 查找文件"C:/node_modules/@types/b.d.ts"(注意,第4~6步与第1~3步的流程相同,区别是在上一级目录"C:/"中继续搜索)。
  • 如果文件"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/",则:
      • 查找文件"C:/node_modules/@types/b/typings.d.ts/index.ts"。
      • 查找文件"C:/node_modules/@types/b/typings.d.ts/index.tsx
      • 查找文件"C:/node_modules/@types/b/typings.d.ts/index.d.ts"。
  • 查找文件"C:/node_modules/@types/b/index.d.ts"。

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

  • 查找文件"C:/app/b.js"。
  • 查找文件"C:/app/b.jsx"。
  • 查找文件"C:/b.js"。
  • 查找文件"C:/b.jsx"。

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

5、模块解析策略之Node

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

5.1、解析相对模块导入

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

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

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

复制代码
C:\app
`-- a.ts

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

ts 复制代码
import * as B from './b'; 

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

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

  • 查找文件"C:/app/b.ts"。
  • 查找文件"C:/app/b.tsx"。
  • 查找文件"C:/app/b.d.ts"。

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

  • 如果"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/",那么:
      • 查找文件"C:/app/b/typings.d.ts/index.ts"。
      • 查找文件"C:/app/b/typings.d.ts/index.tsx"。
      • 查找文件"C:/app/b/typings.d.ts/index.d.ts"。
  • 查找文件"C:/app/b/index.ts"。
  • 查找文件"C:/app/b/index.tsx"。
  • 查找文件"C:/app/b/index.d.ts"。

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

  • 查找文件"C:/app/b.js"。
  • 查找文件"C:/app/b.jsx"。

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

  • 如果"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/",那么:
      • 查找文件"C:/app/b/main.js/index.js"。
      • 查找文件"C:/app/b/main.js/index.jsx"。
  • 查找文件"C:/app/b/index.js"
  • 查找文件"C:/app/b/index.jsx"

5.2、解析非相对模块导入

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

  • 将导入的模块名视为文件,并在当前目录下的"node_modules"文件夹中查找Type-Script文件。
  • 将导入的模块名视为目录,并在当前目录下的"node_modules"文件夹中查找给定目录下的"package.json"文件,然后解析"package.json"文件中的typings属性和types属性。
  • 将导入的模块名视为安装的声明文件,并在当前目录下的"node_modules/@types"文件夹中查找安装的声明文件。
  • 重复第1~3步的查找过程,从当前目录开始向上遍历至系统根目录。
  • 将导入的模块名视为文件,并在当前目录下的"node_modules"文件夹中查找Java-Script文件。
  • 将导入的模块名视为目录,并在当前目录下的"node_modules"文件夹中查找给定目录下的"package.json"文件,然后解析"package.json"文件中的main属性。
  • 重复第5~6步的查找过程,从当前目录开始向上遍历至系统根目录。

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

复制代码
C:\app
`-- a.ts

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

ts 复制代码
import * as B from 'b';

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

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

  • 查找文件"C:/app/node_modules/b.ts"。
  • 查找文件"C:/app/node_modules/b.tsx"。
  • 查找文件"C:/app/node_modules/b.d.ts"。

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

  • 如果"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/",则:
      • 查找文件"C:/app/node_modules/b/typings.d.ts/index.ts"。
      • 查找文件"C:/app/node_modules/b/typings.d.ts/index.tsx"。
      • 查找文件"C:/app/node_modules/b/typings.d.ts/index.d.ts"。
  • 查找文件"C:/app/node_modules/b/index.ts"。
  • 查找文件"C:/app/node_modules/b/index.tsx"。
  • 查找文件"C:/app/node_modules/b/index.d.ts"。

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

  • 查找文件"C:/app/node_modules/@types/b.d.ts"。
  • 如果"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/",那么:
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.ts"。
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.tsx"。
      • 查找文件"C:/app/node_modules/@types/b/typings.d.ts/index.d.ts"。
  • 查找文件"C:/app/node_modules/@types/b/index.d.ts"。

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

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

  • 查找文件"C:/app/node_modules/b.js"。
  • 查找文件"C:/app/node_modules/b.jsx"。

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

  • 如果"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/",那么:
      • 查找文件"C:/app/node_modules/b/main.js/index.js"。
      • 查找文件"C:/app/node_modules/b/main.js/index.jsx"。
  • 查找文件"C:/app/node_modules/b/index.js"。
  • 查找文件"C:/app/node_modules/b/index.jsx"。

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

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

6、--baseUrl

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

6.1、设置--baseUrl

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

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

shell 复制代码
tsc --baseUrl ./

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

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

json 复制代码
{
    "compilerOptions": {
        "baseUrl": "./"
    }
}

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

6.2、解析--baseUrl

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

  • 根据"--baseUrl"的值和导入的模块名,计算出导入模块的路径。
  • 将导入的模块名视为文件,并查找TypeScript文件。
  • 将导入的模块名视为目录,在该目录中查找"package.json"文件,然后解析"package.json"文件中的typings属性和types属性。
  • 将导入的模块名视为目录,在该目录中查找"package.json"文件,然后解析"package.json"文件中的main属性。
  • 忽略"--baseUrl"的设置并回退到使用Classic模块解析策略或Node模块解析策略来解析模块。

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

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

复制代码
C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

"tsconfig.json"文件的内容如下:

json 复制代码
{
    "compilerOptions": {
        "baseUrl": "./"
    }
}

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

"a.ts"文件的内容如下:

ts 复制代码
import * as B from 'bar/b';

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

7、paths

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

7.1、设置paths

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

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

复制代码
C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

"tsconfig.json"文件的内容如下:

json 复制代码
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "b": ["bar/b"]
        }
    }
}

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

"a.ts"文件的内容如下:

ts 复制代码
import * as B from 'b';

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

7.2、使用通配符

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

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

复制代码
C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

"tsconfig.json"文件的内容如下:

ts 复制代码
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "@bar/*": ["bar/*"]
        }
    }
}

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

"a.ts"文件的内容如下:

ts 复制代码
import * as B from '@bar/b';

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

7.3、使用场景

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

ts 复制代码
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"模块:

ts 复制代码
import { logger } from "../../../core/logger/logger";

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

json 复制代码
{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "@core/*": ["core/*"]
        }
    }
}

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

ts 复制代码
import { logger } from '@core/logger/logger';

8、rootDirs

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

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

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

复制代码
C:\app
|-- bar
|   `-- b.ts
|-- foo
|   `-- a.ts
`-- tsconfig.json

"tsconfig.json"文件的内容如下:

json 复制代码
{
    "compilerOptions": {
        "rootDirs": ["bar", "foo"]
    }
}

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

"a.ts"文件的内容如下:

ts 复制代码
import * as B from './b';

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

9、导入外部模块声明

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

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

复制代码
C:\app
|-- foo
|   |---a.ts
|   `-- typings.d.ts
`-- tsconfig.json

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

ts 复制代码
declare module 'mod' {
    export function add(x: number, y: number): number;
}

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

ts 复制代码
import * as Mod from 'mod';

Mod.add(1, 2);

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

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

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

10、--traceResolution

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

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

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

shell 复制代码
tsc --traceResolution

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

json 复制代码
{
    "compilerOptions": {
        "traceResolution": true
    }
}

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

复制代码
C:\app
`-- a.ts

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

ts 复制代码
import * as B from './b'; 

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

shell 复制代码
tsc a.ts --moduleResolution classic --traceResolution

tsc命令的执行结果如下:

shell 复制代码
01 ======== Resolving module './b' from 'C:/app/a.ts'. ========
02 Explicitly specified module resolution kind: 'Classic'.
03 File 'C:/app/b.ts' does not exist.
04 File 'C:/app/b.tsx' does not exist.
05 File 'C:/app/b.d.ts' does not exist.
06 File 'C:/app/b.js' does not exist.
07 File 'C:/app/b.jsx' does not exist.
08 ======== Module name './b' was not resolved. ========

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

相关推荐
han_2 小时前
JavaScript设计模式(五):装饰者模式实现与应用
前端·javascript·设计模式
ProgramHelpOa2 小时前
Amazon SDE Intern OA 2026 最新复盘|70分钟两题 Medium-Hard
java·前端·javascript
smchaopiao3 小时前
如何用CSS和JS搞定全屏图片展示
前端·javascript·css
还是大剑师兰特3 小时前
将 Utils.js 挂载为全局(window.Utils.xx)完整配置方案
开发语言·javascript·ecmascript
cnnews3 小时前
手机通过Termux安装unbuntu,开启SSH
linux·运维·ubuntu·ssh
前端Hardy3 小时前
Qwik 2.0 Beta 来了:不靠 AI,只靠 Resumability,首屏交互快到离谱
前端·javascript·面试
吴声子夜歌3 小时前
TypeScript——声明合并
linux·ubuntu·typescript
前端Hardy4 小时前
NW.js v0.109.1 最新稳定版发布:被遗忘的桌面开发神器?启动快 3 倍,内存省 70%!
前端·javascript·vue.js
Jinuss4 小时前
源码分析之React中副作用Effect全流程
前端·javascript·react.js