실무에서 모노레포(monorepo) + 터보레포(turborepo)를 사용하며 배운 점을 공유합니다.
모노레포는 무엇인가?
보통 프로젝트 별로 레포지토리를 따로 파는 관행이 있습니다.
이 경우의 문제는 코드 공유가 어렵다는 것입니다.
또한 독립성과 코드 공유를 공존하게끔 하기 위한 복잡성이 추가됩니다.
- package/shared의 오류 수정을 커밋합니다.
- package/shared을 publish 합니다.
- apps/web에서 package/shared의 오류를 수정하기 위한 범핑 커밋을 만듭니다.
- apps/docs에서 package/shared의 오류를 수정하기 위한 범핑 커밋을 만듭니다.
각 레포의 담당 팀이 나누어져 있을 경우 의사소통 비용을 수반합니다.
모노레포를 사용하면 다음 작업만 하면 됩니다.
- package/shared의 오류 수정을 커밋합니다.
모노레포를 한 줄로 요약하면 다음과 같습니다.
같이 깨지는건 같이 깨지도록 관련된 프로젝트를 하나에 모아둔 레포지토리.
- 모노레포를 공유하는 한 팀
- 각 워크스페이스 별 개발자
- 모든 프로젝트 공유 모듈(shared)
터보레포
물론 모노레포를 사용하게 되면,
여러 프로젝트가 한 프로젝트에 공존하기에, 여러 프로젝트를 통합 관리하는 복잡성이 수반됩니다.
각 프로젝트 별로 독립적으로 빌드를 수행해야 한다고 가정해 봅시다.
명령어를 몇번이나 쳐야 할까요?
물론 해당 작업을 위해 스크립트를 개발할 수도 있습니다만,
Turborepo를 사용하면, 해당 작업들을 간단하게 수행할 수 있습니다.
devops 엔지니어가 파이프라인을 작업하는 것처럼, turbo.json에 파이프라인을 설정하면,
모든 레포지토리, 혹은 개별 레포지토리의 작업을 루트 레포지토리에서 개별적으로 수행할 수 있습니다.
아래와 같이 turbo.json에 파이프라인을 정의하기만 하면, 파이프라인이 생성되며
turbo run build와 같이 실행할 수 있습니다.
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
+ // A workspace's `build` task depends on that workspace's
+ // topological dependencies' and devDependencies'
+ // `build` tasks being completed first. The `^` symbol
+ // indicates an upstream dependency.
"dependsOn": ["^build"]
},
"test": {
+ // A workspace's `test` task depends on that workspace's
+ // own `build` task being completed first.
"dependsOn": ["build"],
"outputs": [],
+ // A workspace's `test` task should only be rerun when
+ // either a `.tsx` or `.ts` file has changed.
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
},
"lint": {
+ // A workspace's `lint` task has no dependencies and
+ // can be run whenever.
"outputs": []
},
"deploy": {
+ // A workspace's `deploy` task depends on the `build`,
+ // `test`, and `lint` tasks of the same workspace
+ // being completed.
"dependsOn": ["build", "test", "lint"],
"outputs": []
}
}
}
터보레포의 이점
이전에 언급한 태스크 오케스트레이션을 제외하고.
터포레포의 이점은 단 두줄로 요약할 수 있습니다.
- 같은 작업을 두번 다시 하지 않는다.
- 최대 멀티태스킹
같은 작업을 두번 다시 하지 않는다
터보레포는 hashing을 사용하여 task의 수행 결과를 캐싱해 둡니다.
해싱 대상을 전역 환경변수, 특정 파일, 특정 포맷 등으로 커스터마이징 할 수 있으며
해시에 매핑되는 타겟 또한 직접 지정하는 것부터,
심지어 지정하지 않을 경우 로그 결과까지 재활용 할 수 있습니다.
lint 결과도 재사용 가능합니다.
최대 멀티태스킹
아래 이미지와 같이 터보레포가 아닌 패키지 매니저의 monorepo 기능을 활용하면 다음과 같이 각 단계가 sequential 하게 수행됩니다.
yarn workspaces run lint
yarn workspaces run test
yarn workspaces run build
다음과 같이 turbo.json을 설정하고
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
// ^build means build must be run in dependencies
// before it can be run in this workspace
"dependsOn": ["^build"]
},
"test": {
"outputs": []
},
"lint": {
"outputs": []
}
}
}
turbo run으로 각 작업을 패러랠하게 수행할 수 있습니다.
turbo run lint test build
터보레포의 이점을 가장 크게 누릴 수 있는 대상은 프론트엔드 플랫폼 엔지니어라 할 수 있습니다.
가장 큰 이득은 빌드 시간을 줄이는 것인데,
보통 CI 러너는 stateless하므로
remote-caching을 이용하여 빌드 아티팩트를 재활용해야 turborepo의 퍼포먼스를 최대한 누릴 수 있을 것입니다.
또한 turborepo를 사용해 프론트엔드 태스크 파이프라인을 쉽게 작성할 수 있습니다.
아쉽게도 빌드 아티팩트를 재사용할 수 없는 로컬 개발자들한테는 린트 시간 절약 정도의 효과가 다일 듯 합니다.
보통 개발 모드는 캐시를 disable하고 사용하니까요.
참고
https://turbo.build/repo/docs/handbook/what-is-a-monorepo
'FrontEnd' 카테고리의 다른 글
npm link를 이용하여 서드파티 npm 패키지 커스터마이징 (0) | 2023.01.12 |
---|---|
[번역] Commander.js와 Typescript를 이용하여 CLI 만들기 (0) | 2023.01.12 |
Vue3 리렌더링 최적화 with ComputedEager (0) | 2023.01.10 |
npm의 checksum과 integrity checksum(EINTEGRITY) 오류 해결 방법 (0) | 2023.01.10 |
Tailwind CSS 간단하게 배워보기 (0) | 2023.01.09 |