대규모 TypeScript 모노레포 빌드 및 종속성 관리 가속화
Emily Parker
Product Engineer · Leapcell

소개
웹 개발의 진화하는 환경에서 모노레포는 복잡한 풀스택 애플리케이션을 관리하기 위한 점점 더 인기 있는 아키텍처 선택이 되었습니다. 코드 공유 간소화, 일관된 도구, 배포 간소화와 같은 부인할 수 없는 이점을 제공합니다. 그러나 이러한 모노레포가 확장될수록, 특히 TypeScript로 구축된 경우, 개발자는 종종 심각한 문제에 직면합니다: 느린 빌드 시간과 복잡한 종속성 관리. 정적 타이핑 및 컴파일 요구 사항이 있는 대규모 TypeScript 코드베이스는 종종 상호 의존적인 여러 애플리케이션 및 라이브러리를 포함하는 풀스택 아키텍처와 결합되어 긴 피드백 루프에 시달리는 개발 경험으로 빠르게 이어질 수 있습니다. 이는 개발자를 좌절시킬 뿐만 아니라 민첩성과 전달 속도를 저해합니다. 이 글에서는 이러한 장애물을 극복하고 느리고 번거로운 모노레포를 효율적이고 즐거운 개발 환경으로 전환하기 위한 실용적인 전략을 탐색하고 다양한 도구를 시연할 것입니다.
핵심 개념 이해
최적화 기법에 뛰어들기 전에 이 논의의 중심이 되는 핵심 개념에 대한 공통된 이해를 확립해 봅시다.
모노레포
모노레포는 종종 상호 관련 코드를 포함하는 여러 개의 별도 프로젝트를 포함하는 단일 리포지토리입니다. 풀스택 컨텍스트에서 여기에는 프런트엔드 애플리케이션, 백엔드 API, 공유 UI 구성 요소 및 유틸리티 라이브러리가 포함될 수 있으며, 모두 동일한 Git 리포지토리에 상주합니다.
TypeScript
TypeScript는 일반 JavaScript로 컴파일되는 강력한 유형의 JavaScript 슈퍼셋입니다. 정적 유형 검사를 통해 코드 품질과 유지 관리성을 크게 향상시키지만, 이 컴파일 단계는 특히 대규모 프로젝트에서 빌드 프로세스에 오버헤드를 추가합니다.
빌드 속도
이는 소스 코드(TypeScript, JSX 등)를 배포 가능한 아티팩트(JavaScript, CSS, 종종 번들링 및 축소됨)로 변환하는 데 걸리는 시간을 나타냅니다. 모노레포에서 "빌드"는 일반적으로 여러 프로젝트와 해당 종속성을 컴파일하는 것을 포함합니다.
종속성 관리
이는 모노레포 내의 패키지가 서로 어떻게 관련되는지, 외부 서드파티 라이브러리가 어떻게 처리되는지, 이러한 관계가 빌드 프로세스에 어떻게 영향을 미치는지를 포함합니다. npm, Yarn, pnpm과 같은 도구가 이를 위해 사용됩니다.
모노레포 최적화의 기둥
대규모 TypeScript 풀스택 모노레포를 최적화하는 것은 일반적으로 병렬 처리, 캐싱, 증분 빌드 및 효율적인 종속성 그래프 작성과 같은 여러 핵심 원칙을 중심으로 이루어집니다.
1. 종속성 그래프 작성을 위한 워크스페이스 활용
최신 패키지 관리자는 모노레포에 필수적인 "워크스페이스" 기능을 제공합니다. 워크스페이스를 사용하면 단일 루트 리포지토리 내에서 여러 패키지를 관리하여 패키지 간 종속성을 처리하고 공통 외부 종속성을 루트 node_modules
디렉터리로 호이스팅하여 공간을 절약하고 설치 시간을 개선할 수 있습니다.
모노레포 구조를 고려하십시오:
my-monorepo/
├── packages/
│ ├── frontend/
│ │ ├── src/
│ │ └── package.json
│ ├── backend/
│ │ ├── src/
│ │ └── package.json
│ └── shared-ui/
│ ├── src/
│ └── package.json
└── package.json
루트 package.json
은 워크스페이스를 정의합니다:
// my-monorepo/package.json { "name": "my-monorepo-root", "private": true, "workspaces": [ "packages/*" ], "scripts": { "build": "turbo run build" // Turborepo와 같은 모노레포 도구 사용 } }
각 패키지의 package.json
은 내부 종속성을 선언합니다:
// my-monorepo/packages/frontend/package.json { "name": "frontend", "version": "1.0.0", "dependencies": { "shared-ui": "workspace:*" // 내부 패키지 참조 } }
이 설정은 패키지 관리자가 정확한 종속성 그래프를 구축할 수 있도록 하며, 이는 모노레포 도구가 활용할 수 있습니다.
2. 모노레포 인식 빌드 도구
npm run build
와 같은 범용 작업 실행기는 모노레포의 패키지 간 종속성 및 캐싱 요구 사항을 처리하는 데 어려움을 겪습니다. 여기서 전문 모노레포 도구가 빛을 발합니다. Turborepo 또는 Nx와 같은 도구는 모노레포의 종속성 그래프를 이해하고 빌드 프로세스를 최적화하도록 설계되었습니다.
Turborepo를 사용하여 설명해 보겠습니다:
- 작업 그래프: Turborepo는 다른 작업에 종속되는 작업(예:
build
,test
,lint
)을 나타내는 작업 그래프를 구성합니다. 예를 들어,frontend#build
는shared-ui#build
에 종속될 수 있습니다. - 지능형 캐싱: 각 작업에 대해 파일 내용,
package.json
및tsconfig.json
파일, 심지어node_modules
까지 해싱합니다. 로컬 또는 원격 캐시에서 마지막 실행 이후 작업의 입력이 변경되지 않은 경우 Turborepo는 작업을 건너뛰고 캐시에서 출력을 복원합니다. - 병렬 실행: 서로 종속되지 않는 작업은 병렬로 실행할 수 있어 전체 빌드 시간을 크게 줄입니다.
예: Turborepo 구성
먼저 모노레포 루트에 Turborepo를 설치합니다: npm install turbo --save-dev
.
그런 다음 루트의 turbo.json
파일에 작업을 정의합니다:
// turbo.json { "$schema": "https://turbo.build/schema.json", "pipeline": { "build": { "dependsOn": ["^build"], // 종속성의 'build'에 종속됨 "outputs": ["dist/**", ".next/**"] // 이러한 출력을 캐싱 }, "test": { "dependsOn": ["^build"], "outputs": [] }, "lint": { "outputs": [] }, "dev": { "cache": false, // 개발 서버는 일반적으로 캐싱되지 않음 "persistent": true // 계속 실행 } } }
이제 turbo run build
를 실행하면 Turborepo는 다음을 수행합니다:
- 패키지의 모든
build
스크립트에 대한 종속성 그래프를 분석합니다. - 가능한 경우 빌드를 병렬화합니다.
- 빌드 출력을 캐싱합니다. 관련 코드가 변경되지 않은 경우 후속 실행은 거의 즉시 수행됩니다.
shared-ui
의 변경을 상상해 보십시오. Turborepo는 shared-ui
와 shared-ui
에 종속되는 frontend
만 다시 빌드하고 backend
는 건드리지 않고 전체 모노레포에 대해 turbo run build
를 실행하더라도 마찬가지입니다.
3. 증분 TypeScript 빌드
TypeScript 자체는 증분 컴파일을 지원하며, 변경되었거나 종속성이 변경된 파일만 다시 컴파일합니다. 이는 tsconfig.json
의 incremental
컴파일러 옵션을 통해 활성화됩니다.
// tsconfig.json (또는 tsconfig.build.json) { "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./.tsbuildinfo", // 빌드 정보가 저장되는 위치 // ... 기타 옵션 } }
incremental
이 true
이면 TypeScript는 프로젝트 그래프 및 빌드 상태에 대한 정보를 저장하는 .tsbuildinfo
파일을 생성합니다. 후속 컴파일에서 tsc
는 이 파일을 사용하여 다시 컴파일해야 하는 항목을 빠르게 결정합니다.
도움이 되지만, 모노레포에서는 tsc --build --watch
또는 단순히 tsc --build
가 모노레포 도구에 의해 조정되지 않으면 패키지 전반에 걸쳐 필요한 것보다 더 많이 다시 빌드할 수 있습니다. Turborepo 및 Nx는 일반적으로 tsc
호출을 래핑하므로 캐싱 메커니즘은 TypeScript의 증분 기능과 함께 작동합니다.
4. 최적화된 TypeScript 구성
tsconfig.json
파일을 미세 조정하면 성능 이점을 얻을 수 있습니다.
noEmit
및emitDeclarationOnly
: 순수하게 유형 정의인 패키지(예: 공유 유형 패키지)의 경우 런타임 JS를 컴파일하지 않고.d.ts
파일만 생성하려면 실제 JS 출력이 필요하지 않은 경우noEmit: true
를 사용하거나emitDeclarationOnly: true
를 사용할 수 있습니다.references
: TypeScript의 프로젝트 참조 기능은 대규모 프로젝트를 더 작은tsconfig.json
파일로 분할할 수 있도록 합니다. 이를 통해tsc
는 영향을 받는 프로젝트만 다시 컴파일하여 더 빠른 증분 검사를 수행할 수 있습니다. 이는 모노레포 도구와 긴밀하게 통합됩니다.
// my-monorepo/packages/frontend/tsconfig.json { "extends": "../../tsconfig.base.json", "compilerOptions": { // ... }, "references": [ { "path": "../shared-ui" } // 내부 패키지 참조 ] }
이런 식으로 shared-ui
가 변경되면 frontend
의 tsconfig
는 새 shared-ui
유형에 대해 유형 검사를 받아야 함을 알게 됩니다.
5. 효율적인 패키지 관리자
npm 및 Yarn Classic도 작동하지만, pnpm 및 **Yarn Plug'n'Play (PnP)**는 주로 node_modules
를 최적화하여 모노레포에 상당한 성능 이점을 제공합니다.
-
pnpm: 콘텐츠 주소 지정 가능한 저장소를 사용하여 디스크 공간을 절약하고 설치를 가속화합니다. 여러 프로젝트에서 동일한 버전의 패키지에 종속되는 경우 pnpm은 해당 패키지를 디스크에 한 번만 저장한 다음 개별 프로젝트의
node_modules
폴더에 하드 링크하거나 심볼릭 링크합니다. 이렇게 하면npm install
이 훨씬 빠르고 디스크 공간 사용량이 줄어듭니다.pnpm을 사용하려면 모노레포 루트에서
npm install
을pnpm install
로 바꾸기만 하면 됩니다. -
Yarn PnP: 패키지 이름을 실제 위치에 매핑하는
.pnp.cjs
파일을 생성하여node_modules
"호이스팅 문제"를 해결합니다. 이렇게 하면 많은 경우 물리적인node_modules
디렉터리가 필요하지 않아 설치 속도가 훨씬 빠르고 종속성 해결이 더 안정적입니다.
6. 원격 캐싱
팀의 경우 로컬 캐싱은 좋지만 원격 캐싱은 혁신적입니다. Turborepo와 같은 도구는 캐시 아티팩트를 공유 원격 캐시(예: Vercel의 원격 캐시, AWS S3 또는 사용자 지정 HTTP 서버)에 업로드하고 다운로드할 수 있습니다.
이는 팀원(또는 CI/CD 파이프라인)이 프로젝트를 빌드하면 팀의 다른 사람이나 CI/CD에서 후속 빌드도 해당 캐시의 이점을 얻을 수 있음을 의미하며, 처음부터 시작하더라도 마찬가지입니다. 이를 통해 CI 빌드 시간을 몇 분에서 몇 초로 줄일 수 있습니다.
Turborepo로 원격 캐싱을 구성하려면 일반적으로 환경 변수 또는 캐싱 공급자의 자격 증명이 있는 ~/.config/turborepo/config.json
파일을 설정해야 합니다.
결론
대규모 TypeScript 풀스택 모노레포를 최적화하려면 지능형 도구와 신중한 구성을 결합한 다각적인 접근 방식이 필요합니다. Turborepo 또는 Nx와 같은 모노레포 인식 빌드 도구를 활용하고, TypeScript의 증분 컴파일 및 프로젝트 참조를 활성화하고, pnpm과 같은 효율적인 패키지 관리자를 사용하고, 원격 캐싱을 활용함으로써 개발 팀은 빌드 속도를 크게 개선하고 종속성 관리를 간소화할 수 있습니다. 결과는 더 생산적인 개발 환경, 더 빠른 피드백 루프, 프로젝트와 함께 원활하게 확장되는 모노레포입니다. 이러한 최적화 전략에 투자하면 잠재적인 성능 병목 현상을 개발 워크플로의 강력한 가속기로 전환할 수 있습니다.