包配置

许多单仓可以声明一个 turbo.json 在根目录下,其中包含一个 任务描述,适用于所有包。但是,有时一个单仓可能包含需要不同配置任务的包。

为了满足这种需求,Turborepo 允许你使用任何包中的 turbo.json 来扩展根目录的配置。这种灵活性使得更多样化的应用和包可以在工作区中共存,并允许包所有者在不影响单仓中其他应用和包的情况下维护专门的任务和配置。

工作原理

要覆盖根目录 turbo.json 中定义的任何任务的配置,请在你的单仓中的任何包中添加一个 turbo.json 文件,并使用一个顶级的 extends 键。

Turborepo logo
./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      // Custom configuration for the build task in this package
    },
    "special-task": {} // New task specific to this package
  }
}

extends 数组必须以 ["//"] 开头。// 是一个用于标识单仓根目录的特殊名称。你也可以通过在 // 之后添加它们来扩展其他包(例如 ["//", "shared-config"])。

继承行为

当一个包配置扩展了根目录的 turbo.json 时,任务属性的继承方式取决于它们的类型。

标量字段被继承

outputLogs, cache, persistentinteractive 这样的标量字段会从根目录配置中**继承**。你只需要在包配置中指定它们,如果你想覆盖它们的话。

例如,如果你的根目录 turbo.json 为某个任务设置了 "outputLogs": "hash-only",所有包都会自动继承该设置。

默认情况下,数组字段会替换

outputs, env, inputs, dependsOnpassThroughEnv 这样的数组字段默认会**完全替换**根目录配置的值。

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "outputs": ["dist/**"],
      "env": ["NODE_ENV"]
    }
  }
}
Turborepo logo
./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      // This REPLACES the root outputs - "dist/**" is NOT included
      "outputs": [".next/**"]
    }
  }
}

使用 $TURBO_EXTENDS$ 扩展数组

要**添加**到继承的数组值而不是替换它们,请使用 $TURBO_EXTENDS$ 微语法

Turborepo logo
./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      // Inherits "dist/**" from root AND adds ".next/**"
      "outputs": ["$TURBO_EXTENDS$", ".next/**"]
    }
  }
}

$TURBO_EXTENDS$ 标记必须是数组中的第一个元素。它适用于 outputs, env, inputs, dependsOn, passThroughEnvwith

从其他包扩展

包配置可以扩展其他包的 turbo.json 文件,而不仅仅是根目录。这使得跨包组合共享的任务配置成为可能。

通过在 extends 数组中使用 package.json 中的 name 来从任何包进行扩展。例如,如果你在 ./apps/web 有一个 Next.js 应用,其 package.json 中有 "name": "web"

Turborepo logo
./apps/web/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

另一个 Next.js 应用可以从此扩展以共享相同的配置。

Turborepo logo
./apps/docs/turbo.json
{
  "extends": ["//", "web"],
  "tasks": {
    "build": {
      // Additional customization specific to this package
      "env": ["NEXT_PUBLIC_DOCS_URL"]
    }
  }
}

当从多个配置进行扩展时,根目录("//")必须始终**首先**列在 extends 数组中。

继承顺序

当从多个配置进行扩展时,任务定义会按照它们在 extends 数组中出现的顺序合并。

  1. 根目录 turbo.json ("//") 首先应用。
  2. 每个额外的包配置按顺序应用。
  3. 当前包的配置最后应用。

后来的配置会覆盖早期的配置(针对标量字段)。对于数组字段,请参阅 使用 $TURBO_EXTENDS$ 扩展数组 以进行追加而不是替换。

共享配置的模式

扩展现有包:如果你已经有一个包含你想要共享的配置的包,其他包可以直接从它扩展。当一个包充当类似包的“典范”时,这会很好(例如,你的主要 Next.js 应用,其他 Next.js 应用可以从中扩展)。

创建专用的配置包:对于大型单仓,你可能想创建专门用于共享配置的包。这会将配置与应用代码分开,并清楚地表明其他包依赖于这些设置。这些包通常只包含一个 package.jsonturbo.json

./packages/shared-config/package.json
{
  "name": "shared-config",
  "private": true
}

排除继承的任务

当扩展根目录或其他包时,你的包默认会继承它们所有的任务定义。你可以使用任务级别的 extends 字段来选择退出特定的任务。

完全排除任务

要从你的包中完全排除一个继承的任务,只需设置 extends: false,不带其他配置。

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {},
    "lint": {},
    "test": {}
  }
}
Turborepo logo
./packages/ui/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "lint": {
      "extends": false // This package does not have a lint task
    }
  }
}

当你运行 turbo run lint 时,ui 包将完全跳过 lint 任务。

创建新的任务定义

要创建一个不继承 extends 链中任何配置的新任务定义,请使用 extends: false 和其他任务配置。

Turborepo logo
./packages/special-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "extends": false, // Don't inherit from root
      "outputs": ["out/**"],
      "env": ["SPECIAL_VAR"]
    }
  }
}

这对于需要完全不同的任务配置并且不应与继承值合并的情况很有用。

排除会通过链传递

当一个包排除某个任务时,该排除会传递给扩展它的包。

Turborepo logo
./packages/base-config/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "lint": {
      "extends": false // Exclude lint from this config
    }
  }
}
Turborepo logo
./apps/web/turbo.json
{
  "extends": ["//", "base-config"],
  "tasks": {
    // web does not inherit lint from root because base-config excluded it
  }
}

任务级别的 extends 仅在包配置中可用。在根目录 turbo.json 中对任务使用 extends 会导致验证错误。

示例

同一个工作区中的不同框架

假设你的单仓有多个 Next.js 应用和一个 SvelteKit 应用。这两个框架都通过各自 package.json 中的 build 脚本创建构建输出。你可以像这样在根目录使用一个单一的 turbo.json 来配置 Turborepo 运行这些任务:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**"]
    }
  }
}

请注意,.next/**.svelte-kit/** 都需要被指定为 outputs,即使 Next.js 应用不生成 .svelte-kit 目录,反之亦然。

使用包配置,你可以改为在 apps/my-svelte-kit-app/turbo.json 中的 SvelteKit 包中添加自定义配置:

Turborepo logo
./apps/my-svelte-kit-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": [".svelte-kit/**"]
    }
  }
}

并从根目录配置中移除特定于 SvelteKit 的 outputs

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
-      "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**"]
+      "outputs": [".next/**", "!.next/cache/**"]
    }
  }
}

这不仅使每个配置更易于阅读,还将配置放在更靠近使用它的地方。

专用任务

在另一个例子中,假设一个包的 build 任务 dependsOn 一个 compile 任务。你可以普遍声明它为 dependsOn: ["compile"]。这意味着你的根目录 turbo.json 必须有一个空的 compile 任务条目。

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["compile"]
    },
    "compile": {}
  }
}

使用包配置,你可以将该 compile 任务移到 apps/my-custom-app/turbo.json 中:

Turborepo logo
./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "dependsOn": ["compile"]
    },
    "compile": {}
  }
}

并从根目录中移除它。

Turborepo logo
./turbo.json
{
  "tasks": {
+    "build": {}
-    "build": {
-      "dependsOn": ["compile"]
-    },
-    "compile": {}
  }
}

现在,my-app 的所有者可以完全拥有他们的 build 任务,但可以继续继承根目录中定义的任何其他任务。

与特定于包的任务的比较

根目录 turbo.json 中的 package#task 语法会**完全覆盖**所有任务配置——没有任何继承。

使用包配置,标量字段会被继承,只有你指定的字段才会被覆盖。这意味着当你只需要更改一两个属性时,重复会更少。

虽然没有计划移除特定于包的任务配置,但我们预计包配置可以用于大多数用例。

边界标签 实验性

包配置也用于声明边界的标签。为此,请将 tags 字段添加到你的 turbo.json

Turborepo logo
./apps/my-app/turbo.json
{
+ "tags": ["my-tag"],
  "extends": ["//"],
  "tasks": {
    "build": {
      "dependsOn": ["compile"]
    },
    "compile": {}
  }
}

在那里,你可以定义标签可以拥有的依赖项或被依赖项的规则。有关更多详细信息,请查看 边界文档

限制

虽然基本思想与根目录 turbo.json 相同,但包配置附带了一系列保护措施,可以防止包创建潜在的混淆情况。

包配置不能使用 workspace#task 语法 作为任务条目。

package 是根据配置的位置推断出来的,无法更改另一个包的配置。例如,在 my-nextjs-app 的包配置中:

Turborepo logo
./apps/my-nextjs-app/turbo.json
{
  "tasks": {
    "my-nextjs-app#build": {
      // This is not allowed. Even though it's
      // referencing the correct package, "my-nextjs-app"
      // is inferred, and we don't need to specify it again.
      // This syntax also has different behavior, so we do not want to allow it.
      // (see "Comparison to package-specific tasks" section)
    },
    "my-sveltekit-app#build": {
      // Changing configuration for the "my-sveltekit-app" package
      // from Package Configuration in "my-nextjs-app" is not allowed.
    },
    "build": {
      // Just use the task name!
    }
  }
}

请注意,build 任务仍然可以依赖于特定于包的任务。

Turborepo logo
./apps/my-nextjs-app/turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["some-pkg#compile"] 
    }
  }
}

包配置只能覆盖 tasks 键中的值。

无法在包配置中覆盖 全局配置,如 globalEnvglobalDependencies。需要在包配置中更改的配置不是真正全局的,应该以不同的方式进行配置。

根目录 turbo.json 不能使用 extends 键。

为了避免创建循环依赖于包,根目录 turbo.json 不能扩展任何东西。extends 键将被忽略。

故障排除

在大型单仓中,有时很难理解 Turborepo 是如何解释你的配置的。为了提供帮助,我们在 Dry Run 输出中添加了 resolvedTaskDefinition。例如,如果你运行 turbo run build --dry-run,输出将包含在运行 build 任务之前考虑的所有 turbo.json 配置的组合。