wandres.dev
TURBOREPO · task graph y caching

Qué es Turborepo y el task graph

Turborepo no sustituye a pnpm: se sienta encima como orquestador de tareas. Su idea central es modelar no los paquetes, sino las tareas del monorepo y sus dependencias, como un grafo dirigido acíclico donde cada nodo es un par paquete-tarea y cada arista una precedencia. Ese grafo es lo que habilita paralelizar de verdad, cachear por contenido y no repetir trabajo.

⏱ 16 min

Un monorepo con pnpm ya sabe qué paquete depende de cuál y sabe construirlos en orden. Lo que no sabe es no repetirse: pnpm -r run build reconstruye los cincuenta paquetes cada vez, aunque solo tocaras uno. Turborepo se sienta encima de ese workspace y cambia la unidad de razonamiento: deja de pensar en paquetes y empieza a pensar en tareas. Modela cada script de cada paquete como un nodo de un grafo, y las precedencias entre ellos como aristas. Sobre ese grafo puede hacer las tres cosas que un gestor de paquetes no hace por sí solo: paralelizar de verdad, cachear por contenido y saltarse todo lo que no cambió.

🎯 Al terminar esta lección sabrás
  • Situar Turborepo como orquestador sobre pnpm o npm, no como su sustituto.
  • Distinguir el grafo de paquetes del grafo de tareas que Turborepo deriva.
  • Leer cada nodo como un par paquete#tarea y cada arista como una precedencia.
  • Entender por qué el grafo, y no el script, es la unidad de planificación y caché.

Un orquestador, no un gestor de paquetes

Conviene separar dos responsabilidades que a menudo se confunden. El gestor de paquetes —pnpm, npm, yarn— resuelve versiones, escribe el lockfile y enlaza node_modules; además conoce el grafo de dependencias workspace: y puede correr scripts recursivos en orden topológico. El orquestador —Turborepo— no instala nada: lee ese mismo workspace, lee su propio turbo.json, y decide qué tareas correr, en qué orden, en paralelo y cuáles saltarse. Turborepo delega la instalación en el gestor y se concentra en programar el trabajo.

turbo run build             # orquesta el build de todo el workspace
turbo run test lint         # varias tareas en el mismo grafo
turbo build                 # forma corta de turbo run build

Turborepo 2.x lleva su motor de planificación y hashing escrito en Rust, y opcionalmente levanta un daemon que vigila el sistema de archivos para no rehashear todo en cada invocación. Funciona indistintamente sobre workspaces de pnpm, npm, yarn o bun: no impone gestor, se adapta al que ya usas. La frase clave para no perderse: pnpm te da el grafo de paquetes; Turborepo construye encima un grafo de tareas y lo memoiza.

Dicho en capas, cada herramienta responde a una pregunta distinta:

  • Gestor de paquetes (pnpm): ¿qué versiones instalo y cómo enlazo node_modules?
  • Grafo workspace:: ¿qué paquete depende de cuál?
  • Turborepo: ¿qué tareas corro, en qué orden, en paralelo y cuáles me salto?

Confundir estas capas es la fuente de la mayoría de los malentendidos: se le pide a turbo que instale, o se espera que pnpm memoice. Cada una hace bien una sola cosa, y su composición limpia es lo que da un monorepo veloz sin acoplar responsabilidades que no se pertenecen.

Turborepo no define los paquetes: los descubre. Lee el pnpm-workspace.yaml —o el campo workspaces del package.json raíz en npm y yarn— para saber qué carpetas son paquetes, y lo cruza con los scripts de cada package.json para saber qué tareas existen. Por eso no duplicas información: si un paquete no declara un script test, Turborepo sencillamente no crea el nodo paquete#test, y ese paquete se salta en un turbo run test sin que tengas que excluirlo a mano.

Del grafo de paquetes al grafo de tareas

El grafo de paquetes tiene por nodos los paquetes y por aristas las dependencias workspace:. Turborepo lo lee y deriva algo más fino: un grafo cuyos nodos son pares paquete y tarea@acme/ui#build, @acme/web#build, @acme/web#test— y cuyas aristas salen de las reglas de precedencia que declaras. Esa granularidad es el salto conceptual: la unidad ya no es el paquete entero, sino cada ejecución concreta de un script.

flowchart TD
subgraph Paquetes
  utils1[acme utils] --> ui1[acme ui]
  ui1 --> web1[acme web]
end
subgraph Tareas
  utb[utils build] --> ub[ui build]
  ub --> wb[web build]
  wb --> wt[web test]
end
style ub fill:#a6e3a1,color:#11111b
style wb fill:#89b4fa,color:#11111b
style wt fill:#f9e2af,color:#11111b

La diferencia rinde en dos direcciones. Hacia el paralelismo: dos tareas del mismo paquete que no dependen entre sí —lint y test— pueden correr a la vez, cosa que el gestor de paquetes, que razona por paquete, no expresa. Hacia la precedencia cruzada: la tarea build de web puede exigir que primero termine la build de ui, aunque sean paquetes distintos. El grafo de tareas captura ambas relaciones en un único DAG, que sigue siendo acíclico por la misma razón matemática que el de paquetes: un ciclo volvería irresoluble la pregunta de qué correr primero.

Puedes materializar esta distinción con el propio Turborepo. La simulación imprime, para cada nodo, sus dependencias dentro del grafo de tareas, y deja ver que web#build cuelga de ui#build y api#build aunque en el package.json de web solo figure una dependencia de datos.

turbo run build --dry=json      # el plan completo con el taskId de cada nodo
turbo run build --graph=dag.png # exporta el grafo de tareas a una imagen
📝
La notación paquete#tarea nombra un nodo, no un archivo

Cuando veas @acme/web#build en la salida de Turborepo, léelo como la identidad de un nodo del grafo: el paquete @acme/web corriendo su tarea build. Esa misma notación te sirve para apuntar a un nodo concreto en un dependsOn o en un --filter. No es una ruta de archivo ni un comando: es el nombre propio de una tarea dentro de un paquete, la unidad atómica que Turborepo programa y cachea.

Por qué el grafo es la unidad de planificación

Que la unidad sea el nodo paquete#tarea y no el paquete tiene tres consecuencias que definen todo lo que hará Turborepo en las lecciones siguientes.

🧭

Paralelizar de verdad

Turborepo lanza a la vez todos los nodos sin aristas pendientes entre ellos, en todo el grafo. No se limita a las olas por paquete de pnpm -r: exprime cada núcleo respetando solo las precedencias reales entre tareas.

🧠

Cachear por nodo

Cada nodo se memoiza por un hash de su contenido. Si el hash coincide con una ejecución anterior, Turborepo restaura sus salidas y reproduce sus logs en vez de volver a ejecutar el script.

✂️

No repetir

Un nodo cuyo hash no cambió se salta por completo. El trabajo total de un turbo run no es la suma de las tareas, sino solo el de los nodos cuyas entradas cambiaron.

Compara con el gestor de paquetes puro. pnpm -r run build recorre el grafo de paquetes en orden topológico, pero ejecuta siempre el script de cada paquete: no tiene memoria de que la salida de la vez anterior sigue siendo válida. Turborepo añade exactamente esa memoria. El orden lo sigue dictando la topología —igual que pnpm construye utils antes que ui— pero cada nodo se convierte en una función que, ante las mismas entradas, devuelve las mismas salidas sin recomputar.

La diferencia se ve en números. En un monorepo de cincuenta paquetes donde tocas uno, pnpm -r run build ejecuta los cincuenta scripts de build; turbo run build calcula los cincuenta hashes, encuentra que cuarenta y nueve coinciden con la caché y ejecuta uno, más los pocos que dependieran de él. El trabajo pasa de ser proporcional al tamaño del repo a serlo al tamaño del cambio, y esa es, resumida en una frase, toda la propuesta de valor de un orquestador con caché.

Este modelo descansa en una premisa que conviene hacer explícita desde el principio: los scripts deben ser deterministas. Un build que incrusta una marca de tiempo, un identificador aleatorio o el nombre de la máquina en su salida producirá artefactos distintos ante entradas idénticas, y romperá la promesa de que un nodo es una función pura. Buena parte del trabajo de configurar Turborepo bien es, en el fondo, empujar tus tareas hacia el determinismo para que la memoización sea legítima; las lecciones siguientes son las herramientas para lograrlo.

💡
turbo no instala; compón las dos herramientas

Un error de principiante es esperar que turbo resuelva dependencias o escriba el lockfile. No lo hace ni debe hacerlo. El flujo sano es pnpm install --frozen-lockfile para materializar el workspace y turbo run build para orquestar el trabajo sobre él. Cada herramienta en su capa: pnpm el grafo de paquetes y los enlaces, Turborepo el grafo de tareas y la memoización.

Para ver el grafo antes de ejecutarlo, Turborepo ofrece un modo de simulación. Es el primer reflejo que conviene adquirir: mirar el plan antes de lanzarlo.

turbo run build --dry-run       # imprime tareas, orden y hashes sin ejecutar
turbo run build --graph         # emite el grafo de tareas en formato dot

El planificador: del grafo al camino crítico

Tener el grafo permite algo que una secuencia de comandos no: ejecutarlo como grafo. Turborepo no recorre los nodos en fila, sino que mantiene un frente de trabajo —todos los nodos cuyas dependencias ya terminaron— y lo lanza en paralelo, abriendo nodos nuevos a medida que se completan aristas. Dos tareas sin arista entre ellas corren a la vez aunque vivan en paquetes distintos, sin las olas rígidas por paquete de pnpm -r.

La consecuencia es un cambio en el modelo de coste. El tiempo de un turbo run no es la suma de las tareas, sino la longitud de su camino crítico: la cadena más larga de precedencias que hay que atravesar en orden. Ensanchar el grafo —muchos paquetes independientes— paraleliza bien; alargarlo —cadenas profundas de dependsOn— lo serializa. Y la caché acorta ese camino, porque cada nodo que resulta ser un acierto desaparece del cálculo: restaurar es casi instantáneo frente a ejecutar.

turbo run build
#  Tasks:    12 successful, 12 total
# Cached:     0 cached, 12 total
#   Time:    8.4s

El resumen final de cada corrida es la lectura directa de este modelo: cuántos nodos había, cuántos se sirvieron de caché y el tiempo real, que tiende al camino crítico menos lo memoizado. Para no rehashear el mundo entre invocaciones, el daemon mantiene caliente la información del grafo y los metadatos de archivos, y por eso la segunda invocación arranca casi al instante.

📝
Concurrencia sí, pero el orden es sagrado

El paralelismo de Turborepo nunca viola una arista: un nodo no arranca hasta que sus dependencias declaradas terminaron. Por eso un dependsOn olvidado no se manifiesta como un error limpio, sino como una carrera —dos tareas que deberían ordenarse corren a la vez y a veces una lee el dist/ de la otra a medio escribir—. La cantidad de paralelismo se ajusta luego con --concurrency, pero el orden lo fija el grafo, no una bandera.

Modelar el build como un grafo de funciones puras

El aporte profundo de Turborepo no es la velocidad, es un cambio de modelo mental sobre qué es construir un monorepo. En el modelo ingenuo, un build es una secuencia de comandos imperativos que se ejecutan de arriba abajo cada vez, sin memoria y sin más paralelismo que el que el gestor de paquetes deduzca por paquete. En el modelo de Turborepo, un build es la evaluación de un grafo dirigido acíclico cuyos nodos son tareas idealmente puras: dado el mismo conjunto de entradas —archivos, variables de entorno, hashes de las dependencias internas, la propia configuración de la tarea— una tarea debe producir siempre las mismas salidas. Esa pureza es lo que hace legítima la memoización: si una función pura ya se evaluó con estas entradas, su resultado se puede restaurar sin volver a ejecutarla, y eso es exactamente lo que significa un acierto de caché. Todo lo demás que estudiaremos en este nivel —dependsOn, inputs, outputs, el hash, --filter, --affected— son mecanismos para hacer ese grafo explícito y para que la hipótesis de pureza se sostenga. Cuando una tarea depende de algo que no declaraste, la pureza se rompe y la caché miente; cuando el grafo está bien modelado, el monorepo deja de recomputar el mundo y solo evalúa los nodos que de verdad cambiaron. Interiorizar esto convierte el turbo.json de un archivo de configuración misterioso en lo que realmente es: la especificación de un grafo de cómputo.

⚔️ Piensa en tareas, no en paquetes
  1. En un workspace con ui, utils y web, dibuja en papel el grafo de paquetes y, debajo, el grafo de tareas para build y test.
  2. Ejecuta turbo run build --dry-run y localiza en la salida el nodo web#build y sus dependencias.
  3. Compara mentalmente pnpm -r run build con turbo run build: identifica qué hace cada uno con la salida de la ejecución anterior.
  4. Genera el grafo con turbo run build --graph e identifica qué dos nodos podrían correr en paralelo por no tener arista entre ellos.
  5. Lanza turbo run build dos veces seguidas sin tocar nada y observa que la segunda no ejecuta ningún script: acabas de ver la memoización del grafo.