λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ—‚ WIL/πŸ“ Etc

빈 λ””λ ‰ν„°λ¦¬μ—μ„œ μ‹œμž‘ν•΄ ν”„λ‘œλ•μ…˜ NPM νŒ¨ν‚€μ§€λ₯Ό μ„€μ •ν•˜λŠ” 방법

by nalong 2024. 10. 20.

https://www.totaltypescript.com/how-to-create-an-npm-package#39-set-up-a-ci-script

ν•΄λ‹Ή 글을 λ°”νƒ•μœΌλ‘œ NPM νŒ¨ν‚€μ§€ μ„€μ • 과정을 κ°„λ‹¨νžˆ μš”μ•½ν•΄ λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 

https://github.com/mattpocock/tt-package-demo

1. Git μ„€μ •

νŒ¨ν‚€μ§€λ₯Ό 버전 κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄ Git을 μ‚¬μš©ν•©λ‹ˆλ‹€.

Git μ΄ˆκΈ°ν™”

git init

.gitignore 파일 생성

node_modules

컀밋 + 리포지토리 생성 + ν‘Έμ‹œ

gh repo create tt-package-demo --source=. --public
git push --set-upstream origin main

2. package.json μ„€μ •

package.json νŒŒμΌμ€ νŒ¨ν‚€μ§€μ˜ 메타데이터λ₯Ό μ •μ˜ν•˜λŠ” 파일둜, νŒ¨ν‚€μ§€ 이름, 버전, λΌμ΄μ„ μŠ€, README λ“±μ˜ 정보λ₯Ό ν¬ν•¨λ©λ‹ˆλ‹€.

{
  "name": "tt-package-demo",
  "version": "1.0.0",
  "description": "A demo package for Total TypeScript",
  "keywords": ["demo", "typescript"],
  "license": "MIT",
  "author": "Matt Pocock <team@totaltypescript.com>",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/mattpocock/tt-package-demo.git"
  },
  "files": ["dist"],
  "type": "module"
}
  • name: νŒ¨ν‚€μ§€μ˜ κ³ μœ ν•œ μ΄λ¦„μž…λ‹ˆλ‹€. NPM λ ˆμ§€μŠ€νŠΈλ¦¬μ—μ„œ μœ μΌν•΄μ•Ό ν•˜λ©°, 쑰직 λ²”μœ„λ‘œ νŒ¨ν‚€μ§€λ₯Ό μƒμ„±ν•˜λ €λ©΄ @your-org/package-nameκ³Ό 같은 ν˜•νƒœλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • version: νŒ¨ν‚€μ§€μ˜ λ²„μ „μž…λ‹ˆλ‹€. SemVer κ·œμΉ™μ— 따라 κ΄€λ¦¬ν•˜λ©°, 배포할 λ•Œλ§ˆλ‹€ 적절히 μ¦κ°€μ‹œμΌœμ•Ό ν•©λ‹ˆλ‹€.
  • files: NPM에 배포할 λ•Œ 포함될 파일 λͺ©λ‘μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” λΉŒλ“œλœ μ½”λ“œκ°€ 포함될 dist ν΄λ”λ§Œ μ§€μ •ν–ˆμŠ΅λ‹ˆλ‹€.
  • type: ECMAScript λͺ¨λ“ˆμ„ μ‚¬μš©ν•˜λ €λ©΄ "module"둜 μ„€μ •ν•©λ‹ˆλ‹€.

λΌμ΄μ„ μŠ€ μ„€μ •

{
  "license": "MIT"
}
MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
...

NPM νŒ¨ν‚€μ§€ λ ˆμ§€μŠ€νŠΈλ¦¬μ—μ„œ νŒ¨ν‚€μ§€μ˜ μ„€λͺ…을 λ³΄μ—¬μ£ΌλŠ” README.md νŒŒμΌλ„ 포함해야 ν•©λ‹ˆλ‹€.

# tt-package-demo

A demo package for Total TypeScript.

3. TypeScript μ„€μ •

TypeScriptλ₯Ό μ„€μΉ˜ν•˜κ³  tsconfig.json νŒŒμΌμ„ μ„€μ •ν•©λ‹ˆλ‹€.

npm install --save-dev typescript

tsconfig.json μž‘μ„± μ‹œ μ΅œμ‹  ES λͺ¨λ“ˆμ„ νƒ€κΉƒμœΌλ‘œ ν•˜λ©°, λΉŒλ“œ κ²°κ³Όλ₯Ό dist 폴더에 좜λ ₯ν•˜λ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

{
  "compilerOptions": {
    "target": "es2022",
    "module": "NodeNext",
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src",
    "strict": true
  }
}

μ†ŒμŠ€ 파일 μž‘μ„± 및 λΉŒλ“œ 슀크립트 μ„€μ •ν•©λ‹ˆλ‹€.

export const add = (a: number, b: number) => a + b;

 

{
  "scripts": {
    "build": "tsc"
  }
}

.gitignore νŒŒμΌμ— dist 폴더λ₯Ό μΆ”κ°€ν•˜μ—¬, 컴파일된 μ½”λ“œκ°€ Git 리포지토리에 ν¬ν•¨λ˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€.

package.json에 CI 슀크립트λ₯Ό μΆ”κ°€ν•˜μ—¬, CIμ—μ„œ ν•„μš”ν•œ μž‘μ—…μ„ λΉ λ₯΄κ²Œ μ‹€ν–‰ν•  수 μžˆλ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "ci": "npm run build"
  }
}

4. Prettier μ„€μ •

PrettierλŠ” μ½”λ“œλ₯Ό μΌκ΄€λœ μŠ€νƒ€μΌλ‘œ μžλ™ ν¬λ§·νŒ…ν•΄μ£ΌλŠ” λ„κ΅¬μž…λ‹ˆλ‹€.

npm install --save-dev prettier

.prettierrc νŒŒμΌμ„ μž‘μ„±ν•˜μ—¬ κΈ°λ³Έ 섀정을 μ •μ˜ν•©λ‹ˆλ‹€.

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 80,
  "tabWidth": 2
}

package.json에 Prettier ν¬λ§·νŒ… 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "format": "prettier --write ."
  }
}

μ•„λž˜ λͺ…λ Ήμ–΄λ‘œ ν”„λ‘œμ νŠΈ λ‚΄ λͺ¨λ“  νŒŒμΌμ„ ν¬λ§·νŒ… ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

npm run format

package.json에 포맷이 μ œλŒ€λ‘œ μ μš©λ˜μ—ˆλŠ”μ§€ 확인할 수 μžˆλŠ” 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "check-format": "prettier --check ."
  }
}

ci μŠ€ν¬λ¦½νŠΈμ— 포맷 체크λ₯Ό μΆ”κ°€ν•˜μ—¬ CI κ³Όμ •μ—μ„œλ„ μ½”λ“œ μŠ€νƒ€μΌμ΄ μΌκ΄€λ˜κ²Œ μœ μ§€λ˜λ„λ‘ ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "ci": "npm run build && npm run check-format"
  }
}

5. @arethetypeswrong/cli둜 exports 검사

@arethetypeswrong/cliλ₯Ό μ„€μΉ˜ν•˜κ³ , check-exports 슀크립트λ₯Ό μ„€μ •ν•˜κ³  μ‹€ν–‰ν•˜λ©°, main ν•„λ“œλ₯Ό μΆ”κ°€ν•œ ν›„ λ‹€μ‹œ check-exportsλ₯Ό μ‹€ν–‰ν•˜κ³ , λ§ˆμ§€λ§‰μœΌλ‘œ ci μŠ€ν¬λ¦½νŠΈμ— 이λ₯Ό μΆ”κ°€ν•œ ν›„ μ‹€ν–‰ν•©λ‹ˆλ‹€.

( ++ @arethetypeswrong/cliλŠ” νŒ¨ν‚€μ§€μ˜ exportsκ°€ μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 도ꡬ이며, μ΄λŸ¬ν•œ 섀정은 μ‹€μˆ˜ν•˜κΈ° μ‰¬μš°λ©°, 잘λͺ»λœ 경우 νŒ¨ν‚€μ§€λ₯Ό μ‚¬μš©ν•˜λŠ” μ‚¬λžŒλ“€μ—κ²Œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€κ³  함!! )

 

일단 μ„€μΉ˜ ν›„ package.json 에 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

npm install --save-dev @arethetypeswrong/cli
{
  "scripts": {
    "check-exports": "attw --pack ."
  }
}

check-exports 슀크립트 μ‹€ν–‰ν•©λ‹ˆλ‹€.

npm run check-exports
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚ "tt-package-demo"    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node10            β”‚ πŸ’€ Resolution failed β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from CJS) β”‚ πŸ’€ Resolution failed β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from ESM) β”‚ πŸ’€ Resolution failed β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ bundler           β”‚ πŸ’€ Resolution failed β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

μœ„ κ²°κ³ΌλŠ” ν˜„μž¬ νŒ¨ν‚€μ§€κ°€ μ–΄λ–€ λ²„μ „μ˜ Nodeλ‚˜ λ²ˆλ“€λŸ¬μ—μ„œλ„ μ‚¬μš©λ  수 μ—†λ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. 

λ‹€μ‹œ μˆ˜μ •ν•˜μ—¬ package.json에 main ν•„λ“œλ₯Ό μΆ”κ°€ν•˜μ—¬, Node.jsκ°€ νŒ¨ν‚€μ§€μ˜ μ§„μž…μ μ„ 찾을 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

{
  "main": "dist/index.js"
}

이제 λ‹€μ‹œ exports 섀정을 ν™•μΈν•©λ‹ˆλ‹€.

npm run check-exports
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚ "tt-package-demo"            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node10            β”‚ 🟒                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from CJS) β”‚ ⚠️ ESM (dynamic import only) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from ESM) β”‚ 🟒 (ESM)                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ bundler           β”‚ 🟒                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

이 λ©”μ‹œμ§€λŠ” νŒ¨ν‚€μ§€κ°€ ESM(ECMAScript Module) ν˜•μ‹μ—μ„œ ν˜Έν™˜λœλ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. CJS(CommonJS)λ₯Ό μ‚¬μš©ν•˜λŠ” μ‚¬λžŒλ“€μ€ 동적 import둜 νŒ¨ν‚€μ§€λ₯Ό λΆˆλŸ¬μ™€μ•Ό ν•©λ‹ˆλ‹€.

CJS(CommonJS)λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠμœΌλ €λ©΄ check-exports 슀크립트λ₯Ό λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•©λ‹ˆλ‹€. 

{
  "scripts": {
    "check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm"
  }
}
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚ "tt-package-demo" β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node10            β”‚ 🟒                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from CJS) β”‚ 🟒 (ESM)          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from ESM) β”‚ 🟒 (ESM)          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ bundler           β”‚ 🟒                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ci μŠ€ν¬λ¦½νŠΈμ— check-exportsλ₯Ό μΆ”κ°€ν•˜μ—¬, 지속적 톡합(CI) ν”„λ‘œμ„ΈμŠ€μ—μ„œ 이 검사λ₯Ό μžλ™μœΌλ‘œ μ‹€ν–‰ν•˜λ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "ci": "npm run build && npm run check-format && npm run check-exports"
  }
}

이 μŠ€ν¬λ¦½νŠΈλŠ” νŒ¨ν‚€μ§€ λΉŒλ“œ, μ½”λ“œ 포맷 확인, exports 검사λ₯Ό 순차적으둜 μ‹€ν–‰ν•˜κ²Œ λ©λ‹ˆλ‹€.

6. tsup을 μ‚¬μš©ν•΄ CJS와 ESM λ™μ‹œ 배포

tsup은 ES λͺ¨λ“ˆκ³Ό CommonJSλ₯Ό λ™μ‹œμ— 배포할 수 있게 ν•΄μ£ΌλŠ” λ„κ΅¬μž…λ‹ˆλ‹€. ν•΄λ‹Ή κ°€μ΄λ“œλŠ” 기본적으둜 ESM만 λ°°ν¬ν•˜λ„λ‘ μ„€μ •λ˜μ–΄ μžˆμ§€λ§Œ, CJS도 μ§€μ›ν•˜λ €λ©΄ tsup을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

일단 μ„€μΉ˜ν•˜κ³ , μ„€μ • νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€.

npm install --save-dev tsup
import { defineConfig } from "tsup";

export default defineConfig({
  entryPoints: ["src/index.ts"],
  // esm λͺ¨λ“ˆκ³Ό commonJS λΉŒλ“œλ˜λ„λ‘ μ„€μ •
  format: ["cjs", "esm"],
  dts: true,
  outDir: "dist",
  clean: true,
});

package.json의 λΉŒλ“œ 슀크립트λ₯Ό tsc λŒ€μ‹  tsup을 μ‚¬μš©ν•˜λ„λ‘ λ³€κ²½ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "build": "tsup"
  }
}

package.json 에 exports ν•„λ“œλ₯Ό μΆ”κ°€ν•˜μ—¬ CJS와 ESM λͺ¨λ“ˆμ„ 각각 μ§€μ›ν•©λ‹ˆλ‹€.

{
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "import": "./dist/index.js",
      "default": "./dist/index.cjs"
    }
  }
}

exports 검사λ₯Ό λ‹€μ‹œ μ‹€ν–‰ν•˜μ—¬ λͺ¨λ“  것이 μ •μƒμ μœΌλ‘œ μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

npm run check-exports

tsup은 TypeScript 였λ₯˜λ₯Ό μ²΄ν¬ν•˜μ§€ μ•Šκ³  JavaScript둜만 λ³€ν™˜ν•©λ‹ˆλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ TypeScriptλ₯Ό λ¦°ν„°λ‘œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

tsconfig.json 에 noEmit ν•„λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  "compilerOptions": {
    "noEmit": true
  }
}

tsconfig.jsonμ—μ„œ tsup이 μ§€μ›ν•˜λŠ” κΈ°λŠ₯듀인 outDir, rootDir, sourceMap, declaration, declarationMap ν•„λ“œλ₯Ό μ œκ±°ν•©λ‹ˆλ‹€.

tsconfig.jsonμ—μ„œ module을 Preserve둜 λ³€κ²½ν•˜μ—¬ .js ν™•μž₯자 없이도 μ‚¬μš©ν•  수 있게 ν•©λ‹ˆλ‹€.

{
  "compilerOptions": {
    "module": "Preserve"
  }
}

package.json에 TypeScriptλ₯Ό λ¦°ν„°λ‘œ μ‚¬μš©ν•˜λŠ” 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "lint": "tsc"
  }
}

ci μŠ€ν¬λ¦½νŠΈμ— lintλ₯Ό μΆ”κ°€ν•˜μ—¬ CI κ³Όμ •μ—μ„œ TypeScript 였λ₯˜κ°€ μžˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

{
  "scripts": {
    "ci": "npm run build && npm run check-format && npm run check-exports && npm run lint"
  }
}

7. Vitest둜 ν…ŒμŠ€νŠΈ μ„€μ •

VitestλŠ” ESMκ³Ό TypeScriptλ₯Ό μ§€μ›ν•˜λŠ” ν…ŒμŠ€νŠΈ λ„κ΅¬μž…λ‹ˆλ‹€.

μ„€μΉ˜ + ν…ŒμŠ€νŠΈ 파일 μž‘μ„± + ν…ŒμŠ€νŠΈ 슀크립트 μΆ”κ°€

npm install --save-dev vitest
import { add } from "./utils.js";
import { test, expect } from "vitest";

test("add", () => {
  expect(add(1, 2)).toBe(3);
});
{
  "scripts": {
    "test": "vitest run"
  }
}

개발 도쀑 ν…ŒμŠ€νŠΈλ₯Ό μžλ™μœΌλ‘œ μ‹€ν–‰ν•˜λ„λ‘ dev 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "dev": "vitest"
  }
}

ci μŠ€ν¬λ¦½νŠΈμ— testλ₯Ό μΆ”κ°€ν•˜μ—¬, CI κ³Όμ •μ—μ„œ ν…ŒμŠ€νŠΈκ°€ 항상 μ‹€ν–‰λ˜λ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€.

{
  "scripts": {
    "ci": "npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test"
  }
}

8. GitHub Actions둜 CI μ„€μ •

GitHub Actionsλ₯Ό μ‚¬μš©ν•˜λ©΄ μ½”λ“œλ₯Ό ν‘Έμ‹œν•  λ•Œλ§ˆλ‹€ μžλ™μœΌλ‘œ CI(Continuous Integration) ν”„λ‘œμ„ΈμŠ€λ₯Ό μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

.github/workflows/ci.yml νŒŒμΌμ„ μž‘μ„±ν•˜μ—¬ κΈ°λ³Έ CI ν”„λ‘œμ„ΈμŠ€λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.

name: CI

on:
  pull_request:
  push:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm install

      - name: Run CI
        run: npm run ci
  • name: μ›Œν¬ν”Œλ‘œμš°μ˜ μ΄λ¦„μž…λ‹ˆλ‹€.
  • on: μ›Œν¬ν”Œλ‘œμš°κ°€ 싀행될 쑰건을 μ •μ˜ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” ν’€ λ¦¬ν€˜μŠ€νŠΈ 및 main 브랜치둜의 ν‘Έμ‹œ μ‹œ μ‹€ν–‰λ©λ‹ˆλ‹€.
  • concurrency: λ™μΌν•œ μ›Œν¬ν”Œλ‘œμš°κ°€ 쀑볡 μ‹€ν–‰λ˜λŠ” 것을 λ°©μ§€ν•©λ‹ˆλ‹€.
  • jobs: μ‹€ν–‰ν•  μž‘μ—…λ“€μ„ μ •μ˜ν•©λ‹ˆλ‹€.(ci μž‘μ—…λ§Œ μžˆμŠ΅λ‹ˆλ‹€.)
    • actions/checkout@v4: μ½”λ“œλ² μ΄μŠ€λ₯Ό μ²΄ν¬μ•„μ›ƒν•©λ‹ˆλ‹€.
    • actions/setup-node@v4: Node.js ν™˜κ²½μ„ μ„€μ •ν•©λ‹ˆλ‹€.
    • npm install: ν”„λ‘œμ νŠΈμ˜ μ˜μ‘΄μ„±μ„ μ„€μΉ˜ν•©λ‹ˆλ‹€.
    • npm run ci: CI 슀크립트λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.

μ½”λ“œλ₯Ό GitHub에 ν‘Έμ‹œν•œ ν›„, GitHub λ¦¬ν¬μ§€ν† λ¦¬μ˜ Actions νƒ­μ—μ„œ μ›Œν¬ν”Œλ‘œμš°κ°€ μ„±κ³΅μ μœΌλ‘œ μ‹€ν–‰λ˜μ—ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

9. Changesets둜 버전 관리 및 배포

ChangesetsλŠ” νŒ¨ν‚€μ§€μ˜ 버전 관리 및 배포λ₯Ό μžλ™ν™”ν•˜λŠ” λ„κ΅¬μž…λ‹ˆλ‹€. λ³€κ²½ 사항을 μ‰½κ²Œ κ·Έλ£Ήν™”ν•˜μ—¬, NPM에 배포할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ„€μΉ˜ + μ΄ˆκΈ°ν™” 및 κΈ°λ³Έ μ„€μ • 파일 μƒμ„±ν•©λ‹ˆλ‹€.

npm install --save-dev @changesets/cli
npx changeset init

.changeset/config.json νŒŒμΌμ„ μ—΄κ³ , access ν•„λ“œλ₯Ό public으둜 μ„€μ •ν•©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ ν•˜λ©΄ Changesetsκ°€ NPM에 νŒ¨ν‚€μ§€λ₯Ό 배포할 수 μžˆμŠ΅λ‹ˆλ‹€.

{
  "access": "public"
}

.changeset/config.json νŒŒμΌμ—μ„œ commit ν•„λ“œλ₯Ό true둜 μ„€μ •ν•©λ‹ˆλ‹€. 

( -> 버전 관리 ν›„ λ³€κ²½ 사항이 μžλ™μœΌλ‘œ μ»€λ°‹λ©λ‹ˆλ‹€.)

{
  "commit": true
}

 

package.json에 local-release 슀크립트(CI 과정을 μ‹€ν–‰ν•˜κ³ , νŒ¨ν‚€μ§€λ₯Ό NPM에 배포)λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€. 

ν•΄λ‹Ή μŠ€ν¬λ¦½νŠΈλŠ” λ‘œμ»¬μ—μ„œ μƒˆλ‘œμš΄ 버전을 배포할 λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

{
  "scripts": {
    "local-release": "changeset version && changeset publish"
  }
}

package.json에 prepublishOnly 슀크립트λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€. 이 μŠ€ν¬λ¦½νŠΈλŠ” NPM에 λ°°ν¬ν•˜κΈ° 전에 CI 과정을 μžλ™μœΌλ‘œ μ‹€ν–‰ν•©λ‹ˆλ‹€.

이 섀정은 μ‚¬μš©μžκ°€ μ‹€μˆ˜λ‘œ npm publish λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν–ˆμ„ λ•Œλ„, 미리 CIλ₯Ό 톡해 검사λ₯Ό μˆ˜ν–‰ν•˜μ—¬ 였λ₯˜λ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 

{
  "scripts": {
    "prepublishOnly": "npm run ci"
  }
}

 

Changeset을 μΆ”κ°€ν•˜μ—¬ λ³€κ²½ 사항을 κΈ°λ‘ν•©λ‹ˆλ‹€.

npx changeset

이 λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•˜λ©΄ μΈν„°λž™ν‹°λΈŒ ν”„λ‘¬ν”„νŠΈκ°€ 열리고, μ—¬κΈ°μ„œ λ³€κ²½ 사항을 κ·Έλ£Ήν™”ν•˜κ³  ν•΄λ‹Ή 릴리슀λ₯Ό 패치 λ²„μ „μœΌλ‘œ μ§€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ "Initial release"λΌλŠ” μ„€λͺ…을 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. -> 이 κ³Όμ •μ—μ„œ .changeset 폴더에 μƒˆλ‘œμš΄ Changeset 파일이 μƒμ„±λ©λ‹ˆλ‹€.

 

λ³€κ²½ 사항을 μ»€λ°‹ν•˜μ—¬ μ €μž₯μ†Œμ— λ°˜μ˜ν•©λ‹ˆλ‹€.

git add .
git commit -m "Prepare for initial release"

NPM에 νŒ¨ν‚€μ§€λ₯Ό λ°°ν¬ν•©λ‹ˆλ‹€.

npm run local-release

NPM λ ˆμ§€μŠ€νŠΈλ¦¬μ—μ„œ νŒ¨ν‚€μ§€κ°€ μ„±κ³΅μ μœΌλ‘œ λ°°ν¬λ˜μ—ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

https://npmjs.com/package/<your-package-name>