Build and start with Turborepo
This lesson preview is part of the Bundling and Automation in Monorepos course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Bundling and Automation in Monorepos, plus 90+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:07] We're going to start using Turborepo in this lesson. And the first thing that I want to look at is the way that we currently do builds.
[00:08 - 00:17] We have our "pnpm run -r build" command in our root. So when I do "pnpm run build" it's going to recursively run build in all projects.
[00:18 - 00:30] And this takes a moment just because some of our builds, specifically the Next.js build, is a little bit slow. But after a while it is going to successfully finish and give us our builds.
[00:31 - 00:42] However, if I rerun it, obviously it's just going to do the builds from the start without any cache. So let's create a turbo.json file.
[00:43 - 00:53] And I'm going to paste in some JSON that I have prepared ahead of time. We're declaring the schema to be Turborepo schema definition.
[00:54 - 01:26] And we're defining a "build" task that we say depends on our dependencies' build tasks and outputs the folders "dist", ".next" and omits from the output the ".next/cache" folder. So this is a general config that captures all our build outputs. We have one Next.js project, that's why we need these two things here and all our other projects actually built to a dist folder.
[01:27 - 01:43] With this in place, I can open my package.json and I can replace my current build script with "turbo build". If I go back to the command line and run turbo build.
[01:44 - 01:57] We get the interleaved output of turbo, it looks a bit different from pnpm, but it basically is the same thing. It shows you which workspace is executing which command.
[01:58 - 02:16] In our case, we're only executing the build command and it will eventually complete and cache the outputs. If I run it again, it uses those cached outputs and running it takes no time whatsoever. If I were to go to one of my apps.
[02:17 - 02:37] So I'm going to go to "apps/frontend-vite" and I'm going to change my "app.tsx". I'm going to change it by setting the initial counter here to one. It doesn't matter any type of change, a file change would have affected this.
[02:38 - 03:11] I can again run my root build script with "pnpm -w run build", which is going to internally run "pnpm turbo build", so our build task and it ended quite quickly because it noticed that only one project had changed, which was frontend-vite. So the other four build projects were cached, only frontend-vite executed because there was a change inside it.
[03:12 - 03:31] We can see this at the beginning of the output that it said "frontend-nextjs", for example, has a "cache hit, output already on disk," so no need to write it down and it's just replaying the logs while we can find our frontend-vite. It says "cache missed" and we need to execute again.
[03:32 - 03:39] So Turborepo correctly only run for the changed files. What if I change something that everything else depends on?
[03:40 - 04:01] Let's first see what our dependency graph looks like. I'm going to do is do this with "pnpm turbo run build --graph", and then output the graph into my temporary folder and then "/tmp/build-graph.jpg".
[04:02 - 04:24] Then I can "open /tmp/build-graph.jpg" and I actually did a mistake here, but actually, this was a useful mistake. So because I am currently in the frontend-vite folder, it gave me the build graph specifically for only frontend-vite.
[04:25 - 04:51] So it's saying that frontend-vite depends on the "@monorepo/ui#build" build task, which depends on the "@monorepo/config#build" task, which depends on like the root changes to the monorepo. To explain these in term, when turbo is trying to figure out if frontend-vite should be rebuilt, it's going to check for any file changes to any of the other dependencies.
[04:52 - 05:19] You will notice that it lists @monorepo/config#built as a dependency, even though @monorepo/config actually only contains config files and it has no build task. Still, any change to these config files would invalidate the cache of frontend-vite. So even though there is no build task here that needs to execute the changes to files in here would invalidate this frontend-vite build.
[05:20 - 05:46] And the __ROOT__ one for the root one you can think about in terms of changes to either pnpm-lock.yml would invalidate the root one, or changes to the root turbo.json file would invalidate the root one. Basically, changes that affect the whole project are what the root dependency represents. Now I'm going to go back to the monorepo root and do the same thing.
[05:47 - 05:56] Generate the graph again and then open it. And now this one looks this one looks different.
[05:57 - 06:39] From the root, we can see the full dependency graph of running build with all possible paths. So server-express depends on the logger which depends on config which depends on root. frontend-nextjs depends on the UI and on the config and on the root, while frontend-vite depends on the UI and the config and then the root. So we have different dependency graphs for different packages and we can visualize them very easily. This way I'm going to show you how to define one more task which is going to be the "start" task. So the start task depends on running build first we first must run build, then we can run start.
[06:40 - 06:55] It's a persistent task, which means we are telling turbo that this is not a task that executes and then finishes. This is this is a task that's going to start servers that are going to persist, and any output of it should not be cached.
[06:56 - 07:01] So let's change it in here. This is going to be now "turbo start" as well.
[07:02 - 07:10] If I run "pnpm start" from the root... I have a typo in my turbo okay.
[07:11 - 07:32] It's going to first build everything and then start all the servers. For example, we can see that our express-server is running on localhost 5000, our vite-server is on 4173 and our Next.js is on 3000.
[07:33 - 07:46] So all of these have been started, and when we stop this and run it again, it's going to reuse the cache to very quickly start the servers again. I think that's enough for this lesson.
[07:47 - 08:01] I'm going to commit my changes. We have modified our package.json and we have added turbo.json, so I'm going to do "git add" everything "git diff --cached".
[08:02 - 08:16] We have converted our build and start tasks to turbo tasks. So I'm going to do "git commit -m "Convert build and start tasks to turbo tasks".
[08:17 - 08:21] This is going to be everything for this lesson and I will see you in the next one.