내부 패키지

내부 패키지는 소스 코드가 워크스페이스 내부에 있는 라이브러리입니다. 모노레포 내에서 코드를 공유하기 위해 내부 패키지를 빠르게 만들 수 있으며, 나중에 필요한 경우 npm 레지스트리에 게시하도록 선택할 수 있습니다.

내부 패키지는 npm 레지스트리에서 가져오는 외부 패키지와 유사하게 package.json에 설치하여 리포지토리에서 사용됩니다. 하지만 설치할 버전을 표시하는 대신, 패키지 매니저의 워크스페이스 설치 구문을 사용하여 패키지를 참조할 수 있습니다:

./apps/web/package.json
{
  "dependencies": {
    "@repo/ui": "workspace:*"
  }
}

내부 패키지 생성 가이드에서는 컴파일된 패키지 전략을 사용하여 처음부터 내부 패키지를 구축할 수 있습니다. 이 페이지에서는 외부 패키지를 생성하기 위해 npm 레지스트리에 패키지를 게시하는 것을 포함하여 내부 패키지를 생성하는 다른 전략과 그 트레이드오프에 대해 설명합니다.

그런 다음 외부 패키지를 사용할 때와 마찬가지로 패키지를 코드로 가져올 수 있습니다:

./apps/web/app/page.tsx
import { Button } from '@repo/ui'; 
 
export default function Page() {
  return <Button>Submit</Button>;
}

컴파일 전략

라이브러리에서 필요한 것에 따라 세 가지 컴파일 전략 중 하나를 선택할 수 있습니다:

  • Just-in-Time 패키지: 애플리케이션 번들러가 패키지를 사용할 때 컴파일하도록 허용하여 패키지에 대한 최소한의 구성을 생성합니다.
  • 컴파일된 패키지: 적당한 양의 구성으로 tsc와 같은 빌드 도구나 번들러를 사용하여 패키지를 컴파일합니다.
  • 게시 가능한 패키지: npm 레지스트리에 게시하기 위해 패키지를 컴파일하고 준비합니다. 이 접근 방식은 가장 많은 구성이 필요합니다.

Just-in-Time 패키지

Just-in-Time 패키지는 이를 사용하는 애플리케이션에 의해 컴파일됩니다. 이는 TypeScript(또는 컴파일되지 않은 JavaScript) 파일을 직접 사용할 수 있음을 의미하며, 이 페이지의 다른 전략보다 훨씬 적은 구성이 필요합니다.

이 전략은 다음과 같은 경우에 가장 유용합니다:

  • 애플리케이션이 Turbopack, webpack 또는 Vite와 같은 최신 번들러를 사용하여 빌드되는 경우
  • 구성 및 설정 단계를 피하고 싶은 경우
  • 패키지에 대한 캐시를 사용할 수 없는 경우에도 애플리케이션의 빌드 시간에 만족하는 경우

Just-in-Time 패키지의 package.json은 다음과 같이 보일 수 있습니다:

./packages/ui/package.json
{
  "name": "@repo/ui",
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx"
  },
  "scripts": {
    "lint": "eslint . --max-warnings 0",
    "check-types": "tsc --noEmit"
  }
}

package.json에서 주목해야 할 몇 가지 중요한 사항이 있습니다:

  • TypeScript 직접 내보내기: exports 필드는 패키지의 진입점을 표시하며, 이 경우 TypeScript 파일을 직접 참조하고 있습니다. 이는 애플리케이션의 번들러가 빌드 프로세스에서 코드를 사용할 때 컴파일하기 때문에 가능합니다.
  • build 스크립트 없음: 이 패키지는 TypeScript를 내보내기 때문에 패키지를 트랜스파일하기 위한 빌드 단계가 필요하지 않습니다. 즉, 워크스페이스에서 작동하도록 이 패키지에 빌드 도구를 구성할 필요가 없습니다.

제한 사항 및 트레이드오프

  • 소비자가 트랜스파일하는 경우에만 적용 가능: 이 전략은 패키지가 번들러를 사용하거나 TypeScript를 기본적으로 이해하는 도구에서 사용될 때만 사용할 수 있습니다. 소비자의 번들러는 TypeScript 패키지를 JavaScript로 트랜스파일할 책임이 있습니다. 빌드 또는 패키지의 다른 사용이 TypeScript를 소비할 수 없는 경우 컴파일된 패키지 전략으로 이동해야 합니다.
  • TypeScript paths 사용 불가: 소비자에 의해 트랜스파일되는 라이브러리는 compilerOptions.paths 구성을 사용할 수 없습니다. 이는 TypeScript가 소스 코드가 작성된 패키지에서 트랜스파일된다고 가정하기 때문입니다. TypeScript 5.4 이상을 사용하는 경우 Node.js 하위 경로 import 사용을 권장합니다. 방법을 알아보려면 TypeScript 페이지를 참조하세요.
  • Turborepo는 Just-in-Time 패키지의 빌드를 캐시할 수 없음: 패키지에 자체 build 단계가 없기 때문에 Turborepo에서 캐시할 수 없습니다. 구성을 최소화하고 애플리케이션의 빌드 시간이 괜찮다면 이 트레이드오프가 적절할 수 있습니다.
  • 내부 의존성의 오류가 보고됨: TypeScript를 직접 내보낼 때 내부 의존성의 코드에 TypeScript 오류가 있으면 의존하는 패키지의 타입 검사가 실패합니다. 일부 상황에서는 이것이 혼란스럽거나 문제가 될 수 있습니다.

컴파일된 패키지

컴파일된 패키지는 tsc(TypeScript 컴파일러)와 같은 빌드 도구를 사용하여 자체 컴파일을 처리하는 패키지입니다.

./packages/ui/package.json
{
  "name": "@repo/ui",
  "exports": {
    "./button": {
      "types": "./src/button.tsx",
      "default": "./dist/button.js"
    },
    "./card": {
      "types": "./src/card.tsx",
      "default": "./dist/card.js"
    }
  },
  "scripts": {
    "build": "tsc"
  }
}

라이브러리를 컴파일하면 패키지의 진입점으로 사용할 디렉토리(dist, build 등)에 컴파일된 JavaScript 출력이 생성됩니다. 빌드 출력은 태스크의 outputs에 추가되면 Turborepo에서 캐시되어 더 빠른 빌드 시간을 가질 수 있습니다.

제한 사항 및 트레이드오프

  • TypeScript 컴파일러 사용: 대부분의 컴파일된 패키지는 tsc를 사용해야 합니다. 패키지가 번들러를 사용하는 애플리케이션에서 소비될 가능성이 높기 때문에, 애플리케이션의 번들러가 애플리케이션의 최종 번들에 배포하기 위해 라이브러리 패키지를 준비하고 polyfill, downleveling 및 기타 문제를 처리합니다. 번들러는 패키지 출력에 정적 자산을 번들링하는 것과 같이 필요한 특정 사용 사례가 있는 경우에만 사용해야 합니다.
  • 더 많은 구성: 컴파일된 패키지는 빌드 출력을 생성하기 위해 더 깊은 지식과 구성이 필요합니다. 관리하고 이해하기 어려울 수 있는 TypeScript 컴파일러를 위한 많은 구성이 있으며, package.jsonsideEffects와 같이 번들러를 위해 최적화하기 위한 추가 구성이 있습니다. TypeScript 전용 가이드에서 일부 권장 사항을 찾을 수 있습니다.

게시 가능한 패키지

npm 레지스트리에 패키지를 게시하는 것은 이 페이지의 패키징 전략 중 가장 엄격한 요구 사항을 가지고 있습니다. 레지스트리에서 패키지를 다운로드하는 소비자가 패키지를 어떻게 사용할지 전혀 알 수 없기 때문에, 강력한 패키지를 위해 필요한 수많은 구성으로 인해 어려움을 겪을 수 있습니다.

또한 npm 레지스트리에 패키지를 게시하는 프로세스에는 전문적인 지식과 도구가 필요합니다. 버전 관리, 변경 로그 및 게시 프로세스를 관리하기 위해 changesets를 권장합니다.

자세한 가이드는 패키지 게시 가이드를 참조하세요.