NestJS SWC 빌드 최적화

왜 SWC인가?

NestJS의 기본 TypeScript 컴파일러는 tsc다. 프로젝트가 커지면 빌드 시간이 30초, 1분을 넘기기 시작한다. SWC(Speedy Web Compiler)는 Rust로 작성된 TypeScript/JavaScript 컴파일러로, tsc 대비 약 20배 빠른 빌드 속도를 제공한다. NestJS 10부터 공식 SWC 지원이 추가되었다.

핵심 차이는 이렇다: tsc는 타입 체크 + 트랜스파일을 동시에 수행하지만, SWC는 트랜스파일만 수행한다. 타입 체크는 IDE나 CI의 별도 단계에서 처리하고, 빌드는 SWC로 최대한 빠르게 수행하는 전략이다.

SWC 적용: 설정 방법

Step 1: 의존성 설치

# SWC 코어 + NestJS CLI 플러그인
npm install -D @swc/core @swc/cli @swc/helpers

# NestJS CLI가 SWC를 인식하도록 설정
# nest-cli.json 수정

Step 2: nest-cli.json 설정

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "builder": "swc",
    "typeCheck": true,
    "assets": [
      {
        "include": "**/*.graphql",
        "watchAssets": true
      }
    ]
  }
}

"builder": "swc" 한 줄이 핵심이다. "typeCheck": true를 설정하면 SWC 빌드와 동시에 별도 프로세스로 tsc 타입 체크를 병렬 실행한다. 빌드는 SWC 속도로 즉시 완료되고, 타입 에러는 비동기로 보고된다.

빌드 속도 비교

프로젝트 규모 tsc SWC 개선율
소규모 (50 파일) 3.2s 0.3s 10.7x
중규모 (200 파일) 12.5s 0.6s 20.8x
대규모 (500+ 파일) 35.0s 1.4s 25.0x
HMR (단일 파일) 1.5s 0.05s 30.0x

.swcrc 커스텀 설정

// .swcrc — 프로젝트 루트에 생성
{
  "$schema": "https://swc.rs/schema.json",
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,          // NestJS 데코레이터 필수
      "dynamicImport": true
    },
    "transform": {
      "legacyDecorator": true,     // experimentalDecorators 호환
      "decoratorMetadata": true,   // emitDecoratorMetadata 호환 (DI 필수!)
      "decoratorVersion": "2022-03"
    },
    "target": "es2022",
    "keepClassNames": true,        // NestJS DI가 클래스명 의존
    "baseUrl": ".",
    "paths": {
      "@app/*": ["src/*"],
      "@modules/*": ["src/modules/*"],
      "@common/*": ["src/common/*"]
    }
  },
  "module": {
    "type": "commonjs"             // NestJS는 CJS 사용
  },
  "minify": false                  // 서버 코드는 minify 불필요
}

핵심 설정 해설

decoratorMetadata: true반드시 활성화해야 한다. NestJS의 의존성 주입은 TypeScript의 emitDecoratorMetadata에 의존하기 때문이다. 이 옵션이 없으면 @Injectable() 클래스의 생성자 매개변수 타입을 알 수 없어 DI가 실패한다.

keepClassNames: true도 중요하다. NestJS는 에러 메시지, 로깅, 프로바이더 토큰에서 클래스 이름을 사용한다. 이 옵션이 없으면 클래스 이름이 맹글링되어 디버깅이 어려워진다.

Path Alias 설정: tsconfig paths 연동

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@app/*": ["src/*"],
      "@modules/*": ["src/modules/*"],
      "@common/*": ["src/common/*"],
      "@config/*": ["src/config/*"]
    }
  }
}

// SWC는 paths를 트랜스파일 시 해석하지 않음
// 런타임 해석을 위해 tsconfig-paths 사용

// main.ts 또는 package.json scripts에서:
// "start:dev": "nest start --watch"
// nest CLI가 SWC 빌드 시 자동으로 path 매핑 처리

개발 환경: HMR(Hot Module Replacement)

// main.ts — HMR 활성화
declare const module: any;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}
bootstrap();

// nest-cli.json — HMR + SWC 조합
{
  "compilerOptions": {
    "builder": "swc",
    "typeCheck": true
  },
  "watchOptions": {
    // 특정 디렉토리 감시 제외 (성능 향상)
    "ignored": [
      "node_modules",
      "dist",
      "test",
      ".git"
    ]
  }
}

// 실행
// nest start --watch --builder swc
// → 파일 변경 시 50ms 이내 리빌드

CI/CD 파이프라인 최적화

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci

      # 1단계: 타입 체크 (tsc)
      - name: Type Check
        run: npx tsc --noEmit
        # 타입 에러만 검사, 출력 파일 생성 안 함

      # 2단계: 빌드 (SWC) — 타입 체크와 병렬 가능
      - name: Build
        run: nest build
        # SWC로 빌드 → 1-2초 완료

      # 3단계: 테스트
      - name: Test
        run: npm run test

      # 4단계: E2E 테스트
      - name: E2E Test
        run: npm run test:e2e

Jest + SWC: 테스트 속도 최적화

테스트도 SWC로 트랜스파일하면 체감 속도가 크게 향상된다.

# @swc/jest 설치
npm install -D @swc/jest

// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  moduleFileExtensions: ['js', 'json', 'ts'],
  rootDir: '.',
  testRegex: '.*\.spec\.ts$',
  // ts-jest 대신 @swc/jest 사용
  transform: {
    '^.+\.ts$': [
      '@swc/jest',
      {
        jsc: {
          parser: {
            syntax: 'typescript',
            decorators: true,
          },
          transform: {
            legacyDecorator: true,
            decoratorMetadata: true,
          },
          target: 'es2022',
          keepClassNames: true,
        },
      },
    ],
  },
  moduleNameMapper: {
    '^@app/(.*)$': '/src/$1',
    '^@modules/(.*)$': '/src/modules/$1',
    '^@common/(.*)$': '/src/common/$1',
  },
  collectCoverageFrom: ['src/**/*.ts', '!src/**/*.spec.ts'],
  coverageDirectory: './coverage',
  testEnvironment: 'node',
};

테스트 속도 비교

트랜스포머 100개 테스트 500개 테스트
ts-jest 18s 85s
@swc/jest 5s 22s

Docker 빌드 최적화

# Dockerfile — SWC 멀티스테이지 빌드
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && cp -R node_modules /prod_modules
RUN npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# SWC 빌드 — 대규모 프로젝트도 2초 이내
RUN npx nest build
# tsc 빌드였다면 여기서 30초+ 소요

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=deps /prod_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./

EXPOSE 3000
CMD ["node", "dist/main.js"]

트러블슈팅: 자주 만나는 문제

1. DI 실패: “Nest can’t resolve dependencies”

// 원인: decoratorMetadata가 비활성화됨
// 해결: .swcrc에서 확인
{
  "jsc": {
    "transform": {
      "decoratorMetadata": true  // ← 반드시 true
    }
  }
}

// 또는 nest-cli.json에서 SWC 빌더 사용 시
// NestJS CLI가 자동으로 메타데이터 설정을 주입함

2. Path Alias 해석 실패

// 증상: Cannot find module '@app/common/utils'
// 원인: SWC는 paths를 빌드 시 해석하지 않음

// 해결 1: nest CLI 사용 (자동 처리)
nest build  // ✅ paths 자동 해석

// 해결 2: swc 직접 사용 시 tsconfig-paths 필요
npm install -D tsconfig-paths
// package.json
{
  "scripts": {
    "start:prod": "node -r tsconfig-paths/register dist/main.js"
  }
}

3. Circular Dependency 감지 안 됨

// SWC는 타입 체크를 안 하므로 순환 참조를 감지 못함
// 해결: forwardRef() 사용 + CI에서 tsc --noEmit 실행

// 순환 참조 방지 패턴
@Module({
  imports: [forwardRef(() => UserModule)],  // 명시적 forwardRef
})
export class OrderModule {}

// CI에서 반드시 타입 체크 단계 추가
// npx tsc --noEmit → 순환 참조 등 타입 에러 잡기

4. GraphQL Code-First 스키마 생성

// GraphQL의 @ObjectType(), @Field() 데코레이터는
// 메타데이터에 의존하므로 추가 설정 필요

// nest-cli.json
{
  "compilerOptions": {
    "builder": "swc",
    "plugins": ["@nestjs/graphql"]  // GraphQL 플러그인 추가
  }
}

SWC vs esbuild vs tsc 비교

항목 tsc SWC esbuild
언어 TypeScript Rust Go
타입 체크
데코레이터 메타데이터
NestJS 공식 지원
NestJS 호환성 완벽 완벽 ⚠️ DI 불가

esbuild는 데코레이터 메타데이터를 지원하지 않아 NestJS에서 사용할 수 없다. SWC가 NestJS 프로젝트의 유일한 대안 고속 빌더다.

SWC는 NestJS 개발 생산성의 가장 효과적인 투자다. nest-cli.json에 "builder": "swc" 한 줄 추가하면 빌드 20배, HMR 30배, 테스트 4배 빨라진다. 대규모 모노레포에서는 분 단위 빌드가 초 단위로 줄어드는 체감 효과를 경험할 수 있다.

관련 글: NestJS Monorepo 구조 설계 | Docker BuildKit 캐시 최적화

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux