Bun v1.3.12 — 브라우저 자동화와 인프로세스 Cron이 기본 내장됐다

8 min read
Bun브라우저 자동화cronWebViewJavaScript
Bun v1.3.12 — 브라우저 자동화와 인프로세스 Cron이 기본 내장됐다

Bun이 브라우저를 품은 날

Bun v1.3.12가 나왔다. 이번 릴리스의 핵심은 두 가지다. 하나는 외부 의존성 없이 브라우저를 직접 제어할 수 있는 Bun.WebView, 다른 하나는 서버 프로세스 내부에서 cron 작업을 실행하는 Bun.cron 콜백 오버로드다. 둘 다 "별도 패키지를 설치하지 않아도 된다"는 점에서 Bun의 방향성을 잘 보여준다.

비유하면 이렇다. 지금까지 Node.js 생태계에서 브라우저 자동화는 Playwright나 Puppeteer라는 별도 도구를 챙겨야 하는 일이었다. 짐을 쌀 때 세탁기와 냉장고를 따로 챙기는 것처럼. Bun은 그 두 가지를 런타임 본체에 넣어버렸다.


Bun.WebView — 네이티브 브라우저 자동화

작동 원리

Bun.WebView는 두 가지 백엔드를 지원한다.

백엔드플랫폼의존성
WebKitmacOS 기본값시스템 WKWebView 사용, 외부 설치 없음
Chrome크로스플랫폼Chrome/Chromium + DevTools Protocol

가장 중요한 특징은 모든 입력 이벤트가 OS 수준의 네이티브 이벤트로 전달된다는 것이다. 사이트가 event.isTrusted를 검사해도 true로 보인다. 봇 탐지를 우회할 수 있다는 뜻이 아니라, 실제 사용자와 구분할 수 없을 만큼 자연스러운 이벤트가 발생한다는 뜻이다.

[💡 잠깐! 이 용어는?] isTrusted: 브라우저 이벤트 객체의 속성. 실제 사용자 입력은 true, JavaScript로 직접 생성된 이벤트는 false다. 일부 사이트는 이 값으로 자동화 여부를 탐지한다.

선택자 기반 메서드는 Playwright처럼 요소가 클릭 가능한 상태가 될 때까지 자동으로 대기한다. await만 붙이면 된다.

기본 사용법

scripts/screenshot.js
await using view = new Bun.WebView({ width: 800, height: 600 })
await view.navigate("https://bun.sh")
 
await view.click("a[href='/docs']")
await view.scroll(0, 400)
await view.scrollTo("#install")
 
const title = await view.evaluate("document.title")
const png = await view.screenshot({ format: "jpeg", quality: 90 })
await Bun.write("page.jpg", png)

await using 구문은 블록이 끝나면 자동으로 WebView를 정리한다.

[💡 잠깐! 이 용어는?] Explicit Resource Management (using): TC39 Stage 4 제안으로 구현된 자원 관리 패턴. using 키워드로 선언된 객체는 스코프를 벗어날 때 자동으로 [Symbol.dispose]()가 호출된다. try/finally로 직접 정리하지 않아도 된다.

주요 API

메서드설명
navigate(url)URL로 이동, 로드 완료까지 대기
evaluate(expr)페이지 컨텍스트에서 JavaScript 실행
screenshot({ format, quality })PNG/JPEG/WebP 스크린샷 반환
click(selector)CSS 선택자로 클릭, 작업 가능 상태 자동 대기
type(text)포커스된 요소에 텍스트 입력
scroll(dx, dy)델타 기준 스크롤
scrollTo(selector)요소가 보이도록 스크롤
cdp(method, params)Raw Chrome DevTools Protocol 호출

Bun.cron — 인프로세스 작업 스케줄러

OS cron과 뭐가 다른가

기존 Bun.cron(path, schedule, title)은 OS 수준의 crontab/launchd/Task Scheduler 항목을 등록했다. 이번 릴리스에서 추가된 콜백 오버로드는 다르다. 프로세스 내부에서 실행된다.

방식실행 위치앱 상태 접근의존성
OS cron별도 프로세스불가 (새 프로세스)crontab/systemd
인프로세스 cron현재 프로세스 내DB 풀, 캐시, 모듈 상태 직접 접근없음

데이터베이스 커넥션 풀, 인메모리 캐시, 모듈 수준 상태를 그대로 쓸 수 있다는 게 핵심이다.

기본 사용법

server/jobs.js
Bun.cron("* * * * *", async () => {
  await cleanupExpiredSessions()
})

에러가 발생해도 다음 실행은 정상적으로 예약된다. setTimeout과 같은 방식으로 처리된다.

server/jobs.js
process.on("unhandledRejection", (err) => {
  console.error("cron 실패:", err)
})
 
Bun.cron("0 3 * * *", async () => {
  await generateDailyReport()
})

Disposable 패턴으로 자원 관리

server/cleanup.js
{
  using job = Bun.cron("0 9 * * *", async () => {
    await sendMorningDigest()
  })
  // 블록을 벗어나면 job이 자동으로 중지된다
}

서버 종료 타이밍이나 테스트 후 정리에 유용하다.

동작 보장

  • 겹침 없음: 이전 실행(및 반환된 Promise)이 완료된 뒤에만 다음 실행을 예약한다. 실행 시간이 주기보다 길어져도 중복 실행이 없다.
  • UTC 기준: 0 9 * * *는 시스템 시간대와 무관하게 9:00 UTC다.
  • --hot 안전: bun --hot으로 핫 리로드할 때 모든 인프로세스 cron은 모듈 재평가 전에 자동 제거된다.

Markdown 터미널 렌더링

이번 릴리스에 포함된 소소한 기능 하나 더. Bun.markdown.ansi()로 Markdown 텍스트를 터미널 ANSI 색상 문자열로 변환할 수 있다.

scripts/help.js
const output = Bun.markdown.ansi("# Hello\n\n**볼드** and *이탤릭*\n")
process.stdout.write(output)
 
const withLinks = Bun.markdown.ansi("[공식 문서](https://bun.sh)", {
  hyperlinks: true,
})
process.stdout.write(withLinks)

CLI 도구를 만들 때 별도 라이브러리 없이 도움말 텍스트를 예쁘게 출력할 수 있다.


마무리

Bun이 계속 "런타임 안에 다 넣겠다"는 전략을 실행하고 있다. Bun.WebView는 스크린샷 서비스나 테스트 자동화에서 Playwright 의존성을 제거하는 선택지가 된다. Bun.cron 콜백 오버로드는 별도 큐 서비스 없이 간단한 반복 작업을 서버 내부에서 처리할 수 있게 한다.

두 기능 모두 "이미 있는 도구가 충분한가"를 먼저 따져봐야 한다. Playwright의 크로스 브라우저 지원이나 Redis-backed 잡 큐가 필요한 상황이라면 다른 선택이 맞다. 하지만 의존성을 줄이고 싶은 프로젝트라면 꽤 매력적인 옵션이다.


참고:

관심 있을 만한 포스트

Bun vs Node.js vs Deno — 뭐가 다른지, 그래서 뭘 쓰면 좋은지 (2026 기준)

런타임 3대장 비교: 호환성(Node), 속도/번들(Bun), 올인원/보안(Deno). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.

BunNode.js

Zustand 소프트 삭제 — enumerable:false로 컴포넌트 크래시 없이 처리하기

JavaScript property descriptor의 enumerable 플래그를 활용해 삭제된 엔티티를 투명하게 처리하는 Zustand 패턴을 소개한다.

Zustand상태 관리

JavaScript 이미지 프리로딩 — 5가지 방법 비교

new Image, link preload, hidden div, Cache API, fetch — 각 프리로딩 방식의 장단점과 상황별 선택 기준을 정리한다.

JavaScript성능

JavaScript 물리 엔진 만들기 — 100줄로 구현하는 2D 물리 시뮬레이션

벡터 연산, 원 충돌 감지, 충격량 기반 응답까지 순수 JavaScript로 2D 물리 엔진을 직접 만든다.

Physics EngineCanvas

Native JSON Modules — 번들러 없이 JSON을 import하는 시대

Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.

JavaScriptESM

Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1

2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.

BabelJavaScript

Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다

Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.

BunNode.js

Error.isError() — realm을 넘나드는 안전한 에러 검사 API

instanceof Error가 iframe과 worker에서 실패하는 이유, 그리고 이를 근본적으로 해결하는 Error.isError()의 동작 원리를 정리한다.

JavaScriptError.isError