Skip to main content

Command Palette

Search for a command to run...

타입스크립트 라이브러리 프로젝트 진행하면서 겪은 시행착오

pnpm, tsup으로 라이브러리 프로젝트를 세팅하면서 겪은 시행착오를 정리했습니다.

Updated
3 min read

계기

리액트 프론트엔드 프로젝트에 참여해서 캘린더 컴포넌트를 작성했던 적이 있었다.

외부 라이브러리로 제공되는 캘린더 컴포넌트는 디자인을 변경하거나 라이브러리에 없는 필요한 기능을 추가하는데 시간이 많이 소요됐던 적이 있었다. 디자인에 관계없이 캘린더 기능을 제공해주는 리액트 훅이 있었으면 좋겠다고 생각했다.

깃허브에서 찾아볼 수 있는 라이브러리 저장소는 여러 패키지로 이뤄진 단일 저장소로 구성되어있는 경우가 많았다.

  • 실제 라이브러리

  • 예시 프로젝트

  • 공식문서

모노레포를 도입함으로써 다음과 같은 이점을 확보할 수 있다는 것을 알게 되었다.

  • 프로젝트를 일관성있게 관리하고 여러 프로젝트 간 버전 관리를 편리하게 관리할 수 있다.

  • 프로젝트에서 외부 라이브러리를 도입할 때 라이브러리의 버전을 보다 쉽게 관리할 수 있다.

리액트 Hooks 라이브러리와 테스트할 수 있는 예제 프로젝트를 한 프로젝트에 관리하기 위해 모노레포를 선택했다. 모노레포를 처음 도입했을 때는 다음과 같은 의문점이 있었다.

  • 프로젝트 폴더 구조와 외부 의존성을 어떻게 관리해야 하는가?

  • 각각 프로젝트에 대해 명령어를 실행시키는 방법이 있을까?

  • 작성한 라이브러리를 예시 프로젝트에서 사용하려면 어떻게 해야 하는가?

  • pnpm이 라이브러리 작성과 빌드에도 관여해야 하는가?

프로젝트 관리

모노레포를 구성하기 위해 pnpm 워크스페이스를 도입할 수 있다. pnpm init으로 package.json 파일을 생성한다. 폴더 구조는 다음과 같이 설정했다.

  • packages/awesome-ts-library는 라이브러리 프로젝트를 세팅했다.

  • app/example는 예시 프로젝트를 세팅했다.

libs/
├── packages/
│   ├── awesome-ts-library/
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsup.config.ts
│   └── another-package/
├── app/
│   ├── example/
│   │   ├── src/
│   │   ├── package.json
│   │   └── vite.config.ts
├── pnpm-workspace.yml
└── package.json

pnpm-workspace.yml로 어떤 프로젝트가 워크스페이스에 포함되어야 하는지 설정할 수 있다. 패키지명에 느낌표(!)를 붙여 워크스페이스에서 제외할 프로젝트를 설정할 수 있다.

packages/*는 packages 폴더 내 프로젝트를 전부 포함하겠다는 의미이다.

packages:
  - "packages/*"
  - "app/*"
  - "!**/test/**"

라이브러리 프로젝트 세팅

Vite로 리액트 프로젝트를 빠르게 세팅할 수 있지만 다음과 같은 이슈가 있었다.

  • 라이브러리 빌드하기 위해 여러 모듈 시스템에 대응해야 한다.

  • 타입스크립트 프로젝트의 경우 타입 정의 파일도 생성해야 한다.

어플리케이션이 아니라 라이브러리를 작성하는 프로젝트기 때문에 create-vite 대신 다른 방법을 찾아봤다. tsup을 도입했을 때 다음과 같은 이점을 챙길 수 있다.

  • 코드 스플리팅

  • 외부 의존성 관리

  • 모듈 시스템 지원

  • 타입 파일 생성

import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["./src/index.ts"],
  clean: true,
  splitting: true,
  dts: true,
  format: ["cjs", "esm"],
  external: ["react", "date-fns"],
});

라이브러리 프로젝트 폴더에서 package.json에는 다음과 같은 속성들을 추가해야 한다.

  • type: 모듈 시스템을 지정한다.

  • types: 타입스크립트 타입 정의 파일을 지정할 수 있다.

  • exports: 패키지가 공개하는 파일을 정의할 수 있다.

    • import: ES 모듈 프로젝트 환경에서의 엔트리 파일

    • require: Common JS 프로젝트 환경에서의 엔트리 파일

  • files: 패키지에 포함할 파일을 명시할 수 있다.

{
  "name": "@nwleedev/awesome-ts-library",
  "author": "Nowon Lee",
  "version": "0.1.0",
  "type": "module",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "license": "MIT",
  "files": ["dist/*"]
}

프로젝트를 계속 하면서 pnpm과 tsup이 각각 다음과 같은 역할로 나눠지는 것을 알 수 있었다.

  • pnpm: 워크스페이스 기능을 통해 프로젝트 폴더 구조와 의존성, 명령어 등을 세팅한다.

  • tsup: 타입스크립트 프로젝트의 빌드를 담당한다.

    • pnpm 스크립트를 통해 tsup 빌드를 실행시킬 수 있다.

명령어 실행

pnpm 워크스페이스에서는 --filter를 통해 특정 프로젝트에서만 명령어를 실행시키도록 할 수 있다. 예를 들어 다음과 같은 명령어로 라이브러리를 빌드할 수 있다.

pnpm --filter ./packages/awesome-ts-library run build

만약 여러 프로젝트에 걸쳐 일관된 외부 패키지를 설치하게 하려면 다음과 같은 명령어를 입력할 수 있다.

pnpm add react date-fns -w

루트 위치의 package.json에 스크립트를 추가해서 명령어를 실행시키도록 할 수 있다.

// 루트 package.json
{
  "scripts": {
    "dev:app": "pnpm --filter ./app/example dev",
    "build:lib": "pnpm --filter ./packages/awesome-ts-library run build",
    "build:all": "pnpm -r run build"
  }
}

예시 프로젝트에서 불러오기

빌드가 끝난 라이브러리는 예제 프로젝트에서 다음과 같이 사용할 수 있다.

예제 프로젝트는 create-vite 명령어로 빠르게 세팅할 수 있었다.

{
  "ts-library": "workspace://*"
}
import Libs from "ts-library";

const libs = Libs();

More from this blog

AI 에이전트에서 작업 맥락을 관리하기 위해 사용한 방법

AI 에이전트의 불편함 Codex CLI와 같은 AI 코딩 에이전트를 통해서 프로젝트 개발 속도를 높일 수 있다. 그리고 혼자 작업했을 때는 놓칠 수 있는 부분도 발생할 수 있는데 AI 에이전트를 통해 보완할 수 있었다. 컴포넌트에 대해서 접근성 태그 작성 API 호출하는 비동기 코드 작성할 때 에러 처리 그리고 AI 에이전트를 잘 활용하면 불가능하

Mar 8, 202610 min read

지시사항을 분할하여 Codex로 효율적인 작업하기

이슈 Codex와 같은 AI 기반 코드 에이전트는 출력 토큰의 수가 제한되어 있다. 그래서 다양한 작업을 하나의 프롬프트에 전부 몰아서 작성하면 원하는 결과가 나오지 않을 수 있다. Codex와 같은 LLM 기반 서비스는 출력 토큰 수를 초과하게 되면 일부 단계가 누락되거나 특정 요소를 과하게 요약할 수 있고 이는 전체적인 답변 퀄리티를 낮출 수 있기 때문이다. 그래서 다양한 AI 공식문서는 특정 작업을 더 작은 단위로 분할하는 방식을 강조한다...

Nov 3, 20255 min read

Key를 활용하여 컴포넌트 상태를 초기화하기

개요 컴포넌트의 상태가 업데이트되어도 컴포넌트 내부에 배치된 컴포넌트의 상태는 그대로 유지되는 경우가 있었다. 예전에는 Props 전달 및 내부에서 useEffect를 통해서 상태를 초기 상태로 업데이트하도록 했는데 useEffect를 남용하게 되면 상태 변화 추적이 어려워지는 문제가 있었다. 그런데 useEffect 외에도 Key를 통해서 컴포넌트 상태를 초기화할 수 있다는 사실을 알게 되었다. 리스트 렌더링 React 프론트엔드에서 목록 형...

Sep 21, 20253 min read

내가 AGENTS.md를 작성하는 방법

Codex CLI 요즘 프로젝트 개발에 Codex CLI를 활용하는 이유는 다음과 같다. 특정 프로젝트를 작업할 때 Claude Code는 종종 사용량을 초과한 것과 다르게 Codex CLI는 ChatGPT Plus 요금제만으로도 토큰 사용량 초과 걱정 없이 충분하게 활용할 수 있었다. 타 CLI 기반 AI 개발 도구에 비해서 요구사항을 더 정확하게 구현하고 꼭 필요한 작업만 진행하기 때문에 코드를 검토하는 시간을 줄일 수 있다. 반복적인 코드...

Sep 21, 20254 min read
N

Nowon Lee

22 posts