Turborepo

TypeScript

TypeScript 是 monorepo 中一个优秀的工具,它允许团队安全地向 JavaScript 代码添加类型。虽然设置过程有些复杂,但本指南将引导您完成大多数用例的 TypeScript 设置的重要部分。

本指南假设您正在使用最新版本的 TypeScript,并使用了一些仅在这些版本中可用的功能。如果您无法使用这些版本的功能,您可能需要调整本页上的指导。

共享 tsconfig.json

您希望在 TypeScript 配置中建立一致性,以便您的整个仓库可以使用出色的默认值,并且您的开发人员同事可以在工作区中编写代码时知道会发生什么。

TypeScript 的 tsconfig.json 设置 TypeScript 编译器的配置,并具有一个 extends,您将使用它在整个工作区中共享配置。

本指南将使用 create-turbo 作为示例。

终端
npx create-turbo@latest

使用基本 tsconfig 文件

packages/typescript-config 内部,您有几个 json 文件,它们代表您可能希望在各种包中配置 TypeScript 的不同方式。 base.json 文件被工作区中的每个其他 tsconfig.json 扩展,如下所示

./packages/typescript-config/base.json
"compilerOptions": {
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "es2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "module": "NodeNext"
}
tsconfig 选项参考

创建包的其余部分

此包中的其他 tsconfig 文件使用 extends 键来从基本配置开始,并为特定类型的项目进行自定义,例如 Next.js (nextjs.json) 和 React 库 (react-library.json)。

package.json 内部,命名该包以便可以在工作区的其余部分中引用它

packages/typescript-config/package.json
{
  "name": "@repo/typescript-config"
}

构建 TypeScript 包

使用配置包

首先,将 @repo/typescript-config 包安装到您的包中

./apps/web/package.json
{
  "devDependencies": {
     "@repo/typescript-config": "*",
     "typescript": "latest",
  }
}

然后,从 @repo/typescript-config 包扩展该包的 tsconfig.json。在此示例中,web 包是一个 Next.js 应用程序

./apps/web/tsconfig.json
{
  "extends": "@repo/typescript-config/nextjs.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

创建包的入口点

首先,请确保您的代码使用 tsc 进行编译,以便会有一个 dist 目录。您将需要一个 build 脚本以及一个 dev 脚本

./packages/ui/package.json
{
  "scripts": {
    "dev": "tsc --watch",
    "build": "tsc"
  }
}

然后,在 package.json 中设置包的入口点,以便其他包可以使用编译后的代码

./packages/ui/package.json
{
  "exports": {
    "./button": {
      "types": "./src/button.ts"
      "default": "./dist/button.js",
    },
    "./input": {
      "types": "./src/input.ts"
      "default": "./dist/input.js",
    }
  }
}

以这种方式设置 exports 有几个优点

  • 使用 types 字段允许 tsserver 使用 src 中的代码作为代码类型的真实来源。您的编辑器将始终使用代码的最新接口保持最新状态。
  • 您可以快速向您的包添加新的入口点,而无需创建 危险的 barrel 文件
  • 您将在编辑器中收到跨包边界的导入的自动导入建议。有关为什么您可能不希望对入口点使用通配符的更多信息,请参阅限制部分

如果您正在发布该包,则不能在 types 中使用对源代码的引用,因为只有编译后的代码会发布到 npm。您需要生成并引用声明文件和源映射。

代码库的 Lint

要将 TypeScript 用作 linter,您可以使用 Turborepo 的缓存和并行化 **快速** 检查整个工作区的类型。

首先,将 check-types 脚本添加到您要检查其类型的任何包中

./apps/web/package.json
{
  "scripts": {
    "check-types": "tsc --noEmit"
  }
}

然后,在 turbo.json 中创建一个 check-types 任务。从 配置任务指南中,我们可以使用传输节点使任务并行运行,同时尊重其他包的源代码更改

./turbo.json
{
  "tasks": {
    "topo": {
      "dependsOn": ["^topo"]
    },
    "check-types": {
      "dependsOn": ["topo"]
    }
  }
}

然后,使用 turbo check-types 运行您的任务。

最佳实践

使用 tsc 编译您的包

对于内部包,我们建议您尽可能使用 tsc 来编译 TypeScript 库。虽然您可以使用 bundler,但这不是必需的,并且会为您的构建过程增加额外的复杂性。此外,捆绑库可能会在代码到达应用程序的 bundler 之前对其进行修改,从而导致难以调试的问题。

启用跨包边界的跳转到定义

“跳转到定义”是编辑器的一项功能,用于通过单击或热键快速导航到符号(如变量或函数)的原始声明或定义。正确配置 TypeScript 后,您可以轻松地跨内部包导航。

即时包

只要您不使用入口点通配符,从即时包导出的内容将自动将您带到原始的 TypeScript 源代码。跳转到定义将按预期工作。

已编译的包

已编译的包导出的内容需要使用 declarationdeclarationMap 配置才能使跳转到定义正常工作。在为该包启用这两个配置后,使用 tsc 编译该包,并打开输出目录以查找声明文件和源映射。

button.js
button.d.ts
button.d.ts.map

有了这两个文件,您的编辑器现在将导航到原始源代码。

使用 Node.js 子路径导入代替 TypeScript 编译器 paths

可以使用 TypeScript 编译器的 paths 选项在您的包中创建绝对导入,但是在使用 即时包时,这些路径可能会导致编译失败。从 TypeScript 5.4 开始,您可以使用 Node.js 子路径导入来获得更强大的解决方案。

即时包

即时 (Just-in-Time) 包中,由于不会创建像 dist 这样的构建输出,imports 必须指向包中的源代码。

./packages/ui/package.json
{
  "imports": {
    "#*": "./src/*"
  }
}

已编译的包

已编译包中,imports 指向包的构建输出。

./packages/ui/package.json
{
  "imports": {
    "#*": "./dist/*"
  }
}

您可能不需要在项目的根目录中放置 tsconfig.json 文件

正如构建存储库指南中所述,您需要将工具中的每个包都视为独立的单元。这意味着每个包都应该有自己的 tsconfig.json,而不是引用项目根目录中的 tsconfig.json。遵循此实践将使 Turborepo 更容易缓存您的类型检查任务,从而简化您的配置。

您可能需要在工作区根目录中放置 tsconfig.json 的唯一情况是为不在包中的 TypeScript 文件设置配置。例如,如果您有一个使用 TypeScript 编写的脚本,需要从根目录运行,则可能需要为该文件配置 tsconfig.json

然而,这种做法也是不鼓励的,因为工作区根目录中的任何更改都会导致所有任务错过缓存。相反,请将这些脚本移动到存储库中的其他目录。

您可能不需要 TypeScript 项目引用

我们不建议使用 TypeScript 项目引用,因为它们会引入另一个配置点和另一个工作区缓存层。这两个都可能在您的存储库中引起问题,而几乎没有好处,因此我们建议在使用 Turborepo 时避免使用它们。

限制

您的编辑器不会使用包的 TypeScript 版本

tsserver 无法在您的代码编辑器中为不同的包使用不同的 TypeScript 版本。相反,它会发现一个特定的版本并在所有地方使用它。

这可能导致编辑器中显示的 lint 错误与您运行 tsc 脚本检查类型时出现的错误之间存在差异。如果这对您来说是一个问题,请考虑保持 TypeScript 依赖项的版本相同

包入口点通配符

我们建议显式列出包的入口点 - 但对于某些人来说,这感觉太冗长了。相反,您可以使用通配符来捕获入口点

./packages/ui/package.json
{
  "exports": {
    "./*": {
      "types": "./src/*.ts",
      "default": "./dist/*.js"
    }
  }
}

虽然这会起作用,但它的缺点是无法跨包边界自动导入,这是由于 TypeScript 编译器的性能原因。这个权衡是否值得取决于您的用例。

小时

节省的总计算时间
开始使用
远程缓存 →

本页内容