项目引用

项目引用是 TypeScript 3.0 中的一项新功能,它允许你将 TypeScript 程序构造成更小的部分。

¥Project references are a new feature in TypeScript 3.0 that allow you to structure your TypeScript programs into smaller pieces.

通过这样做,你可以大大缩短构建时间,强制组件之间的逻辑分离,并以新的更好的方式组织你的代码。

¥By doing this, you can greatly improve build times, enforce logical separation between components, and organize your code in new and better ways.

我们还为 tsc 引入了一种新模式,即 --build 标志,它与项目引用协同工作,以实现更快的 TypeScript 构建。

¥We’re also introducing a new mode for tsc, the --build flag, that works hand in hand with project references to enable faster TypeScript builds.

一个示例项目

¥An Example Project

让我们看一个相当正常的程序,看看项目引用如何帮助我们更好地组织它。假设你有一个项目,其中包含两个模块 converterunits,每个模块都有一个相应的测试文件:

¥Let’s look at a fairly normal program and see how project references can help us better organize it. Imagine you have a project with two modules, converter and units, and a corresponding test file for each:

/ ├── src/ │ ├── converter.ts │ └── units.ts ├── test/ │ ├── converter-tests.ts │ └── units-tests.ts └── tsconfig.json

测试文件导入实现文件并做一些测试:

¥The test files import the implementation files and do some testing:

ts
// converter-tests.ts
import * as converter from "../src/converter";
assert.areEqual(converter.celsiusToFahrenheit(0), 32);

以前,如果你使用单个 tsconfig 文件,则使用此结构相当尴尬:

¥Previously, this structure was rather awkward to work with if you used a single tsconfig file:

  • 实现文件可以导入测试文件

    ¥It was possible for the implementation files to import the test files

  • 如果没有 src 出现在输出文件夹名称中,就不可能同时构建 testsrc,这是你可能不想要的

    ¥It wasn’t possible to build test and src at the same time without having src appear in the output folder name, which you probably don’t want

  • 仅更改实现文件中的内部结构需要再次对测试进行类型检查,即使这不会导致新的错误

    ¥Changing just the internals in the implementation files required typechecking the tests again, even though this wouldn’t ever cause new errors

  • 仅更改测试需要再次对实现进行类型检查,即使没有任何更改

    ¥Changing just the tests required typechecking the implementation again, even if nothing changed

你可以使用多个 tsconfig 文件来解决其中一些问题,但会出现新问题:

¥You could use multiple tsconfig files to solve some of those problems, but new ones would appear:

  • 没有内置的最新检查,所以你最终总是运行 tsc 两次

    ¥There’s no built-in up-to-date checking, so you end up always running tsc twice

  • 调用 tsc 两次会导致更多的启动时间开销

    ¥Invoking tsc twice incurs more startup time overhead

  • tsc -w 不能同时在多个配置文件上运行

    ¥tsc -w can’t run on multiple config files at once

项目引用可以解决所有这些问题以及更多问题。

¥Project references can solve all of these problems and more.

什么是项目引用?

¥What is a Project Reference?

tsconfig.json 文件有一个新的顶层属性 references。它是一个对象数组,指定要引用的项目:

¥tsconfig.json files have a new top-level property, references. It’s an array of objects that specifies projects to reference:

js
{
"compilerOptions": {
// The usual
},
"references": [
{ "path": "../src" }
]
}

每个引用的 path 属性可以指向包含 tsconfig.json 文件的目录,或者指向配置文件本身(可以有任何名称)。

¥The path property of each reference can point to a directory containing a tsconfig.json file, or to the config file itself (which may have any name).

当你引用一个项目时,会发生新的事情:

¥When you reference a project, new things happen:

  • 从引用的项目导入模块将加载其输出声明文件 (.d.ts)

    ¥Importing modules from a referenced project will instead load its output declaration file (.d.ts)

  • 如果引用的项目产生 outFile,则输出文件 .d.ts 文件的声明将在此项目中可见

    ¥If the referenced project produces an outFile, the output file .d.ts file’s declarations will be visible in this project

  • 如果需要,构建模式(见下文)将自动构建引用的项目

    ¥Build mode (see below) will automatically build the referenced project if needed

通过分成多个项目,你可以大大提高类型检查和编译的速度,减少使用编辑器时的内存使用量,并改进程序逻辑分组的执行。

¥By separating into multiple projects, you can greatly improve the speed of typechecking and compiling, reduce memory usage when using an editor, and improve enforcement of the logical groupings of your program.

composite

引用的项目必须启用新的 composite 设置。需要此设置以确保 TypeScript 可以快速确定在哪里可以找到引用项目的输出。启用 composite 标志会改变一些事情:

¥Referenced projects must have the new composite setting enabled. This setting is needed to ensure TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the composite flag changes a few things:

  • rootDir 设置,如果没有显式设置,默认为包含 tsconfig 文件的目录

    ¥The rootDir setting, if not explicitly set, defaults to the directory containing the tsconfig file

  • 所有实现文件必须与 include 模式匹配或列在 files 数组中。如果违反此约束,tsc 将通知你未指定哪些文件

    ¥All implementation files must be matched by an include pattern or listed in the files array. If this constraint is violated, tsc will inform you which files weren’t specified

  • declaration 必须开启

    ¥declaration must be turned on

declarationMap

我们还添加了对 声明源映射 的支持。如果启用 declarationMap,你将能够使用 “跳转到定义” 和重命名等编辑器功能在支持的编辑器中跨项目边界透明地导航和编辑代码。

¥We’ve also added support for declaration source maps. If you enable declarationMap, you’ll be able to use editor features like “Go to Definition” and Rename to transparently navigate and edit code across project boundaries in supported editors.

prependoutFile

¥prepend with outFile

你还可以使用引用中的 prepend 选项启用在依赖的输出之前添加:

¥You can also enable prepending the output of a dependency using the prepend option in a reference:

js
"references": [
{ "path": "../utils", "prepend": true }
]

前置项目将在当前项目的输出之上包含项目的输出。所有输出文件(.js.d.ts.js.map.d.ts.map)都将正确触发。

¥Prepending a project will include the project’s output above the output of the current project. All output files (.js, .d.ts, .js.map, .d.ts.map) will be emitted correctly.

tsc 只会使用磁盘上的现有文件来执行此过程,因此可以创建一个无法生成正确输出文件的项目,因为某些项目的输出将在结果文件中出现多次。例如:

¥tsc will only ever use existing files on disk to do this process, so it’s possible to create a project where a correct output file can’t be generated because some project’s output would be present more than once in the resulting file. For example:

txt
A ^ ^ / \ B C ^ ^ \ / D

在这种情况下,重要的是不要在每个引用前面添加,因为你最终会在 D 的输出中得到 A 的两个副本 - 这可能会导致意想不到的结果。

¥It’s important in this situation to not prepend at each reference, because you’ll end up with two copies of A in the output of D - this can lead to unexpected results.

项目引用的注意事项

¥Caveats for Project References

项目引用有一些你应该注意的权衡取舍。

¥Project references have a few trade-offs you should be aware of.

由于依赖目使用从其依赖构建的 .d.ts 文件,因此你必须检查某些构建输出或在克隆项目后构建项目,然后才能在编辑器中导航项目而不会看到虚假错误。

¥Because dependent projects make use of .d.ts files that are built from their dependencies, you’ll either have to check in certain build outputs or build a project after cloning it before you can navigate the project in an editor without seeing spurious errors.

使用 VS Code(自 TS 3.7 起)时,我们有一个幕后的内存 .d.ts 生成过程,应该能够缓解这种情况,但它有一些性能影响。对于非常大的复合项目,你可能希望使用 disableSourceOfProjectReferenceRedirect 选项 禁用此功能。

¥When using VS Code (since TS 3.7) we have a behind-the-scenes in-memory .d.ts generation process that should be able to mitigate this, but it has some perf implications. For very large composite projects you might want to disable this using disableSourceOfProjectReferenceRedirect option.

此外,为了保持与现有构建工作流的兼容性,除非使用 --build 开关调用,否则 tsc 不会自动构建依赖。让我们进一步了解 --build

¥Additionally, to preserve compatibility with existing build workflows, tsc will not automatically build dependencies unless invoked with the --build switch. Let’s learn more about --build.

TypeScript 的构建模式

¥Build Mode for TypeScript

期待已久的功能是 TypeScript 项目的智能增量构建。在 3.0 中,你可以将 --build 标志与 tsc 一起使用。这实际上是 tsc 的一个新入口点,它的行为更像是一个构建协调器,而不是一个简单的编译器。

¥A long-awaited feature is smart incremental builds for TypeScript projects. In 3.0 you can use the --build flag with tsc. This is effectively a new entry point for tsc that behaves more like a build orchestrator than a simple compiler.

运行 tsc --build(简称 tsc -b)将执行以下操作:

¥Running tsc --build (tsc -b for short) will do the following:

  • 查找所有引用的项目

    ¥Find all referenced projects

  • 检测它们是否是最新的

    ¥Detect if they are up-to-date

  • 以正确的顺序构建过时的项目

    ¥Build out-of-date projects in the correct order

你可以为 tsc -b 提供多个配置文件路径(例如 tsc -b src test)。与 tsc -p 一样,如果配置文件名为 tsconfig.json,则无需指定配置文件名本身。

¥You can provide tsc -b with multiple config file paths (e.g. tsc -b src test). Just like tsc -p, specifying the config file name itself is unnecessary if it’s named tsconfig.json.

tsc -b 命令行

¥tsc -b Commandline

你可以指定任意数量的配置文件:

¥You can specify any number of config files:

shell
> tsc -b # Use the tsconfig.json in the current directory
> tsc -b src # Use src/tsconfig.json
> tsc -b foo/prd.tsconfig.json bar # Use foo/prd.tsconfig.json and bar/tsconfig.json

不用担心在命令行上传递的文件的顺序 - 如果需要,tsc 将重新排序它们,以便始终首先构建依赖。

¥Don’t worry about ordering the files you pass on the commandline - tsc will re-order them if needed so that dependencies are always built first.

还有一些特定于 tsc -b 的标志:

¥There are also some flags specific to tsc -b:

  • --verbose:打印出详细日志以解释发生了什么(可以与任何其他标志结合使用)

    ¥--verbose: Prints out verbose logging to explain what’s going on (may be combined with any other flag)

  • --dry:显示将要做什么,但实际上并没有构建任何东西

    ¥--dry: Shows what would be done but doesn’t actually build anything

  • --clean:删除指定项目的输出(可与 --dry 组合)

    ¥--clean: Deletes the outputs of the specified projects (may be combined with --dry)

  • --force:就好像所有项目都过时一样

    ¥--force: Act as if all projects are out of date

  • --watch:监视模式(不得与除 --verbose 之外的任何标志组合)

    ¥--watch: Watch mode (may not be combined with any flag except --verbose)

警告

¥Caveats

通常,tsc 将在存在语法或类型错误时产生输出(.js.d.ts),除非 noEmitOnError 已打开。在增量构建系统中这样做会非常糟糕 - 如果你的某个过时的依赖出现新错误,你只会看到它一次,因为后续构建将跳过构建现在最新的项目。出于这个原因,tsc -b 的作用就像为所有项目启用了 noEmitOnError

¥Normally, tsc will produce outputs (.js and .d.ts) in the presence of syntax or type errors, unless noEmitOnError is on. Doing this in an incremental build system would be very bad - if one of your out-of-date dependencies had a new error, you’d only see it once because a subsequent build would skip building the now up-to-date project. For this reason, tsc -b effectively acts as if noEmitOnError is enabled for all projects.

如果你签入任何构建输出(.js.d.ts.d.ts.map 等),你可能需要在某些源代码控制操作之后运行 --force 构建,具体取决于你的源代码控制工具是否保留本地副本和远程副本之间的时间戳。

¥If you check in any build outputs (.js, .d.ts, .d.ts.map, etc.), you may need to run a --force build after certain source control operations depending on whether your source control tool preserves timestamps between the local copy and the remote copy.

MSBuild

如果你有一个 msbuild 项目,你可以通过添加启用构建模式

¥If you have an msbuild project, you can enable build mode by adding

xml
<TypeScriptBuildMode>true</TypeScriptBuildMode>

到你的项目文件。这将启用自动增量构建和清理。

¥to your proj file. This will enable automatic incremental build as well as cleaning.

请注意,与 tsconfig.json / -p 一样,现有的 TypeScript 项目属性将不会受到尊重 - 所有设置都应使用 tsconfig 文件进行管理。

¥Note that as with tsconfig.json / -p, existing TypeScript project properties will not be respected - all settings should be managed using your tsconfig file.

一些团队已经建立了基于 msbuild 的工作流,其中 tsconfig 文件具有与它们配对的托管项目相同的隐式图形排序。如果你的解决方案是这样的,你可以继续使用 msbuildtsc -p 以及项目引用;这些是完全可互操作的。

¥Some teams have set up msbuild-based workflows wherein tsconfig files have the same implicit graph ordering as the managed projects they are paired with. If your solution is like this, you can continue to use msbuild with tsc -p along with project references; these are fully interoperable.

指南

¥Guidance

整体结构

¥Overall Structure

对于更多 tsconfig.json 文件,你通常会希望使用 配置文件继承 来集中你的常用编译器选项。这样,你可以更改一个文件中的设置,而不必编辑多个文件。

¥With more tsconfig.json files, you’ll usually want to use Configuration file inheritance to centralize your common compiler options. This way you can change a setting in one file rather than having to edit multiple files.

另一个好的做法是有一个 “solution” tsconfig.json 文件,它只是将 references 用于所有叶节点项目,并将 files 设置为空数组(否则解决方案文件将导致文件的双重编译)。请注意,从 3.0 开始,如果 tsconfig.json 文件中至少有一个 reference,则有一个空的 files 数组不再是错误。

¥Another good practice is to have a “solution” tsconfig.json file that simply has references to all of your leaf-node projects and sets files to an empty array (otherwise the solution file will cause double compilation of files). Note that starting with 3.0, it is no longer an error to have an empty files array if you have at least one reference in a tsconfig.json file.

这是一个简单的入口点;例如 在 TypeScript 存储库中,我们只需运行 tsc -b src 来构建所有端点,因为我们列出了 src/tsconfig.json 中的所有子项目

¥This presents a simple entry point; e.g. in the TypeScript repo we simply run tsc -b src to build all endpoints because we list all the subprojects in src/tsconfig.json

你可以在 TypeScript 存储库中看到这些模式 - 请参阅 src/tsconfig_base.jsonsrc/tsconfig.jsonsrc/tsc/tsconfig.json 作为关键示例。

¥You can see these patterns in the TypeScript repo - see src/tsconfig_base.json, src/tsconfig.json, and src/tsc/tsconfig.json as key examples.

相关模块的结构

¥Structuring for relative modules

一般来说,使用相关模块转换 repo 不需要太多。只需将 tsconfig.json 文件放置在给定父文件夹的每个子目录中,并将 reference 添加到这些配置文件中以匹配程序的预期分层即可。你需要将 outDir 设置为输出文件夹的显式子文件夹,或将 rootDir 设置为所有项目文件夹的公共根目录。

¥In general, not much is needed to transition a repo using relative modules. Simply place a tsconfig.json file in each subdirectory of a given parent folder, and add references to these config files to match the intended layering of the program. You will need to either set the outDir to an explicit subfolder of the output folder, or set the rootDir to the common root of all project folders.

outFiles 的结构

¥Structuring for outFiles

使用 outFile 进行编译的布局更加灵活,因为相对路径并不重要。需要记住的一件事是,在 “last” 项目之前,你通常不希望使用 prepend - 这将缩短构建时间并减少任何给定构建中所需的 I/O 量。TypeScript 存储库本身就是一个很好的参考 - 我们有一些 “library” 项目和一些 “endpoint” 项目;“endpoint” 项目尽可能小,只引入他们需要的库。

¥Layout for compilations using outFile is more flexible because relative paths don’t matter as much. One thing to keep in mind is that you’ll generally want to not use prepend until the “last” project - this will improve build times and reduce the amount of I/O needed in any given build. The TypeScript repo itself is a good reference here - we have some “library” projects and some “endpoint” projects; “endpoint” projects are kept as small as possible and pull in only the libraries they need.