工程引用

随着工程规模的扩大,一个工程中的代码量可能会达到数十万行的级别。当 TypeScript 编译器对数十万行代码进行类型检查时可能会遇到性能问题。分而治之 是解决该问题的一种策略,我们可以将一个较大的工程拆分为独立的子工程,然后将多个子工程关联在一起。

工程引用是 TypeScript 3.0 引入的新功能。它允许一个 TypeScript 工程引用一个或多个其他的 TypeScript 工程。借助于工程引用,我们可以将一个原本较大的 TypeScript 工程拆分成多个 TypeScript 工程,并设置它们之间的依赖关系。每个 TypeScript 工程都可以进行独立的配置与类型检查。当我们修改其中一个工程的代码时,会按需对其他工程进行类型检查,因此能够显著地提高类型检查的效率。同时,使用工程引用还能够更好地组织代码结构,从逻辑上将软件拆分为可以重用的组件,将实现细节封装在组件内部,并通过定义好的接口与外界交互。

本节将通过一个具体示例来介绍如何使用 tsconfig.json 配置文件来配置不同 TypeScript 工程之间的引用。在本节的最后会介绍一种 solution 模式,它能够帮助我们很好地管理包含多个 TypeScript 工程的项目。

使用工程引用

若一个目录中包含 tsconfig.json 配置文件,那么该目录将被视为 TypeScript 工程的根目录。在使用工程引用时,需要在 tsconfig.json 配置文件中进行以下配置:

  • 使用 references 属性配置当前工程所引用的其他工程。

  • 被引用的工程必须启用 composite 编译选项。

references

tsconfig.json 配置文件有一个顶层属性 references。它的值是对象数组,用于设置引用的工程。示例如下:

{
    "references": [
        { "path": "../pkg1" },
        { "path": "../pkg2/tsconfig.release.json" },
    ]
}

其中,path 的值既可以是指向含有 tsconfig.json 配置文件的目录,也可以直接指向某一个配置文件,此时配置文件名可以不为 tsconfig.json。此例中的工程引用了两个工程。

--composite

--composite 编译选项的值是一个布尔值。通过启用该选项,TypeScript 编译器能够快速地定位依赖工程的输出文件位置。如果一个工程被其他工程所引用,那么必须将该工程的 --composite 编译选项设置为 true

当启用了该编译选项时,它会影响以下的配置:

  • 如果没有设置 --rootDir 编译选项,那么它将会被设置为包含 tsconfig.json 配置文件的目录。

  • 如果设置了 includefiles 属性,那么所有的源文件必须被包含在内,否则将产生编译错误。

  • 必须启用 --declaration 编译选项。

--declarationMap

--declarationMap 是推荐启用的编译选项。如果启用了该选项,那么在生成 .d.ts 声明文件时会同时生成对应的 Source Map 文件。这样在代码编辑器中使用 跳转到定义 的功能时,编辑器会自动跳转到代码实现的位置,而不是跳转到声明文件中类型声明的位置。示例如下:

{
    "compilerOptions": {
        "declaration": true,
        "declarationMap": true
    }
}

工程引用示例

该示例项目由 C:\app\srcC:\app\test 两个工程组成,其目录结构如下:

C:\app
|-- src
|   |-- index.ts
|   `-- tsconfig.json
`-- test
    |-- index.spec.ts
    `-- tsconfig.json

index.ts 文件的内容如下:

export function add(x: number, y: number) {
    return x + y;
}

index.spec.ts 文件的内容如下:

import { add } from '../src/index';

if (add(1, 2) === 3) {
    console.log('pass');
} else {
    console.log('failed');
}

配置references

在该项目中,C:\app\test 工程依赖于 C:\app\src 工程中的模块。因此,C:\app\test 工程依赖于 C:\app\src 工程。

C:\app\test 工程的 tsconfig.json 配置文件中设置对 C:\app\src 工程的依赖。C:\app\test\tsconfig.json 配置文件的内容如下:

{
    "references": [
        {
            "path": "../src"
        }
    ]
}

此例中,通过 references 属性设置了对 C:\app\src 工程的引用。

配置composite

在该项目中,C:\app\src 工程被 C:\app\test 工程所依赖。因此,必须在 C:\app\src 工程的 tsconfig.json 配置文件中将 --composite 编译选项设置为 true

C:\app\src\tsconfig.json 配置文件的内容如下:

{
    "compilerOptions": {
        "composite": true,
        "declarationMap": true
    }
}

至此,所有必须的工程引用配置已经设置完毕。

--build

TypeScript 提供了一种新的构建模式来配合工程引用的使用,它就是 --build 模式(简写为 -b)。在该模式下,编译器能够进行增量构建。具体的使用方式如下所示:

tsc --build

当使用该命令构建 TypeScript 工程时,编译器会执行如下操作:

  • 查找当前工程所引用的工程。

  • 检查当前工程和引用的工程是否有更新。

  • 若工程有更新,则根据依赖顺序重新构建它们;若没有更新,则不进行重新构建。

下面还是以上一节中的示例项目为例,目录结构如下:

C:\app
|-- src
|   |-- index.ts
|   `-- tsconfig.json
`-- test
    |-- index.spec.ts
    `-- tsconfig.json

C:\app 目录下使用 --build 模式首次构建 C:\app\test 工程。示例如下:

tsc --build test --verbose

tsc 命令的运行结果如下:

[6:00:00 PM] Projects in this build:
                 * src/tsconfig.json
                 * test/tsconfig.json

[6:00:00 PM] Project 'src/tsconfig.json' is out of date because
             output file 'src/index.js' does not exist

[6:00:00 PM] Building project 'src/tsconfig.json'...

[6:00:00 PM] Project 'test/tsconfig.json' is out of date because
             output file 'test/index.spec.js' does not exist

[6:00:00 PM] Building project 'test/tsconfig.json'...

通过以上运行结果能够看到,当构建 C:\app\test 工程时,编译器先确定参与本次构建的所有工程,然后根据引用顺序对待编译的工程进行排序。此例中的顺序为先编译 C:\app\src 工程,后编译 C:\app\test 工程。接下来开始尝试编译 C:\app\src 工程。由于之前没有编译过该工程,它不处于最新状态,因此编译器会编译 C:\app\src 工程。同理,在编译完 C:\app\src 工程后会接着编译 C:\app\test 工程。

假设自上一次编译完 C:\app\test 工程之后,没有对 C:\app\src 工程和 C:\app\test 工程做任何修改。当再一次使用 --build 模式构建 C:\app\test 工程时,运行结果如下:

[6:00:01 PM] Projects in this build:
                 * src/tsconfig.json
                 * test/tsconfig.json

[6:00:01 PM] Project 'src/tsconfig.json' is up to date because
             newest input 'src/index.ts' is older than oldest
             output 'src/index.js'

[6:00:01 PM] Project 'test/tsconfig.json' is up to date because
             newest input 'test/index.spec.ts' is older than
             oldest output 'test/index.spec.js'

通过以上运行结果能够看到,编译器能够检测出 C:\app\src 工程和 C:\app\test 工程没有发生变化,因此也就没有重新编译这两个工程。

C:\app\src 工程和 C:\app\test 工程编译后的目录结构如下:

C:\app
|-- src
|   |-- index.d.ts
|   |-- index.js
|   |-- index.ts
|   |-- tsconfig.tsbuildinfo
|   `-- tsconfig.json
`-- test
    |-- index.spec.ts
    |-- index.spec.js
    `-- tsconfig.json

在 C:\app\src 工程和 C:\app\test 工程中都编译生成了对应的 .js 文件。

在 C:\app\src 工程中,由于启用了 --composite 编译选项,它会自动启用 --declaration 编译选项,因此编译后生成了 index.d.ts 声明文件。

在 C:\app\src 工程中还生成了 tsconfig.tsbuildinfo 文件,它保存了该工程本次构建的详细信息,编译器正是通过查看该文件来判断当前工程是否需要重新编译。

TypeScript 还提供了以下一些仅适用于 --build 模式的编译选项:

  • --verbose,打印构建的详细日志信息,可以与以下编译选项一起使用。

  • --dry,打印将执行的操作,而不进行真正的构建。

  • --clean,删除工程中编译输出的文件,可以与 --dry 一起使用。

  • --force,强制重新编译工程,而不管工程是否有更新。

  • --watch,观察模式,执行编译命令后不退出,等待工程有更新后自动地再次编译工程。

solution模式

当一个项目由多个工程组成时,我们可以新创建一个 solution 工程来管理这些工程。solution 工程本身不包含任何实际代码,它只是引用了项目中所有的工程并将它们关联在一起。在构建项目时,只需要构建 solution 工程就能够构建整个项目。

接下来,我们将之前的 C:\app\src 工程和 C:\app\test 工程重构为 solution 模式。修改后的目录结构如下:

C:\app
|-- src
|   |-- index.ts
|   `-- tsconfig.json
|-- test
|   |-- index.spec.ts
|   `-- tsconfig.json
`-- tsconfig.json

我们在 C:\app 目录下新建了一个 tsconfig.json 配置文件,目的是让 C:\app 成为一个 solution 工程。tsconfig.json 配置文件的内容如下:

{
    "files": [],
    "include": [],
    "references": [
        { "path": "src" },
        { "path": "test" }
    ]
}

在该配置文件中同时将 C:\app\src 工程和 C:\app\test 工程设置为引用工程。此外,必须将 filesinclude 属性设置为空数组,否则编译器将会重复编译 C:\app\src 工程和 C:\app\test 工程。

在 C:\app 目录下使用 --build 模式来编译该工程,如下所示:

tsc --build --verbose

tsc 命令的运行结果如下:

[6:00:10 PM] Projects in this build:
                 * src/tsconfig.json
                 * test/tsconfig.json
                 * tsconfig.json

[6:00:10 PM] Project 'src/tsconfig.json' is out of date because
             output file 'src/index.js' does not exist

[6:00:10 PM] Building project 'src/tsconfig.json'...

[6:00:10 PM] Project 'test/tsconfig.json' is out of date because
             output file 'test/index.spec.js' does not exist

[6:00:10 PM] Building project 'test/tsconfig.json'...

通过以上运行结果能够看到,编译器能够正确地编译 C:\app\src 工程和 C:\app\test 工程。