Logo

Bun: 귀엽지만 강력한 새로운 자바스크립트 런타임

최근에 번(Bun) v1.0이 출시되면서 차세대 자바스크립트 런타임(Runtime)으로 많은 기대를 한몸에 받고 있습니다. 2023년 JavaScript Rising Stars에서도 Bun이 당당이 2위를 차지하였는데요.

이번 포스팅에서는 귀여운 이름과 로고 뒤에 무시무시한 기능과 성능으로 무장하고 하고 있는 Bun이라는 새로운 자바스크립트 런타임에 대해서 살펴보겠습니다.

Bun v1.0

자바스크립트 런타임

Bun에 대해서 소개드리기 전에 먼저 자바스크립트 런타임(Runtime)이 무엇인지 가볍게 짚고 넘어가면 좋을 것 같아요.

자바스크립트 런타임이란 쉽게 말해 자바스크립트로 작성된 프로그램을 실행해주는 소프트웨어를 의미합니다. 가장 흔한 예로, 우리가 매일 사용하는 크롬이나 사파리, 파이어폭스, 엣지와 같은 웹 브라우저를 들 수 있죠. 웹 브라우저는 HTML 문서에서 <script/> 태그를 통해 삽입된 자바스크립트 코드를 실행하여 웹페이지가 사용자와 상호작용할 수 있도록 도와줍니다.

자바스크립트로 작성된 프로그램은 브라우저를 통해서 클라이언트 측에서만 돌아가는 게 아니라 서버 측에서도 실행될 수 있는데요. 2009년에 Node.js가 등장하면서 백엔드에서도 자바스크립트 프로그램을 실행할 수 있는 길이 열리게 되었기 때문입니다. Node.js는 C++로 개발되었으며, 그 이후에 Node.js 만드신 분이 Rust라는 새로운 언어로 Deno라는 또 다른 자바스크립트 런타임도 개발하셨습니다.

Bun은 Node.js와 Deno처럼 기본적으로 백엔드에서 사용하기 위해서 만들어진 자바스크립트 런타임이며, Zig라는 아직 많은 분들에게 생소할 수도 있는 프로그래밍 언어로 개발이 되었습니다.

참고로 하나의 프로그래밍 언어에 여러 개의 런타임이 있다는 것은 젼혀 이상한 현상이 아니에요. 예를 들어, 파이썬(Python)에도 CPython, PyPy, Jython 등 다양한 런타임이 있고요, 루비(Ruby)에도 MRI, JRuby, Rubinius 등 다양한 런타임이 있습니다.

Bun 설치

Bun은 MacOS와 같은 리눅스 계열 운영체제를 사용하고 계신다면 curl 명령어로 간편하게 설치해서 사용해볼 수 있습니다.

$ curl -fsSL https://bun.sh/install | bash

다음과 같이 터미널에서 Bun의 버전이 확인된다면 설치가 완료된 것입니다.

$ bun -v
1.0.4

현재 제가 블로그를 쓰는 시점에서 윈도우즈 운영체제에서는 Bun을 온전하게 사용하기 어려운 상황이오니 참고 바라겠습니다.

Bun 프로젝트 생성

터미널에서 bun init 명령어를 실행하면 쉽게 Bun 프로젝트를 생성할 수 있는데요. 패키지 이름과 진입 지점(entry point)를 대화형으로 물어보는데 귀찮으면 -y 옵션을 사용하면 됩니다. 그러면 현재 폴더명이 패키지 이름으로 사용되고 index.ts 파일이 진입 지점이 됩니다.

$ bun init -y
Done! A package.json file was saved in the current directory.
 + index.ts
 + .gitignore
 + tsconfig.json (for editor auto-complete)
 + README.md

To get started, run:
  bun run index.ts

템플릿을 이용해서 Bun 프로젝트를 시작하고 싶다면 bun create 명령어를 사용할 수도 있습니다.

$ bun create react-app

타입스크립트 실행

Bun의 가장 큰 매력은 타입스크립트로 작성된 프로그램을 자바스크립트로 변환하지 않고 바로 실행할 수 있다는 것인데요. 즉, Bun은 타입스크립트 런타임이라고도 부를 수도 있겟습니다.

예를 들어, 아주 간단한 타입스크립트 프로그램을 작성해보겠습니다.

index.ts
let message: string = "안녕하세요!";
console.log(message);

다음과 같이 이 프로그램 파일은 Bun을 통해서 터미널에서 바로 실행할 수 있습니다.

$ bun index.ts
안녕하세요!

만약에, 동일한 작업을 Node.js로 하려면 어떻게 했어야 했을까요?

우선 타입스크립트로 작성된 프로그램을 tsc 커맨드로 컴파일(compile)했어야 겠죠?

$ npx tsc index.ts

그래야지 결과물로 자바스크립트 파일을 얻을 수 있을테니까요.

index.ts
var message = "안녕하세요!";
hello(message);

그 다음에 이 자바스크립트 프로그램을 Node.js로 실행했어야 했을 것입니다.

$ node index.ts
안녕하세요!

타입스크립트 코드를 자바스크립트로 코드로 컴파일하는 기본적인 방법에 대해서는 별도 포스팅을 참고 바랍니다.

물론, 이 두 단계의 작업을 한 번으로 줄여주는 ts-node라는 개발 도구를 사용할 수도 있는데요. 이 방법은 추가 패키지를 설치해줘야 한다는 나름의 귀찮음이 있습니다.

$ npm install -D ts-node
$ npx ts-node index.ts
안녕하세요!

Bun을 사용하면 이와 같은 번거로운 컴파일이나 개발 도구 설치가 필요 없어지기 때문에 개발 생산성이나 개발자 경험을 크게 향상시킬 수 있습니다.

모듈 시스템 호환성

현재 자바스크립트 생태계에서는 수년에 걸쳐 상당히 고통스러운 모듈 시스템의 전환 작업이 일어나고 있습니다.

예전에는 모듈을 내보내거나 불러오기 위해서 Node.js를 중심으로 CJS(CommonJS)를 많이 사용했는데요. 최근에는 ES6(ES2105)에서 ESM(ES Module)이라는 새로운 모듈 시스템으로 표준으로 채택이 되면서, 신규 프로젝트에서는 ESM을 사용하는 것을 권장하는 분위기입니다. 하지만 npm 패키지 저장소에는 10년을 훌쩍 넘는 기간동안 CommonJS을 사용한 패키지들이 축적되어 왔고, 그 중 많은 오픈 소스 패키지는 더 이상 업데이트가 되지 않아서 ESM으로 언제 전환될지 알 수 없습니다.

그러므로, 우리는 아직까지도 모듈을 내보니거나 불러올 때 CommonJS를 완전히 배제할 수 없는 상황인데요. ESM 프로젝트에서 CJS 모듈을 불러오거나, 반대로 CJS 프로젝트에서 ESM 모듈을 불러올 때 갖가지 해결하기 까다로운 문제들이 발생할 수 있죠.

자바스크립트의 모듈 시스템을 양분하고 있고 있는 CJS와 ESM에 대해서 더 궁금하시는 분들께는 다음 포스팅을 추천드리겠습니다.

하지만 Bun은 이렇게 골치 아플 수 있는 CJS와 ESM 간의 모듈 시스템 호환 문제를 런타임 수준에서 깔끔하게 해결해줍니다.

예를 들어, 인자로 넘어온 메시지를 콘솔에 출력해주는 hello()라는 함수를 작성해보겠습니다.

hello.cjs 파일에는 CJS 방식으로 함수를 내보내겠습니다.

hello.cjs
exports.hello = function (message) {
  console.log("CJS:", message);
};

hello.mjs 파일에는 ESM 방식으로 함수를 내보내겠습니다.

hello.mjs
export function hello(message) {
  console.log("ESM:", message);
}

이제 index.ts 파일에서 작성한 함수를 불러와볼까요?

우선 두 파일을 모두 ESM의 import 키워드를 사용하서 불러올 수 있습니다.

index.ts
import { hello } from "./hello.cjs";

hello("안녕하세요");
Terminal
$ bun index.ts
CJS: 안녕하세요
index.ts
import { hello } from "./hello.mjs";

hello("안녕하세요");
Terminal
$ bun index.ts
ESM: 안녕하세요

뿐만 아니라 두 파일을 모두 CJS의 require 키워드를 사용하서 불러올 수 있습니다.

index.ts
const { hello } = require("./hello.cjs");

hello("안녕하세요");
Terminal
$ bun index.ts
CJS: 안녕하세요
index.ts
const { hello } = require("./hello.mjs");

hello("안녕하세요");
Terminal
$ bun index.ts
ESM: 안녕하세요

더 놀라운 부분은 파일을 불러올 때 확장자를 명시하지 않으면, 똑똑하게 알아서 모듈 시스템에 맞는 파일을 선택해서 불러 온다는 것입니다.

예를 들어, import 키워드를 썼을 때는, hello.mjs 파일에서 불러옵니다.

index.ts
import { hello } from "./hello";

hello("안녕하세요");
Terminal
$ bun index.ts
ESM: 안녕하세요

반면에 require 키워드를 썼을 때는, hello.cjs 파일에서 불러오는 것을 볼 수 있습니다.

index.ts
const { hello } = require("./hello");

hello("안녕하세요");
Terminal
$ bun index.ts
CJS: 안녕하세요

패키지 매니저 내장

Node.js에서는 npm이 표준 패키지 매니저임에도 불구하고, Yarn이나 Pnpm과 같은 서드 파티 패키지 매니저도 많이 사용되고 있습니다. 이렇게 다양한 패키지 매니저가 사용되는 이유는 아무래도 표준 패키지 매니저인 npm이 여러가지 부분에서 자바스크립트 커뮤니티의 기대에 부흥하지 못해서가 아닐까 싶은데요. 물론 최근에 출시되고 있는 버전의 npm에서는 이렇게 부족한 부분이 많이 보완되어서 npm으로 회귀하는 프로젝트도 많아지고 있습니다.

반면에, Bun은 아예 자체적으로 패키지 매니저를 내장하고 있으며, npm이나 Yarn과 같은 기존에 많이 사용되던 패키지 매니저랑 호환까지 됩니다.

예를 들어, package.json 파일에 명시되어 있는 모든 패키지를 설치하고 싶다면 bun install 명령어를 실행하면 됩니다.

$ bun install

새로운 패키지를 추가하고 싶다면 bun add 명령어를 시용하면 되고요.

$ bun add react

기존 패키지를 제거하고 싶다면 bun remove 명령어를 사용하면 됩니다.

$ bun remove react

명령줄 인터페이스(CLI)가 기존에 우리가 쓰던 패키지 매니저와 거의 동일하기 때문에 큰 이질감없이 사용할 수 있다는 장점이 있습니다. 게다가 더 이상 어떤 패키지 매니저를 선택할지 고민할 필요도 없어지고요.

테스트 러너 내장

자바스크립트에서는 보통 테스트 코드를 실행할 때 Jest와 같은 별도의 라이브러리를 사용하는데요. 다른 프로그래밍 언어를 보면 런타임에서 테스트 러너(Runner)를 지원하는 경우가 많습니다. 왜냐하면 테스트를 작성하고 실행하는 작업은 대부분의 소프트웨어 프로젝트에서 필수적이기 때문입니다.

이러한 이유로 자바스크립트 런타임도 테스트 실행 기능과 관련 유틸리티를 내장하는 경우가 점점 늘어나고 있는데요. Node.js의 테스트 러너 API는 v20에서서 안정화가 되었고, Bun도 아예 처음부터 자체 테스트 러너를 내장하여 출시되었습니다.

bun:test 패키지를 통해서 테스트에 필요한 대부분의 유틸리티를 불러와서 사용할 수 있으며,

import { expect, test, spyOn } from "bun:test";
import { hello } from "./hello";

test("hello", () => {
  const spy = spyOn(console, "log");

  hello("안녕?");

  expect(spy).toHaveBeenCalledTimes(1);
  expect(spy.mock.calls[0]).toEqual(["ESM:", "안녕?"]);
});

터미널에서 bun test 명령어를 통해서 작성한 테스트를 실행할 수 있습니다.

$ bun test
bun test v1.0.0 (822a00c4)

hello.test.ts:
ESM: 안녕?
✓ hello [0.35ms]

 1 pass
 0 fail
 2 expect() calls
Ran 1 tests across 1 files. [10.00ms]

트랜스파일러/번들러 내장

그런데 Bun을 쓰면 항상 컴파일 과정을 생략할 수 있을까요? 이 부분 오해하기 쉬운데요. Bun을 쓰더라도 프론트엔드 프로젝트에서는 타입스크립트로 작성된 코드를 자바스크립트를 변환하는 작업은 필수입니다. 왜냐하면 브라우저는 타입스크립트로 작성된 코드를 그대로 실행할 수 없기 때문입니다.

하지만 Bun은 마치 타입스크립트처럼 JSX로 쓰여진 코드도 트랜스파일(transpile)없이 바로 처리할 수 있습니다.

Button.tsx
function Button(props: { message: string }) {
  return <button>{props.message}</button>;
}

console.log(<Button message="Hello world!" />);
Terminal
$ bun Button.ts
<Button message="Hello world!" />

심지어 브라우저가 내려받아 실행해야 하는 자바스크립트 코드를 최적화하기 위해 번들러(bundler)까지 bun build라는 명령어를 통해 자체적으로 지원합니다.

따라서 Bun 하나만 있으면, TypeScript나 Babel, Webpack과 같은 개발 도구가 없이도 웹사이트 배포를 위한 애플리케이션 빌드(build)를 처리할 수 있습니다.

Web 표준 API 지원

Bun은 fetch()와 같은 Node.js가 다소 지원을 소홀히 하던 Web 표준 API도 적극적으로 지원하고 있습니다. 엣지 컴퓨팅(Edge Computing)이나 SSR(서버 사이드 렌더링)과 같은 최신 웹 개발 트랜드를 충실히 반영하려는 노력이 엿보입니다.

Bun의 뛰어난 성능

Bun의 웹사이트를 방문해보시면 기존 자바스크립트 런타임 대비 작게는 몇 배 크게는 수십 배 성능이 빠르다는 문구나 도표를 이곳 저곳에서 쉽게 접할 수 있는데요. 그만큼 Bun은 기능 뿐만 아니라 성능 측면에서도 매우 우수한 자바스크립트 런타임이라는 것을 알 수 있습니다.

Bun Benchmark

Bun이 Node.js를 대체할까?

Bun은 스스로를 Node.js의 완벽하게 대체할 수 있는 자바스크립트 런타임으로 마케팅하고 있는데요. 하지만 Bun의 주장처럼 “a drop-in replacement for Node.js”, 즉 Node.js를 쓰고 있던 프로젝트에서 아무런 변경없이 바로 Bun을 사용해도 문제가 되지 않을 수준이 되기에는 아직 갈 길이 많이 남은 것으로 보입니다. Node.js 진영에서도 Bun은 Node.js를 대체할 수 없다고 맞불을 놓으며 여러 커뮤니티에서 신경전이 벌어지고 있는데요.

과연 Bun이 Node.js를 대체할 수 있을까요? 글쎄요… 거의 15년이 다 되어가는 Node.js의 아성을 Bun이 얼마나 빠른 시간에 무너뜨릴 수 있을지 의문입니다. 2018년에 Deno가 처음 나왔을 때도 비슷한 기대와 논쟁이 있었지만 여전히 Node.js의 시장 점유율에 크게 미치치 못합니다.

하지만 Bun은 Deno가 했던 실수들을 반복하지 않으며 처음부터 npm을 지원하고 Node.js와의 호환성을 최우선하고 있어서 개발자들 사이에서 아주 긍정적인 반응을 얻고 있습니다. 뿐만 아니라, 커뮤니티 활동에 다소 인색한 Node.js에 비해, Bun의 커뮤니티는 매우 활발하게 사용자들 소통하고 있는 것으로 보입니다.

기업 입장에서 특별히 현재 성능 이슈가 있지 않다면 단순히 Bun이 더 빠르다고 해서 오랫동안 안정성이 검증된 Node.js를 떠나기는 쉽지 않을 것입니다. 아무래도 백엔드 서버의 런타임을 교체하는데는 적지 않은 위험 부담이 따르기 때문입니다. 하지만 팀 단위로 진행되는 신규 프로젝트에서는 당장 Bun을 도입하여 개발 프로세스를 단순화하고 개발자 경험을 개선할 수도 있을 것 같습니다. 개인 프로젝트에서는 Bun을 쓰지 않을 이유를 딱히 찾기 어려울 정도로 사용성이 좋은 것 같습니다.

마치면서

지금까지 차세대 자바스크립트 런타임으로 주목받고 있는 Bun에 대해서 알아보았습니다.

컴파일, 테스트, 트랜스파일링, 번들링을 Bun 하나로 해결할 수 있으니 가히 올인원(All-in-one) 런타임이라고 불러도 손색이 없을 것 같습니다. 이렇게 다양한 기능을 한 번에 제공하면서 성능도 기존의 다른 런타임을 압도할 수 있다니 정말 놀랍지 않나요?

마지막으로 Bun의 등장을 너무 기존 런타임과의 경쟁 구도에서 바라 보실 필요는 없다라고 말씀드리고 싶은데요. 저는 개인적으로 자바스크립트 생태계에 새로운 런타임이 생겼다는 것은 매우 반길만한 일이라고 생각합니다. Node.js와 Deno, Bun 그리고 앞으로 등장할 다른 런타임들이 서로의 좋은 점을 취하면서 선의의 경쟁을 펼칠테니까요. 경쟁에서 어떤 런타임이 살아남고 도태되든, 자바스크립트 개발자들은 더 나은 런타임을 누릴 수 있겠죠?

여러분들도 배워야 할 기술이 하나 더 늘었다고 너무 스트레스 받으시지 말고, 당장 쓰실 일은 없더라도 재미삼아 Bun을 한 번 직접 써보시기를 추천드리고 싶습니다. 😄