项目引用

项目引用允许你将 TypeScript 程序构建为更小的部分,在 TypeScript 3.0 及更新版本中可用。

🌐 Project references allows you to structure your TypeScript programs into smaller pieces, available in TypeScript 3.0 and newer.

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

🌐 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:

  • 实现文件可以导入测试文件
  • 在没有让 src 出现在输出文件夹名称中的情况下,同时构建 testsrc 是不可能的,而你可能不希望出现这种情况
  • 仅更改实现文件中的_内部内容_仍然需要对测试进行_类型检查_,即使这从未会导致新的错误
  • 仅更改测试需要再次对实现进行类型检查,即使没有任何更改

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

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

  • 没有内置的最新检查,所以你最终总是要运行 tsc 两次
  • 调用 tsc 两次会增加启动时间开销
  • tsc -w 不能同时在多个配置文件上运行

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

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

什么是项目引用?

🌐 What is a Project Reference?

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

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:

  • 从引用的项目导入模块将改为加载其 output 声明文件(.d.ts)。
  • 如果引用的项目生成 outFile,输出文件 .d.ts 的声明将在此项目中可见
  • 如果需要,构建模式(见下文)将自动构建引用的项目

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

🌐 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 文件的目录
  • 所有实现文件必须与 include 模式匹配或列在 files 数组中。如果违反此约束,tsc 会告诉你哪些文件未被指定
  • declaration 必须开启

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.

项目引用的注意事项

🌐 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.

此外,为了保持与现有构建工作流的兼容性,tsc 不会 自动构建依赖,除非使用 --build 开关进行调用。让我们进一步了解 --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 中,你可以在 tsc 下使用 --build 标志。这实际上是 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:

  • 查找所有引用的项目
  • 检测它们是否是最新的
  • 以正确的顺序构建过时的项目

你可以为 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:输出详细的日志以解释发生了什么(可以与其他任何标志组合使用)
  • --dry:显示将要做什么,但实际上并没有构建任何东西
  • --clean:删除指定项目的输出(可与 --dry 结合使用)
  • --force:假装所有项目都已过时
  • --watch:观察模式(可能无法与除 --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>

到你的 proj 文件。这将启用自动增量构建以及清理功能。

🌐 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.

另一个好做法是拥有一个“解决方案”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

一般来说,将使用相对模块的仓库迁移并不需要太多操作。只需在给定父文件夹的每个子目录中放置一个 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 进行编译的布局更灵活,因为相对路径的重要性不大。TypeScript 仓库本身是一个很好的参考——我们有一些“库”项目和一些“端点”项目;“端点”项目保持尽可能小,并且只引入它们需要的库。

🌐 Layout for compilations using outFile is more flexible because relative paths don’t matter as much. 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.