Repo
文档
入门
创建新的单仓库

创建新的单仓库

本指南使用 turbo 的全局安装。请遵循 安装指南 来完成此设置。或者,你可以在下面的命令中使用你的包管理器来运行本地安装的 turbo

快速入门

要创建一个新的单仓库,请使用我们的 create-turbo (在新标签页打开) npm 包

npx create-turbo@latest

你也可以克隆一个 Turborepo 启动器仓库来为你的单仓库快速上手。要查看 Turborepo 示例和启动器,请参阅 GitHub 上的 Turborepo 示例目录 (在新标签页打开).

完整教程

本教程将引导你设置一个基本示例。到最后,你将对使用 turbo 感到自信,并了解所有基本功能。

在本教程中,代码示例中省略了一些代码行。例如,在显示 package.json 时,我们不会显示所有键 - 只有重要的键。

1. 运行 create-turbo

首先,运行

npx create-turbo@latest

这将安装 create-turbo (在新标签页打开) CLI,并运行它。你将被问到几个问题

你想在哪个位置创建你的 turborepo?

选择任何你喜欢的位置。默认位置是 ./my-turborepo

你想使用哪个包管理器?

Turborepo 不处理包安装,因此你需要选择以下其中一个

create-turbo 将检测你的系统上可用的包管理器。如果你不确定选择哪个,Turborepo 建议使用 pnpm

安装

选择包管理器后,create-turbo 将在您选择的文件夹名称中创建一堆新文件。它还会安装默认情况下随 basic 示例一起提供的依赖项。

2. 探索您的新仓库

您可能在终端中注意到了一些东西。 create-turbo 给您提供了它正在添加的所有内容的描述。

>>> Creating a new turborepo with the following:

 - apps/web: Next.js with TypeScript
 - apps/docs: Next.js with TypeScript
 - packages/ui: Shared React component library
 - packages/eslint-config: Shared configuration (ESLint)
 - packages/typescript-config: Shared TypeScript `tsconfig.json`

这些都是工作区 - 包含一个 package.json 的文件夹。每个工作区都可以声明自己的依赖项,运行自己的脚本,并导出代码供其他工作区使用。

打开根文件夹 - ./my-turborepo - 在您最喜欢的代码编辑器中。

了解 packages/ui

首先,打开 ./packages/ui/package.json。您会注意到该包的名称是 "name": "@repo/ui" - 位于文件的最顶部。

接下来,打开 ./apps/web/package.json。您会注意到此包的名称是 "name": "web"。但也要看看它的依赖项。

您会看到 "web" 依赖于一个名为 "@repo/ui" 的包。

{
  "dependencies": {
    "@repo/ui": "*"
  }
}

这意味着我们的网络应用程序依赖于我们的本地 @repo/ui

如果您查看 apps/docs/package.json,您会看到相同的内容。 webdocs 都依赖于 @repo/ui - 一个共享的组件库。

这种在应用程序之间共享代码的模式在单体仓库中非常常见 - 这意味着多个应用程序可以共享一个设计系统。

了解导入和导出

查看 ./apps/docs/app/page.tsx 内部。 docsweb 都是 Next.js (opens in a new tab) 应用程序,它们都以类似的方式使用 @repo/ui

import { Button } from "@repo/ui/button";
//       ^^^^^^         ^^^^^^^^^^^^^^^
 
export default function Page() {
  return (
    <>
      <Button appName="web" className={styles.button}>
        Click me!
      </Button>
    <>
  );
}

它们从名为 @repo/ui/button 的依赖项中直接导入 Button!这是怎么工作的? Button 来自哪里?

打开 packages/ui/package.json。您会注意到 exports 字段

{
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx",
    "./code": "./src/code.tsx"
  },
}

当工作区从 @repo/ui/button 导入时, exports 会告诉它们在哪里访问它们正在导入的代码。

所以,让我们看看 packages/ui/src/button.tsx 内部

"use client";
 
import { ReactNode } from "react";
 
interface ButtonProps {
  children: ReactNode;
  className?: string;
  appName: string;
}
 
export const Button = ({ children, className, appName }: ButtonProps) => {
  return (
    <button
      className={className}
      onClick={() => alert(`Hello from your ${appName} app!`)}
    >
      {children}
    </button>
  );
};

我们找到了我们的按钮!

此文件中的所有内容都可以被依赖于 @repo/ui/button 的工作区使用。

我们在此文件中进行的任何更改都将在 webdocs 中共享。太酷了!

尝试从该文件中导出不同的函数。也许是 add(a, b) 用于将两个数字加在一起。

然后, webdocs 可以导入它。

了解 tsconfig

我们还有两个工作区要查看, typescript-configeslint-config。这些都允许在整个单体仓库中共享配置。让我们看看 typescript-config

{
  "name": "@repo/typescript-config",
}

在这里我们看到包的名称是 @repo/typescript-config

现在,让我们看看位于我们的 web 应用程序中的 tsconfig.json 文件。

{
  "extends": "@repo/typescript-config/nextjs.json",
}

如您所见,我们正在将 @repo/typescript-config/nextjs.json 直接导入到我们的 tsconfig.json 文件中。

这种模式允许单体仓库在所有工作区中共享一个 tsconfig.json,从而减少代码重复。

了解 eslint-config

我们最后一个工作区是 eslint-config

让我们从查看 packages/eslint-config/package.json 内部开始

{
  "name": "@repo/eslint-config",
  "files": [
    "library.js",
    "next.js",
    "react-internal.js"
  ],
}

如您所见,该包名为 @repo/eslint-config,它公开了三个文件: library.jsnext.jsreact-internal.js

要了解如何使用自定义 ESLint 配置,让我们看看 apps/docs/.eslintrc.js 内部

module.exports = {
  extends: ["@repo/eslint-config/next.js"],
};

在这里您可以看到,我们正在将 @repo/eslint-config/next.js 直接导入到我们的 .eslintrc.js 文件中。

就像 typescript-config 一样, eslint-config 让我们在整个单体仓库中共享 ESLint 配置,无论您在哪个项目上工作,都能保持一致性。

总结

了解这些工作区之间的依赖关系很重要。让我们将它们映射出来

  • web - 依赖于 ui, typescript-configeslint-config
  • docs - 依赖于 ui, typescript-configeslint-config
  • ui - 依赖于 typescript-configeslint-config
  • typescript-config - 无依赖
  • eslint-config - 无依赖

请注意,**Turborepo CLI 不负责管理这些依赖项**。以上所有内容都由您选择的包管理器(npm, pnpmyarn)处理。

3. 了解 turbo.json

现在我们了解了我们的仓库及其依赖关系。Turborepo 如何提供帮助?

Turborepo 通过简化任务运行并使其更加高效来提供帮助。

让我们看一下根目录下的 turbo.json

{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

我们在这里看到的是,我们已经使用 turbo 注册了三个任务:lint, devbuild。在 turbo.json 中注册的每个任务都可以使用 turbo run <task>(或简写为 turbo <task>)运行。

在我们继续之前,让我们尝试运行一个名为 hello 的任务,该任务turbo.json 中注册

turbo hello

您将在终端中看到错误。类似于

Could not find the following tasks in project: hello

值得记住的是 - **为了使 turbo 运行任务,它必须在 turbo.json 中**。

让我们调查一下我们已经设置的脚本。

4. 使用 Turborepo 进行代码风格检查

尝试运行我们的 lint 脚本

turbo lint

您将在终端中注意到发生了一些事情。

  1. 几个脚本将同时运行,每个脚本都以 docs:lint, @repo/ui:lintweb:lint 为前缀。
  2. 它们将全部成功,您将在终端中看到 3 successful
  3. 您还将看到 0 cached, 3 total。我们将在后面介绍这意味着什么。

每个运行的脚本都来自每个工作区的 package.json。每个工作区可以选择指定自己的 lint 脚本

{
  "scripts": {
    "lint": "next lint"
  }
}
{
  "scripts": {
    "lint": "next lint"
  }
}
{
  "scripts": {
    "lint": "eslint \"**/*.ts*\""
  }
}

当我们运行 turbo lint 时,Turborepo 会查看每个工作区中的每个 lint 脚本并运行它。有关更多详细信息,请参阅我们的 管道 文档。

使用缓存

让我们再次运行我们的 lint 脚本。您将在终端中注意到一些新内容出现

  1. cache hit, replaying logs 出现在 docs:lint, web:lint@repo/ui:lint 中。
  2. 您将看到 3 cached, 3 total
  3. 总运行时间应该在 100ms 以下,并且 >>> FULL TURBO 会出现。

刚刚发生了一些有趣的事情。Turborepo 意识到,**我们的代码自上次运行代码风格检查脚本以来没有发生变化**。

它保存了上次运行的日志,因此它只是重播了它们。

让我们尝试更改一些代码以查看会发生什么。对 apps/docs 中的文件进行更改

import { Button } from "@repo/ui/button";
//       ^^^^^^         ^^^^^^^^^^^^^^^
 
export default function Page() {
  return (
    <>
      <Button appName="web" className={styles.button}>
-        Click me!
+        Click me now!
      </Button>
    <>
  );
}

现在,再次运行 lint 脚本。您会注意到

  1. docs:lint 有一条评论说 cache miss, executing。这意味着 docs 正在运行其代码风格检查。
  2. 2 cached, 3 total 出现在底部。

这意味着 **我们之前任务的结果仍然被缓存了**。只有 docs 中的 lint 脚本实际上运行了 - 再次加快了速度。要了解更多信息,请查看我们的 缓存文档

5. 使用 Turborepo 进行构建

让我们尝试运行我们的 build 脚本

turbo build

您将看到与运行 lint 脚本时类似的输出。只有 apps/docsapps/web 在其 package.json 中指定了 build 脚本,因此只运行这些脚本。

查看 turbo.json 中的 build 部分。那里有一些有趣的配置。

{
  "pipeline": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"]
    }
  }
}

您会注意到已指定了一些 outputs。声明输出意味着当 turbo 完成运行您的任务时,它会将您指定的输出保存到其缓存中。

apps/docsapps/web 都是 Next.js 应用程序,它们将构建输出到 ./.next 文件夹。

让我们尝试一下。删除 apps/docs/.next 构建文件夹。

再次运行 build 脚本。您会注意到

  1. 我们遇到了 FULL TURBO - 构建在不到 100ms 内完成。
  2. .next 文件夹重新出现!

Turborepo 缓存了我们之前构建的结果。当我们再次运行 build 命令时,它从缓存中恢复了整个 .next/** 文件夹。要了解更多信息,请查看我们关于 缓存输出 的文档。

6. 运行开发脚本

现在让我们尝试运行 dev

turbo dev

您会注意到终端中的一些信息

  1. 只有两个脚本将执行 - docs:devweb:dev。这是唯一两个指定了 dev 的工作区。
  2. 两个 dev 脚本同时运行,在端口 30003001 上启动您的 Next.js 应用程序。
  3. 在终端中,您将看到 cache bypass, force executing

尝试退出脚本,然后重新运行它。您会注意到我们没有进入 FULL TURBO。这是为什么呢?

查看 turbo.json

{
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

dev 中,我们指定了 "cache": false。这意味着我们告诉 Turborepo *不要*缓存 dev 脚本的结果。 dev 运行一个持久化的开发服务器,并且不产生任何输出,因此没有可缓存的内容。在我们的文档中了解更多关于 关闭缓存 的信息。

此外,我们设置了 "persistent": true,以让 turbo 知道这是一个长时间运行的开发服务器,以便 turbo 可以确保没有其他任务依赖于它。您可以在 persistent 选项的文档 中了解更多信息。

一次只在一个工作区上运行 dev

默认情况下, turbo dev 将在所有工作区上同时运行 dev。但有时,我们可能只想选择一个工作区。

为了处理这种情况,我们可以向我们的命令添加一个 --filter 标志。

turbo dev --filter docs

您会注意到它现在只运行 docs:dev。从我们的文档中了解更多关于 过滤工作区 的信息。

总结

做得好!您已经了解了您的新单体仓库,以及 Turborepo 如何让处理您的任务变得更容易。

下一步