본문 바로가기
Translate

[번역] Bun vs Node.js 알아야 할 모든 것

by viae 2023. 10. 27.
출처: Bun vs Node.js: Everything you need to know

 

 

9월 8일, 자바스크립트 커뮤니티에 새로운 소식이 들려왔습니다. Jarred Sumner가 만든 Bun v1.0이 출시되었기 때문입니다. 하지만 많은 사람들이 궁금해하는 것이 있습니다: Bun의 본질은 무엇일까요? 왜 모두가 이미 검증된 Node.js와 유사점을 찾는 걸까요? Bun은 일시적인 트렌드에 불과한 것일까요, 아니면 게임을 재정의하기 위해 등장한 것일까요? 이 글에서는 Bun에 대해 자세히 알아보고, 그 기능을 확인하며, 잘 정립된 Node.js와 어떻게 비교되는지 알아보겠습니다.

 

 

Bun이란 무엇인가요?

Bun은 자바스크립트 및 타입스크립트 앱을 위한 초고속 올인원 툴킷입니다. Bun의 장점은 개발 프로세스를 간소화하여 그 어느 때보다 원활하고 효율적으로 만들 수 있다는 점입니다. 이는 Bun이 단순한 런타임이 아니라 패키지 관리자, 번들러, 테스트 러너의 역할을 동시에 수행하기 때문에 가능합니다. JS 개발을 위한 스위스 군용 칼이 있다고 상상해 보세요. 바로 Bun입니다.

 

 

Bun의 솔루션

2009년 Node.js의 등장은 획기적이었습니다. 하지만 많은 기술이 그렇듯 성장하면서 복잡성도 함께 증가했습니다. 도시라고 생각해보세요. 도시가 확장되면 교통 혼잡이 문제가 될 수 있습니다.

Bun은 이러한 혼잡을 완화하여 더 원활하고 빠르게 움직일 수 있는 새로운 인프라가 되는 것을 목표로 합니다. 기존의 것을 재창조하는 것이 아니라 개선하여 속도와 단순성을 확보하는 동시에 자바스크립트의 독특하고 강력한 본질을 잃지 않도록 하는 것입니다.

Bun은 더 빠르고 간결하며 현대적인 Node.js를 대체할 수 있도록 설계되었으므로 몇 가지 비교를 자세히 살펴보겠습니다. 하지만 먼저 한 가지 다른 주제에 대해 논의해 보겠습니다.

 

 

Node.js 대 Deno 대 Bun 비교

자바스크립트 런타임의 진화를 논할 때 Deno를 간과하기는 어렵습니다. Node.js의 창시자인 Ryan Dahl은 Node.js의 문제점과 아쉬움을 해결하기 위한 새로운 런타임으로 Deno를 소개했습니다.

Deno는 JavaScript와 TypeScript를 위한 보안 런타임입니다. 이 런타임은 Node.js의 여러 단점을 직접적으로 해결합니다. 예를 들어, Deno는 외부 도구 없이도 TypeScript를 기본적으로 지원합니다. 스크립트에 기본적으로 광범위한 권한이 부여되는 Node.js와 달리 Deno는 개발자가 파일 시스템 액세스 또는 네트워크 연결과 같이 잠재적으로 민감한 작업에 대한 권한을 명시적으로 부여해야 하는 보안 우선 접근 방식을 채택합니다.

Deno는 Node.js에 대한 강력한 대안을 제시하지만, Node.js의 광범위한 채택에는 미치지 못합니다. 따라서 이 글에서는 Bun과 잘 정립된 Node.js를 대조하는 데 중점을 두었습니다.

 

 

시작하기

Bun을 사용하면 bun init -y 명령으로 빈 프로젝트를 스캐폴드할 수 있습니다. 몇 개의 파일이 생성되었으며 index.ts에 console.log("Hello, Bun!") 줄을 추가합니다. 터미널에서 bun index.ts 명령을 실행하면 "Hello, Bun!"이 기록되는 것을 확인할 수 있습니다.

 

 

Bun과 Node.js 비교: JavaScript 런타임

자바스크립트 런타임은 자바스크립트 프로그램을 사용하고 실행하는 데 필요한 모든 구성 요소를 제공하는 환경입니다.

Node.js와 Bun은 모두 런타임입니다. Node.js는 주로 C++로 작성된 반면, Bun은 Zig라는 저수준 범용 프로그래밍 언어로 작성되었습니다. 하지만 이것은 빙산의 일각에 불과합니다. Bun을 런타임으로만 취급할 때 다른 차이점을 자세히 살펴보겠습니다.

 

자바스크립트 엔진

자바스크립트 엔진은 우리가 작성한 자바스크립트 코드를 컴퓨터가 특정 작업을 수행할 수 있도록 기계어 코드로 변환하는 프로그램입니다.

Node.js는 Chrome 브라우저를 구동하는 Google의 V8 엔진을 사용하는 반면, Bun은 Apple에서 Safari용으로 개발한 오픈 소스 JavaScript 엔진인 JavaScriptCore(JSC)를 사용합니다.

V8과 JSC는 아키텍처와 최적화 전략이 다릅니다. JSC는 실행 시간은 약간 느리지만 빠른 시작 시간과 메모리 사용량 감소를 우선시합니다. 반면 V8은 런타임 최적화를 통해 빠른 실행을 우선시하므로 메모리 사용량이 늘어날 수 있습니다.

따라서 Bun은 Node.js보다 최대 4배 빠르게 시작됩니다.

요약: bun은 deno보다 2.19배, node보다 4.81배 빠르게 실행되었습니다.

 

트랜스파일러

Node.js는 JavaScript를 위한 강력한 런타임이지만 기본적으로 TypeScript 파일을 지원하지 않습니다. Node.js 환경에서 TypeScript를 실행하려면 외부 종속성이 필요합니다. 한 가지 일반적인 접근 방식은 빌드 단계를 사용하여 TypeScript(TS)를 JavaScript(JS)로 트랜스파일한 다음 결과 JS 코드를 실행하는 것입니다. 다음은 ts-node 패키지를 사용하는 기본 설정입니다:

 

1. Installation

 
npm install -D typescript ts-node

 

2. Script configuration

package.json에서 스크립트를 설정하여 프로세스를 간소화할 수 있습니다.

{
  "scripts": {
    "start": "ts-node ./path/to/your/file.ts"
  }
}

 

3. Execution

위의 스크립트를 사용하면 TypeScript 파일을 쉽게 실행할 수 있습니다.

npm start

 

이와는 대조적으로 Bun은 보다 간소화된 접근 방식을 제공합니다. 런타임에 JavaScript 트랜스파일러가 통합되어 있습니다. 이를 통해 .js, .ts, .jsx 및 .tsx 파일을 직접 실행할 수 있습니다. Bun에 내장된 트랜스파일러는 이러한 파일을 바닐라 JavaScript로 원활하게 변환하여 추가 단계 없이 즉시 실행할 수 있도록 지원합니다.

bun index.ts

 

Node.js를 실행하기 전에 변환 단계를 거쳐야 하므로 TypeScript 파일을 실행할 때 속도 차이가 더 커집니다.

 

 

ESM 및 CommonJS 호환성

모듈 시스템을 사용하면 개발자가 코드를 재사용 가능한 세그먼트로 구성할 수 있습니다. JavaScript에서 두 가지 주요 모듈 시스템은 CommonJS와 ES 모듈(ESM)입니다. Node.js에서 시작된 CommonJS는 동기식 모듈 처리를 위해 require 및 module.exports를 사용하며, 서버 측 작업에 이상적입니다.

ES6에 도입된 ESM은 가져오기 및 내보내기 문을 사용하여 브라우저와 최신 빌드 도구에 최적화된 보다 정적이고 비동기적인 접근 방식을 제공합니다. 콘솔에 컬러 출력을 추가하고 모듈 시스템을 더 잘 이해하기 위해 널리 사용되는 두 가지 패키지인 CommonJS에는 색상을, ESM에는 초크를 사용해 보겠습니다.

Node.js는 전통적으로 CommonJS 모듈 시스템과 연관되어 있습니다. 다음은 일반적인 사용 예시입니다:

// CommonJS in Node.js (index.js)
const colors = require("colors");
console.log(colors.green('Hello, world!'));

Node.js의 ES 모듈의 경우 두 가지 옵션 중 하나를 사용할 수 있습니다:

  1. 패키지 json에 "type": "module"을 포함해야 합니다.
  2. .mjs 확장자를 사용합니다.
// ESM in Node.js (index.mjs)
import chalk from 'chalk';
console.log(chalk.blue('Hello, world!'));

 

CommonJS에서 ES 모듈(ESM)로의 전환은 복잡한 여정이었습니다. Node.js는 ESM이 도입된 지 반년이 지난 후에야 실험적 플래그 없이 이를 지원하게 되었습니다. 그럼에도 불구하고 CommonJS는 여전히 에코시스템에서 널리 사용되고 있습니다.

Bun은 특별한 구성 없이 두 가지를 모두 지원하여 모듈 시스템을 단순화합니다. Bun의 뛰어난 기능은 동일한 파일에서 임포트와 require()를 모두 지원할 수 있다는 점인데, 이는 Node.js에서는 기본적으로 불가능한 기능입니다:

// Mixed modules in Bun (index.js)
import chalk from "chalk";
const colors = require("colors");

console.log(chalk.magenta('Hello from chalk!'));
console.log(colors.cyan('Hello from colors!'));

 

 

웹 API

브라우저 기반 애플리케이션에 필수적인 웹 API는 웹 상호 작용을 위한 페치 및 웹소켓과 같은 도구를 제공합니다. 이러한 도구는 브라우저 표준이 되었지만, Node.js와 같은 서버 측 환경에서의 지원은 일관되지 않았습니다.


이전 버전의 Node.js에서는 브라우저에서 일반적으로 사용할 수 있는 웹 표준 API가 기본적으로 지원되지 않았습니다. 개발자는 이 기능을 복제하기 위해 node-fetch와 같은 타사 패키지에 의존해야 했습니다. 하지만 Node.js v18부터는 fetch API가 실험적으로 지원되어 이러한 패키지가 필요하지 않게 될 가능성이 있습니다.


Bun은 이러한 웹 표준 API에 대한 기본 지원을 제공함으로써 이 작업을 간소화합니다. 개발자는 추가 패키지 없이도 안정적인 페치, 요청, 응답, 웹소켓 및 기타 브라우저와 유사한 API를 직접 사용할 수 있습니다. 또한, Bun은 이러한 웹 API를 기본적으로 구현하기 때문에 타사 대안에 비해 더 빠르고 안정적입니다.


다음은 Node.js(v18 이상) 및 Bun과 모두 호환되는 예제입니다. Node.js에서는 실험적이지만 Bun에서도 동일한 기능이 안정적으로 작동합니다.

// Experiment fetch in Node.js (v18 and above) and built-in fetch in Bun
async function fetchUserData() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  console.log(user.name);
}

fetchUserData(); // Leanne Graham

 

 

핫 리로딩

핫 리로딩은 코드가 변경되면 전체 재시작 없이도 애플리케이션의 일부를 실시간으로 자동으로 새로 고치거나 다시 로드하여 개발자의 생산성을 높여주는 기능입니다.

 

Node.js 에코시스템에는 핫 리로딩을 달성하기 위한 몇 가지 옵션이 있습니다. 널리 사용되는 도구 중 하나는 전체 프로세스를 하드 리스타트하는 nodemon입니다.

nodemon index.js

 

또는 Node.js v18부터 실험적으로 --watch 플래그가 도입되었습니다.

node --watch index.js

 

두 방법 모두 코드 변경에 따라 애플리케이션을 실시간으로 다시 로드하는 것을 목표로 합니다. 그러나 특히 특정 환경이나 시나리오에서 서로 다른 동작을 보일 수 있습니다.

 

예를 들어, nodemon은 HTTP 및 WebSocket 연결이 끊어지는 등의 장애를 일으킬 수 있으며, --watch 플래그는 실험적이기 때문에 전체 기능을 제공하지 않을 수 있으며 GitHub 이슈에서 몇 가지 문제가 보고되었습니다.

 

Bun은 핫 리로딩을 한 단계 더 발전시켰습니다. hot 플래그와 함께 Bun을 실행하면 핫 리로딩이 활성화됩니다.

bun --hot index.ts

전체 프로세스를 다시 시작해야 하는 Node.js 메서드와 달리 Bun은 이전 프로세스를 종료하지 않고 제자리에서 코드를 다시 로드합니다. 따라서 HTTP 및 웹소켓 연결이 중단되지 않고 애플리케이션 상태가 유지되므로 보다 원활한 개발 환경을 제공합니다.

 

 

Node.js 호환성

새로운 런타임이나 환경으로 전환할 때 호환성은 개발자의 주요 관심사입니다. Bun은 Node.js를 대체하는 드롭인 방식으로 포지셔닝하여 이 문제를 해결합니다. 즉, 기존 Node.js 애플리케이션과 npm 패키지를 수정하지 않고도 Bun과 원활하게 통합할 수 있습니다. 이러한 호환성을 보장하는 주요 기능은 다음과 같습니다.

  • fs, path, net과 같은 기본 제공 Node.js 모듈 지원.
  • 디렉터리 이름 및 프로세스 같은 전역 변수를 인식합니다.
  • 익숙한 node_modules 구조를 포함한 Node.js 모듈 확인 알고리즘을 준수합니다.
Bun은 계속 진화하고 있습니다. 개발 워크플로우를 개선하도록 맞춤화되어 있으며 서버리스 기능처럼 리소스가 제한된 환경에 이상적입니다. Bun의 개발팀은 포괄적인 Node.js 호환성과 널리 사용되는 프레임워크와의 더 나은 통합을 위해 노력하고 있습니다.


Bun은 Node.js와의 호환성을 보장하지만, 여기서 멈추지 않습니다. Bun은 개발자에게 가장 필요한 기능을 위해 고도로 최적화된 표준 라이브러리 API를 제공합니다.

 

 

Bun API

Bun.file()

파일을 느리게 로드하고 다양한 형식으로 콘텐츠에 액세스합니다. 이 메서드는 Node.js 메서드보다 최대 10배 빠릅니다.

// Bun (index.ts)
const file = Bun.file("package.json");
await file.text();

// Node.js (index.mjs)
const fs = require("fs/promises");
const fileContents = await fs.readFile("package.json", "utf-8");

 

Bun.write()

문자열부터 블롭까지 디스크에 데이터를 쓸 수 있는 다용도 API입니다. Node.js보다 최대 3배 빠르게 기록합니다.

// Bun (index.ts)
await Bun.write("index.html", "<html/>");

// Node.js (index.mjs)
const fs = require("fs/promises");
await fs.writeFile("index.html", "<html/>");

 

Bun.serve()

웹 표준 API를 사용하여 HTTP 서버 또는 웹소켓 서버를 설정합니다. Node.js보다 초당 4배 더 많은 요청을 처리할 수 있고 Node.js의 ws 패키지보다 5배 더 많은 WebSocket 메시지를 처리할 수 있습니다. 이 백엔드 기능은 개발자가 Node.js에서 Express를 사용하는 방식과 유사하지만 Bun의 성능 최적화를 통해 추가적인 이점을 얻을 수 있습니다.

// Bun (index.ts)
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello from Bun!");
  },
});

// Node.js (index.mjs)
import http from "http";
const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello from Node.js!");
});
server.listen(3000);

Bun은 또한 sqlite와 비밀번호를 기본적으로 지원합니다.

 

 

Bun과 Node.js 비교: 패키지 관리자

Bun은 단순한 런타임이 아니라 강력한 패키지 관리자가 포함된 고급 툴킷입니다. 종속 요소를 설치하는 동안 인내심을 갖고 기다려야 했던 경험이 있다면 Bun은 놀랍도록 빠른 대안을 제공합니다. Bun을 런타임으로 사용하지 않더라도 내장된 패키지 관리자를 사용하면 개발 워크플로우의 속도를 높일 수 있습니다.


이 표에서 Bun 명령과 Node의 패키지 관리자 npm을 비교해 보세요.

Bun Npm Purpose
bun install
npm install
Install all dependencies from package.json
bun add <package>
npm install <package>
Add a new package to the project
bun add <package> --dev
npm install <package> --dev
Add a new development-only package
bun remove <package>
npm uninstall <package>
Remove a package from the project
bun update <package>
npm update <package>
Update a specific package to its latest version
bun run <script>
npm run <script>
Execute a specified script from package.json

언뜻 보기에 Bun의 명령은 익숙해 보이지만 경험은 전혀 평범하지 않습니다. Bun은 npm보다 몇 배나 빠른 설치 속도를 자랑합니다. 이는 글로벌 모듈 캐시를 활용하여 npm 레지스트리에서 중복 다운로드를 제거함으로써 달성할 수 있습니다. 또한 Bun은 각 운영 체제에서 사용할 수 있는 가장 빠른 시스템 호출을 사용하여 최적의 성능을 보장합니다.

 

다음은 캐시에서 시작 Remix 프로젝트의 종속 요소를 설치하는 속도 비교로, Bun과 npm을 비교한 것입니다.

bun CLI에는 npm, yarn, pnpm을 획기적으로 빠르게 대체할 수 있도록 설계된 Node.js 호환 패키지 관리자가 포함되어 있습니다.

또한, bun 실행 <command>는 7ms밖에 걸리지 않는 반면, npm 실행 <command>는 176ms가 걸립니다. Node.js의 npm은 수년 동안 자바스크립트 패키지 관리의 표준으로 사용되어 왔지만, Bun은 속도 면에서 정말 강력하며 매력적인 대안을 제시합니다.

 

 

Bun과 Node.js 비교: 번들러

번들링은 여러 JavaScript 파일을 가져와서 하나 이상의 최적화된 번들로 병합하는 프로세스입니다. 이 프로세스에는 TypeScript를 JavaScript로 변환하거나 코드를 축소하여 크기를 줄이는 등의 변환 작업도 포함될 수 있습니다.

 

Node.js 생태계에서 번들링은 일반적으로 Node.js 자체보다는 타사 도구에서 처리합니다. Node.js 세계에서 가장 인기 있는 번들러로는 코드 분할, 트리 쉐이킹, 핫 모듈 교체와 같은 기능을 제공하는 Webpack, Rollup, Parcel 등이 있습니다.


반면에 Bun은 런타임 및 패키지 관리자일 뿐만 아니라 그 자체로 번들러이기도 합니다. 브라우저의 프런트엔드 애플리케이션(React 또는 Next.js 애플리케이션) 및 Node.js를 비롯한 다양한 플랫폼용 JavaScript 및 TypeScript 코드를 번들링하도록 설계되었습니다.

 

Bun으로 번들링하려면 간단한 명령을 사용하면 됩니다.

bun build ./index.ts --outdir ./build

 

이 명령은 index.ts 파일을 번들링하여 ./build 디렉터리에 결과를 출력합니다. 번들링 프로세스는 매우 빠르며, Bun은 esbuild보다 1.75배 빠르며 Parcel 및 Webpack과 같은 다른 번들러보다 훨씬 빠릅니다.

Bun은 0.17초, esbuild 0.3초, rspack 4.45초, Parcel 2 26.32초, 롤업 32초, Webpack 5 38.02초가 소요됩니다.

 

Bun에서 눈에 띄는 기능은 자바스크립트 매크로를 도입한 것입니다. 이를 통해 번들링 중에 자바스크립트 함수를 실행하고 그 결과를 최종 번들에 직접 인라인 처리할 수 있습니다. 이 메커니즘은 번들링에 대한 새로운 관점을 제공합니다.

 

번들링 프로세스 중에 사용자 이름을 가져오는 데 Bun의 JavaScript 매크로를 활용하는 이 예시를 확인해 보세요. 매크로는 런타임 API를 호출하는 대신 번들 타임에 데이터를 가져와서 결과를 최종 출력에 직접 인라인 처리합니다:

// users.ts
export async function getUsername() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  return user.name;
}

// index.ts
import { getUsername } from "./users.ts" with { type: "macro" };
const username = await getUsername();

// build/index.js
var user = await "Leanne Graham";
console.log(user);

Node.js에는 이미 확립된 번들링 도구가 있지만, Bun은 번들링 환경을 재편할 수 있는 통합적이고 빠르며 혁신적인 대안을 제공합니다.

 

 

Bun과 Node.js 비교: 테스트 러너

테스트는 소프트웨어 개발의 중요한 측면으로, 코드가 예상대로 작동하는지 확인하고 프로덕션에 도달하기 전에 잠재적인 문제를 포착합니다. 런타임, 패키지 관리자, 번들러 역할을 하는 것 외에도 Bun은 테스트 러너 역할도 합니다.

 

Node.js 개발자는 전통적으로 테스트 요구 사항을 충족하기 위해 Jest에 의존해 왔지만, Bun은 속도, 호환성 및 최신 개발 워크플로우에 맞는 다양한 기능을 약속하는 빌트인 테스트 런너를 도입했습니다.

 

Bun의 테스트 러너인 bun:test는 '기대' 스타일 API로 잘 알려진 테스트 프레임워크인 Jest와 완벽하게 호환되도록 설계되었습니다. 이러한 호환성 덕분에 Jest에 익숙한 개발자는 가파른 학습 곡선 없이도 Bun으로 쉽게 전환할 수 있습니다.

import { test, expect } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

 

bun test 명령으로 간단하게 테스트를 실행할 수 있습니다. 또한 Bun의 런타임은 TypeScript와 JSX를 기본적으로 지원하므로 추가 구성이나 플러그인이 필요하지 않습니다.

 


Jest 또는 Vitest에서 마이그레이션

호환성에 대한 Bun의 노력은 Jest의 글로벌 임포트를 지원함으로써 빛을 발합니다. 예를 들어, @jest/global 또는 vitest에서 임포트하면 내부적으로 bun:test로 리매핑됩니다. 즉, 코드를 수정하지 않고도 기존 테스트 스위트를 Bun에서 실행할 수 있습니다.

// index.test.ts
import { test } from "@jest/globals";

describe("test suite", () => {
  test("addition", () => {
    expect(1 + 1).toBe(2);
  });
});

 

 

 

성능 벤치마크

Bun의 테스트 러너는 호환성뿐만 아니라 속도도 뛰어납니다. Zod 테스트 스위트에 대한 벤치마크에서 Bun은 Jest보다 13배, Vitest보다 8배 빠른 것으로 입증되었습니다. 이러한 속도 우위는 빠른 네이티브 코드로 구현된 Bun의 매처를 통해 더욱 두드러집니다. 예를 들어, Bun의 expect().toEqual()은 Jest보다 무려 100배, Vitest보다 10배 빠릅니다.

 

기존 테스트를 마이그레이션하든 새 프로젝트를 시작하든, Bun은 최신 개발 요구사항에 부합하는 강력한 테스트 환경을 제공합니다.

 

 

결론

Node.js는 오랫동안 자바스크립트 세계에서 벤치마크를 설정하고 개발자를 안내하는 초석 역할을 해왔습니다. 하지만 Bun은 한계를 뛰어넘으며 주목할 만한 도전자로 등장하고 있습니다.

 

아직은 초기 단계이지만 Bun이 불러일으키고 있는 화제는 부인할 수 없습니다. 현재 MacOS와 Linux에 최적화되어 있으며, Windows 지원도 진행 중이지만 일부 기능은 곧 추가될 예정입니다. 다양한 기능을 제공하는 Bun은 꼭 한 번 살펴봐야 할 툴킷입니다.