Task dependencies
Define and manage task execution order with sequential and parallel dependencies, cross-workspace dependencies, and task includes.
Task dependencies allow you to define the execution order of tasks in your project. When running a task, Yarn automatically resolves and executes all required dependencies first, ensuring that prerequisites are met before each task starts.
If you already use Turborepo, see Migrating from Turbo for equivalent task definitions and behavior differences.
Defining tasks#
Tasks are defined in a taskfile at the root of each workspace. Each task has a name, optional dependencies, and a script to execute:
build:
echo "Building the project"
test: build
echo "Running tests"
In this example, running yarn tasks run test will first execute build, then test. Use yarn tasks run <name> to explicitly run a task: yarn run <name> executes a matching package.json script first, if one exists.
Task names start with a letter or underscore and can contain letters, digits, underscores, and hyphens. Colons are reserved for workspace:task references. A package script with a colon in its name can still be invoked explicitly:
test-ci:
yarn run test:ci "$@"
Default tasks for workspaces#
Annotate a task in the root taskfile with @defaultTask to supply its definition to every non-root workspace:
# Root taskfile
@defaultTask
build: *:build
Each child workspace gets a build task, even if it has no taskfile. The *:build dependency waits for the build tasks of its dependency workspaces. Use *:build& to allow those dependency tasks to run in parallel.
An inherited task without commands runs the workspace’s matching package.json script, when present, forwarding arguments to it. Otherwise, it only waits for its task dependencies.
For example, suppose app depends on bridge, which depends on core. Both app and core have a build script; bridge has none. Running yarn tasks run build in app follows this order:
core:build → runs core's build script
bridge:build → completes without running a command
app:build → runs app's build script
The task in bridge preserves the dependency chain, so app still waits for core to build.
A default can also provide commands explicitly. These commands run in each workspace that inherits the task:
@defaultTask
check:
yarn run typecheck "$@"
yarn run lint "$@"
A local task with the same name replaces the entire default, including its dependencies and commands. For example:
# packages/app/taskfile
build: *:build
yarn run build "$@"
yarn run generate-manifest
Keep *:build in the local definition if it should still wait for dependency builds. An empty local task does not inherit commands or implicitly run a matching package script.
@defaultTask definitions are templates for child workspaces; they aren’t tasks of the root workspace itself. The annotation supplies definitions and does not start tasks across the project.
Sequential dependencies#
By default, dependencies are sequential. Each dependency must complete before the next one starts:
lint:
echo "Linting"
typecheck:
echo "Type checking"
build: lint typecheck
echo "Building"
When you run yarn build, the execution order is:
-
lintruns first -
typecheckruns afterlintcompletes -
buildruns aftertypecheckcompletes
This creates a strict ordering where each task waits for the previous one to finish.
Parallel dependencies#
To run dependencies in parallel, add the & suffix to the dependency name:
lint:
echo "Linting"
typecheck:
echo "Type checking"
build: lint& typecheck&
echo "Building"
Now when you run yarn build:
-
lintandtypecheckrun simultaneously -
buildruns after both complete
Parallel execution can significantly speed up your build pipeline when tasks are independent of each other.
Mixing sequential and parallel dependencies#
You can combine sequential and parallel dependencies in a single task definition. Tasks with & form parallel groups, while tasks without & create sequential barriers:
a:
echo "Task A"
b:
echo "Task B"
c:
echo "Task C"
d:
echo "Task D"
e: a b& c& d
echo "Task E"
The execution order for yarn e is:
-
aruns first (sequential barrier) -
bandcrun in parallel (both have&) -
druns afterbandccomplete (sequential barrier) -
eruns afterdcompletes
This pattern is useful when you have a setup task that must run first, followed by independent tasks that can run in parallel, and finally tasks that need all previous work to be done.
Cross-workspace dependencies#
Tasks can depend on tasks from other workspaces that are listed as dependencies in your package.json. Use the workspace:task syntax:
# In packages/app/taskfile
build: pkg-utils:build pkg-core:build
echo "Building app"
This ensures that both pkg-utils and pkg-core are built before app. Note that pkg-utils and pkg-core must be declared as dependencies (or devDependencies) of app in its package.json.
You can also use glob patterns to match multiple dependency workspaces:
# Depend on all dependency packages matching the pattern
build: @my-scope/*:build
echo "Building after all @my-scope packages"
The glob pattern only matches workspaces that are both:
-
Listed as dependencies of the current workspace
-
Match the glob pattern
This ensures that task dependencies follow the same dependency graph as your packages, preventing accidental coupling between unrelated workspaces.
Parallel cross-workspace dependencies#
Cross-workspace dependencies also support the parallel & modifier:
# In packages/app/taskfile
build: pkg-utils:build& pkg-core:build&
echo "Building app"
Now pkg-utils:build and pkg-core:build run in parallel before app:build.
You can mix local and cross-workspace dependencies with any combination of sequential and parallel:
build: setup pkg-utils:build& pkg-core:build& finalize
echo "Building app"
Execution order:
-
setupruns first -
pkg-utils:buildandpkg-core:buildrun in parallel -
finalizeruns after both complete -
buildruns last
Transitive dependencies#
Yarn automatically resolves transitive dependencies. If task A depends on B, and B depends on C, running A will execute C, then B, then A:
# packages/pkg-a/taskfile
build:
echo "Building pkg-a"
# packages/pkg-b/taskfile
build: pkg-a:build
echo "Building pkg-b"
# packages/pkg-c/taskfile
build: pkg-b:build
echo "Building pkg-c"
Running yarn build in pkg-c will:
-
Execute
pkg-a:buildfirst (no dependencies) -
Execute
pkg-b:buildafterpkg-acompletes -
Execute
pkg-c:buildafterpkg-bcompletes
The dependency resolution computes the full transitive closure, so pkg-c:build knows it must wait for both pkg-a:build and pkg-b:build.
Including tasks from other workspaces#
You can include task definitions from dependency workspaces using the include directive. This allows you to reuse common task definitions across multiple workspaces without duplicating them.
Basic include#
To include all tasks from a dependency workspace’s taskfile:
include pkg-utils
build: lint
echo "Building"
This imports all tasks defined in pkg-utils’s taskfile into the current workspace. If pkg-utils has a lint task, it becomes available as if it were defined locally.
Include with custom path#
By default, include loads the taskfile at the root of the target workspace. You can specify a custom path:
include pkg-utils/tasks/common.tasks
build: lint typecheck
echo "Building"
This loads tasks from tasks/common.tasks within the pkg-utils workspace instead of the default taskfile.
Scoped package includes#
Scoped packages are fully supported:
include @my-scope/my-lib
build: lint
echo "Building"
You can also specify a custom path for scoped packages:
include @my-scope/my-lib/tasks/shared.tasks
Include requirements#
The include target must be declared as a dependency (or devDependency) in your package.json. This ensures that task includes follow the same dependency graph as your packages:
{
"name": "my-app",
"dependencies": {
"pkg-utils": "workspace:*"
}
}
If you try to include a workspace that isn’t a dependency, you’ll get an error:
Error: Cannot include 'pkg-other' from 'my-app': not listed as a dependency
Task precedence#
When including tasks, local task definitions take precedence over included ones. If both your taskfile and an included taskfile define the same task name, your local definition is used:
include pkg-utils
# This overrides any 'build' task from pkg-utils
build:
echo "Custom build"
Multiple includes#
You can include from multiple workspaces:
include pkg-utils
include pkg-core
build: lint typecheck test
echo "Building"
Tasks from earlier includes take precedence over later ones if there are naming conflicts.
Cycle detection#
Yarn detects circular dependencies and reports an error:
# This will fail!
a: b
echo "A"
b: c
echo "B"
c: a
echo "C"
Running any of these tasks will result in an error indicating the cycle: a -> b -> c -> a.