Skip to Content
WebTypeScriptTypeScript 실무19. npm 배포 절차

이번 편의 결과물: packages/uinpm pack으로 만든 tarball에 번들과 d.ts가 정확히 포함된 것을 확인하고, 실제 배포 명령과 스코프 이름 충돌을 피하는 방법을 정리합니다. · 다루는 개념: package.json 배포 필드(files, publishConfig), semver 규칙, npm pack·npm publish --dry-run, 모노레포에서 workspace: 프로토콜이 배포에 미치는 영향

18편에서 packages/ui는 tsdown으로 dist에 번들과 선언 파일을 만들고, devExports 덕분에 개발 중에는 소스를, 배포 시에는 dist를 가리키는 exports를 갖추게 됐습니다. 이번 편은 실제로 npm에 올리기 전 마지막 단계, 즉 배포 필드를 확정하고 npm pack으로 결과물을 검증하는 절차를 다룹니다. 이 편에서는 실제로 배포하지 않습니다.

이 편에서 만드는 파일

bookshelf-ts/ └── packages/ └── ui/ └── package.json ~ (version, description, license, files, publishConfig.access 확정)

개념 정리

스코프 패키지명 충돌 주의

@bookshelf/uibookshelf는 이 과목에서 쓰는 예시 스코프입니다. npm의 스코프(@ 뒤 이름)는 실제로 소유한 npm 사용자명이거나, npm에 만들어 둔 조직 이름이어야 합니다. 다른 사람이 이미 쓰고 있는 스코프에는 배포할 수 없습니다. 실제로 배포하려면 다음을 먼저 확인합니다.

  • npm view @bookshelf/ui 실행 결과가 404면 그 이름은 비어 있다는 뜻입니다. 다만 스코프 자체를 내가 소유했는지는 별개로 확인해야 합니다.
  • 자신의 npm 계정 사용자명이 chulsoo92라면 @chulsoo92/ui처럼 개인 스코프를 쓰는 것이 가장 안전합니다.
  • 스코프 없는 이름(bookshelf-ui처럼)은 전역에서 단 하나만 존재할 수 있어 충돌 가능성이 훨씬 큽니다. 실무에서는 거의 항상 스코프를 씁니다.

package.json 배포 필드

필드역할
versionsemver(주.부.수) 규칙. 호환 안 되는 변경은 주, 기능 추가는 부, 버그 수정은 수를 올림
filestarball에 포함할 경로 목록. 지정하지 않은 파일·폴더는 제외됨(package.json, README는 항상 포함)
sideEffectsfalse면 번들러가 안전하게 사용하지 않는 코드를 제거할 수 있음을 알림
publishConfig.access스코프 패키지의 공개 범위. public을 명시하지 않으면 유료 비공개로 시도되어 무료 계정에서 배포가 실패함

0.1.0처럼 주 버전이 0인 동안은 API가 아직 불안정하다는 뜻으로 통용됩니다. 실사용자에게 배포할 준비가 되면 1.0.0으로 올립니다.

workspace 프로토콜과 배포 도구의 관계

packages/uidependencies에는 "@bookshelf/shared-types": "workspace:*"가 들어 있습니다. pnpm publish·pnpm pack은 패키징 직전에 이 문자열을 실제 설치 가능한 버전 범위로 자동 치환합니다. 반면 npm pack·npm publish는 pnpm 워크스페이스를 알지 못해 workspace:*를 그대로 tarball에 남깁니다. 이 상태로 배포하면, 모노레포 밖에서 설치하는 사람은 workspace:*를 해석하지 못해 설치가 실패합니다.

실습

1. packages/ui/package.json 배포 필드 확정

// packages/ui/package.json (18편 이후 상태에 배포 필드 추가) { "name": "@bookshelf/ui", "version": "0.1.0", "description": "bookshelf-ts 재사용 UI 컴포넌트", "license": "MIT", "type": "module", "files": ["dist"], "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": "./src/index.ts" }, "publishConfig": { "access": "public", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } } }, "peerDependencies": { "react": "^19.3.0", "react-dom": "^19.3.0" }, "dependencies": { "@bookshelf/shared-types": "workspace:*" } }

exports, publishConfig.exports는 18편에서 tsdown이 자동으로 채워둔 값을 그대로 둡니다. 이번 편에서 새로 추가하는 것은 description, license, files, sideEffects, publishConfig.access입니다.

2. npm pack으로 1차 검증

cd packages/ui npm pack --dry-run
npm notice npm notice 📦 @bookshelf/ui@0.1.0 npm notice === Tarball Contents === npm notice 1.2kB package.json npm notice 0.4kB dist/index.d.ts npm notice 0.3kB dist/index.d.cts npm notice 2.1kB dist/index.js npm notice 2.3kB dist/index.cjs npm notice === Tarball Details === npm notice name: @bookshelf/ui npm notice version: 0.1.0

files: ["dist"] 덕분에 srctsdown.config.ts는 포함되지 않고, dist 아래 번들과 선언 파일만 들어갑니다. 여기서 package.json을 열어 dependencies를 확인하면 "@bookshelf/shared-types": "workspace:*"가 그대로 남아 있는 것도 보입니다.

3. pnpm pack으로 workspace 프로토콜 치환 확인

pnpm pack --pack-destination /tmp
tar -xOf /tmp/bookshelf-ui-0.1.0.tgz package/package.json | grep shared-types
"@bookshelf/shared-types": "^0.1.0"

같은 파일인데 pnpm pack으로 만들면 workspace:*가 실제 버전 범위(^0.1.0)로 바뀌어 있습니다. 모노레포 안 패키지를 배포할 때는 npm pack·npm publish가 아니라 pnpm pack·pnpm publish를 쓰는 것이 안전한 이유입니다.

4. 최종 드라이런

pnpm publish --access public --dry-run

이 명령은 실제로 레지스트리에 아무것도 올리지 않고, 배포됐을 때와 동일한 검증·패키징 과정만 수행합니다. 출력 마지막의 tarball 목록이 2단계에서 본 것과 같은지 확인합니다.

확인

  • npm pack --dry-run 결과에 dist/index.js, dist/index.cjs, dist/index.d.ts, dist/index.d.cts가 모두 보입니다.
  • src 폴더나 tsdown.config.ts는 tarball 목록에 없습니다.
  • pnpm pack으로 만든 tarball 안 package.json에서 workspace:*가 실제 버전으로 바뀐 것을 확인합니다.
  • pnpm publish --access public --dry-run이 오류 없이 끝납니다.

직접 해보기

  1. npm view react version을 실행해 실제 배포된 패키지 정보를 조회하는 형식을 확인하고, 아직 존재하지 않는 이름(npm view @bookshelf/ui)과 결과가 어떻게 다른지 비교해 보세요.
  2. package.jsonversion0.2.0으로 올려야 하는 변경(기존 컴포넌트의 동작은 그대로 두고 Pagination처럼 새 컴포넌트만 추가하는 경우)과 1.0.0으로 올려야 하는 변경(기존 BookCard의 props 이름을 바꾸는 경우)을 구분해 적어 보세요.

정답 보기

새 컴포넌트 추가는 기존 사용자의 코드를 깨뜨리지 않는 기능 추가이므로 부 버전을 올립니다(0.1.00.2.0). BookCard의 props 이름을 바꾸는 것은 이미 이 컴포넌트를 쓰던 코드가 깨지는 호환성 파괴 변경이므로 주 버전을 올립니다. 아직 0.x인 상태에서는 관례상 부 버전을 올리는 것으로 파괴적 변경을 표시하기도 하지만, 1.0.0 이후에는 반드시 주 버전을 올려야 합니다.

자주 하는 실수

증상원인고치는 법
배포 시 비공개 패키지 등록 요금 안내와 함께 실패스코프 패키지인데 --access public을 지정하지 않음publishConfig.accesspublic으로 명시하거나 배포 명령에 --access public 추가
모노레포 밖에서 설치한 사람이 의존성 설치 실패를 겪음npm publish로 직접 배포해 workspace:*가 실제 버전으로 안 바뀜pnpm publish로 배포해 workspace 프로토콜을 자동 치환
tarball에 src, 테스트 파일까지 포함됨files 필드를 지정하지 않음files: ["dist"]처럼 배포할 폴더만 명시
이미 있는 이름이라 배포가 거부됨스코프를 내가 소유하지 않은 이름으로 정함개인 npm 사용자명 스코프(@본인아이디/이름)로 바꾸거나 조직을 새로 만듦

확인 문제

문제 14지선다
스코프 패키지를 배포할 때 --access public이 필요한 이유는
문제 24지선다
npm pack이 아니라 pnpm pack을 써야 하는 상황은
문제 34지선다
package.json의 files 필드에 dist만 지정하는 이유는
문제 44지선다
기존 BookCard의 props 이름을 바꾸는 변경에 맞는 버전 규칙은

참고 자료

Last updated on