NPM Package 만들기

김민기·2025년 9월 25일

자바스크립트로 개발하다 보면 누구나 필수적으로 NPM을 사용하게 된다.
리액트, Next.js 같은 프레임워크/라이브러리 부터 작은 유틸 함수까지 누군가 만들어둔 패키지를 가져다 쓰면 훨씬 편하게 개발할 수 있다.

개발자로 일하면 "패키지는 어떻게 만들고, 어떻게 배포하는거지?"라는 궁금증이 항상 있었지만 일단 급하니까 그냥 가져다 쓰면서 넘어갔고 Webpack/Vite 같은 빌드 도구도 "그냥 쓰면 된다" 수준으로만 알고 있었다

이번에 애플에서 "Liquid Glass" 효과를 적극적으로 밀어주는것 같은데
CSS와 리액트 컴포넌트로 간단하게 구현해서 패키지로 배포해보면 좋을것 같다는 생각이들었다.

Liquid Glass 컴포넌트의 상세한 코드는 별도 게시글로 정리하고, 여기서는 NPM 패키지용 프로젝트 세팅부터 라이브러리 빌드, 배포까지 한 번에 정리해본다.

✅ 프로젝트 구조

my-lib/
  src/
    components/LiquidGlass.tsx
    hooks/useLiquidGlass.ts
    index.ts                # public API (re-exports)
  .gitignore
  package.json
  rollup.config.js
  tsconfig.json
  tsconfig.build.json       # 타입 전용 빌드 설정
  README.md
  LICENSE

💡 타입스크립트를 사용해서 리액트 컴포넌트를 개발할 것이기 때문에 당연히 이에 대한 설치는 완료되어 있어야 한다.

✅ 1. 빌드/번들 간단 정의

NPM에 업로드할 프로젝트이기 때문에 일반적으로 사용하던 React, Next.js 처럼 프로젝트를 만들면 안될것 같다는 생각이 들어서 AI를 활용해서 프로젝트 세팅을 어떻게 해야할지 물어본 결과
빌드도구와 번들러를 선택해야 한다는 것을 알았고 가장 추천하는 rollup을 사용하기로 결정했다.
먼저 빌드 도구란

빌드 도구와 번들러

개발자가 작성한 소스 코드를 실제 배포 가능한 형태로 변환하는 도구를 말한다.
쉽게 말해 브라우저 또는 Node.js에서 그대로 실행하지 못하는 소스(TypeScript, 최신 ES 문법, JSX, 모듈로 나뉜 코드, CSS등)를 실행/배포 가능한 형태로 바꾸는 전체 파이프라인을 "빌드"라고 하고
이걸 가능하게 해주는걸 빌드 도구라고 한다.

무엇을 개발하려고 하는지에 따라 빌드 도구 선택이 달라진다.
가벼운 라이브러리 개발 -> rollup
애를리케이션 개발(리액트 애플리케이션 같은) -> Webpack 또는 Vite

✅ 2. 빌드 도구로 rollup을 선택한 이유?

AI, 검색을 통해 빌드 도구에 대해서 알아본 결과
rollup은 라이브러리 개발에 최적화된 빌드 도구라고 소개된다.
그 이유로
1. Tree Shaking : 사용하지 않는 코드를 자동으로 제거해준다.
2. ES 모듈 우선 지원 :최신 JavaScript 표준 활용
3. 작은 번들 크기 :라이브러리용으로 최적화
4. 다양한 출력 방식 : ESM, CommonJS, UMD 모두 지원

다른 도구와 비교

  • webpack: 애플리케이션 용, 설정이 복잡함
  • Vite: 개발 서버용, 라이브러리 빌드에는 제한적
  • esbuild: 빠르지만 라이브러리 기능 부족

따라서 간단한 라이브러리 개발이기 때문에 Rollup을 사용했다.

✅ 3.필수 패키지 다운로드

필요한 패키지를 다운로드

npm install -D rollup @rollup/plugin-typescript @rollup/plugin-node-resolve
@rollup/plugin-commonjs rollup-plugin-peer-deps-external rollup-plugin-postcss
  • rollup
    핵심 빌드도구로 TypeScript + CSS를 하나의 번들로 묶어서 CJS/ESM 형태로 출력
  • @rollup/plugin-typescript
    .tsx 파일을 JavaScript로 변환
    .d.ts 타입 정의 파일도 자동 생성
  • @rollup/plugin-node-resolve
    node_modules 해석 import React from 'react' 같은 경로 해석
  • @rollup/plugin-commonjs
    CommonJS 모듈 변환 require() 형태의 모듈을 ES6 import로 변환
  • rollup-plugin-peer-deps-external
    React, React-DOM을 번들에 포함하지 않고 외부 의존성으로 처리함으로써 사용자가 이미 설치한 React 버전 사용
  • rollup-plugin-postcss
    .css 파일을 번들에 포함. CSS 압축, 변수 처리 등

✅ package.json 파일

{
  "name": "liquid-glass-mk",
  "version": "1.1.4",
  "type": "module",
  "description": "A React library for beautiful liquid glass effects with customizable hover animations",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "README.md"
  ],
  "scripts": {
    "dev": "vite",
    "build": "rollup -c",
    "preview": "vite preview",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "react",
    "liquid-glass",
    "glassmorphism",
    "ui",
    "effects",
    "hover",
    "animation",
    "backdrop-filter",
    "typescript"
  ],
  "author": "",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/93minki/liquid-glass-mk.git"
  },
  "bugs": {
    "url": "https://github.com/93minki/liquid-glass-mk/issues"
  },
  "homepage": "https://github.com/93minki/liquid-glass-mk#readme",
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },
  "devDependencies": {
    ...
  },
  "dependencies": {
	...
  }
}

기본 정보

  • name: npm 패키지 이름
  • version: 패키지 버전
  • description: 패키지 설명 (npm 검색 시 표시됨)
  • keywords: 검색 키워드 배열 (npm에서 패키지를 찾을 때 사용)

진입점 설정

  • main: CommonJS 방식으로 import 할 때 사용되는 파일(dist/index.cjs.js)
  • module: ES6 모듈 방식으로 import 할 때 사용되는 파일(dist/index.esm.js)
  • types: TypeScript 타입 정의 파일 (dist/index.d.ts)

배포 파일 관리

  • files: npm에 업로드할 파일/폴더 목록 (dist 폴더와 README.md만 포함)
  • type: “module”: ES6 모듈을 기본으로 사용한다는 선언

의존성 관리

  • peerDependencies: 이 패키지를 사용하는 프로젝트에서 반드시 설치해야 하는 패키지(React, React-DOM)
  • devDependencies: 개발 시에만 필요한 패키지들 (빌드 도구, 타입 정의 등)

저장소 정보

  • repository: 소스코드가 있는 Git 저장소 정보
  • bugs: 버그 리포트를 받을 URL
  • homepage: 패키지 홈페이지 URL

배포 스크립트

  • prepublishOnly: npm publish 실행 전에 자동으로 실행되는 스크립트 (빌드 과정)

✅ rollup.config.js

import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import typescript from "@rollup/plugin-typescript";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import postcss from "rollup-plugin-postcss";

export default {
  input: "src/index.ts",
  output: [
    {
      file: "dist/index.cjs.js",
      format: "cjs",
      sourcemap: true,
    },
    {
      file: "dist/index.esm.js",
      format: "esm",
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(),
    resolve(),
    commonjs(),
    typescript({ tsconfig: "./tsconfig.json" }),
    postcss(),
  ],
};

기본 설정

  • input: “src/index.ts” : 빌드 진입점 파일. src/index.ts를 시작점으로 해서 모든 코드를 하나로 합친다.

출력 설정

  • CJS (CommonJS): require() 방식으로 import 하는 구식 방법
  • ESM (ES6 Module): import/export 방식으로 import 하는 현대적 방법

package.json 파일 내용
”main”: “dist/index.cjs.js”,
”module”: “dist/index.esm.js”

번들러가 자동으로 선택함.

  • Webpack: main 필드의 CJS 파일 사용
  • Vite/Rollup: module 필드의 ESM 파일 사용
  • Node.js: type: module 이 있으면 ESM, 없으면 CJS

sourcemap?

  • 원본 Typescript 코드와 빌드된 JavaScript 코드를 연결해주는 파일
  • 개발자 도구에서 디버깅할 때 원본 코드를 볼 수 있게 해줌
  • .js.map 파일로 생성됨

플러그인

  • peerDepsExternal : React 같은 peerDependencies를 번들에 포함시키지 않음 (사용자가 직접 설치해야 함.)
  • resolve : import React from ‘react’ 같은 경로를 실제 파일 위치로 해석
  • commonjs: module.exports 방식을 export 방식으로 변환
  • typescript : .ts 파일을 .js 파일로 컴파일하고 타입 체크
  • postcss : CSS 파일을 처리하고 최적화

✅ 빌드 rollup -c

rollup -c
  • -c = --config
  • 설정 파일(rollup.config.js)을 사용해서 빌드하라는 의미

✅ 빌드 후 생성되는 dist 폴더

JavaScript 파일들

index.cjs.js          # CommonJS 방식 빌드 결과
index.esm.js          # ES6 모듈 방식 빌드 결과
  • index.cjs.js : require() 방식으로 import 할 때 사용
  • index.esm.js : import 방식으로 import 할 떄 사용

Sourcemap 파일들

index.cjs.js.map      # CJS 파일의 소스맵
index.esm.js.map      # ESM 파일의 소스맵
  • .map 파일들: 디버깅 시 원본 TypeScript 코드를 보여주기 위한 매핑 정보

Typescript 타입 정의 파일들 (.d.ts)

index.d.ts                    # 메인 타입 정의
App.d.ts                      # App 컴포넌트 타입
main.d.ts                     # main 파일 타입
components/LiquidGlass.d.ts   # LiquidGlass 컴포넌트 타입
hooks/useLiquidGlass.d.ts     # useLiquidGlass 훅 타입
utils/liquidGlassCursor.d.ts  # 커서 유틸리티 타입
  • .d.ts 파일들: TypeScript 프로젝트에서 타입 정보를 제공
  • 사용자가 import { LiquidGlass } from 'liquid-glass-mk 할 때 자동완성과 타입 체크 지원
  1. 타입 정의만 별도로 제공: JavaScript는 하나로 합쳐지지만, 타입 정의는 모듈별로 분리
  2. 트리 쉐이킹 지원: 사용자가 특정 모듈만 import 할 때 해당 타입만 로드
  3. 개발자 경험 향상: IDE에서 정확한 자동완성과 타입 체크 제공

✅ NPM Publish

로그인

아이디가 없다면 npm 웹페이지에서 만들면 된다.

npm login

패키지 이름 확인

같은 이름의 패키지가 존재하는지 확인하는 용도

npm view liquid-glass-mk

만약 같은 이름의 패키지가 존재한다면 패키지 정보가 나오고 없으면 에러같은게 나오는데
만약 이미 존재하는 패키지 이름을 입력하면 패키지에 대한 내용이 나온다. 존재하는 패키지가 없을 경우 에러 메세지 같은게 나오는데, 에러 메세지가 나와야 내가 사용할 수 있는 패키지 이름이다.

빌드 및 배포

npm run build

npm publish

npm run build를 실행하면 rollup으로 빌드가 실행되고 dist 폴더가 생성됨

그리고 npm publish를 실행하면 알아서 배포된다.

배포에 오랜 시간이 소요될줄 알았는데 빠르게 배포가 완료되었다 (거의 바로)

✅ Version Update

업데이트 내용이 발생하고 package.json에 있는 버전을 직접 수정하는 것보다

# 패치 버전 (1.0.0 -> 1.0.1)
npm version patch

# 마이너 버전 (1.1.0 -> 1.1.0)
npm version minor

# 메이저 버전 (1.0.0 -> 2.0.0)
npm version major

메이저, 마이너, 패치 버전중 어떤것을 올릴지 결정하고 아래 명령어를 실행하면 버전이 업데이트된다.

npm publish

📍 배포 확인하기

게시글을 작성하기 1주일전에 패키지를 배포했는데
어느새 345번이나 다운로드 횟수가 발생했다.(누가 받은거지...)

CSS 틀려서 수정하고, Readme 업데이트 안해서 수정하고
수정 할때마다 패키지 반영을 위해 억지로 패치 버전을 올리면서...
역시 신중하게 테스트를 해보고 문제가 없을 때 배포를 해야하는구나를 느꼈다.. 1.1.4 이지만
사실 1.1.0이지 않을까...

그래도 내가 만든걸 누군가 써보고 있다는게 신기한 기분이고
재미있는 경험이었다

레포지토리에 관심있는 분들이라면 여기에서 확인하시면 됩니다.
https://github.com/93minki/liquid-glass-mk

클론하시고 로컬에서 실행하면 간단한 예시도 확인할 수 있습니다.

0개의 댓글