Turbo is an incremental bundler and build system optimized for JavaScript and TypeScript, written in Rust.
Turborepo는 Vercel에서 만든 모노레포를 관리하는 도구로, 여러 프로젝트를 하나의 레포지토리에서 효율적으로 관리할 수 있게 도와준다.
모노레포란 버전 관리 시스템에서 두 개 이상의 프로젝트 코드가 동일한 저장소에 저장되는 소프트웨어 개발 전략 (feat. 위키백과)
모노레포(Monorepo)는 단일 레포지토리(Single Repository)의 준말이다.
여러 애플리케이션과 라이브러리를 하나의 리포지토리 내에서 관리하는 방식으로, 각 애플리케이션은 독립적일 수 있지만, 공통의 설정, 코드, 빌드, 테스트 등을 공유하는 레포지토리로 구성된다.
모놀리스 애플리케이션, 모듈 프로그래밍, 멀티레포, 모노레포. 모노레포가 등장하기까지 어떤 배경과 불편함이 있었을까?
태초에 모놀리스 애플리케이션(모듈화 없이 설계된 소프트웨어 애플리케이션)이 있었다. 거대한 프로젝트가 하나의 버전으로 관리하는 것에 사람들은 어려움을 느꼈다.
모듈식 프로그래밍으로 전체 교체 없이 애플리케이션 일부를 수정할 수 있게 되었다. 이런 모듈을 다른 애플리케이션에서도 사람들은 사용하고 싶어했다.
폴리레포(polyrepo)라고도 부르는 멀티레포 구조가 등장했다. 앞서의 각 모듈들은 멀티레포 구조에서 고유한 저장소가 있는 프로젝트가 된다.
그러나 각각의 레포를 하나하나 관리하고 새로운 레포를 추가할 때마다 동일한 세팅을 해줘야 한다는 문제가 있었다.
이러한 한계를 해결해주기 위해 모노레포가 등장했다.
프로젝트 사이에 의존성이 존재하거나 같은 제품군이거나 하는 정의된 관계가 존재할 때, 이러한 관계를 모노레포는 효율적으로 관리해준다.
JavaScript와 TypeScript 코드베이스를 위한 고성능 빌드 도구이다. 주로 모노레포를 확장하는 데 적합하며, 단일 패키지 워크스페이스에서도 워크플로우를 더 빠르게 만들어주기도 한다.
모노레포는 많은 장점이 있지만, 확장성에서 어려움을 겪는다. 각 워크스페이스는 고유한 테스트, 린팅, 빌드 프로세스를 갖고 있으며 하나의 모노레포는 수천 개의 작업을 실행해야 할 수 있다.
Turborepo는 모노레포의 확장성 문제를 해결한다고 한다. 모든 작업의 결과를 캐싱하여 CI(Continuous Integration)가 동일한 작업을 두 번 이상 수행하지 않도록 만들기 때문이다.
또한, 작업을 효율적으로 병렬화하여 빌드 속도를 개선한다는 특징도 있다.
npx create-turbo@latest my-monorepo
npm install turbo --save-dev
{
"pipeline": {
"build": {
"outputs": ["dist/**"]
},
"test": {}
}
}
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.json
파일에서 dev
설정 조회cache: false
: dev
작업은 캐시되지 않으므로 매번 새로 실행. 변경 내용 반영.persistent: true
: 이 작업은 지속적으로 실행되며, 일반적으로 개발 서버처럼 동작."dev": {
"cache": false,
"persistent": true
}
apps 디렉토리 안에 있는 각 앱(디렉토리)에 대해 dev 작업을 병렬로 실행한다.
기본적으로 각 앱에 대해 독립적으로 작업을 수행하지만, 특정 작업 간 의존성을 설정하여 작업 실행 순서를 조정할 수 있다.
Turborepo는 개발 중 실시간 파일 감지 및 변경 사항을 핫로드로 자동으로 반영한다.
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
작업을 병렬 또는 의존성에 따라 순서대로 실행.outputs
에 정의된 경로로 빌드 결과물이 생성.outputs
경로에 빌드 결과물이 없으면 경고 메시지.dependsOn
을 통해 다른 작업들이 먼저 실행되도록 할 수 있다.