Task 구성하기

Task는 Turborepo가 실행하는 스크립트입니다. turbo.json 구성Package Graph에서 task 간의 관계를 표현할 수 있습니다.

Turborepo는 모든 것이 최대한 빠르게 실행되도록 가능한 모든 작업을 병렬화합니다. 이는 task를 하나씩 실행하는 것보다 빠르며, Turborepo를 빠르게 만드는 요소 중 하나입니다.

예를 들어, yarn workspaces run lint && yarn workspaces run build && yarn workspaces run test는 다음과 같습니다:

A graphical representation of `turbo run lint test build`. It shows all tasks running in parallel, with much less empty space where scripts are not being ran.

하지만 Turborepo로 동일한 작업을 더 빠르게 완료하려면 turbo run lint build test를 사용할 수 있습니다:

A graphical representation of `turbo run lint test build`. It shows all tasks running in parallel, with much less empty space where scripts are not being ran.

시작하기

루트 turbo.json 파일은 Turborepo가 실행할 task를 등록하는 곳입니다. task를 정의하면 turbo run을 사용하여 하나 이상의 task를 실행할 수 있습니다.

  • 처음부터 시작하는 경우, create-turbo를 사용하여 새 리포지토리 생성하고 turbo.json 파일을 편집하여 이 가이드의 스니펫을 시도해 보는 것을 권장합니다.
  • 기존 리포지토리에서 Turborepo를 도입하는 경우, 리포지토리 루트에 turbo.json 파일을 생성합니다. 이 가이드의 나머지 구성 옵션에 대해 학습하는 데 사용할 것입니다.
turbo.json
package.json

Task 정의하기

tasks 객체의 각 키는 turbo run으로 실행할 수 있는 task입니다. Turborepo는 task와 동일한 이름을 가진 package.json의 스크립트를 패키지에서 검색합니다.

task를 정의하려면 turbo.json에서 tasks 객체를 사용합니다. 예를 들어, 종속성과 출력이 없는 build라는 기본 task는 다음과 같을 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {} // Incorrect!
  }
}

이 시점에서 turbo run build를 실행하면 Turborepo는 패키지의 모든 build 스크립트를 병렬로 실행하며 파일 출력을 캐싱하지 않습니다. 이는 빠르게 오류로 이어집니다. 예상대로 작동하도록 하려면 몇 가지 중요한 부분이 누락되었습니다.

올바른 순서로 task 실행하기

dependsOn는 다른 task가 실행되기 전에 완료해야 하는 task를 지정하는 데 사용됩니다. 예를 들어, 대부분의 경우 애플리케이션의 build 스크립트가 실행되기 전에 라이브러리의 build 스크립트가 완료되기를 원합니다. 이렇게 하려면 다음 turbo.json을 사용합니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"] 
    }
  }
}

이제 종속성의존하는 것 이전에 빌드하는 예상한 빌드 순서를 갖게 되었습니다.

하지만 주의하세요. 이 시점에서는 캐싱을 위한 빌드 출력을 표시하지 않았습니다. 그렇게 하려면 출력 지정하기 섹션으로 이동하세요.

^를 사용하여 종속성의 task에 의존하기

^ 마이크로 구문은 Turborepo에 대상 패키지 이전에 직접 종속성의 task를 실행하도록 지시합니다. 애플리케이션이 ui라는 라이브러리에 종속되어 있고 라이브러리에 build task가 있는 경우, uibuild 스크립트가 먼저 실행됩니다. 성공적으로 완료되면 애플리케이션의 build task가 실행됩니다.

이는 애플리케이션의 build task가 컴파일하는 데 필요한 모든 필수 종속성을 갖도록 보장하므로 중요한 패턴입니다. 이 개념은 종속성 그래프가 여러 수준의 task 종속성을 가진 더 복잡한 구조로 성장함에 따라 적용됩니다.

동일한 패키지의 task에 의존하기

때로는 동일한 패키지의 두 task가 특정 순서로 실행되도록 해야 할 수 있습니다. 예를 들어, 동일한 라이브러리에서 test task를 실행하기 전에 라이브러리의 build task를 실행해야 할 수 있습니다. 이렇게 하려면 dependsOn 키에서 스크립트를 일반 문자열로 지정합니다(^ 없이).

Turborepo logo
./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["build"] 
    }
  }
}

특정 패키지의 특정 task에 의존하기

특정 패키지의 개별 task를 의존하도록 지정할 수도 있습니다. 아래 예제에서는 모든 lint task 전에 utilsbuild task를 실행해야 합니다.

Turborepo logo
./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

의존 task에 대해 더 구체적으로 지정하여 특정 패키지로 제한할 수도 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "web#lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

이 구성을 사용하면 web 패키지의 lint task는 utils 패키지의 build task가 완료된 후에만 실행할 수 있습니다.

종속성 없음

일부 task에는 종속성이 없을 수 있습니다. 예를 들어, Markdown 파일에서 오타를 찾는 task는 다른 task의 상태를 신경 쓸 필요가 없을 것입니다. 이 경우 dependsOn 키를 생략하거나 빈 배열을 제공할 수 있습니다.

Turborepo logo
./turbo.json
{
  "tasks": {
    "spell-check": {
      "dependsOn": [] 
    }
  }
}

outputs 지정하기

Turborepo는 task의 출력을 캐싱하므로 동일한 작업을 두 번 수행하지 않습니다. 이에 대해서는 캐싱 가이드에서 자세히 다루겠지만, 먼저 task가 올바르게 구성되었는지 확인해야 합니다.

outputs 키는 task가 성공적으로 완료되었을 때 캐싱해야 하는 파일과 디렉토리를 Turborepo에 알려줍니다. 이 키가 정의되지 않으면 Turborepo는 파일을 캐싱하지 않습니다. 후속 실행에서 캐시를 적중해도 파일 출력을 복원하지 않습니다.

다음은 일반적인 도구의 출력 예제입니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"] 
    }
  }
}

Glob은 패키지에 상대적이므로 dist/**는 각 패키지에 대해 출력되는 dist를 각각 처리합니다. outputs 키에 대한 globbing 패턴 구축에 대한 자세한 내용은 globbing 사양을 참조하세요.

inputs 지정하기

inputs 키는 캐싱을 위해 task의 해시에 포함하려는 파일을 지정하는 데 사용됩니다. 기본적으로 Turborepo는 Git에서 추적하는 패키지의 모든 파일을 포함합니다. 그러나 inputs 키를 사용하여 해시에 포함할 파일을 더 구체적으로 지정할 수 있습니다.

예를 들어, Markdown 파일에서 오타를 찾는 task는 다음과 같이 정의할 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "spell-check": {
      "inputs": ["**/*.md", "**/*.mdx"] 
    }
  }
}

이제 Markdown 파일의 변경 사항 spell-check task가 캐시를 미스하게 합니다.

이 기능은 소스 제어에서 추적하는 변경 사항을 따르는 것을 포함하여 Turborepo의 모든 기본 inputs 동작을 거부합니다. 이는 .gitignore 파일이 더 이상 존중되지 않으며, glob으로 해당 파일을 캡처하지 않도록 해야 함을 의미합니다.

기본 동작을 복원하려면 $TURBO_DEFAULT$ 마이크로 구문을 사용하세요.

$TURBO_DEFAULT$로 기본값 복원하기

기본 inputs 동작은 종종 task에 원하는 것입니다. 그러나 task 출력에 영향을 주지 않는 것으로 알려진 파일의 변경 사항을 무시하도록 inputs를 미세 조정하여 특정 task의 캐시 적중률을 높일 수 있습니다.

이러한 이유로 $TURBO_DEFAULT$ 마이크로 구문을 사용하여 기본 inputs 동작을 미세 조정할 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", "!README.md"] 
    }
  }
}

이 task 정의에서 Turborepo는 build task에 대해 기본 inputs 동작을 사용하지만 README.md 파일의 변경 사항은 무시합니다. README.md 파일이 변경되어도 task는 여전히 캐시를 적중합니다.

Root Task 등록하기

turbo를 사용하여 Workspace 루트의 package.json에서 스크립트를 실행할 수도 있습니다. 예를 들어, 각 패키지의 lint task 외에도 Workspace 루트 디렉토리의 파일에 대한 lint:root task를 실행할 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["^lint"]
    },
    "//#lint:root": {} 
  }
}

Root Task가 등록되면 turbo run lint:root가 task를 실행합니다. 또한 turbo run lint lint:root를 실행하여 모든 린팅 task를 실행할 수 있습니다.

Root Task를 사용하는 경우

  • Workspace 루트의 린팅 및 포맷팅: Workspace 루트에 린팅 및 포맷팅하려는 코드가 있을 수 있습니다. 예를 들어, 루트 디렉토리에서 ESLint 또는 Prettier를 실행할 수 있습니다.
  • 점진적 마이그레이션: Turborepo로 마이그레이션하는 동안 아직 패키지로 이동하지 않은 일부 스크립트가 있는 중간 단계가 있을 수 있습니다. 이 경우 Root Task를 생성하여 마이그레이션을 시작하고 나중에 task를 패키지로 분산할 수 있습니다.
  • 패키지 범위가 없는 스크립트: 특정 패키지의 컨텍스트에서 의미가 없는 일부 스크립트가 있을 수 있습니다. 이러한 스크립트는 Root Task로 등록할 수 있으므로 캐싱, 병렬화 및 워크플로우 목적으로 여전히 turbo로 실행할 수 있습니다.

고급 사용 사례

Package Configuration 사용하기

Package Configuration은 패키지에 직접 배치되는 turbo.json 파일입니다. 이를 통해 패키지는 리포지토리의 나머지 부분에 영향을 주지 않고 자체 task에 대한 특정 동작을 정의할 수 있습니다.

많은 팀이 있는 대규모 monorepo에서는 팀이 자체 task에 대해 더 큰 제어권을 갖습니다. 자세한 내용은 Package Configuration 문서를 참조하세요.

런타임 종속성이 있는 장기 실행 task

동시에 항상 실행되어야 하는 다른 task가 필요한 장기 실행 task가 있을 수 있습니다. 이를 위해 with를 사용합니다.

Turborepo logo
./apps/web/turbo.json
{
  "tasks": {
    "dev": {
      "with": ["api#dev"],
      "persistent": true,
      "cache": false
    }
  }
}

장기 실행 task는 종료되지 않으므로 이에 의존할 수 없습니다. 대신 with 키워드는 web#dev task가 실행될 때마다 api#dev task를 실행합니다.

부작용 수행하기

캐시된 빌드 후 배포 스크립트와 같이 일부 task는 항상 실행되어야 합니다. 이러한 task의 경우 task 정의에 "cache": false를 추가합니다.

Turborepo logo
./turbo.json
{
  "tasks": {
    "deploy": {
      "dependsOn": ["^build"],
      "cache": false
    },
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

병렬로 실행할 수 있는 종속 task

일부 task는 다른 패키지에 종속되어 있음에도 불구하고 병렬로 실행할 수 있습니다. 이 설명에 맞는 task의 예는 타입 체커입니다. 타입 체커는 성공적으로 실행하기 위해 종속성의 출력을 기다릴 필요가 없기 때문입니다.

이 때문에 check-types task를 다음과 같이 정의하고 싶을 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "check-types": {} // Incorrect!
  }
}

이렇게 하면 task가 병렬로 실행되지만 종속성의 소스 코드 변경 사항을 고려하지 않습니다. 이는 다음을 의미합니다:

  1. ui 패키지의 인터페이스에 중단 변경을 합니다.
  2. turbo check-types를 실행하여 ui에 종속되는 애플리케이션 패키지에서 캐시를 적중합니다.

애플리케이션 패키지가 새 인터페이스를 사용하도록 업데이트되지 않았음에도 불구하고 성공적인 캐시 적중을 표시하므로 이는 올바르지 않습니다. 편집기에서 애플리케이션 패키지의 TypeScript 오류를 수동으로 확인하면 오류가 나타날 것입니다.

이 때문에 check-types task 정의를 약간 변경합니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "check-types": {
      "dependsOn": ["^check-types"] // This works...but could be faster!
    }
  }
}

ui 패키지에서 중단 변경을 다시 테스트하면 캐싱 동작이 이제 올바른 것을 알 수 있습니다. 그러나 task는 더 이상 병렬로 실행되지 않습니다.

두 요구 사항(정확성과 병렬성)을 모두 충족하려면 Task Graph에 Transit Node를 도입할 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "transit": {
      "dependsOn": ["^transit"]
    },
    "check-types": {
      "dependsOn": ["transit"]
    }
  }
}

이러한 Transit Node는 package.json의 스크립트와 일치하지 않아 아무 작업도 수행하지 않는 task를 사용하여 패키지 종속성 간의 관계를 생성합니다. 이 때문에 task는 병렬로 실행되고 내부 종속성의 변경 사항을 인식할 수 있습니다.

이 예제에서는 transit이라는 이름을 사용했지만 Workspace에서 이미 스크립트가 아닌 이름을 사용할 수 있습니다.

다음 단계

향후 가이드에서 탐색할 turbo.json 구성 문서에서 더 많은 옵션을 사용할 수 있습니다. 지금은 기본 작동 방식을 확인하기 위해 몇 가지 task를 실행할 수 있습니다.