仓库结构
turbo
构建于 Workspaces 之上,它是 JavaScript 生态系统中包管理器的一项功能,允许你将多个包分组到一个仓库中。
遵循这些约定非常重要,因为它允许你
- 为你的仓库的所有工具依赖这些约定
- 快速、渐进地将 Turborepo 引入现有仓库
在本指南中,我们将引导你设置多包工作区 (monorepo),以便为 turbo
奠定基础。
开始使用
手动设置工作区的结构可能很繁琐。如果你是 monorepo 新手,我们建议使用 create-turbo
立即开始使用有效的工作区结构。
然后你可以查看仓库是否具有本指南中描述的特征。
工作区剖析
在 JavaScript 中,工作区可以是单个包或包的集合。在这些指南中,我们将重点关注多包工作区,通常称为“monorepo”。
下面,突出显示了 create-turbo
的结构元素,这些元素使其成为有效的工作区。
最低要求
在 monorepo 中指定包
声明包的目录
首先,你的包管理器需要描述你的包的位置。我们建议首先将你的包拆分为 apps/
用于应用程序和服务,以及 packages/
用于所有其他内容,例如库和工具。
使用此配置,apps
或 packages
目录中每个带有 package.json
的目录都将被视为一个包。
由于 JavaScript 生态系统中包管理器之间存在歧义行为,Turborepo 不支持嵌套包,例如 apps/**
或 packages/**
。使用将包放在 apps/a
和另一个包放在 apps/a/b
的结构将导致错误。
如果你想按目录对包进行分组,你可以使用 glob 模式,例如 packages/*
和 packages/group/*
,并且不要创建 packages/group/package.json
文件。
每个包中的 package.json
在包的目录中,必须有一个 package.json
,以使包对你的包管理器和 turbo
可发现。包的 package.json
的要求如下。
根目录 package.json
根目录 package.json
是你的工作区的基础。下面是在根目录 package.json
中可以找到的常见示例
根目录 turbo.json
turbo.json
用于配置 turbo
的行为。要了解有关如何配置任务的更多信息,请访问配置任务页面。
包管理器锁定文件
锁定文件是包管理器和 turbo
可重现行为的关键。此外,Turborepo 使用锁定文件来了解你的工作区中内部包之间的依赖关系。
如果在运行 turbo
时没有锁定文件,你可能会看到不可预测的行为。
包剖析
通常最好开始考虑将包设计为工作区内的独立单元。在较高层面,每个包几乎都像它自己的小型“项目”,具有自己的 package.json
、工具配置和源代码。这个想法有一些局限性——但这是一个很好的起点。
此外,包具有特定的入口点,工作区中的其他包可以使用这些入口点来访问该包,由 exports
指定。
包的 package.json
name
name
字段用于标识包。它在你的工作区中应该是唯一的。
最佳实践是为你的内部包使用命名空间前缀,以避免与 npm 注册表上的其他包冲突。例如,如果你的组织名为 acme
,你可以将你的包命名为 @acme/package-name
。
我们在文档和示例中使用 @repo
,因为它是在 npm 注册表上未使用的、无法声明的命名空间。你可以选择保留它或使用你自己的前缀。
scripts
scripts
字段用于定义可以在包的上下文中运行的脚本。Turborepo 将使用这些脚本的名称来标识要在包中运行哪些脚本(如果有)。我们在运行任务页面上详细讨论了这些脚本。
exports
exports
字段用于指定想要使用该包的其他包的入口点。当你想在一个包中使用来自另一个包的代码时,你将从该入口点导入。
例如,如果你有一个 @repo/math
包,你可能会有以下 exports
字段
请注意,此示例为了简单起见,使用了即时包模式。它直接导出 TypeScript,但你也可以选择使用编译包模式。
此示例中的 exports
字段需要 Node.js 和 TypeScript 的现代版本。
这将允许你从 @repo/math
包中导入 add
和 subtract
函数,如下所示
以这种方式使用 exports 提供了三个主要好处
- 避免 barrel 文件:Barrel 文件是在同一包中重新导出其他文件的文件,为整个包创建一个入口点。虽然它们可能看起来很方便,但它们对于编译器和打包器来说很难处理,并且可能很快导致性能问题。
- 更强大的功能:与
main
字段 相比,exports
还具有其他强大的功能,例如 条件导出。总的来说,我们建议尽可能使用exports
而不是main
,因为它是更现代的选择。 - IDE 自动完成:通过使用
exports
指定包的入口点,你可以确保你的代码编辑器可以为包的导出提供自动完成。
imports
(可选)
imports
字段为你提供了一种在包中创建指向其他模块的子路径的方法。你可以将它们视为“快捷方式”,用于编写更简单的导入路径,这些路径更能抵抗移动文件的重构。要了解如何操作,请访问TypeScript 页面。
你可能更熟悉 TypeScript 的 compilerOptions#paths
选项,它实现了类似的目标。从 TypeScript 5.4 开始,TypeScript 可以从 imports
推断子路径,使其成为更好的选择,因为你将使用 Node.js 约定。有关更多信息,请访问我们的 TypeScript 指南。
源代码
当然,你需要在你的包中添加一些源代码。包通常使用 src
目录来存储它们的源代码,并编译到 dist
目录(也应该位于包内),但这并不是必需的。
常见陷阱
- 如果你正在使用 TypeScript,你可能不需要在工作区的根目录中放置
tsconfig.json
。包应该独立指定自己的配置,通常基于工作区中单独包的共享tsconfig.json
构建。有关更多信息,请访问TypeScript 指南。 - 你要尽可能避免跨包边界访问文件。如果你发现自己编写
../
以从一个包转到另一个包,你可能有一个机会重新思考你的方法,即在需要的地方安装该包并将其导入到你的代码中。
下一步
配置好你的工作区后,你现在可以使用你的包管理器将依赖项安装到你的包中。