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는 두 가지 백엔드를 지원한다.
| 백엔드 | 플랫폼 | 의존성 |
|---|---|---|
| WebKit | macOS 기본값 | 시스템 WKWebView 사용, 외부 설치 없음 |
| Chrome | 크로스플랫폼 | Chrome/Chromium + DevTools Protocol |
가장 중요한 특징은 모든 입력 이벤트가 OS 수준의 네이티브 이벤트로 전달된다는 것이다. 사이트가 event.isTrusted를 검사해도 true로 보인다. 봇 탐지를 우회할 수 있다는 뜻이 아니라, 실제 사용자와 구분할 수 없을 만큼 자연스러운 이벤트가 발생한다는 뜻이다.
[💡 잠깐! 이 용어는?]
isTrusted: 브라우저 이벤트 객체의 속성. 실제 사용자 입력은 true, JavaScript로 직접 생성된 이벤트는 false다. 일부 사이트는 이 값으로 자동화 여부를 탐지한다.
선택자 기반 메서드는 Playwright처럼 요소가 클릭 가능한 상태가 될 때까지 자동으로 대기한다. await만 붙이면 된다.
기본 사용법
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 풀, 캐시, 모듈 상태 직접 접근 | 없음 |
데이터베이스 커넥션 풀, 인메모리 캐시, 모듈 수준 상태를 그대로 쓸 수 있다는 게 핵심이다.
기본 사용법
Bun.cron("* * * * *", async () => {
await cleanupExpiredSessions()
})에러가 발생해도 다음 실행은 정상적으로 예약된다. setTimeout과 같은 방식으로 처리된다.
process.on("unhandledRejection", (err) => {
console.error("cron 실패:", err)
})
Bun.cron("0 3 * * *", async () => {
await generateDailyReport()
})Disposable 패턴으로 자원 관리
{
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 색상 문자열로 변환할 수 있다.
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). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.
Zustand 소프트 삭제 — enumerable:false로 컴포넌트 크래시 없이 처리하기
JavaScript property descriptor의 enumerable 플래그를 활용해 삭제된 엔티티를 투명하게 처리하는 Zustand 패턴을 소개한다.
JavaScript 이미지 프리로딩 — 5가지 방법 비교
new Image, link preload, hidden div, Cache API, fetch — 각 프리로딩 방식의 장단점과 상황별 선택 기준을 정리한다.
JavaScript 물리 엔진 만들기 — 100줄로 구현하는 2D 물리 시뮬레이션
벡터 연산, 원 충돌 감지, 충격량 기반 응답까지 순수 JavaScript로 2D 물리 엔진을 직접 만든다.
Native JSON Modules — 번들러 없이 JSON을 import하는 시대
Import Attributes와 함께 표준이 된 native JSON module. 어떻게 동작하고, 기존 번들러 방식과 뭐가 다른지 정리했다.
Babel 7.29.0 — 10년 역사의 마지막 마이너, 그리고 8 RC1
2026년 1월 31일, Babel 7의 마지막 마이너 릴리스가 공개됐다. 이 버전이 갖는 역사적 의미와 Babel 8 RC1의 핵심 변화를 정리한다.
Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다
Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.
Error.isError() — realm을 넘나드는 안전한 에러 검사 API
instanceof Error가 iframe과 worker에서 실패하는 이유, 그리고 이를 근본적으로 해결하는 Error.isError()의 동작 원리를 정리한다.