项目引用是 TypeScript 3.0 中的一项新功能,它允许您将 TypeScript 程序分解成更小的部分。
通过这样做,您可以极大地提高构建时间,强制执行组件之间的逻辑分离,并以新的更好的方式组织您的代码。
我们还为 tsc
引入了一种新模式,即 --build
标志,它与项目引用协同工作,以实现更快的 TypeScript 构建。
示例项目
让我们来看一个相当普通的程序,看看项目引用如何帮助我们更好地组织它。假设您有一个包含两个模块的项目,converter
和 units
,以及每个模块对应的测试文件。
/ ├── src/ │ ├── converter.ts │ └── units.ts ├── test/ │ ├── converter-tests.ts │ └── units-tests.ts └── tsconfig.json
测试文件导入实现文件并进行一些测试。
ts
// converter-tests.tsimport * as converter from "../src/converter";assert.areEqual(converter.celsiusToFahrenheit(0), 32);
以前,如果您使用单个 tsconfig 文件,这种结构相当笨拙。
- 实现文件可以导入测试文件。
- 无法同时构建
test
和src
,除非src
出现在输出文件夹名称中,而您可能不希望这样。 - 仅更改实现文件中的内部内容需要再次类型检查测试,即使这永远不会导致新的错误。
- 仅更改测试需要再次类型检查实现,即使没有任何更改。
您可以使用多个 tsconfig 文件来解决一些这些问题,但会产生新的问题。
- 没有内置的最新检查,因此您最终总是要运行两次
tsc
。 - 两次调用
tsc
会产生更多的启动时间开销。 tsc -w
无法同时在多个配置文件上运行。
项目引用可以解决所有这些问题以及更多问题。
什么是项目引用?
tsconfig.json
文件有一个新的顶级属性,references
。它是一个对象数组,指定要引用的项目。
js
{"compilerOptions": {// The usual},"references": [{ "path": "../src" }]}
每个引用的 path
属性可以指向包含 tsconfig.json
文件的目录,也可以指向配置文件本身(可以具有任何名称)。
当您引用一个项目时,会发生一些新的事情。
- 从引用的项目导入模块将改为加载其输出声明文件 (
.d.ts
)。 - 如果引用的项目生成一个
outFile
,则输出文件.d.ts
文件的声明将在本项目中可见。 - 构建模式(见下文)将在需要时自动构建引用的项目。
通过分离成多个项目,您可以大大提高类型检查和编译的速度,减少使用编辑器时的内存使用量,并加强对程序逻辑分组的执行。
composite
引用的项目必须启用新的 composite
设置。此设置对于确保 TypeScript 可以快速确定在何处查找引用项目的输出至关重要。启用 composite
标志会改变一些事情
- 如果未显式设置
rootDir
设置,则默认情况下它将设置为包含tsconfig
文件的目录 - 所有实现文件必须与
include
模式匹配或列在files
数组中。如果违反此约束,tsc
将告知您哪些文件未指定 declaration
必须打开
declarationMap
我们还添加了对 声明源映射 的支持。如果您启用 declarationMap
,您将能够使用编辑器功能(如“转到定义”和重命名)透明地跨项目边界导航和编辑代码,在支持的编辑器中。
prepend
与 outFile
您还可以通过在引用中启用 prepend
选项来启用将依赖项的输出预置到前面
js
"references": [{ "path": "../utils", "prepend": true }]
预置项目将包括项目输出在当前项目输出之上。所有输出文件(.js
、.d.ts
、.js.map
、.d.ts.map
)将被正确地发出。
tsc
将始终仅使用磁盘上的现有文件来执行此过程,因此有可能创建一个项目,其中无法生成正确的输出文件,因为某些项目的输出在结果文件中出现多次。例如
txt
A ^ ^ / \ B C ^ ^ \ / D
在这种情况下,重要的是不要在每个引用处预置,因为您最终将在 D
的输出中得到两个 A
的副本 - 这会导致意外的结果。
项目引用注意事项
项目引用有一些权衡,您应该了解。
由于依赖项目使用从其依赖项构建的 .d.ts
文件,您要么必须签入某些构建输出,要么在克隆项目后构建项目,然后才能在编辑器中导航项目而不会看到虚假错误。
在使用 VS Code(从 TS 3.7 开始)时,我们有一个幕后的内存中 .d.ts
生成过程,应该能够缓解这种情况,但它有一些性能影响。对于非常大的复合项目,您可能希望使用 disableSourceOfProjectReferenceRedirect 选项 禁用此功能。
此外,为了保持与现有构建工作流程的兼容性,tsc
不会自动构建依赖项,除非使用 --build
开关调用。让我们进一步了解 --build
。
TypeScript 的构建模式
一项期待已久的功能是 TypeScript 项目的智能增量构建。在 3.0 中,您可以将 --build
标志与 tsc
一起使用。这实际上是 tsc
的一个新的入口点,它的行为更像一个构建协调器,而不是一个简单的编译器。
运行 tsc --build
(简写为 tsc -b
)将执行以下操作
- 查找所有引用的项目
- 检测它们是否是最新的
- 以正确的顺序构建过时的项目
您可以为 tsc -b
提供多个配置文件路径(例如 tsc -b src test
)。就像 tsc -p
一样,如果配置文件名为 tsconfig.json
,则指定配置文件名本身是不必要的。
tsc -b
命令行
您可以指定任意数量的配置文件
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
会在需要时重新排序它们,以确保依赖项始终先被构建。
还有一些特定于 tsc -b
的标志
--verbose
: 打印详细日志以解释正在发生的事情(可以与任何其他标志组合使用)--dry
: 显示将要执行的操作,但实际上不构建任何内容--clean
: 删除指定项目的输出(可以与--dry
组合使用)--force
: 就像所有项目都已过期一样--watch
: 监视模式(不能与除--verbose
之外的任何标志组合使用)
注意事项
通常,tsc
会在存在语法或类型错误的情况下生成输出(.js
和 .d.ts
),除非 noEmitOnError
处于开启状态。在增量构建系统中这样做会非常糟糕 - 如果您过时的依赖项之一出现新的错误,您只会看到它 一次,因为后续构建将跳过构建现在已更新的项目。因此,tsc -b
实际上就像对所有项目都启用了 noEmitOnError
一样。
如果您签入任何构建输出(.js
、.d.ts
、.d.ts.map
等),您可能需要在某些源代码控制操作后运行 --force
构建,具体取决于您的源代码控制工具是否在本地副本和远程副本之间保留时间戳。
MSBuild
如果您有 msbuild 项目,您可以通过添加以下内容来启用构建模式
xml
<TypeScriptBuildMode>true</TypeScriptBuildMode>
到您的 proj 文件。这将启用自动增量构建以及清理。
请注意,与 tsconfig.json
/ -p
一样,现有的 TypeScript 项目属性将不被尊重 - 所有设置都应使用您的 tsconfig 文件进行管理。
一些团队已经设置了基于 msbuild 的工作流程,其中 tsconfig 文件具有与其配对的托管项目的相同 *隐式* 图形排序。如果您的解决方案与此类似,您可以继续使用 msbuild
与 tsc -p
以及项目引用;这些是完全互操作的。
指导
整体结构
使用更多 tsconfig.json
文件,您通常希望使用 配置文件继承 来集中您的通用编译器选项。这样,您就可以在一个文件中更改设置,而不是必须编辑多个文件。
另一个好习惯是拥有一个“解决方案” tsconfig.json
文件,该文件仅包含对所有叶节点项目的 references
,并将 files
设置为空数组(否则解决方案文件将导致文件的双重编译)。请注意,从 3.0 开始,如果您在 tsconfig.json
文件中至少有一个 reference
,则拥有一个空的 files
数组不再是错误。
这提供了一个简单的入口点;例如,在 TypeScript 存储库中,我们只需运行 tsc -b src
即可构建所有端点,因为我们在 src/tsconfig.json
中列出了所有子项目。
您可以在 TypeScript 仓库中看到这些模式 - 请查看 src/tsconfig_base.json
、src/tsconfig.json
和 src/tsc/tsconfig.json
作为关键示例。
针对相对模块进行结构化
通常,使用相对模块转换仓库不需要太多操作。只需在给定父文件夹的每个子目录中放置一个 tsconfig.json
文件,并将 reference
添加到这些配置文件中以匹配程序的预期分层结构。您需要将 outDir
设置为输出文件夹的显式子文件夹,或者将 rootDir
设置为所有项目文件夹的公共根目录。
针对 outFiles 进行结构化
使用 outFile
进行编译的布局更加灵活,因为相对路径的影响较小。需要注意的是,通常情况下,您应该在“最后一个”项目之前不要使用 prepend
- 这将提高构建速度并减少任何给定构建所需的 I/O 量。TypeScript 仓库本身就是一个很好的参考 - 我们有一些“库”项目和一些“端点”项目;“端点”项目尽可能保持精简,只引入它们需要的库。