TypeScript

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

¥TypeScript is an excellent tool in monorepos, allowing teams to safely add types to their JavaScript code. While there is some complexity to getting set up, this guide will walk you through the important parts of a TypeScript setup for most use cases.

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

¥This guide assumes you are using a recent version of TypeScript and uses some features that are only available in those versions. You may need to adjust the guidance on this page if you are unable to features from those versions.

共享 tsconfig.json

¥Sharing tsconfig.json

你希望在 TypeScript 配置中建立一致性,以便你的整个代码库可以使用良好的默认值,并且你的其他开发者在工作区中编写代码时能够了解预期的内容。

¥You want to build consistency into your TypeScript configurations so that your entire repo can use great defaults and your fellow developers can know what to expect when writing code in the Workspace.

TypeScript 的 tsconfig.json 设置了 TypeScript 编译器的配置,并提供了一个 extends,你可以使用它在整个工作区中共享配置。

¥TypeScript's tsconfig.json sets the configuration for the TypeScript compiler and features an extends key that you'll use to share configuration across your workspace.

本指南将以 create-turbo 为例。

¥This guide will use create-turbo as an example.

Terminal
pnpm dlx create-turbo@latest

使用基础 tsconfig 文件

¥Use a base tsconfig file

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

¥Inside packages/typescript-config, you have a few json files which represent different ways you might want to configure TypeScript in various packages. The base.json file is extended by every other tsconfig.json in the workspace and looks like this:

./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 options reference

创建其余部分包

¥Creating the rest of the package

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

¥The other tsconfig files in this package use the extends key to start with the base configuration and customize for specific types of projects, like for Next.js (nextjs.json) and a React library (react-library.json).

package.json 文件中,为包命名,以便可以在工作区的其余部分引用它:

¥Inside package.json, name the package so it can be referenced in the rest of the Workspace:

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

构建 TypeScript 软件包

¥Building a TypeScript package

使用配置包

¥Using the configuration package

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

¥First, install the @repo/typescript-config package into your package:

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

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

¥Then, extend the tsconfig.json for the package from the @repo/typescript-config package. In this example, the web package is a Next.js application:

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

创建包的入口点

¥Creating entrypoints to the package

首先,确保你的代码使用 tsc 编译,这样就会有一个 dist 目录。你需要一个 build 脚本和一个 dev 脚本:

¥First, make sure your code gets compiled with tsc so there will be a dist directory. You'll need a build script as well as a dev script:

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

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

¥Then, set up the entrypoints for your package in package.json so that other packages can use the compiled code:

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

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

¥Setting up exports this way has several advantages:

  • 使用 types 字段允许 tsserver 使用 src 中的代码作为代码类型的真实来源。你的编辑器将始终使用你代码中的最新接口保持更新。

    ¥Using the types field allows tsserver to use the code in src as the source of truth for your code's types. Your editor will always be up-to-date with the latest interfaces from your code.

  • 你可以快速向包中添加新的入口点,而无需创建 危险的桶文件

    ¥You can quickly add new entrypoints to your package without creating dangerous barrel files.

  • 你将在编辑器中收到跨包导入的自动导入建议。

    ¥You'll receive auto-importing suggestions for your imports across package boundaries in your editor.

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

¥If you're publishing the package, you cannot use references to source code in types since only the compiled code will be published to npm. You'll need to generate and reference declaration files and source maps.

代码库的 Linting

¥Linting your codebase

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

¥To use TypeScript as a linter, you can check the types across your workspace fast using Turborepo's caching and parallelization.

首先,向任何你想要检查类型的包添加一个 check-types 脚本:

¥First, add a check-types script to any package that you want to check the types for:

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

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

¥Then, create a check-types task in turbo.json. From the Configuring tasks guide, we can make the task run in parallel while respecting source code changes from other packages using a Transit Node:

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

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

¥Then, run your task using turbo check-types.

最佳实践

¥Best practices

使用 tsc 编译你的软件包

¥Use tsc to compile your packages

对于 内部软件包,我们建议你尽可能使用 tsc 来编译 TypeScript 库。虽然你可以使用打包器,但它不是必需的,而且会增加构建过程的复杂性。此外,打包库可能会在代码到达应用的打包器之前对其进行破坏,从而导致难以调试的问题。

¥For Internal Packages, we recommend that you use tsc to compile your TypeScript libraries whenever possible. While you can use a bundler, it's not necessary and adds extra complexity to your build process. Additionally, bundling a library can mangle the code before it makes it to your applications' bundlers, causing hard to debug issues.

启用跨包边界的跳转定义

¥Enable go-to-definition across package boundaries

"跳转至定义" 是一项编辑器功能,可通过单击或热键快速导航到符号(如变量或函数)的原始声明或定义。正确配置 TypeScript 后,你可以轻松在 内部软件包 中导航。

¥"Go-to-definition" is an editor feature for quickly navigating to the original declaration or definition of a symbol (like a variable or function) with a click or hotkey. Once TypeScript is configured correctly, you can navigate across Internal Packages with ease.

即时软件包

¥Just-in-Time Packages

即时软件包 导出将自动转到原始 TypeScript 源代码。跳转定义功能将按预期工作。

¥Exports from Just-in-Time Packages will automatically bring you to the original TypeScript source code. Go-to-definition will work as expected.

已编译的软件包

¥Compiled Packages

已编译的软件包 导出需要使用 declarationdeclarationMap 配置才能实现跳转定义功能。为包启用这两个配置后,使用 tsc 编译包,然后打开输出目录以查找声明文件和源映射。

¥Exports from Compiled Packages require the use of declaration and declarationMap configurations for go-to-definition to work. After you've enabled these two configurations for the package, compile the package with tsc, and open the output directory to find declaration files and source maps.

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

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

¥With these two files in place, your editor will now navigate to the original source code.

使用 Node.js 子路径导入,而不是 TypeScript 编译器 paths

¥Use Node.js subpath imports instead of TypeScript compiler paths

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

¥It's possible to create absolute imports in your packages using the TypeScript compiler's paths option, but these paths can cause failed compilation when using Just-in-Time Packages. As of TypeScript 5.4, you can use Node.js subpath imports instead for a more robust solution.

即时软件包

¥Just-in-Time Packages

Just-in-Time 包 中,imports 必须以包中的源代码为目标,因为不会创建像 dist 这样的构建输出。

¥In Just-in-Time packages, imports must target the source code in the package, since build outputs like dist won't be created.

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

已编译的软件包

¥Compiled Packages

编译包 中,imports 以包的构建输出为目标。

¥In Compiled packages, imports target the built outputs for the package.

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

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

¥You likely don't need a tsconfig.json file in the root of your project

正如 构建仓库指南 中提到的,你需要将工具中的每个包视为独立的单元。这意味着每个包都应该有自己的 tsconfig.json 可供使用,而不是在项目根目录中引用 tsconfig.json。遵循此做法将使 Turborepo 更轻松地缓存你的类型检查任务,从而简化你的配置。

¥As mentioned in the Structuring your repository guide, you want to treat each package in your tooling as its own unit. This means each package should have its own tsconfig.json to use instead of referencing a tsconfig.json in the root of your project. Following this practice will make it easier for Turborepo to cache your type checking tasks, simplifying your configuration.

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

¥The only case in which you may want to have a tsconfig.json in the Workspace root is to set configuration for TypeScript files that are not in packages. For example, if you have a script written with TypeScript that you need to run from the root, you may need a tsconfig.json for that file.

但是,我们不建议这样做,因为工作区根目录中的任何更改都会导致所有任务缓存丢失。相反,将这些脚本移动到代码库中的其他目录。

¥However, this practice is also discouraged since any changes in the Workspace root will cause all tasks to miss cache. Instead, move those scripts to a different directory in the repository.

你可能不需要 TypeScript 项目引用

¥You likely don't need TypeScript Project References

我们不建议使用 TypeScript 项目引用,因为它们会为你的工作区引入另一个配置点和另一个缓存层。这两种方式都可能在你的仓库中造成问题,而且几乎没有什么好处,所以我们建议在使用 Turborepo 时避免使用它们。

¥We don't recommend using TypeScript Project References as they introduce both another point of configuration as well as another caching layer to your workspace. Both of these can cause problems in your repository with little benefit, so we suggest avoiding them when using Turborepo.

限制

¥Limitations

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

¥Your editor won't use a package's TypeScript version

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

¥tsserver is not able to use different TypeScript versions for different packages in your code editor. Instead, it will discover a specific version and use that everywhere.

这可能会导致编辑器中显示的 linting 错误与你运行 tsc 脚本检查类型时显示的 linting 错误不同。如果这对你来说是一个问题,请考虑使用 保持 TypeScript 依赖在同一版本上

¥This can result in differences between the linting errors that show in your editor and when you run tsc scripts to check types. If this is an issue for you, consider keeping the TypeScript dependency on the same version.