단일 repository에 여러 서브 프로젝트가 존재하는 형태의 repository
기존에 자주 사용되던 모놀리식 레포는 소스코드를 모듈화하지 않고 하나의 레포지토리에 모두 넣은 것으로 아래와 같은 특징을 가진다.
이러한 단점을 보완하기 위해 멀티 레포 형식을 취할 수 있다. 멀티 레포의 특징은 다음과 같다.
따라서 모놀리식과 멀티레포 방식의 장점을 모두 가져가기 위해서 “모노레포” 방식이 등장했다.
모노레포 환경에서 쉽게 개발할 수 있도록 빌드 도구를 제공하는 역할을 하는 turborepo는 기본 원칙을 가지고 있다.
한번 작업 중 수행한 계산은 다시 수행하지 않는다
작업 내용을 캐싱해 이미 계산한 내용은 건너뛰고, 빌드는 한번만 한다.
내용을 기반으로 해싱하여 변경된 내용(파일)만 빌드한다.
클라우드 환경에서도 빠른 빌드를 제공할 수 있도록 캐시 공유함
태스크끼리의 의존성을 판단해 최대한 병렬적으로 작업 진행
태스크간의 연결을 정의해 빌드를 언제 어떻게 실행할지 판단하고 최적화
제로 런타임으로 런타임 단계에서 파악하지 못한 이슈가 나타날 위험이 없음
빌드에 필요한 요소만으로 하위집합을 생성해 배포 속도가 높아짐
JSON으로 설정 가능
빌드 과정 시각화 하여 병목 지점 찾을 수 있음
아래 명령어를 통해 새 프로젝트를 turborepo로 생성할 수 있다.
npx create-turbo@latest
터미널에 보면 다음과 같은 안내가 나오고, 프로젝트는 크게 apps
, packages
의 두개 폴더와 설정파일들로 구성된다.
>>> Creating a new turborepo with the following:
- apps/web: Next.js with TypeScript
- apps/docs: Next.js with TypeScript
- packages/ui: Shared React component library
- packages/eslint-config-custom: Shared configuration (ESLint)
- packages/tsconfig: Shared TypeScript `tsconfig.json`
./apps/web/package.json
에서 dependecies를 확인해보면 다음과 같은 부분을 볼 수 있다. 이는 web 프로젝트가 ./packages/ui
패키지를 의존하고 있음을 나타낸다.
"dependencies": {
"ui": "*"
},
이렇게 의존하는 패키지에서 코드를 가져올 때는 아래와 같이 작성합니다.
import { Button } from "ui";
export하는 쪽은 package.json에서 import하는 쪽이 접근해야하는 파일과 typescript type이 정의된 파일을 다음과 같이 작성해줍니다.
{
"main": "./index.tsx",
"types": "./index.tsx"
}
./packages/tsconfig/package.json
에서는 다음과 같이 export할 파일을 정의해줍니다.
{
"name": "tsconfig",
"files": ["base.json", "nextjs.json", "react-library.json"]
}
Import하는 쪽은 이를 tsconfig.json파일에서 extends 속성을 이용해 사용합니다.
{
"extends": "tsconfig/react-library.json"
}
ESLint같은 경우 eslint-config-
라는 prefix가 있는 workspace를 찾도록 설정되어 있다. ESLint는 기본적으로 가장 가까운 .eslintrc.js
파일을 찾기 때문에 현 디렉토리에 없다면 그 상위 디렉토리에서 찾게 되어 eslint-config-custom
이라는 폴더가 최상위에 있는 것이다. 만약 현 디렉토리에 .eslintrc.js
파일이 있더라도 다음과 같이 extend하여 사용할 수 있다.
module.exports = {
root: true,
extends: ["custom"], // prefix(eslint-config-)뒤에 붙은 이름
};
기본 turbo.json 파일은 다음과 같이 생성된다.
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}
이때 pipeline아래의 build, lint, dev는 모두 태스크로 turbo run <task>
와 같이 실행될 수 있다.
만약 lint 명령을 실행한다면 각각의 workspace 내의 lint script를 확인하여 모두 실행한다.
이때 처음에는 0 cached, 3 total
를 확인할 수 있지만 같은 명령을 한번 더 실행하면 3 cached, 3 total
를 확인할 수 있다. 이것은 turborepo가 코드 변화가 없음을 알아차리고 이전에 캐싱된 내용을 가져온 것이다.
etc. turborepo, lerna, rush를 비교한 글에서 turborepo는 setup면에서 우위를 차지한다는 결과가 있었다.