Internal Package 생성하기

Internal Package는 workspace의 빌딩 블록으로, repo 전체에서 코드와 기능을 공유할 수 있는 강력한 방법을 제공합니다. Turborepo는 package.json의 종속성을 사용하여 Internal Package 간의 관계를 자동으로 이해하고, 내부적으로 Package Graph를 생성하여 리포지토리의 워크플로우를 최적화합니다.

Turborepo의 Package Graph 시각적 표현

패키지의 구조 섹션의 지침과 Compiled Package 패턴을 사용하여 repo에서 수학 유틸리티를 공유하기 위한 첫 번째 Internal Package를 생성해 보겠습니다. 아래 단계에서는 create-turbo를 사용하여 새 리포지토리를 생성했거나 유사하게 구조화된 리포지토리를 사용한다고 가정합니다.

빈 디렉토리 생성하기

패키지를 넣을 디렉토리가 필요합니다. ./packages/math에 하나를 생성해 보겠습니다.

package.json
turbo.json

package.json 추가하기

다음으로, 패키지에 대한 package.json을 생성합니다. 이 파일을 추가하면 Internal Package의 두 가지 요구 사항을 충족하여 Turborepo 및 Workspace의 나머지 부분에서 발견 가능하게 됩니다.

package.jsonname 필드는 workspace 전체에서 패키지를 가져오는 방법을 결정합니다. 여기서 선택한 이름(예: @repo/math)은 다른 패키지가 가져오는 방식과 정확히 동일합니다(예: import {add} from '@repo/math/add').

./packages/math/package.json
{
  "name": "@repo/math",
  "type": "module",
  "scripts": {
    "dev": "tsc --watch",
    "build": "tsc"
  },
  "exports": {
    "./add": {
      "types": "./src/add.ts",
      "default": "./dist/add.js"
    },
    "./subtract": {
      "types": "./src/subtract.ts",
      "default": "./dist/subtract.js"
    }
  },
  "devDependencies": {
    "@repo/typescript-config": "workspace:*",
    "typescript": "latest"
  }
}

package.json을 부분별로 분석해 보겠습니다:

  • name: 패키지 검색 가능성을 위한 가장 중요한 필드입니다. @repo/math 값은 workspace 전체의 import 문에서 사용되는 정확한 식별자가 됩니다. 이 이름을 변경하면 모든 import 문을 그에 맞게 업데이트해야 합니다.
  • scripts: devbuild 스크립트는 TypeScript 컴파일러를 사용하여 패키지를 컴파일합니다. dev 스크립트는 소스 코드의 변경 사항을 감시하고 자동으로 패키지를 재컴파일합니다.
  • devDependencies: typescript@repo/typescript-config@repo/math 패키지에서 해당 패키지를 사용할 수 있도록 devDependencies로 선언되어 있습니다. 실제 패키지에서는 더 많은 devDependenciesdependencies가 필요할 수 있지만, 지금은 간단하게 유지하겠습니다.
  • exports: 패키지가 다른 패키지에서 사용될 수 있도록 여러 진입점을 정의합니다 (import { add } from '@repo/math/add').

주목할 점은, 이 package.json이 Internal Package인 @repo/typescript-config를 종속성으로 선언한다는 것입니다. Turborepo는 @repo/math@repo/typescript-config의 종속 패키지로 인식하여 작업 순서를 정합니다.

tsconfig.json 추가하기

패키지의 루트tsconfig.json 파일을 추가하여 이 패키지의 TypeScript 구성을 지정합니다. TypeScript에는 extends가 있어 리포지토리 전체에서 기본 구성을 사용하고 필요에 따라 다른 옵션으로 덮어쓸 수 있습니다.

./packages/math/tsconfig.json
{
  "extends": "@repo/typescript-config/base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

여기서 네 가지 중요한 작업을 수행했습니다:

  • ./packages/typescript-config에 있는 @repo/typescript-config/base.json 구성에는 필요한 모든 구성이 포함되어 있으므로 이를 확장합니다.
  • compilerOptionsoutDir는 TypeScript에 컴파일된 출력을 어디에 배치할지 알려줍니다. 이는 package.jsonexports에 지정된 디렉토리와 일치합니다.
  • compilerOptionsrootDiroutDir의 출력이 src 디렉토리와 동일한 구조를 사용하도록 보장합니다.
  • includeexclude 키는 TypeScript 사양에 따라 기본 구성에서 상속되지 않으므로 여기에 포함했습니다.

TypeScript 구성에 대해 배울 것이 많지만, 지금은 시작하기에 좋은 지점입니다. 더 자세히 알아보려면 공식 TypeScript 문서 또는 TypeScript 가이드를 참조하세요.

소스 코드가 포함된 src 디렉토리 추가하기

이제 패키지의 코드를 작성할 수 있습니다. src 디렉토리 내에 두 개의 파일을 생성합니다:

./packages/math/src/add.ts
export const add = (a: number, b: number) => a + b;

이 파일들은 잠시 후 turbo build를 실행할 때 tsc에 의해 생성될 출력에 매핑됩니다.

애플리케이션에 패키지 추가하기

이제 애플리케이션에서 새 패키지를 사용할 준비가 되었습니다. web 애플리케이션에 추가해 보겠습니다.

apps/web/package.json
  "dependencies": {
+   "@repo/math": "workspace:*",
    "next": "latest",
    "react": "latest",
    "react-dom": "latest"
  },

리포지토리의 종속성을 변경했습니다. 패키지 매니저의 설치 명령을 실행하여 lockfile을 업데이트하세요.

이제 @repo/mathweb 애플리케이션에서 사용할 수 있으며, 코드에서 사용할 수 있습니다:

apps/web/src/app/page.tsx
import { add } from '@repo/math/add';
 
function Page() {
  return <div>{add(1, 2)}</div>;
}
 
export default Page;

turbo.json 수정하기

turbo.jsonbuild 작업에 대한 outputs에 새 @repo/math 라이브러리의 아티팩트를 추가합니다. 이렇게 하면 빌드 출력이 Turborepo에 의해 캐시되어 빌드를 시작할 때 즉시 복원할 수 있습니다.

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    }
  }
}

turbo build 실행하기

turbo를 전역으로 설치한 경우, Workspace의 루트에서 터미널에서 turbo build를 실행합니다. 패키지 매니저로 package.jsonbuild 스크립트를 실행할 수도 있으며, 이는 turbo run build를 사용합니다.

@repo/math 패키지는 web 애플리케이션이 빌드되기 전에 빌드되므로 ./packages/math/dist의 런타임 코드가 web 애플리케이션이 번들링할 때 사용 가능합니다.

turbo build를 다시 실행하면 web 애플리케이션이 밀리초 단위로 재빌드되는 것을 볼 수 있습니다. 이에 대해서는 캐싱 가이드에서 자세히 설명하겠습니다.

Internal Package 모범 사례

패키지당 하나의 "목적"

Internal Package를 생성할 때는 단일 "목적"을 가진 패키지를 생성하는 것이 권장됩니다. 이것은 엄격한 과학이나 규칙은 아니지만, 리포지토리, 규모, 조직, 팀의 요구 사항 등에 따라 달라지는 모범 사례입니다. 이 전략에는 여러 가지 장점이 있습니다:

  • 이해하기 쉬움: 리포지토리가 확장됨에 따라 리포지토리에서 작업하는 개발자가 필요한 코드를 더 쉽게 찾을 수 있습니다.
  • 패키지당 종속성 감소: 패키지당 더 적은 종속성을 사용하면 Turborepo가 패키지 그래프의 종속성을 더 효과적으로 정리할 수 있습니다.

몇 가지 예시는 다음과 같습니다:

  • @repo/ui: 모든 공유 UI 컴포넌트를 포함하는 패키지
  • @repo/tool-specific-config: 특정 도구의 구성을 관리하는 패키지
  • @repo/graphs: 그래픽 데이터를 생성하고 조작하기 위한 도메인별 라이브러리

Application Package는 공유 코드를 포함하지 않습니다

Application Package를 생성할 때는 해당 패키지에 공유 코드를 넣지 않는 것이 좋습니다. 대신 공유 코드를 위한 별도의 패키지를 생성하고 Application Package가 해당 패키지에 의존하도록 해야 합니다.

또한 Application Package는 다른 패키지에 설치되도록 의도되지 않았습니다. 대신 Package Graph의 진입점으로 생각해야 합니다.

Good to know: 

이 규칙에는 드문 예외가 있습니다.

다음 단계

새 Internal Package가 준비되면 작업 구성을 시작할 수 있습니다.