오늘날 최고의 모노레포 솔루션의 장단점을 살펴봅시다.
아래 링크를 통해 전체 시리즈를 살펴보세요.
이 글은 Nx, PNPM, Turborepo의 기능, 성능, 프로젝트 적합성을 비교하는 시리즈 글 중 하나입니다.
모노레포 관리 및 빌드 시스템의 기초를 다지고 Nx를 철저히 조사했으니, 이제 Turborepo의 기능을 알아보는 데 집중해보겠습니다. ✨
다시 한번 말씀드리지만, 저희의 목표는 개발 워크플로우를 간소화하고 코드 베이스 관리를 개선할 최고의 모노레포 도구를 선발하는 것입니다.
최고의 모노레포 도구가 우승하기를 기원합니다! 우리가 함께 정복할 도전 과제는 다음과 같습니다.
다음 단계가 궁금하신가요? 함께 알아보죠! 🚀
🔳 Turborepo
는 패키지 그래프(Package Graph)와 작업 그래프(Task Graph)를 모두 활용하여 모노레포 내에서 작업을 효율적으로 관리하고 실행합니다.
🔳 패키지 그래프는 package.json
종속성을 분석하여 내부 패키지(Internal Package) 간의 관계를 자동으로 매핑합니다.
🔳 package.json
에서 내부 패키지를 참조할 때는 NPM에서 외부 종속성을 참조하는 방식과 유사한 워크스페이스별 구문을 사용합니다. 다음 예시를 확인해봅시다.
// pnpm: ./apps/web/package.json
{
"dependencies": {
"@repo/ui": "workspace:*"
}
}
🔳 패키지 그래프는 작업 그래프
의 기초로, 개별 작업이 서로 의존하는 방식을 정의합니다.
🔳 작업 그래프의 노드는 작업을 나타내고 간선은 종속성을 나타내는 유향 비순환 그래프(DAG)입니다. 이를 통해 올바른 실행 순서를 보장하고 병렬 작업 실행을 가능하게 합니다.
🔳 작업 종속성(Task dependencies)은 turbo.json
에 명시적으로 정의되어 있습니다.
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"] // 종속된 모든 작업 공간의 빌드 작업에 따라 작업이 달라집니다.
}
}
}
🔳 대규모 프로젝트의 경우 Turborepo
는 daemon 프로세스를 사용하여 백그라운드에서 복잡한 프로젝트 그래프를 지능적으로 계산하여 시작 오버헤드를 크게 줄입니다.
🔳 위상 정렬을 사용하여 작업의 실행 순서를 지정하여 종속성이 충족되도록 합니다.
토폴로지 종속성은 패키지의 종속성이 자체 작업을 실행하기 전에 해당 종속성의 작업을 실행하도록 지정합니다. - https://turbo.build/repo/docs/messages/missing-root-task-in-turbo-json#why-this-error-occurred
🔳 Turborepo
는 Tarjan의 알고리즘을 사용하여 작업 그래프에서 순환 종속성을 감지하여 원활하고 오류 없는 빌드 프로세스를 보장합니다.
// https://github.com/vercel/turbo/blob/main/crates/turborepo-graph-utils/src/lib.rs#L29
// https://github.com/vercel/turbo/pull/5566/files
pub fn validate_graph<G: Display>(graph: &Graph<G, ()>) -> Result<(), Error> {
// 이것은 Go의 dag 라이브러리에 있는 AcyclicGraph.Cycles와 동일합니다.
let cycles_lines = petgraph::algo::tarjan_scc(&graph)
.into_iter()
.filter(|cycle| cycle.len() > 1)
.map(|cycle| {
let workspaces = cycle.into_iter().map(|id| graph.node_weight(id).unwrap());
format!("\t{}", workspaces.format(", "))
})
.join("\n");
// 주기가 감지되면 주기 세부 정보와 함께 오류를 반환합니다.
if !cycles_lines.is_empty() {
return Err(Error::CyclicDependencies(cycles_lines));
}
for edge in graph.edge_references() {
if edge.source() == edge.target() {
let node = graph
.node_weight(edge.source())
.expect("edge pointed to missing node");
return Err(Error::SelfDependency(node.to_string()));
}
}
Ok(())
}
🔳 Turborepo
는 깊이 우선 검색(DFS)을 사용하여 변경 사항의 영향을 받는 작업을 식별하여 불필요한 리빌드의 필요성을 최소화하고 개발 주기를 단축합니다.
// https://github.com/vercel/turbo/blob/main/crates/turborepo-repository/src/package_graph/mod.rs#L371
match direction {
petgraph::Direction::Outgoing => depth_first_search(&self.graph, indices, visitor),
petgraph::Direction::Incoming => {
depth_first_search(Reversed(&self.graph), indices, visitor)
}
};
🔳 Turborepo
는 플로이드-워셜 알고리즘을 활용하여 각 작업에 대한 최단 종속성 체인을 결정합니다. 이를 통해 중복 작업을 최소화하여 빌드 시간을 최적화합니다.
// https://github.com/vercel/turbo/blob/main/crates/turborepo-lib/src/engine/mod.rs#L160
impl Engine<Built> {
/// 지정된 패키지의 다음 패키지에 종속된 작업만 포함하는 `Engine` 인스턴스를 생성합니다.
/// 작업만 포함하는 인스턴스를 생성합니다. 이 기능은 감시 모드에서 유용합니다.
/// 작업 그래프의 일부만 다시 실행해야 하는 경우에 유용합니다.
pub fn create_engine_for_subgraph(
&self,
changed_packages: &HashSet<PackageName>,
) -> Engine<Built> {
let entrypoint_indices: Vec<_> = changed_packages
.iter()
.flat_map(|pkg| self.package_tasks.get(pkg))
.flatten()
.collect();
// 그래프를 뒤집는 이유는 엔트리포인트 작업의 *종속성*을 원하기 때문입니다.
let mut reversed_graph = self.task_graph.clone();
reversed_graph.reverse();
// 이것은 `O(V^3)`이므로 이론적으로는 병목 현상입니다. 각 진입점 작업에 대해 dijkstra의
// 알고리즘을 실행하는 것이 잠재적으로 더 빠를 수 있습니다.
let node_distances = petgraph::algo::floyd_warshall::floyd_warshall(&reversed_graph, |_| 1)
.expect("no negative cycles");
let new_graph = self.task_graph.filter_map(
|node_idx, node| {
if let TaskNode::Task(task) = &self.task_graph[node_idx] {
// 영구적이지 않은 작업만 포함하려고 합니다.
let def = self
.task_definitions
.get(task)
.expect("task should have definition");
if def.persistent {
return None;
}
}
...
💡 전문가 팁: Turborepo는 작업 그래프를 시각화하기 위해 --graph <file type>
옵션을 제공하여 모노레포의 빌드 구조에 대한 통찰력을 얻을 수 있습니다.
turbo run build --graph
turbo run build test lint --graph=my-graph.svg
이제 캐시 메커니즘에 대해 자세히 알아보겠습니다. 🌟
Turborepo
는 작업 입력(종속성, 환경 변수, 소스 코드 포함)을 해싱하고 빌드 출력을 저장합니다. 즉, 아무것도 변경하지 않았다면 작업이 대부분 즉시 완료됩니다!
🔳 Turborepo
는 .turbo/cache
디렉터리에 결과를 로컬 캐시합니다.
🔳 Turborepo
는 두 가지 수준에서 캐싱을 수행합니다.
💡 Turborepo는 기본적으로 캐시를 활성화합니다.
🔳 원격 및 공유 캐시 서버(예: Vercel의 원격 캐싱 또는 사용자 지정 솔루션)가 빌드 결과물을 저장하는 중앙 레포지토리로 지정됩니다.
🔳 작업의 모든 입력(코드, 종속성, 환경 등)을 나타내는 고유 해시는 작업을 실행하기 전에 Turborepo
에 의해 계산됩니다. 그런 다음 이 해시를 원격 캐시에 이미 저장된 해시와 비교합니다.
이러한 해시를 캐시에 사용하기 위해 전체 폴더를 하나의 파일로 압축하는 특수 파일 형식인 tar 파일에 작업의 출력을 저장합니다. 해당 파일은 로컬 파일 시스템과 이 작업 해시로 인덱싱된 Vercel 원격 캐시에 모두 저장됩니다. - https://vercel.com/blog/finishing-turborepos-migration-from-go-to-rust#hashing-tasks-for-the-run-command
🔳 일치하는 파일이 발견되면 캐시된 아티팩트가 밀리초 내에 다운로드되므로 작업을 다시 실행할 필요가 없어 귀중한 시간을 절약할 수 있습니다.
🔳 일치하는 항목이 발견되지 않으면 작업이 정상적으로 실행되고 그 출력은 다른 사람들이 나중에 사용할 수 있도록 원격 캐시에 업로드됩니다.
작업을 실행할 때가 되면 먼저 이 작업 해시를 생성하고 파일 시스템이나 원격 캐시에 있는지 확인합니다. 파일시스템이나 원격 캐시에 있으면 작업의 출력을 밀리초 단위로 복원합니다. 그렇지 않으면 작업을 실행하고 다음 작업을 위해 출력을 캐시에 저장합니다. - https://vercel.com/blog/finishing-turborepos-migration-from-go-to-rust#hashing-tasks-for-the-run-command
이해를 돕기 위해 NX
와 Turborepo
의 차이점을 자세히 살펴보겠습니다. 🌟
monorepo.tools의 인사이트와 이전에 본 내용을 바탕으로 NX
와 Turborepo
를 종합적으로 비교해 볼 수 있습니다.
🔳 NX
🔳 Turborepo
📌 NX는 더 많은 제어 기능을 갖춘 완전한 기능의 모노레포 환경을 제공하는 반면, Turborepo
는 빌드 속도와 단순성을 우선시합니다.
📌 모노레포에 최적화된 툴을 선택하는 것은 민감한 결정이며 정답이 있는 것이 아닙니다. 프로젝트 규모와 복잡성, 팀 구조와 협업, 원하는 제어 수준, 성능 요구 사항, 추가 툴링 요구 사항 등을 면밀히 평가하여 Nx
와 Turborepo
를 적절히 조합해야 합니다.
이제 Turborepo
의 기본 사항을 살펴보았으니 이제 지식을 실제로 적용해 볼 차례입니다. 🚀
✔️ turbo
는 글로벌 또는 로컬로 설치할 수 있습니다.
pnpm add turbo --global
pnpm add turbo --save-dev --ignore-workspace-root-check
✔️ 새 프로젝트를 시작하거나 예제를 사용하여 시작할 수 있습니다.
% npx create-turbo@latest
다음 패키지를 설치해야 합니다:
create-turbo@2.0.6
계속 진행하시겠습니까? (y)
>>> 새 Turborepo를 생성했습니다:
애플리케이션 패키지
- apps/docs
- apps/web
라이브러리 패키지
- packages/eslint-config
- packages/typescript-config
- packages/ui
>>> 성공! 새로운 Turborepo가 준비되었습니다.
시작하려면:
- 원격 캐싱 활성화(권장): pnpm dlx 터보 로그인
- 자세히 알아보기: https://turbo.build/repo/remote-cache
- Turborepo로 명령을 실행합니다:
- pnpm run build: 모든 앱과 패키지 빌드
- pnpm run dev: 모든 앱과 패키지 개발
- pnpm run lint: 모든 앱 및 패키지 린트
- 명령을 두 번 실행하여 캐시
✔️ 작업 공간의 구조는는 다음과 같습니다.
Turborepo
가 작업 공간 관리자(이 경우 PNPM(pnpm-workspace.yaml
)) 위에 추가되어 있는 것을 볼 수 있습니다.
packages:
- "apps/*"
- "packages/*"
Turborepo
의 목적은 모노레포와 관련된 작업을 관리하는 것으로, 보다 유연하고 전문적인 역할(turbo.json
)을 제공합니다.
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
기본 생성된 타입스크립트(tsconfig.json
)와 ESLint(eslint-config
)도 볼 수 있습니다.
eslint-config-turbo
패키지는 코드에 사용된 환경 변수 중 Turborepo의 해싱에 포함되지 않은 환경 변수를 찾는 데 도움이 됩니다. 소스 코드에 사용된 환경 변수 중turbo.json
에 설명되지 않은 환경 변수는 에디터에서 강조 표시되고 오류는 ESLint 출력으로 표시됩니다. - https://turbo.build/repo/docs/reference/eslint-config-turbo
✔️ Turborepo
를 사용하여 작업을 수정하고 실행하려면 아래와 같이 구성하세요.
// main package.json
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
그런 다음, pnpm dev
, pnpm build
, pnpm lint
, pnpm format
등 명령어를 사용할 수 있습니다.
✔️ 다음을 사용하여 작업 영역의 종속성 그래프를 시각화할 수 있습니다.
turbo run build --graph=my-graph.svg
이 SVG
를 생성합니다.
NX
만큼 고급적이고 상호작용에 유용하지는 않지만 Turborepo
는 여전히 사용하기 쉽습니다.
원격 캐싱 구성을 진행해 보겠습니다. 📡
✔️ 로컬 Turborepo
를 원격 캐시에 연결하려면 Vercel 계정으로 Turborepo
CLI를 인증해야 합니다.
turbo login
turbo login --sso-team=team-name
이는 패키지를 게시하기 위해 로그인할 때 NPM과 유사합니다. 😉
✔️ 인증이 완료되면 링크 명령을 실행해야 합니다.
turbo link
이제 캐시 아티팩트가 로컬 및 원격 캐시에 모두 저장됩니다.
그리고 동일한 빌드를 다시 실행합니다. 정상적으로 작동하면
turbo
가 로컬에서 작업을 실행하지 않습니다. 대신 원격 캐시에서 로그와 아티팩트를 다운로드하여 재생합니다. - https://turbo.build/repo/docs/core-concepts/remote-caching
✔️ Vercel 대신 다른 원격 캐시 호스팅 제공업체를 사용할 수도 있습니다.
turbo login --api="https://my-server.example.com/api"
turbo link --api="https://my-server-example.com"
turbo run build --api="https://my-server.example.com" --token="xxxxxxxxxxxxxxxxx"
API의 OpenAPI 사양은 여기에서 확인할 수 있습니다.
이처럼 Turborepo
를 다루는 것은 간단합니다! 이제부터는 Turborepo
를 보고 공부한 것들에 대한 의견을 말할 때가 된거 같습니다.📌.
🔳 Turborepo는 탁월합니다.
🔳 다음과 같은 경우 Turborepo를 고려해야 합니다.
🔳 다음과 같은 프로젝트에는 Turborepo가 최적의 솔루션이 아닐 수 있습니다.
Turborepo
에 대한 균형 잡힌 시각을 위해 다양한 프로젝트에서의 실제 사용 사례를 살펴보고, 직접 테스트 해본 커뮤니티 의견을 모아 보겠습니다.
Turborepo의 인기는 단순한 과대 광고가 아니라 다양한 개발 환경에서 빠르게 주목받으며 실제 환경에서 그 진가를 입증하고 있습니다.
🔳 다음과 같은 많은 기업이 프로덕션 워크플로우에 Turborepo
를 통합했습니다.
전체 목록은 여기에서 확인할 수 있습니다.
🔳 Turborepo의 속도, 사용 편의성 및 간소화된 설정이 자주 언급됩니다.
바닐라 측면을 완전히 무시하기
NX를 사용해야 하는 이유
- 현재 NX가 인수한 Lerna에서 마이그레이션하고 있습니다. 마이그레이션 경로는 매우 간단합니다(멀티 레포지터리가 엉망인 대규모 내부 사이트에서 이 작업을 수행했는데, 레포지토리의 아주 오래된 코드가 몇 가지 문제를 일으켰지만, 예상보다 쉬웠습니다).
- 워크플로에는 시간이 지날수록 점점 더 많은 라이브러리가 생성되므로 새 라이브러리를 생성하고 배포 중인 앱에 연결하기 위한 표준화된 프로세스가 필요합니다.
- 모노레포 구조는 이미 매우 복잡하며, 무엇이 어디로 가는지 더 쉽게 추적할 수 있는 견고한 구조와 시각화가 필요합니다.
모노레포에 적합한 DX/플러그인을 원합니다.
일화적이지만, NX의 캐싱이 Turborepo보다 훨씬 더 성능이 좋다는 것을 알았습니다 - 플랫폼에 따라 다를 수 있으므로 (개발 머신으로 M1 Max) 큰 소금 한 알을 가지고 가져가십시오.
NX는 꽤나 많은 사람들이 사용하고 있으므로 레거시 코드가 어떻게 유지되는지 알기 때문에 오랫동안 사용할 수 있습니다.
Turborepo를 사용하는 이유
단순함이 핵심
- 프로젝트가 비교적 간단하고, 최소한의 구성이나 강제 구조로 모노레포 라이브러리도 간단하기를 원합니다.
- 일반적인 CI/CD 플랫폼과 간단한 캐싱 통합을 원하는 경우
- 이미 Vercel 에코시스템에 연결된 경우
- Vercel은 FB/Meta의 리액트 팀으로부터 직접 지원을 받고 있으므로 당분간은 계속 사용할 수 있습니다.
요약하자면, Vercel의 모든 제품과 마찬가지로 여러분의 프로젝트가 Vercel 제품의 틀 안에 들어맞는다면, Vercel은 여러분에게 가장 적합하고 쉬운 솔루션이 될 것입니다. 그렇지 않은 경우 해결 방법을 찾는 것은 정말 귀찮은 일이 될 것입니다. Turborepo 문서를 읽고 (훨씬 더 큰) NX 문서를 훑어보세요. Turborepo에서 필요한 것을 찾지 못하면 NX가 더 나은 선택일 수 있지만, 이는 작업 중인 프로젝트, 팀 등에 대한 지식이 전혀 없는 상태에서 하는 말입니다. 일반적으로 당면한 요구 사항을 가장 잘 지원하면서 앞으로 나아갈 수 있는 합리적인 공간을 제공하는 솔루션을 고수하세요(이것이 가장 좋은 옵션이 될 수 있습니다). 미리 최적화하지 말고, 6개월, 1년, 2년 후 제품의 위치를 파악하고 거기서부터 더 많은 것을 검토하세요.
NX와 함께 package.json 구성 스타일을 사용하면 제너레이터와 실행기의 복잡성을 피할 수 있습니다. 매우 빠르고 간단하게 시작할 수 있으며 Turborepo와 매우 유사하지만, 나중에 필요할 때 더 많은 기능을 제공합니다(https://nx.dev/reference/project-configuration).
원하는 것은 워크스페이스 지원 및 스크립트 관리 기능이 있는 패키지 관리자입니다(저는 pnpm을 적극 권장합니다).
Turborepo의 주요 목적은 CI 및 대규모 팀 워크 플로우에서 아티팩트를 캐싱하고 스크립트를 병렬로 실행하는 빌드 시간을 줄이는 것이므로이를 위해 pnpm을 사용하고 CI가 너무 많은 시간이 걸리고 중복 작업을 수행하는 경우 Turborepo를 추가하는 것이 좋습니다.
Nx의 주요 목적은 모노레포를 엔지니어링하고 바퀴를 재발명하는 것입니다.
🔳 마지막으로 몇 가지 비교 연구를 소개합니다.
커뮤니티의 피드백은 저희의 연구 결과를 뒷받침합니다. 더 빠르고 효율적이며 안정적인 빌드를 원하는 개발 팀에게 Turborepo
는 게임 체인저입니다. 모든 프로젝트의 요구 사항을 충족하지는 못하지만 속도와 단순성에 중점을 두어 모노레포 환경에서 강력한 경쟁자로 자리매김하고 있습니다. ♨️
Turborepo
에 대한 심층 분석은 여기서 마무리하지만, 모노레포의 모험은 이제 막 시작되었습니다! Nx 및 Turborepo
와 비교하여 강점, 약점, 완벽한 사용 사례를 분석하는 pnpm 작업 공간에 대한 흥미진진한 탐험을 준비하세요. 더 많은 모노레포의 마법을 기대해주세요! ✨
Turborepo
는 자바스크립트 및 타입스크립트 모노레포에 특별히 맞춤화된 강력하고 효율적인 빌드 시스템입니다. 지능적인 작업 관리, 혁신적인 캐싱 메커니즘, 원시 속도에 중점을 두어 간소화된 워크플로와 빠른 피드백 루프를 원하는 개발자에게 획기적인 변화를 불러왔습니다.
Netflix, Datadog, 수많은 오픈 소스 프로젝트와 같은 업계 리더들의 실제 채택은 Turborepo의 효과와 확장성을 더욱 확고히 해줍니다. 활발한 커뮤니티의 긍정적인 피드백은 직관적인 설정, 상당한 성능 향상, 전반적으로 긍정적인 개발자 경험을 강조합니다.
Turborepo
가 모든 프로젝트에 이상적인 것은 아니지만, 간소화된 접근 방식과 빌드 속도에 초점을 맞춘 덕분에 특히 성능을 우선시하고 자바스크립트나 타입스크립트를 사용하는 많은 프로젝트에 매력적인 옵션이 될 수 있습니다.
모노레포의 여정은 여기서 끝나지 않습니다! 다음 시간에는 pnpm
작업 영역에 대해 자세히 알아볼 예정입니다. 이미 pnpm
을 사용하고 있다면 Turborepo
가 정말 필요할까요?
그때까지 계속 호기심을 갖고 계속 빌드하며 끊임없이 진화하는 현대 개발의 세계를 받아들이세요! ❤️
제 글을 읽어주셔서 감사합니다.
저와 연락하고 싶으신가요?
GitHub에서 저를 찾을 수 있습니다: https://github.com/helabenkhalfallah