손쉽게 모노레포 사용하려면, Turborepo

김현조·2023년 3월 14일
2

FrontEnd

목록 보기
4/9
post-thumbnail

모노레포란?

단일 repository에 여러 서브 프로젝트가 존재하는 형태의 repository

기존에 자주 사용되던 모놀리식 레포는 소스코드를 모듈화하지 않고 하나의 레포지토리에 모두 넣은 것으로 아래와 같은 특징을 가진다.

  • 장점
    • 모든 코드가 단일 버전
    • 코드 재사용 용이
    • 빌드, 배포 용이
  • 단점
    • 관심분리 어려움
    • 변경사항이 프로젝트 전체에 영향을 줄 수 있음

이러한 단점을 보완하기 위해 멀티 레포 형식을 취할 수 있다. 멀티 레포의 특징은 다음과 같다.

  • 각 모듈을 서로 다른 레포로 생성
  • 모듈화 및 관심분리 용이
  • 재사용 어려움

따라서 모놀리식과 멀티레포 방식의 장점을 모두 가져가기 위해서 “모노레포” 방식이 등장했다.

  • 레포 하나로 관리하여 서로 의존성 확인하기에 용이함
  • 버전관리를 단일화하여 프로젝트 흐름 읽기 쉬움
  • 코드 재사용에 용이함
  • 명령 하나로 빌드, 테스트 등을 한번에 병렬처리할 수 있음

모노레포 빌드 시스템, Turborepo

모노레포 환경에서 쉽게 개발할 수 있도록 빌드 도구를 제공하는 역할을 하는 turborepo는 기본 원칙을 가지고 있다.

한번 작업 중 수행한 계산은 다시 수행하지 않는다

특징

  • Incremental builds

작업 내용을 캐싱해 이미 계산한 내용은 건너뛰고, 빌드는 한번만 한다.

  • Content-aware hashing

내용을 기반으로 해싱하여 변경된 내용(파일)만 빌드한다.

  • Cloud caching

클라우드 환경에서도 빠른 빌드를 제공할 수 있도록 캐시 공유함

  • Parallel execution

태스크끼리의 의존성을 판단해 최대한 병렬적으로 작업 진행

  • Task pipelines

태스크간의 연결을 정의해 빌드를 언제 어떻게 실행할지 판단하고 최적화

  • Zero Runtime Overhead

제로 런타임으로 런타임 단계에서 파악하지 못한 이슈가 나타날 위험이 없음

  • Pruned(정리된) subset

빌드에 필요한 요소만으로 하위집합을 생성해 배포 속도가 높아짐

  • Json configuration

JSON으로 설정 가능

  • Profile in browser

빌드 과정 시각화 하여 병목 지점 찾을 수 있음

사용 방법

아래 명령어를 통해 새 프로젝트를 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면에서 우위를 차지한다는 결과가 있었다.

출처

0개의 댓글