Turborepo 튜토리얼

sham·2025년 1월 11일
0

개요

Turbo is an incremental bundler and build system optimized for JavaScript and TypeScript, written in Rust.

Turborepo는 Vercel에서 만든 모노레포를 관리하는 도구로, 여러 프로젝트를 하나의 레포지토리에서 효율적으로 관리할 수 있게 도와준다.

개념 정리

모노레포가 뭐지?

모노레포란 버전 관리 시스템에서 두 개 이상의 프로젝트 코드가 동일한 저장소에 저장되는 소프트웨어 개발 전략 (feat. 위키백과)

모노레포(Monorepo)는 단일 레포지토리(Single Repository)의 준말이다.

여러 애플리케이션과 라이브러리를 하나의 리포지토리 내에서 관리하는 방식으로, 각 애플리케이션은 독립적일 수 있지만, 공통의 설정, 코드, 빌드, 테스트 등을 공유하는 레포지토리로 구성된다.

등장 배경

모놀리스 애플리케이션, 모듈 프로그래밍, 멀티레포, 모노레포. 모노레포가 등장하기까지 어떤 배경과 불편함이 있었을까?

태초에 모놀리스 애플리케이션(모듈화 없이 설계된 소프트웨어 애플리케이션)이 있었다. 거대한 프로젝트가 하나의 버전으로 관리하는 것에 사람들은 어려움을 느꼈다.

모듈식 프로그래밍으로 전체 교체 없이 애플리케이션 일부를 수정할 수 있게 되었다. 이런 모듈을 다른 애플리케이션에서도 사람들은 사용하고 싶어했다.

폴리레포(polyrepo)라고도 부르는 멀티레포 구조가 등장했다. 앞서의 각 모듈들은 멀티레포 구조에서 고유한 저장소가 있는 프로젝트가 된다.

그러나 각각의 레포를 하나하나 관리하고 새로운 레포를 추가할 때마다 동일한 세팅을 해줘야 한다는 문제가 있었다.

이러한 한계를 해결해주기 위해 모노레포가 등장했다.

프로젝트 사이에 의존성이 존재하거나 같은 제품군이거나 하는 정의된 관계가 존재할 때, 이러한 관계를 모노레포는 효율적으로 관리해준다.

주요 특징

  • 단일 레포지토리 관리
    • 모든 애플리케이션과 라이브러리가 하나의 레포지토리 안에 존재한다.
    • 여러 프로젝트 간에 코드를 공유하거나 관리할 때의 복잡함을 줄일 수 있다.
  • 공통 코드 및 설정 공유
    • UI 컴포넌트, 유틸리티 함수, TypeScript 설정, ESLint 설정 등 공통된 코드와 설정을 한 곳에서 관리하여 여러 애플리케이션이 이를 공유할 수 있다.
    • UI 라이브러리나 데이터 처리 모듈을 여러 애플리케이션에서 재사용할 수 있다.
  • 빌드와 테스트 최적화
    • 모노레포에서 모든 프로젝트를 한 번에 관리하기 때문에 의존성 그래프를 이용해 필요한 부분만 빌드하거나 테스트할 수 있다.
    • 변경된 부분만 재빌드하고, 불필요한 작업을 최소화하는 것도 가능.
  • 의존성 관리 및 업데이트
    • 여러 프로젝트에서 공통으로 사용하는 라이브러리나 패키지를 관리하기 용이.
    • 모든 프로젝트가 동일한 버전의 라이브러리를 사용하도록 강제하거나, 버전 충돌을 방지할 수 있다.
  • 모듈화 및 분리
    • 하나의 레포지토리에서 여러 애플리케이션과 라이브러리를 관리하지만, 각 프로젝트는 독립적으로 개발 및 배포할 수 있도록 모듈화.
    • 각 애플리케이션은 필요한 부분만 변경하고 빌드할 수 있다.
  • 효율적인 CI/CD
    • CI/CD 파이프라인을 설정할 때, 변경된 부분만 테스트하거나 배포할 수 있도록 최적화할 수 있다.
    • 새로운 프로젝트를 추가하더라도 CI/CD 세팅에 품을 들일 필요가 없다.

Truborepo는 뭐야?

JavaScript와 TypeScript 코드베이스를 위한 고성능 빌드 도구이다. 주로 모노레포를 확장하는 데 적합하며, 단일 패키지 워크스페이스에서도 워크플로우를 더 빠르게 만들어주기도 한다.

모노레포는 많은 장점이 있지만, 확장성에서 어려움을 겪는다. 각 워크스페이스는 고유한 테스트, 린팅, 빌드 프로세스를 갖고 있으며 하나의 모노레포는 수천 개의 작업을 실행해야 할 수 있다.

Turborepo는 모노레포의 확장성 문제를 해결한다고 한다. 모든 작업의 결과를 캐싱하여 CI(Continuous Integration)가 동일한 작업을 두 번 이상 수행하지 않도록 만들기 때문이다.

또한, 작업을 효율적으로 병렬화하여 빌드 속도를 개선한다는 특징도 있다.

본문

환경 세팅

create-turbo 템플릿으로 시작하기

npx create-turbo@latest my-monorepo

직접 설정하기

  • 설치
    • npm install turbo --save-dev
  • turbo.json에 다음과 같이 작성
    {
      "pipeline": {
        "build": {
          "outputs": ["dist/**"]
        },
        "test": {}
      }
    }

Node.js 환경 구축

cd apps/api
npm init -y
npm install express
npm install --save-dev typescript @types/node @types/express

apps/api/tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "./dist",
    "esModuleInterop": true,
    "strict": true
  },
  "include": ["src/**/*.ts"]
}

apps/api/src/server.ts

import express from 'express';

const app = express();
const port = 3001;

app.get('/', (req, res) => {
  res.send('Hello, Node.js Backend!');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

모노레포 구조

my-monorepo/
├── apps/
│   ├── web/      # 프론트엔드 애플리케이션 (Next.js)
│   ├── api/      # 백엔드 애플리케이션 (Node.js)
├── packages/
│   ├── ui/       # 공통 UI 컴포넌트
│   ├── tsconfig/ # 공통 TypeScript 설정
│   ├── eslint-config-custom/ # 공통 ESLint 설정
├── turbo.json    # Turborepo 설정 파일
├── package.json  # 루트 package.json (Workspaces 정의)

turbo.json

Configuring turbo.json | Turborepo

Turborepo에서 작업을 정의하고, 의존성, 캐싱, 작업 흐름 등을 설정하는 데 사용되는 파일이다.

turbo.json 파일의 구성 요소와 각 항목에 대해 알아보자.

  • tasks : 객체의 각 key은 turbo run으로 실행할 수 있는 작업들이며, 각각의 작업들은 작업 간의 의존성, 입력 파일, 출력 파일 등을 설정할 수 있다.
    • dependsOn: 작업이 의존하고 있는 작업.^build현재 프로젝트의 상위 의존성에서 build 작업이 먼저 실행되어야 함을 의미. (^는 부모를 나타내는 기호.)
    • inputs: 이 작업을 실행할 때 고려할 입력 파일들을 정의. "$.TURBO_DEFAULT$"는 기본적으로 Turborepo가 사용하는 환경 변수 등을 의미, ".env*"는 모든 .env 파일을 의미. inputs 파일들이 변경되면 해당 작업이 재실행.
    • outputs: 작업 실행 후 결과로 생성되는 파일들을 정의. "dist/**"는 apps의 애플리케이션 빌드 결과들이며, !.next/cache/**.next/cache 폴더를 제외하는 설정.
{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", "!.next/cache/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

package.json

{
  "name": "my-turborepo",
  "private": true,
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\""
  },
  "devDependencies": {
    "prettier": "^3.2.5",
    "turbo": "^2.3.3",
    "typescript": "5.5.4"
  },
  "engines": {
    "node": ">=18"
  },
  "packageManager": "npm@10.7.0",
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}

turbo dev 실행 시

turbo.json 파일에서 dev 설정 조회

  • cache: false: dev 작업은 캐시되지 않으므로 매번 새로 실행. 변경 내용 반영.
  • persistent: true: 이 작업은 지속적으로 실행되며, 일반적으로 개발 서버처럼 동작.
"dev": {
  "cache": false,
  "persistent": true
}

모노레포 작업 실행

apps 디렉토리 안에 있는 각 앱(디렉토리)에 대해 dev 작업을 병렬로 실행한다.

기본적으로 각 앱에 대해 독립적으로 작업을 수행하지만, 특정 작업 간 의존성을 설정하여 작업 실행 순서를 조정할 수 있다.

파일 감지 및 실시간 개발 환경(HMR)

Turborepo는 개발 중 실시간 파일 감지 및 변경 사항을 핫로드로 자동으로 반영한다.

turbo build 실행 시

turbo.json 파일에서 build 설정 조회

"build": {
  "dependsOn": ["^build"],
  "inputs": ["$TURBO_DEFAULT$", ".env*"],
  "outputs": ["dist/**", "!.next/cache/**"]
}
  • turbo.json 파일의 설정에 따라 build 작업이 실행.
    • dependsOn: ["^build"] - 다른 워크스페이스의 build 작업에 의존.
    • inputs - build 작업이 영향을 받는 파일/환경 변수 목록을 지정. 기본값($TURBO_DEFAULT$)과 .env 파일이 포함.
    • outputs - dist/**와 같은 빌드 결과물 경로를 정의. 캐싱된 결과물은 이 경로를 기준으로 확인.
  • 각 애플리케이션(apps/*) 및 패키지(packages/*)에 정의된 build 작업을 병렬 또는 의존성에 따라 순서대로 실행.
    • create-turbo로 프로젝트를 만들면 packages 디렉토리 안에 @repo/ui, @repo/eslint-config 라는 이름의 프로젝트가 존재하고, workspace에 포함되어 있다.
      • workspace는 루트의 package.json에서 확인 가능.
      • web 디렉토리에서 이를 의존하는 것을 확인할 수 있다.
    • outputs에 정의된 경로로 빌드 결과물이 생성.
      • outputs 경로에 빌드 결과물이 없으면 경고 메시지.
    • dependsOn을 통해 다른 작업들이 먼저 실행되도록 할 수 있다.

레퍼런스

모던 프론트엔드 프로젝트 구성 기법 - 모노레포 개념 편

모노레포 이렇게 좋은데 왜 안써요?

profile
씨앗 개발자

0개의 댓글