在这个monorepo系列过去的一篇文章中,我们讨论了使用Yarn Workspaces为JavaScript包设置CI/CD。这一次,我们将为TypeScript找出同样的方法。我们将学习如何使用Yarn和Semaphore大规模地构建和测试TypeScript项目。
在教程的最后,我们将拥有一个持续集成管道,只构建变化的代码。
统一 Yarn 和 TypeScript
TypeScript扩展了JavaScript,增加了它所缺少的一切:类型、更严格的检查和更深入的IDE整合。TypeScript代码更容易阅读和调试,帮助我们编写更强大的代码。
然而,与JavaScript相比,TypeScript让我们多了一层复杂性:代码必须先被编译,然后才能被执行或作为依赖使用。例如,假设我们有两个包,"child "和 "parent"。子包很容易编译,因为它没有其他依赖关系:
$ npm install -g typescript
$ cd child
$ tsc
然而,当我们试图对依赖它的父包做同样的事情时,我们会得到一个错误,因为没有找到本地的依赖关系:
$ cd parent
$ tsc
src/index.ts:1:20 - error TS2307: Cannot find module 'child' or its corresponding type declarations.
1 import { moduleName } from 'child';
Found 1 error.
如果没有专门的工具,我们必须在保持正确的构建顺序的情况下手工构建和链接软件包。Yarn Workspaces已经解决了JavaScript中类似的问题。幸运的是,通过一些调整,我们可以将其扩展到TypeScript。
在Yarn中设置Workspaces
叉开并克隆下面的GitHub仓库,其中有几个包可以进行实验:
TomFern / semaphore-demo-monorepo-typescript
我们将建立一个由两个小包组成的TypeScript单体:
- shared:包含一些实用的函数。
- sayhi:主包提供了一个 "hello,world "程序。
让我们开始吧,要配置工作空间,请切换到最新的Yarn版本:
$ yarn set version berry
Yarn安装在.yarn/releases ,可以在repo中安全检查。
然后,初始化工作空间。这将创建packages 文件夹,一个.gitignore ,以及package.json 和yarn.lock :
$ yarn init -w
你可以添加根级别的依赖项,以便一次性构建所有项目:
$ yarn add -D typescript
你可以选择安装TypeScript插件,它为你处理类型。foreach插件对于同时在许多包中运行命令也很方便。
接下来,把代码移到packages :
$ git mv sayhi shared packages/
为了确认已经检测到了工作空间,运行:
$ yarn workspaces list --json
{"location":".","name":"semaphore-demo-monorepo-typescript"}
{"location":"packages/sayhi","name":"sayhi"}
{"location":"packages/shared","name":"shared"}
如果这是一个JavaScript单程序,我们就完成了。下面的部分将把TypeScript构建引入到组合中。
TypeScript工作空间
我们的演示包已经带有一个工作的tsconfig.json ,尽管是一个简单的。然而,我们还没有做任何事情来连接它们--到目前为止,它们是完全孤立的,没有互相引用。
我们可以使用项目引用来链接TypeScript包。这个功能是在TypeScript 3.0上引入的,它允许我们将一个应用程序分解成小块,并零散地构建它们。
首先,我们需要一个根级tsconfig.json ,内容如下:
{
"exclude": [
"packages/**/tests/**",
"packages/**/dist/**"
],
"references": [
{
"path": "./packages/shared"
},
{
"path": "./packages/sayhi"
}
]
}
正如你所看到的,我们在 repo 中每个包有一个path 项目。路径必须指向包含包特定的tsconfig.json 的文件夹。
被引用的包也需要启用复合选项。在packages/shared/tsconfig.json 和 packages/sayhi/tsconfig.json 中添加这一行:
{
"compilerOptions": {
"composite": true
. . .
}
}
依赖于monorepo中其他包的包将需要额外的引用。在packages/sayhi/tsconfig.json (父包)中添加一个references 指令。这几行放在文件的顶层,在compilerOptions 之外:
{
"references": [
{
"path": "../shared"
}
]
. . .
}
用yarn install 安装并构建组合的依赖关系。由于我们使用的是Yarn的最新版本,它将生成一个零安装文件,可以检查到存储库中。
现在配置已经准备好了,我们需要运行tsc 来首次构建所有的东西:
$ yarn tsc --build --force
你也可以用分别构建每个项目:
$ yarn workspace shared build
$ yarn workspace sayhi build
而且你可以尝试运行主程序:
$ yarn workspace sayhi node dist/src/sayhi.js
Hi, World
在本节结束时,monorepo的结构应该是这样的:
├── package.json├── packages│ ├── sayhi│ │ ├── dist/│ │ ├── src/│ │ ├── package.json│ │ └── tsconfig.json│ └── shared│ ├── dist/│ ├── src/│ ├── package.json│ └── tsconfig.json├── tsconfig.json└── yarn.lock
就是这样,Yarn和TypeScript一起工作。将所有内容提交到TypeScript monorepo中,这样我们就可以开始下一个阶段:用CI/CD自动测试:
$ git add -A
$ git commit -m "Set up TS and Yarn"
$ git push origin master
使用Semaphore进行构建和测试
该演示包括在final 分支中的一个随时可以工作的、基于变化的管道。但我们将通过从零开始创建它来更快地学习。

如果你以前从未使用过Semaphore,请查看入门指南。一旦你把分叉的演示仓库添加到 Semaphore 中,再回来,我们将完成设置。
我们将从头开始,使用启动器的单一作业模板。选择 "Single Job "并点击 "Customize"。

工作流生成器打开,让你配置管道。

构建TypeScript单作业
我们将设置一个TypeScript monorepo构建阶段。构建阶段将代码编译成JavaScript,并运行测试,如linting和单元测试。
第一个模块将构建shared 包,在作业中添加以下命令:
sem-version node 14.17.3
checkout
yarn workspace shared build

细节在入门指南里有深入介绍。但简而言之,sem-version会切换Node的活动版本(所以我们有版本一致性),而checkout会将版本库克隆到CI机器上。
向下滚动右边的窗格,直到你找到Skip/Run conditions。选择 "当条件满足时运行此块"。在When?字段中输入:
change_in('/packages/shared/')
change_in函数是单版本工作流的一个组成部分。它通过扫描Git历史记录来查找最近有哪些文件被修改。在这种情况下,我们实质上是要求Semaphore在/packages/shared 文件夹中没有文件发生变化时跳过该块。

创建一个新的区块来测试 TypeScript monorepo 的组件。我们将用它来运行ESLint和Jest的单元测试。
在序言中,输入:
sem-version node 14.17.3
checkout
在块中创建两个作业:
- Lint与命令
yarn workspace shared lint - 单元测试
yarn workspace shared test

同样,设置跳过/运行条件,并将条件与之前一样。

管理TypeScript monorepo的依赖性
我们将对sayhi 包重复上述步骤。在这里,我们只需要将yarn workspace shared <command> 的任何实例替换为。yarn workspace sayhi <command>.
现在,创建一个构建块,取消勾选依赖项部分。在流水线中删除块的依赖关系,使块并行运行。

接下来,将新块上的跳过/运行条件设置为:change_in('/packages/sayhi/').
最后,添加一个测试块,包括一个lint作业和一个单元测试作业。由于这个包依赖于shared ,我们可以在这一点上添加一个块级的依赖关系。完成后,你应该有总共四个块:

在这种情况下,跳过/运行条件是不同的,因为如果sayhi 或shared 发生变化,测试块应该运行。因此,我们必须提供一个数组,而不是一个单一的路径,以便让change_in ,正确处理所有的情况:
change_in(['/packages/sayhi', '/packages/shared'])
运行工作流
单击 "运行工作流",然后启动你的TypeScript monorepo。

管道第一次运行时,所有块都将被执行:

在连续的运行中,只有相关的块会被启动;其余的会被跳过,这将大大加快管道的运行速度,特别是当我们在 repo 中处理几十个或几百个包时:

阅读下一页
将TypeScript添加到组合中并不会使事情变得太复杂。这是一个很小的努力,但却能以更高的代码可读性和更少的错误来获得成倍的回报。
想继续学习monorepos吗?请看这些优秀的帖子和教程。