如何使用Yarn和Semaphore大规模地构建和测试TypeScript项目

390 阅读6分钟

在这个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.jsonyarn.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 的组件。我们将用它来运行ESLintJest的单元测试。

在序言中,输入:

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 ,我们可以在这一点上添加一个块级的依赖关系。完成后,你应该有总共四个块:

在这种情况下,跳过/运行条件是不同的,因为如果sayhishared 发生变化,测试块应该运行。因此,我们必须提供一个数组,而不是一个单一的路径,以便让change_in ,正确处理所有的情况:

change_in(['/packages/sayhi', '/packages/shared'])

运行工作流

单击 "运行工作流",然后启动你的TypeScript monorepo。

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

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

阅读下一页

将TypeScript添加到组合中并不会使事情变得太复杂。这是一个很小的努力,但却能以更高的代码可读性和更少的错误来获得成倍的回报。

想继续学习monorepos吗?请看这些优秀的帖子和教程。