TypeScript와 Rollup으둜 ν•΄κ²°ν•˜λŠ” νŒ¨ν‚€μ§€ λΉŒλ“œ 문제

seungbo
  • #Typescript
  • #Rollup
  • #Bundler
  • #JavaScript

λ“€μ–΄κ°€λ©°

μ•ˆλ…•ν•˜μ„Έμš”. λ…Έλ¨ΈμŠ€μ—μ„œ λ°±μ—”λ“œ κ°œλ°œμ„ ν•˜κ³  μžˆλŠ” μ§€μŠΉλ³΄μž…λ‹ˆλ‹€.

저희 νŒ€μ€ TypeScriptλ₯Ό μ£Ό μ–Έμ–΄λ‘œ μ‚¬μš©ν•˜κ³  있으며, ν”„λ‘œμ νŠΈμ˜ ꡬ쑰λ₯Ό 효율적으둜 κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄ λͺ¨λ…Έλ ˆν¬λ₯Ό ν™œμš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, λ°±μ—”λ“œμ™€ ν”„λ‘ νŠΈμ—”λ“œ κ°„ 데이터 ꡬ쑰λ₯Ό κ³΅μœ ν•˜κΈ° μœ„ν•΄ API DTO(Data Transfer Object) λ₯Ό NPM νŒ¨ν‚€μ§€ ν˜•νƒœλ‘œ κ΄€λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ λ°œμƒν•œ λ¬Έμ œμ μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄ Rollup을 λ„μž…ν•˜κ²Œ λ˜μ—ˆκ³ , 이번 κΈ€μ—μ„œλŠ” 저희가 μ–΄λ–€ 과정을 거쳐 문제λ₯Ό ν•΄κ²°ν–ˆλŠ”μ§€ κ³΅μœ ν•˜κ³ μž ν•©λ‹ˆλ‹€.

문제 상황과 ν•΄κ²° λ°©μ•ˆ

λ¬Έμ œμƒν™© λ°œμƒ

μ €ν¬λŠ” packages μ›Œν¬μŠ€νŽ˜μ΄μŠ€μ—μ„œ λ°±μ—”λ“œμ™€ ν”„λ‘ νŠΈμ—”λ“œκ°€ κ³΅μœ ν•˜λŠ” νƒ€μž…κ³Ό μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ“€μ„ κ΄€λ¦¬ν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ°±μ—”λ“œμ—μ„œλŠ” μ΄λŸ¬ν•œ νƒ€μž…λ“€μ„ μœ ν‹Έλ¦¬ν‹°λ₯Ό 톡해 μœ μ—°ν•˜κ²Œ μ‘°μž‘ν•˜μ—¬ μ‚¬μš©ν•˜κ³  μžˆμ—ˆκ³ , μ΄λŠ” μžμ—°μŠ€λŸ½κ²Œ DTO에도 μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μ‹€μ œ ν”„λ‘œμ νŠΈλ₯Ό μš΄μ˜ν•˜λ©΄μ„œ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ°±μ—”λ“œμ—μ„œ packages μ›Œν¬μŠ€νŽ˜μ΄μŠ€μ˜ νƒ€μž…μ„ ν™œμš©ν•΄ λ‹€μŒκ³Ό 같이 DTOλ₯Ό μž‘μ„±ν–ˆμ„ λ•Œμ˜€μŠ΅λ‹ˆλ‹€:

// λ°±μ—”λ“œμ˜ DTO μ½”λ“œ
import { PostType } from '@packages/types';

// @packages/types 에 μ •μ˜λœ enum
enum PostType {
  LiveRecord = 'live_record',
  Video = 'video',
  ...
}

export class SomeDto {
  ...
  type: Exclude<PostType, PostType.LiveRecord>;  // PostTypeμ—μ„œ LiveRecordλ₯Ό μ œμ™Έ
}

이 DTOλ₯Ό npm νŒ¨ν‚€μ§€λ‘œ λΉŒλ“œν•˜μ—¬ ν”„λ‘ νŠΈμ—”λ“œμ— μ œκ³΅ν–ˆμ„ λ•Œ, κ²‰μœΌλ‘œ λ³΄κΈ°μ—λŠ” λ™μΌν•œ μ½”λ“œκ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

// ν”„λ‘ νŠΈμ—”λ“œμ— μ„€μΉ˜λœ νŒ¨ν‚€μ§€μ˜ μ½”λ“œ
import { PostType } from '@packages/types';

export class SomeDto {
  ...
  type: Exclude<PostType, PostType.LiveRecord>;
}

ν•˜μ§€λ§Œ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλŠ” 이 νŒ¨ν‚€μ§€λ₯Ό μ‚¬μš©ν•  λ•Œλ§ˆλ‹€ νƒ€μž… μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹Ήμ‹œ λΉŒλ“œ ν”„λ‘œμ„ΈμŠ€λŠ” λ‹€μŒκ³Ό κ°™μ•˜μŠ΅λ‹ˆλ‹€:

nest build && tsc-alias

이 λΉŒλ“œ 과정을 거치면 λ‹€μŒκ³Ό 같은 결과물이 μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

import { PostType } from '@packages/types';

export class SomeDto {
  id: string;
  age: number;
  name: string;
  type: PostType;
}

문제λ₯Ό μžμ„Ένžˆ λΆ„μ„ν•΄λ³΄λ‹ˆ, 두 가지 핡심적인 μ΄μŠˆκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 첫째둜, λΉŒλ“œλœ νŒ¨ν‚€μ§€μ— λ°±μ—”λ“œμ˜ 곡용 νƒ€μž… μ •μ˜κ°€ ν•¨κ»˜ ν¬ν•¨λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 이둜 인해 ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλŠ” @packages/types의 PostType enum을 찾을 수 μ—†μ—ˆκ³ , type ν•„λ“œλŠ” μ‹€μ§ˆμ μœΌλ‘œ any νƒ€μž…μ²˜λŸΌ λ™μž‘ν–ˆμŠ΅λ‹ˆλ‹€.

λ‘˜μ§Έλ‘œ, 더 μ‹¬κ°ν•œ λ¬Έμ œλŠ” νƒ€μž… μ‘°μž‘κ³Ό 관련이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν”„λ‘ νŠΈμ—”λ“œμ— μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” PostType enum을 Exclude둜 μ‘°μž‘ν•˜λ € μ‹œλ„ν•˜λ‹€ λ³΄λ‹ˆ, TypeScriptλŠ” 이λ₯Ό never νƒ€μž…μœΌλ‘œ μΆ”λ‘ ν–ˆμŠ΅λ‹ˆλ‹€. 결과적으둜 ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλŠ” type ν•„λ“œμ— μ–΄λ–€ 값도 ν• λ‹Ήν•  수 μ—†λŠ” 상황이 λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

problem_slack.png 문제 제보 μŠ¬λž™

μ΄λŸ¬ν•œ νƒ€μž… 뢈일치 λ¬Έμ œλŠ” κ·Έλ™μ•ˆ λ‹¨μˆœν•œ νƒ€μž…λ§Œ μ‚¬μš©ν•  λ•ŒλŠ” λ°œκ²¬λ˜μ§€ μ•Šλ‹€κ°€, νƒ€μž… μœ ν‹Έλ¦¬ν‹°λ₯Ό ν™œμš©ν•˜λ©΄μ„œ λΉ„λ‘œμ†Œ λ“œλŸ¬λ‚˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²°λ°©μ•ˆ λͺ¨μƒ‰

  1. packages μ›Œν¬μŠ€νŽ˜μ΄μŠ€λ„ Publish ν•˜μ—¬ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ μ‚¬μš©
  2. Swagger μ‚¬μš©
  3. DTOμ—μ„œ μ‚¬μš©ν•˜λŠ” λ°±μ—”λ“œ 곡용 νƒ€μž…μ„ λΉŒλ“œ 결과물에 포함 (선택)

1. packages μ›Œν¬μŠ€νŽ˜μ΄μŠ€λ„ Publish ν•˜μ—¬ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ μ‚¬μš©

이 λ°©λ²•μ—λŠ” λ‹€μŒκ³Ό 같은 문제점이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. packagesλŠ” λ°±μ—”λ“œ νŒ€μ› λͺ¨λ‘κ°€ μ‚¬μš©ν•˜κ³  μˆ˜μ‹œλ‘œ μˆ˜μ •λ  수 있기 λ•Œλ¬Έμ—, λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ 변경사항 κ°„ 좩돌이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€:

  1. νŒ€μ› Aκ°€ νŒ¨ν‚€μ§€λ₯Ό μˆ˜μ •ν•˜κ³  λΉŒλ“œ ν›„ Publish
  2. νŒ€μ› Bκ°€ νŒ¨ν‚€μ§€λ₯Ό μˆ˜μ •ν•˜κ³  λΉŒλ“œ ν›„ Publish

이 경우, νŒ€μ› A의 변경사항을 μ‚¬μš©ν•˜λŠ” ν”„λ‘ νŠΈμ—”λ“œλŠ” νŒ€μ› B의 Publish둜 인해 예기치 μ•Šκ²Œ λΉŒλ“œ 결과물이 변경될 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ packages μ›Œν¬μŠ€νŽ˜μ΄μŠ€μ—λŠ” λ°±μ—”λ“œμ—μ„œλ§Œ μ‚¬μš©ν•˜λŠ” μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ“€λ„ ν¬ν•¨λ˜μ–΄ μžˆμ–΄, 이λ₯Ό ν”„λ‘ νŠΈμ—”λ“œμ— λ…ΈμΆœμ‹œν‚€λŠ” 것은 λ°”λžŒμ§ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

2. Swagger μ‚¬μš©

SwaggerλŠ” API λ¬Έμ„œν™”μ™€ νƒ€μž… μ •μ˜λ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•΄μ£ΌλŠ” μœ μš©ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ‹€μŒκ³Ό 같은 이유둜 Swagger둜의 μ „ν™˜μ„ 보λ₯˜ν–ˆμŠ΅λ‹ˆλ‹€:

1. κΈ°μ‘΄ μ½”λ“œλ² μ΄μŠ€μ˜ 변경이 ν•„μš”

  • κΈ°μ‘΄ μ½”λ“œλ² μ΄μŠ€μ— Swagger λ°μ½”λ ˆμ΄ν„°κ°€ μ •ν™•ν•˜κ²Œ μ μš©λ˜μ–΄ μžˆμ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

2. ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλ„ Swagger μŠ€νŽ™μ„ API둜 μ „ν™˜ν•˜λŠ” μž‘μ—…μ΄ ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

3. DTOμ—μ„œ μ‚¬μš©ν•˜λŠ” λ°±μ—”λ“œ 곡용 νƒ€μž…μ„ λΉŒλ“œ 결과물에 포함

이 방법이 저희 νŒ€μ˜ 상황에 κ°€μž₯ μ ν•©ν•œ ν•΄κ²°μ±…μœΌλ‘œ νŒλ‹¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ£Όμš” μž₯점은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

1. μ΅œμ†Œν•œμ˜ μ½”λ“œ λ³€κ²½

  • κΈ°μ‘΄ μ½”λ“œλ² μ΄μŠ€λ₯Ό 크게 μˆ˜μ •ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.
  • λΉŒλ“œ μ„€μ •λ§Œ λ³€κ²½ν•˜λ©΄ λ˜λ―€λ‘œ λ¦¬μŠ€ν¬κ°€ μ μŠ΅λ‹ˆλ‹€.

2. νƒ€μž… μ•ˆμ •μ„± 보μž₯

  • λ°±μ—”λ“œμ˜ μ‹€μ œ νƒ€μž…μ΄ κ·ΈλŒ€λ‘œ ν”„λ‘ νŠΈμ—”λ“œμ— μ „λ‹¬λ©λ‹ˆλ‹€.
  • νƒ€μž… 뢈일치둜 μΈν•œ λŸ°νƒ€μž„ μ—λŸ¬λ₯Ό 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.

3. ν•„μš”ν•œ νƒ€μž…λ§Œ 선택적 포함

  • DTOμ—μ„œ μ‹€μ œλ‘œ μ‚¬μš©λ˜λŠ” νƒ€μž…λ§Œ λΉŒλ“œ 결과물에 ν¬ν•¨λ©λ‹ˆλ‹€.
  • λΆˆν•„μš”ν•œ μ½”λ“œ λ…ΈμΆœμ„ 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.

Rollupκ³Ό rollup-plugin-dts λ„μž…

μ €ν¬λŠ” λ‹€μŒκ³Ό 같은 이유둜 Rollupκ³Ό rollup-plugin-dts ν”ŒλŸ¬κ·ΈμΈμ„ λ„μž…ν–ˆμŠ΅λ‹ˆλ‹€:

1. 효율적인 Tree-shaking

  • Rollup은 ES λͺ¨λ“ˆμ„ 기반으둜 정적 뢄석을 μˆ˜ν–‰ν•˜μ—¬ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” μ½”λ“œλ₯Ό μ œκ±°ν•©λ‹ˆλ‹€.
  • 특히 νƒ€μž… μ •μ˜μ—μ„œ μ‹€μ œ μ‚¬μš©λ˜λŠ” νƒ€μž…λ§Œ ν¬ν•¨ν•˜λ―€λ‘œ, λΆˆν•„μš”ν•œ νƒ€μž…μ΄ λ²ˆλ“€μ— ν¬ν•¨λ˜λŠ” 것을 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.

2. νƒ€μž… μ •μ˜ 파일 μ΅œμ ν™”

  • rollup-plugin-dtsλ₯Ό μ‚¬μš©ν•˜λ©΄ μ—¬λŸ¬ νƒ€μž… μ •μ˜ νŒŒμΌλ“€μ„ ν•˜λ‚˜μ˜ index.d.ts 파일둜 λ²ˆλ“€λ§ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ‹€λ₯Έ νŒŒμΌμ—μ„œ μ°Έμ‘°ν•˜λŠ” νƒ€μž…λ“€μ„ λͺ¨λ‘ 인라인으둜 ν¬ν•¨μ‹œμΌœ ν•˜λ‚˜μ˜ νŒŒμΌμ—μ„œ λͺ¨λ“  νƒ€μž… 정보λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜ˆμ‹œ:

// κΈ°μ‘΄: μ—¬λŸ¬ νŒŒμΌμ— λΆ„μ‚°λœ νƒ€μž…λ“€
// types/user.d.ts
declare interface User { ... }
// types/post.d.ts  
declare interface Post { ... }

// λ²ˆλ“€λ§ ν›„: index.d.ts
declare interface User { ... }
declare interface Post { ... }

Rollup 적용 κ³Όμ •

1. νŒ¨ν‚€μ§€ μ„€μΉ˜

yarn workspace add [workspace_name] rollup rollup-plugin-dts rollup-plugin-typescript-paths

각 νŒ¨ν‚€μ§€μ˜ μ—­ν• :

  • rollup: JavaScript λͺ¨λ“ˆ λ²ˆλ“€λŸ¬
  • rollup-plugin-dts: νƒ€μž… μ •μ˜ 파일(.d.ts)을 ν•˜λ‚˜λ‘œ λ²ˆλ“€λ§
  • rollup-plugin-typescript-paths: TypeScript 경둜 별칭(path alias) 처리

2. Rollup μ„€μ • 파일 생성

μ›Œν¬μŠ€νŽ˜μ΄μŠ€ λ£¨νŠΈμ— rollup.config.js νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€:

const path = require('path');
const { dts } = require('rollup-plugin-dts');
const { typescriptPaths } = require('rollup-plugin-typescript-paths');

module.exports = {
    input: 'index.ts',                        // TypeScript둜 컴파일된 d.ts νŒŒμΌλ“€μ˜ μ§„μž…μ 
    output: {
        file: 'publish/dist/index.d.ts',      // μ΅œμ’… λ²ˆλ“€λ§λœ νƒ€μž… μ„ μ–Έ 파일 경둜
        format: 'es'                          // ES Module ν˜•μ‹μœΌλ‘œ λ²ˆλ“€λ§
    },
    plugins: [
        typescriptPaths({ preserveExtensions: true }),  // TypeScript paths 별칭 해석
        dts({
            respectExternal: true,            // μ™ΈλΆ€ νŒ¨ν‚€μ§€μ˜ νƒ€μž… 선언을 μœ μ§€
            compilerOptions: {
                baseUrl: path.resolve(__dirname)  // TypeScript μ„€μ •μ˜ baseUrlκ³Ό λ™μΌν•˜κ²Œ μ„€μ •
            }
        })
    ]
};

3. λΉŒλ“œ λͺ…λ Ήμ–΄

"scripts": {
  "build": "rollup -c rollup.config.js"
}

4. λΉŒλ“œ κ²°κ³Όλ¬Ό 확인

publish/dist/index.d.ts νŒŒμΌμ„ 확인해보면 λͺ¨λ“  νƒ€μž…μ΄ ν•˜λ‚˜μ˜ 파일둜 λ²ˆλ“€λ§λ˜μ–΄ μžˆμŒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

// λ²ˆλ“€λ§ κ²°κ³Όλ¬Ό
declare enum PostType { 
  LiveRecord = 'live_record',
  Video = 'video',
  ...
}

declare class SomeDto {
  ...
  type: Exclude<PostType, PostType.LiveRecord>;
}

이처럼 Rollup을 톡해 λΉŒλ“œλœ κ²°κ³Όλ¬Όμ—λŠ” PostType enum이 ν•¨κ»˜ ν¬ν•¨λ˜μ–΄, ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλ„ νƒ€μž… μœ ν‹Έλ¦¬ν‹°λ₯Ό ν™œμš©ν•œ νƒ€μž… μ‘°μž‘μ΄ κ°€λŠ₯ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

problem_solved_slack.png 문제 ν•΄κ²° μŠ¬λž™

κ²°λ‘ 

Rollupκ³Ό rollup-plugin-dtsλ₯Ό λ„μž…ν•˜λ©΄μ„œ μš°λ¦¬κ°€ κ²ͺμ—ˆλ˜ νƒ€μž… κ΄€λ ¨ λ¬Έμ œλ“€μ„ ν•΄κ²°ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ꡬ체적으둜 λ‹€μŒκ³Ό 같은 뢀뢄듀이 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

1. νƒ€μž… μ •μ˜ 문제 ν•΄κ²°

  • λ°±μ—”λ“œμ˜ νƒ€μž…μ΄ ν”„λ‘ νŠΈμ—”λ“œ νŒ¨ν‚€μ§€μ— μ˜¬λ°”λ₯΄κ²Œ 포함됨
  • νƒ€μž… μœ ν‹Έλ¦¬ν‹°(Exclude, Pick λ“±)λ₯Ό μ‚¬μš©ν•΄λ„ μ˜ˆμƒν•œ λŒ€λ‘œ λ™μž‘
  • ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ νƒ€μž… μ—λŸ¬κ°€ μ •ν™•ν•˜κ²Œ ν‘œμ‹œλ¨

2. 개발 생산성 ν–₯상

  • IDEμ—μ„œ νƒ€μž… 기반 μžλ™μ™„μ„± 지원
  • 컴파일 νƒ€μž„μ— νƒ€μž… 였λ₯˜ 감지
  • λͺ…ν™•ν•œ νƒ€μž… μ •λ³΄λ‘œ μΈν•œ 디버깅 μš©μ΄μ„± 증가

μ΄λŸ¬ν•œ κ°œμ„ μ„ 톡해 λ°±μ—”λ“œμ™€ ν”„λ‘ νŠΈμ—”λ“œ μ‚¬μ΄μ˜ νƒ€μž… κ³΅μœ κ°€ λ”μš± μ•ˆμ •μ μœΌλ‘œ μ΄λ£¨μ–΄μ§€κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 특히 νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ νƒ€μž… μ‘°μž‘ κΈ°λŠ₯듀을 μ˜λ„ν•œ λŒ€λ‘œ μ‚¬μš©ν•  수 있게 된 점이 κ°€μž₯ 큰 μ΄μ μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€.

← λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ°

Art Changes Life

λ…Έλ¨ΈμŠ€μ™€ ν•¨κ»˜ μ—”ν„°ν…Œν¬ 산업을 ν˜μ‹ ν•΄λ‚˜κ°ˆ 멀버λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.

μ±„μš© 쀑인 곡고 보기