构建仓库
turbo
构建于 工作区 之上,工作区 是 JavaScript 生态系统中包管理器的一项功能,允许你将多个包分组到一个存储库中。
¥turbo
is built on top of Workspaces, a feature of package managers in the JavaScript ecosystem that allows you to group multiple packages in one repository.
遵循这些约定非常重要,因为它可以让你:
¥Following these conventions is important because it allows you to:
-
将这些约定应用于你所有代码库的工具。
¥Lean on those conventions for all your repo's tooling
-
快速、逐步地将 Turborepo 引入现有仓库
¥Quickly, incrementally adopt Turborepo into an existing repository
在本指南中,我们将逐步介绍如何设置多包工作区 (monorepo),以便为 turbo
奠定基础。
¥In this guide, we'll walk through setting up a multi-package workspace (monorepo) so we can set the groundwork for turbo
.
入门
¥Getting started
手动设置工作区结构可能很繁琐。如果你是 Monorepos 新手,我们建议你立即使用具有有效工作区结构的 使用 create-turbo
获取已开始。
¥Setting up a workspace's structure can be tedious to do by hand. If you're new to monorepos, we recommend using create-turbo
to get started with a valid workspace structure right away.
然后,你可以查看存储库中本指南中描述的特性。
¥You can then review the repository for the characteristics described in this guide.
工作区结构
¥Anatomy of a workspace
在 JavaScript 中,工作区可以是 单个包,也可以是包的集合。在这些指南中,我们将重点介绍 多包工作区,通常称为 "monorepo"。
¥In JavaScript, a workspace can either be a single package or a collection of packages. In these guides, we'll be focusing on a multi-package workspace, often called a "monorepo".
下面高亮了使 create-turbo
成为有效工作区的结构元素。
¥Below, the structural elements of create-turbo
that make it a valid workspace are highlighted.
最低要求要求
¥Minimum requirements
在 monorepo 中指定软件包
¥Specifying packages in a monorepo
声明包的目录
¥Declaring directories for packages
首先,你的包管理器需要描述你的包的位置。我们建议首先将你的包拆分为用于应用和服务的 apps/
包和用于其他所有内容(例如库和工具)的 packages/
包。
¥First, your package manager needs to describe the locations of your packages. We recommend starting with splitting your packages into apps/
for applications and services and packages/
for everything else, like libraries and tooling.
使用此配置,apps
或 packages
目录中每个包含 package.json
的目录都将被视为一个包。
¥Using this configuration, every directory with a package.json
in the apps
or packages
directories will be considered a package.
Turborepo 不支持嵌套包,例如 apps/**
或 packages/**
,这是因为 JavaScript 生态系统中的包管理器之间存在行为歧义。使用将一个包放在 apps/a
而另一个包放在 apps/a/b
的结构将导致错误。
¥Turborepo does not support nested packages like apps/**
or packages/**
due to ambiguous behavior among package managers in the JavaScript ecosystem. Using a structure that would put a package at apps/a
and another at apps/a/b
will result in an error.
如果你想按目录对软件包进行分组,可以使用像 packages/*
和 packages/group/*
这样的通配符来实现,而无需创建 packages/group/package.json
文件。
¥If you'd like to group packages by directory, you can do this using globs like packages/*
and packages/group/*
and not creating a packages/group/package.json
file.
每个包中的 package.json
¥package.json
in each package
在包的目录中,必须有一个 package.json
才能使包管理器和 turbo
能够发现该包。package.json
的要求包 如下所示。
¥In the directory of the package, there must be a package.json
to make the package discoverable to your package manager and turbo
. The requirements for the package.json
of a package are below.
Root package.json
根 package.json
是工作区的基础。以下是你在根 package.json
中可以找到的内容的常见示例:
¥The root package.json
is the base for your workspace. Below is a common example of what you would find in a root package.json
:
Root turbo.json
turbo.json
用于配置 turbo
的行为。要了解更多关于如何配置任务的信息,请访问 配置任务 页面。
¥turbo.json
is used to configure the behavior of turbo
. To learn more about how to configure your tasks, visit the Configuring tasks page.
软件包管理器锁定文件
¥Package manager lockfile
锁文件是包管理器和 turbo
可重现行为的关键。此外,Turborepo 使用锁文件来理解工作区内 内部软件包 之间的依赖。
¥A lockfile is key to reproducible behavior for both your package manager and turbo
. Additionally, Turborepo uses the lockfile to understand the dependencies between your Internal Packages within your Workspace.
如果你在运行 turbo
时没有锁文件,你可能会看到不可预测的行为。
¥If you do not have a lockfile present when you run turbo
, you may see
unpredictable behavior.
软件包结构
¥Anatomy of a package
通常最好开始考虑将包设计为工作区中的独立单元。从高层次来看,每个包几乎就像一个小型的 "project",拥有自己的 package.json
、工具配置和源代码。这个想法有其局限性,但它是一个很好的入门思路。
¥It's often best to start thinking about designing a package as its own unit within the Workspace. At a high-level, each package is almost like its own small "project", with its own package.json
, tooling configuration, and source code. There are limits to this idea—but its a good mental model to start from.
此外,包具有特定的入口点,工作区中的其他包可以使用这些入口点访问该包,这些入口点由 exports
指定。
¥Additionally, a package has specific entrypoints that other packages in your Workspace can use to access the package, specified by exports
.
包的 package.json
¥package.json
for a package
name
name
字段 用于标识包。它在你的工作区内应该是唯一的。
¥The name
field is used to identify the package. It should be unique within your workspace.
最佳做法是为 内部软件包 使用命名空间前缀,以避免与 npm 注册表上的其他包冲突。例如,如果你的组织名为 acme
,则可以将你的包命名为 @acme/package-name
。
¥It's best practice to use a namespace prefix for your Internal Packages to avoid conflicts with other packages on the npm registry. For example, if your organization is named acme
, you might name your packages @acme/package-name
.
我们在文档和示例中使用 @repo
,因为它是 npm 注册表中未使用且不可声明的命名空间。你可以选择保留它或使用你自己的前缀。
¥We use @repo
in our docs and examples because it is an unused, unclaimable namespace on the npm registry. You can choose to keep it or use your own prefix.
scripts
scripts
字段用于定义可在包上下文中运行的脚本。Turborepo 将使用这些脚本的名称来确定在包中运行哪些脚本(如果有)。我们在 正在运行的任务 页面上详细介绍了这些脚本。
¥The scripts
field is used to define scripts that can be run in the package's context. Turborepo will use the name of these scripts to identify what scripts to run (if any) in a package. We talk more about these scripts on the Running Tasks page.
exports
exports
字段 用于指定其他想要使用该包的包的入口点。当你想在另一个包中使用一个包中的代码时,你将从该入口点导入。
¥The exports
field is used to specify the entrypoints for other packages that want to use the package. When you want to use code from one package in another package, you'll import from that entrypoint.
例如,如果你有一个 @repo/math
包,则可能具有以下 exports
字段:
¥For example, if you had a @repo/math
package, you might have the following exports
field:
请注意,此示例为了简单起见使用了 Just-in-Time 包 模式。它直接导出 TypeScript,但你也可以选择使用 编译包 模式。
¥Note that this example uses the Just-in-Time Package pattern for simplicity. It exports TypeScript directly, but you might choose to use the Compiled Package pattern instead.
本例中的 exports
字段需要 Node.js 和 TypeScript 的现代版本。
¥The exports
field in this example requires modern versions of Node.js and
TypeScript.
这将允许你从 @repo/math
包中导入 add
和 subtract
函数,如下所示:
¥This would allow you to import add
and subtract
functions from the @repo/math
package like so:
以这种方式使用导出有三大好处:
¥Using exports this way provides three major benefits:
-
避免使用 barrel 文件:Barrel 文件是重新导出同一包中其他文件的文件,为整个包创建一个入口点。虽然它们看起来很方便,但它们是 编译器和打包器难以处理 的固有特性,很快就会导致性能问题。
¥Avoiding barrel files: Barrel files are files that re-export other files in the same package, creating one entrypoint for the entire package. While they might appear convenient, they're difficult for compilers and bundlers to handle and can quickly lead to performance problems.
-
更强大的功能:与
main
字段 相比,exports
还具有其他强大的功能,例如 条件导出。一般来说,我们建议尽可能使用exports
而不是main
,因为它是更现代的选择。¥More powerful features:
exports
also has other powerful features compared to themain
field like Conditional Exports. In general, we recommend usingexports
overmain
whenever possible as it is the more modern option. -
IDE 自动补全:通过使用
exports
指定包的入口点,你可以确保代码编辑器能够为包的导出提供自动补全功能。¥IDE autocompletion: By specifying the entrypoints for your package using
exports
, you can ensure that your code editor can provide auto-completion for the package's exports.
imports
(可选)
¥imports
(optional)
imports
字段 为你提供了一种在软件包中创建指向其他模块的子路径的方法。你可以将这些视为 "shortcuts",以编写更简单的导入路径,这些路径对于移动文件的重构更具弹性。要了解如何操作,请访问 TypeScript 页面。
¥The imports
field gives you a way to create subpaths to other modules within your package. You can think of these like "shortcuts" to write simpler import paths that are more resilient to refactors that move files. To learn how, visit the TypeScript page.
你可能更熟悉 TypeScript 的 compilerOptions#paths
选项,它可以实现类似的目标。从 TypeScript 5.4 开始,TypeScript 可以从 imports
推断子路径,由于你将使用 Node.js 约定,因此它是一个更好的选择。更多信息请访问 我们的 TypeScript 指南。
¥You may be more familiar with TypeScript's compilerOptions#paths
option, which accomplishes a similar goal. As of TypeScript 5.4, TypeScript can infer subpaths from imports
, making it a better option since you'll be working with Node.js conventions. For more information, visit our TypeScript guide.
源代码
¥Source code
当然,你的包中也需要包含一些源代码。软件包通常使用 src
目录来存储其源代码,并编译到 dist
目录(该目录也应位于软件包内),但这不是必需的。
¥Of course, you'll want some source code in your package. Packages commonly use an src
directory to store their source code and compile to a dist
directory (that should also be located within the package), although this is not a requirement.
常见陷阱
¥Common pitfalls
-
如果你使用的是 TypeScript,则可能不需要在工作区根目录中安装
tsconfig.json
。软件包应该独立指定其自身的配置,通常基于工作区中单独软件包的共享tsconfig.json
构建。更多信息请访问 TypeScript 指南。¥If you're using TypeScript, you likely don't need a
tsconfig.json
in the root of your workspace. Packages should independently specify their own configurations, usually building off of a sharedtsconfig.json
from a separate package in the workspace. For more information, visit the TypeScript guide. -
你希望尽可能避免跨包访问文件。如果你发现自己需要编写
../
来从一个包切换到另一个包,那么你可能有机会重新思考你的方法,方法是将包安装在需要的位置并将其导入到代码中。¥You want to avoid accessing files across package boundaries as much as possible. If you ever find yourself writing
../
to get from one package to another, you likely have an opportunity to re-think your approach by installing the package where it's needed and importing it into your code.
后续步骤
¥Next steps
配置好工作区后,你现在可以使用包管理器来运行 将依赖安装到你的包中。
¥With your Workspace configured, you can now use your package manager to install dependencies into your packages.