Configuring tasks
Turborepo will always run tasks in the order described in your turbo.json
configuration and Package Graph, parallelizing work whenever possible to ensure everything runs as fast as possible. This is faster than running tasks one at a time, and it's a part of what makes Turborepo so fast.
For example, yarn workspaces run lint && yarn workspaces run test && yarn workspaces run build
would look like this:
But, to get the same work done faster with Turborepo, you can use turbo run lint test build
:
Getting started
The root turbo.json
file is where you'll register the tasks that Turborepo will run. Once you have your tasks defined, you'll be able to run one or more tasks using turbo run
.
- If you're starting fresh, we recommend creating a new repository using
create-turbo
and editing theturbo.json
file to try out the snippets in this guide. - If you're adopting Turborepo in an existing repository, create a
turbo.json
file in the root of your repository. You'll be using it to learn about the rest of the configuration options in this guide.
Defining tasks
Each key in the tasks
object is a task that can be executed by turbo run
. Turborepo will search your packages for scripts in their package.json
that have the same name as the task.
To define a task, use the tasks
object in turbo.json
. For example, a basic task with no dependencies and no outputs named build
might look like this:
If you run turbo run build
at this point, Turborepo will run all build
scripts in your packages in parallel and won't cache any file outputs. This will quickly lead to errors. You're missing a few important pieces to make this work how you'd expect.
Running tasks in the right order
The dependsOn
key is used to specify the tasks that must complete before a different task begins running. For example, in most cases, you want the build
script for your libraries to complete before your application's build
script runs. To do this, you'd use the following turbo.json
:
You now have the build order you would expect, building dependencies before dependents.
But be careful. At this point, you haven't marked the build outputs for caching. To do so, jump to the Specifying outputs section.
Depending on tasks in dependencies with ^
The ^
microsyntax tells Turborepo to run the task starting at the bottom of the dependency graph. If your application depends on a library named ui
and the library has a build
task, the build
script in ui
will run first. Once it has successfully completed, the build
task in your application will run.
This is an important pattern as it ensures that your application's build
task will have all of the necessary dependencies that it needs to compile. This concept also applies as your dependency graph grows to a more complex structure with many levels of task dependencies.
Depending on tasks in the same package
Sometimes, you may need to ensure that two tasks in the same package run in a specific order. For example, you may need to run a build
task in your library before running a test
task in the same library. To do this, specify the script in the dependsOn
key as a plain string (without the ^
).
Depending on a specific task in a specific package
You can also specify an individual task in a specific package to depend on. In the example below, the build
task in utils
must be ran before any lint
tasks.
You can also be more specific about the dependent task, limiting it to a certain package:
With this configuration, the lint
task in your web
package can only be ran after the build
task in the utils
package is complete.
No dependencies
Some tasks may not have any dependencies. For example, a task for finding typos in Markdown files likely doesn't need to care about the status of your other tasks. In this case, you can omit the dependsOn
key or provide an empty array.
Specifying outputs
Turborepo caches the outputs of your tasks so that you never do the same work twice. We'll discuss this in depth in the Caching guide, but let's make sure your tasks are properly configured first.
The outputs
key tells Turborepo files and directories it should cache when the task has successfully completed. Without this key defined, Turborepo will not cache any files. Hitting cache on subsequent runs will not restore any file outputs.
Below are a few examples of outputs for common tools:
For more on building globbing patterns for the outputs
key, see the globbing specification.
Specifying inputs
The inputs
key is used to specify the files that you want to include in the task's hash for caching. By default, Turborepo will include all files in the package that are tracked by Git. However, you can be more specific about which files are included in the hash using the inputs
key.
As an example, a task for finding typos in Markdown files could be defined like this:
Now, only changes in Markdown files will cause the spell-check
task to miss cache.
This feature opts out of all of Turborepo's default inputs
behavior, including following along with changes tracked by source control. This means that your .gitignore
file will no longer be respected, and you will need to ensure that you do not capture those files with your globs.
To restore the default behavior, use the $TURBO_DEFAULT$
microsyntax.
Restoring defaults with $TURBO_DEFAULT$
The default inputs
behavior is often what you will want for your tasks. However, you can increase your cache hit ratios for certain tasks by fine-tuning your inputs
to ignore changes to files that are known to not affect the task's output.
For this reason, you can use the $TURBO_DEFAULT$
microsyntax to fine-tune the default inputs
behavior:
In this task definition, Turborepo will use the default inputs
behavior for the build
task, but will ignore changes to the README.md
file. If the README.md
file is changed, the task will still hit cache.
Registering Root Tasks
You can also run scripts in the package.json
in the Workspace root using turbo
. For example, you may want to run a lint:root
task for the files in your Workspace's root directory in addition to the lint
task in each package:
With the Root Task now registered, turbo run lint:root
will now run the task. You can also run turbo run lint lint:root
to run all your linting tasks.
When to use Root Tasks
- Linting and formatting of the Workspace root: You might have code in your Workspace root that you want to lint and format. For example, you might want to run ESLint or Prettier in your root directory.
- Incremental migration: While you're migrating to Turborepo, you might have an in-between step where you have some scripts that you haven't moved to packages yet. In this case, you can create a Root Task to start migrating and fan the tasks out to packages later.
- Scripts without a package scope: You may have some scripts that don't make sense in the context of specific packages. Those scripts can be registered as Root Tasks so you can still run them with
turbo
for caching, parallelization, and workflow purposes.
Advanced use cases
Using Package Configurations
Package Configurations are turbo.json
files that are placed directly into a package. This allows a package to define specific behavior for its own tasks without affecting the rest of the repository.
In large monorepos with many teams, this allows teams greater control over their own tasks. To learn more, visit the Package Configurations documentation
Performing side-effects
Some tasks should always be ran no matter what, like a deployment script after a cached build. For these tasks, add "cache": false
to your task definition.
Dependent tasks that can be ran in parallel
Some tasks can be ran in parallel despite being dependent on other packages. An example of tasks that fit this description are linters, since a linter doesn't need to wait for outputs in dependencies to run successfully.
Because of this, you may be tempted to define your check-types
task like this:
This runs your tasks in parallel - but doesn't account for source code changes in dependencies. This means you can:
- Make a breaking change to the interface of your
ui
package. - Run
turbo check-types
, hitting cache in an application package that depends onui
.
This is incorrect, since the application package will show a successful cache hit, despite not being updated to use the new interface. Checking for TypeScript errors in your application package manually in your editor is likely to reveal errors.
Because of this, you make a small change to your check-types
task definition:
If you test out making breaking changes in your ui
package again, you'll notice that the caching behavior is now correct. However, tasks are no longer running in parallel.
To meet both requirements (correctness and parallelism), you can introduce Transit Nodes to your Task Graph:
These Transit Nodes create a relationship between your package dependencies using a task that doesn't do anything because it doesn't match a script in any package.json
s. Because of this, your tasks can run in parallel and be aware of changes to their internal dependencies.
In this example, we used the name transit
- but you can name the task
anything that isn't already a script in your Workspace.
Next steps
There are more options available in the Configuring turbo.json
documentation that you will explore in the coming guides. For now, you can start running a few tasks to see how the basics work.
Was this helpful?