In practice, my type/lint/test tasks all depend on “^build”, but I seem to have the same result as “topo”. I’m just trying to understand the logic, as it remains unexplained in the docs.
Thanks. I’ve ready that section multiple times. I don’t see why “transit” is necessary.
Shouldn’t turbo be able to detect what tasks can be parallel based on the individual task’s inputs? If the inputs of an upstream task have changed, preserve the dependency. The way I read the docs, it suggests that changes are detected at the package level—which I can’t take to be correct.
Perhaps a more realistic example? How can this graph be improved, given the normal inputs are “build”/“type”? Does “topo” actually do anything? What if there were packages that didn’t require a build step?
The topo task collapses the dependency graph into one, flat, graph, allowing you to have source code dependency on tasks that you still want to run in parallel. This is helpful when dependent tasks don’t have outputs, but you don’t want ui#type-check to delay the start of web#type-check.
If you don’t have topo, you can change code in ui but still hit cache for web.
In the case you’re showing, topo may or may not do something depending on your package graph. Turborepo will always parallelize everything that it can, so if ^build is the bottleneck, then topo won’t have any effect.
Ok, so “topo” is specifically for a task that does NOT depend on it’s dependencies’ task, but DOES depend on changes to its dependencies.
This is always useful for tasks like code formatting, which don’t truly depend on “^format”.
It MIGHT be useful for type checking, ONLY IF your shared packages don’t require a build step.
It is NEVER useful for build tasks, which should always depend on upstream builds.
So the magic in “topo” is really the default turbo “inputs” (basically everything). You can conceivably have multiple “topo” tasks. For example, “topo:typescript” would be useful to narrow cache invalidation to *.ts and tsconfig.json.