Bun 1.1 완벽 가이드 — 빌드·번들링·테스트·Node.js 대비 성능·프레임워크 통합
이 글의 핵심
Bun 1.1은 1.0 대비 1,700개 이상의 커밋과 수천 건의 버그 수정을 거쳐, 런타임·패키지 관리자·번들러·테스트 러너를 하나의 도구 체인으로 묶은 올인원 JavaScript 런타임으로 한 단계 성숙해졌습니다. 특히 Windows 10 이상 공식 지원으로 개발자 접근성이 크게 넓어졌고, bun install·bun run·bunx의 속도, --watch 기반 반복 개발, Node.js API 호환성 강화가 실무에서 체감되는 수준으로 다듬어졌습니다.
이 글에서는 Bun 1.1의 핵심 기능(초고속 런타임, 내장 번들러, Jest 호환 테스트)을 정리하고, Node.js·npm과의 성능을 비교하는 관점(벤치마크 수치는 환경에 따라 달라질 수 있으므로 해석 방법 포함)을 제시합니다. 이어 Next.js, React, Astro와 함께 쓰는 패턴, 소규모 실전 프로젝트 예제, Docker·CI·호스팅 관점의 배포 전략까지 연결합니다.
주의: Bun은 빠르게 진화하는 프로젝트입니다. 프로덕션 적용 전에는 사용 중인 프레임워크·플러그인·네이티브 모듈 조합으로 회귀 테스트를 수행하는 것을 권장합니다.
1. Bun 1.1이 바꾼 것: 한 줄 요약
| 영역 | Bun 1.1에서 강조되는 변화 |
|---|---|
| 플랫폼 | Windows에서 런타임·번들러·테스트·패키지 관리자 전 구간 지원 |
| 패키지 | bun install 속도, 라이프사이클 스크립트 병렬 실행, bun pm migrate로 lockfile 이전 |
| 런타임 | 대용량 파일 트랜스파일 캐시, Bun Shell(Bun.$), import.meta.env, Node.js 호환 API 확대 |
| 번들러 | --target=node 안정화, --compile과 NAPI(.node) 임베드, 매크로에서 내장 모듈 사용 |
| 테스트 | Jest 스타일 matcher 확장, ESM/CJS 모듈 목(mock), 런타임 인플레이스 목킹 |
2. 핵심 기능 ① 초고속 런타임과 도구 일원화
2.1 런타임으로서의 Bun
Bun은 JavaScriptCore 기반의 JavaScript 런타임으로, TypeScript·JSX를 별도 설정 없이 실행할 수 있습니다. Node.js가 npm·코어 API·생태계로 “서버 사이드 JavaScript의 사실상 표준”이 된 것과 달리, Bun은 실행·의존성 설치·번들·테스트를 단일 바이너리로 제공해 도구 전환 비용을 줄이는 방향입니다.
2.2 트랜스파일러 캐시와 CLI 체감 성능
Bun 1.1에서는 50KB를 넘는 파일에 대해 콘텐츠 주소 방식 캐시를 적용해, 동일 파일을 반복 트랜스파일하는 비용을 줄였습니다. 그 결과 tsc 같은 CLI 도구를 Bun 위에서 실행할 때 이전 대비 최대 약 2배에 가까운 체감 속도 개선이 보고된 바 있습니다(프로젝트 크기·디스크·CPU에 따라 다름).
2.3 Bun Shell과 크로스 플랫폼 스크립팅
Bun 1.1은 bash에 가까운 문법을 Windows에서도 일관되게 쓰기 위한 Bun Shell을 강조합니다. package.json의 스크립트를 bun run으로 실행할 때, 플랫폼별 cmd/sh 차이로 깨지기 쉬운 부분을 줄이는 데 목적이 있습니다.
import { $ } from "bun";
// 표준 출력으로 파이프
await $`ls *.ts`;
// 결과를 문자열로
const listing = await $`ls *.ts`.text();
왜 필요한가: Windows에서는 shebang(#!/usr/bin/env node)이 무시되고, rm -rf 같은 명령이 기본적으로 동작하지 않으며, 환경 변수 설정 문법도 다릅니다. 팀에 Windows·macOS·Linux가 섞이면 쉘 스크립트 호환이 곧 생산성입니다.
주의할 점: 보안 측면에서 사용자 입력을 그대로 쉘에 넣지 말고, Bun Shell이 제공하는 인자 이스케이프 패턴을 따르는 것이 안전합니다.
3. 핵심 기능 ② 내장 번들러(bun build)
Bun의 번들러는 브라우저·Bun·Node.js 타깃으로 산출물을 만들 수 있으며, 트랜스파일·번들·축소를 한 경로에서 처리합니다.
3.1 Node.js 타깃: --target=node
라이브러리나 CLI를 Node.js에서 그대로 실행 가능한 단일 파일로 내고 싶을 때 --target=node를 사용합니다. Bun 1.0에서 보고되던 require 관련 오류 등이 1.1에서 정리되어, node:fs 같은 내장 모듈을 포함한 번들을 Node에서 실행하기 쉬워졌습니다.
bun build ./src/cli.ts --target=node --outfile=dist/cli.mjs
node dist/cli.mjs
언제 쓰는가: npm에 배포하는 CLI 패키지, 서버리스 핸들러 단일 파일, 레거시 Node 환경으로의 산출물 고정이 필요할 때 유용합니다.
3.2 단일 실행 파일: --compile
--compile은 TypeScript·JavaScript를 단일 바이너리로 묶습니다. Bun 1.1에서는 NAPI 애드온(.node) 임베드가 가능해져, 캔버스·네이티브 의존이 있는 도구도 “배포물 하나”로 가져가기 쉬워졌습니다.
bun build --compile ./src/app.ts --outfile=myapp
./myapp
한계 인지: 네이티브 모듈·플랫폼 ABI·외부 공유 라이브러리에 따라 빌드 산출물이 달라질 수 있으므로, 타깃 OS별 빌드 파이프라인을 설계해야 합니다.
3.3 매크로와 번들 타임 실행
Bun의 매크로는 빌드 시점에 코드를 실행해 결과를 번들에 박아 넣을 수 있습니다. Bun 1.1에서는 node:fs 읽기·child_process 실행 같은 내장 모듈을 매크로에서 가져올 수 있어, 빌드 타임 코드 생성 패턴이 단순해집니다.
import { readFileSync } from "node:fs" with { type: "macro" };
export const buildTimeText = readFileSync("./version.txt", "utf8");
실무 팁: 매크로는 재현 가능한 빌드를 깨기 쉽습니다. CI에서 동일 입력에 동일 산출물이 나오도록, 읽는 파일을 리포지토리에 고정하고 버전 관리하십시오.
4. 핵심 기능 ③ 테스트 러너(bun test)
Bun 테스트는 Jest와 유사한 API(describe, test, expect)를 제공하며, 별도 설정 없이 TypeScript 테스트를 실행할 수 있습니다.
4.1 풍부해진 matcher
Bun 1.1은 expect 계열 matcher를 대폭 확장했습니다. 객체 포함 관계, 공백 무시 비교, Promise 결과 매칭 등 스냅샷 대신 명시적 단언을 선호하는 코드베이스에 잘 맞습니다.
import { expect, test } from "bun:test";
test("객체 부분 일치", () => {
expect({ a: { b: 1, c: 2 } }).toEqual({
a: expect.objectContaining({ b: 1 }),
});
});
4.2 모듈 목(mock)과 ESM/CJS
Bun은 ESM과 CommonJS 모두 목킹할 수 있다는 점을 강조합니다. 특히 “이미 import된 모듈”도 런타임에 제자리에서 갱신할 수 있어, Jest에서 흔히 겪는 “호이스팅/모킹 순서” 문제를 완화하는 사례가 있습니다.
import { mock, test, expect } from "bun:test";
import { fn } from "./math";
test("로컬 모듈 목", () => {
mock.module("./math", () => ({ fn: () => 42 }));
expect(fn()).toBe(42);
});
주의: 목킹은 테스트 간 전역 상태 오염을 남길 수 있습니다. 파일 단위 격리(--preload로 리셋 훅 구성 등) 전략을 함께 설계하십시오.
4.3 --watch와 개발 루프
bun test --watch는 파일 변경 시 빠르게 재실행됩니다. Bun 1.1 블로그에서는 Windows에서도 저장과 재실행 사이 지연을 줄이기 위한 최적화를 강조합니다. 대규모 모노레포에서는 테스트 범위를 디렉터리로 제한하는 편이 유리합니다.
5. Node.js·npm과의 성능 비교: 올바른 읽는 법
5.1 공식 블로그가 제시한 Windows 기준 사례
Bun 1.1 발표 자료에는 Windows에서 Vite React 템플릿 기준 bun install이 yarn 대비 최대 약 18배, npm 대비 최대 약 30배 빠른 사례가 소개됩니다. 또한 bun run이 npm run 대비 최대 약 11배, bunx가 npx 대비 최대 약 11배 빠른 사례가 함께 제시됩니다.
중요한 해석 원칙은 다음과 같습니다.
- 측정 조건(캐시 유무,
node_modules삭제 여부, 네트워크, 디스크, 안티바이러스)에 따라 배율은 크게 달라집니다. - 프로덕션 런타임 성능과 패키지 설치 속도는 다른 문제입니다. 전자는 애플리케이션 코드·I/O·GC 특성에 좌우됩니다.
- 호환성이 맞지 않으면 속도는 의미가 없습니다. 반드시 핵심 의존성을 기준으로 검증합니다.
5.2 .bunx와 Windows에서의 실행 경로
Windows는 심볼릭 링크·shebang·배치 종료 프롬프트(“Terminate batch job?”) 이슈로 스크립트 실행 경험이 나빠지기 쉽습니다. Bun 1.1은 이를 완화하기 위해 .bunx 형식을 도입했습니다. Bun만 런타임으로 쓰지 않고 패키지 관리자로만 쓰는 경우에도, 생성된 실행 파일이 Node와 함께 동작하도록 설계되었다는 점이 실무적으로 큽니다.
5.3 파일 시스템 API 미세 벤치
동일한 API라도 구현에 따라 속도 차가 납니다. 예를 들어 Windows에서 fs.readdir()은 Bun 측 벤치마크에서 Node.js 대비 유의미하게 빠른 사례가 보고된 바 있습니다. 반대로 아직 최적화되지 않은 API는 이슈 트래커에 올리고 회피 전략을 세우는 편이 안전합니다.
6. Next.js·React·Astro와의 통합
6.1 공통: 패키지 관리자로서의 Bun
대부분의 프론트엔드 프로젝트에서 가장 안전한 첫걸음은 npm/pnpm 대신 bun install로 의존성을 설치하고 package.json 스크립트를 bun run으로 실행하는 것입니다. 기존 package-lock.json이 있다면 bun pm migrate 또는 Bun이 자동으로 이전해 bun.lockb를 생성할 수 있습니다.
bun install
bun run dev
6.2 React(예: Vite·CRA 계열)
React 자체는 런타임에 Node를 요구하지 않고, 빌드 체인이 중요합니다. Vite + React라면 Bun은 주로 의존성 설치·스크립트 실행·때로는 테스트(bun test)에 기여합니다. 번들러를 Bun으로 완전히 대체할지는 팀 표준과 플러그인 호환성에 따라 결정합니다.
6.3 Next.js: 런타임과 번들러를 구분해 이해하기
Next.js는 자체 빌드 파이프라인(Turbopack/Webpack 등)을 가집니다. 최근 생태계에서는 패키지 설치·개발 서버 프로세스를 Bun으로 돌리되, Next의 번들러는 그대로 두는 접근이 흔합니다. Vercel 등 일부 플랫폼은 Bun 런타임을 함수 실행 옵션으로 제공하기도 합니다.
로컬에서 Bun을 Next와 함께 쓰려면 프로젝트·버전에 따라 다음과 같은 형태가 논의됩니다(프로젝트 템플릿·Next 버전에 맞게 조정 필요).
{
"scripts": {
"dev": "bun --bun next dev",
"build": "bun --bun next build",
"start": "bun --bun next start"
}
}
실무 권고: Next는 릴리스 주기가 빠르고 네이티브·서버 컴포넌트·엣지 런타임 조합이 복잡하므로, 스테이징에서 E2E·핵심 API 경로를 반드시 검증합니다.
6.4 Astro(SSG·엣지 친화)
Astro는 정적 사이트 생성과 아일랜드 아키텍처로 번들 크기를 줄이는 데 초점이 있습니다. 이 블로그 스택(Astro + Cloudflare Pages)에서도 Bun은 개발자 루프 가속에 잘 맞습니다.
bun create astro@latest my-astro-site
cd my-astro-site
bun install
bun run dev
Cloudflare 어댑터·이미지 최적화·MDX 등 플러그인 조합은 Node 전용 가정이 남아 있을 수 있으므로, 문제가 생기면 해당 이슈를 추적합니다.
7. 실전 프로젝트 예제: 미니 API + 테스트 + 단일 바이너리 배포
아래는 Bun 내장 HTTP 서버, 단위 테스트, 컴파일 산출물까지 한 흐름으로 묶는 최소 예시입니다.
7.1 프로젝트 구조
mini-bun-service/
├─ package.json
├─ src/
│ ├─ server.ts
│ └─ server.test.ts
└─ README.md
7.2 package.json
{
"name": "mini-bun-service",
"type": "module",
"scripts": {
"dev": "bun --watch src/server.ts",
"start": "bun src/server.ts",
"test": "bun test",
"build:bin": "bun build --compile src/server.ts --outfile=dist/server"
}
}
bun --watch는 코드 변경 시 서버를 재시작해 로컬 반복 속도를 높입니다.
7.3 src/server.ts
const port = Number(process.env.PORT ?? 3000);
export function createServer() {
return Bun.serve({
port,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return Response.json({ ok: true, bun: process.versions.bun ?? "dev" });
}
return new Response("Not Found", { status: 404 });
},
});
}
if (import.meta.main) {
const server = createServer();
console.log(`listening on ${server.url}`);
}
설명: Bun.serve는 개발용 API 서버를 매우 적은 코드로 띄울 수 있습니다. 운영 환경에서는 리버스 프록시·로깅·요청 한도·보안 헤더를 앞단에 두는 것이 일반적입니다.
7.4 src/server.test.ts
import { describe, test, expect } from "bun:test";
import { createServer } from "./server.ts";
describe("/health", () => {
test("200과 JSON 본문", async () => {
const server = createServer();
try {
const res = await fetch(`${server.url}health`);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.ok).toBe(true);
} finally {
server.stop();
}
});
});
7.5 빌드와 실행
bun test
bun run build:bin
./dist/server
확장 포인트: 환경 변수 검증(Zod 등), 구조화 로깅,OpenTelemetry 추적, 컨테이너 헬스체크 엔드포인트(/health)를 로드밸런서와 맞추는 패턴으로 확장할 수 있습니다.
8. 배포 전략
8.1 컨테이너(Docker) 기반
Bun 공식 이미지 또는 멀티 스테이지 빌드로 의존성 설치 → 테스트 → --compile 산출물 복사 순서를 권장합니다. 런타임 이미지를 슬림하게 유지하면 콜드 스타트·보안 노출 면에서 유리합니다.
# 예시: 빌드 스테이지에서 테스트까지 수행
FROM oven/bun:1 AS build
WORKDIR /app
COPY package.json bun.lockb* ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun test && bun build --compile src/server.ts --outfile /out/server
FROM gcr.io/distroless/base-debian12
COPY --from=build /out/server /server
ENV PORT=8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
주의: 베이스 이미지와 libc 호환성, .node 바이너리 포함 여부에 따라 런타임 이미지를 맞춰야 합니다.
8.2 정적·하이브리드 프론트(Astro·Next export 등)
- 정적 산출물(Astro
dist/)은 Cloudflare Pages, Netlify, S3+CloudFront 등 정적 호스팅으로 배포합니다. Bun은 빌드 파이프라인 가속에 쓰입니다. - 서버 기능이 필요하면 어댑터별로 엣지/서버리스 타깃을 명시하고, 런타임이 Bun인지 Node인지 플랫폼 문서를 확인합니다.
8.3 CI(GitHub Actions 등)
캐시 키로 bun.lockb를 포함하고, bun install --frozen-lockfile로 재현 가능한 설치를 보장합니다. 테스트 단계에서 bun test --coverage 등(프로젝트 설정에 따라) 품질 게이트를 두면 회귀를 줄일 수 있습니다.
# 개념 예시 (실제 워크플로는 저장소에 맞게 조정)
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun test
8.4 롤백과 관측
배포 파이프라인에서 이전 바이너리/이전 자산으로 즉시 롤백할 수 있게 버전 태깅을 명확히 하고, 에러율·지연·CPU를 대시보드화합니다. Bun으로 바꿨다고 해서 애플리케이션 알고리즘 복잡도가 사라지지는 않습니다.
9. 문제 해결 체크리스트
- 네이티브 모듈:
better-sqlite3,sharp등은 Node ABI에 묶입니다. Bun 전용 대안(bun:sqlite등)을 검토하거나, 해당 경로는 Node로 유지합니다. - Next·특정 번들러 플러그인: 실험적 조합은 이슈가 많습니다. 문제가 나면 문제를 격리한 최소 재현 저장소를 만듭니다.
- 날짜·정규식·엔진 차이: Bun은 JavaScriptCore를 사용하므로, V8과 미세하게 다른 동작이 남을 수 있습니다. Bun 1.1은
Date.parse등 호환을 강화했지만, 시간대·포맷은 여전히 테스트가 필요합니다. - Windows 경로:
node:path처리와 관련된 버그는 버전별로 해결됩니다. 경로 결합·정규화는 항상path.join계열을 사용합니다.
10. 정리
Bun 1.1은 Windows 지원, 도구 체인 완성도, Node·npm과의 체감 속도 경쟁, 번들·컴파일·테스트의 일체화라는 네 축에서 “실무 투입 가능성”을 높였습니다. 다만 프레임워크·플랫폼·네이티브 의존성은 프로젝트마다 다르므로, 이 글의 권장 접근은 패키지 관리·스크립트·테스트부터 단계적으로 도입하고, 프로덕션 런타임 전환은 검증 후 진행하는 것입니다.
추가로 다루길 원하는 주제(예: 모노레포, 특정 호스팅의 Bun 런타임 설정, 마이그레이션 체크리스트)가 있으면 알려 주십시오.