20 августа 2026 года вышел Bun 1.4 — первая стабильная версия после переписывания ядра с Zig на Rust. Среди кучи нововведений особо выделился Bun.WebView — API для управления браузером, встроенный прямо в рантайм. Simon Willison, автор sqlite-utils и Datasette, в тот же день попросил Claude Code for web собрать на базе этого API JSON-сервис для скриншотов и исполнения JavaScript на страницах. Результат — 139 строк TypeScript, ноль зависимостей, работает. Ниже — сам кейс: что за инструменты, как устроен код, сколько RAM жрёт и где есть подводные камни.
Это перевод-адаптация поста Simon Willison — A shot-scraper-style JSON API on Bun 1.4’s new Bun.WebView (20.08.2026) и опубликованного им исходного кода server.ts.
Что такое Bun.WebView
Bun.WebView — экспериментальный API, появившийся в Bun 1.3.12 и.shipнутый в 1.4. Суть: рантайм Bun умеет запускать headless-браузер и управлять им через Chrome DevTools Protocol (CDP), причём без сторонних зависимостей вроде Puppeteer или Playwright (документация Bun).
Как это работает на разных платформах:
- macOS — используется системный WKWebView (движок Safari). Браузер ставить не нужно: он уже есть в системе.
- Linux / Windows — Bun запускает установленный Chrome или Chromium headless с флагом
--remote-debugging-pipeи управляет им через CDP. Автоматически находит бинарник; если не находит — задаётся черезBUN_CHROME_PATH.
Один процесс Bun = один процесс Chrome. Каждый новый Bun.WebView() после первого — просто новая вкладка (вызов Target.createTarget). Поэтому создание WebView на каждый запрос дёшево по ресурсам.
API_Surface:
| Метод | Что делает |
|---|---|
navigate(url) |
Открывает страницу |
evaluate(js) |
Выполняет JavaScript, JSON-сериализует результат, ждёт промисы |
screenshot({format}) |
Снимает скриншот в PNG/JPEG/WebP |
click / type / press / scroll |
События ввода как настоящий пользователь |
.cdp(method, params) |
Прямой вызов любого метода CDP |
Bun 1.4 adds +1,517 tests from the Node.js test suite, reduces idle CPU usage by 5x, reduces memory usage by up to 35%, and starts 50% faster on Linux. It adds Bun.Image, Bun.WebView, Bun.markdown, Bun.cron(), Bun.Terminal…
«Bun 1.4 добавляет +1 517 тестов из тестового набора Node.js, снижает потребление CPU в простое в 5 раз, уменьшает потребление памяти до 35% и запускается на 50% быстрее в Linux. Добавлены Bun.Image, Bun.WebView, Bun.markdown, Bun.cron(), Bun.Terminal…»
— Bun Blog, Bun 1.4 (20.08.2026)
Для context: Willison уже писал о переписывании Bun с Zig на Rust — релиз 1.4 стал первым стабильным релизом после этого перехода.
Что такое Claude Code for web
Claude Code — агентный кодинг-инструмент от Anthropic: запускается в терминале, общается с моделью Claude, умеет читать файлы, писать код, запускать команды и работать с git. Версия «for web» — облачная, работает через браузер без локальной установки (Anthropic).
В данном кейсе Willison использовал Claude Code for web для написания всего проекта — от архитектуры до финального server.ts. На выходе один файл на 139 строк и ноль npm-зависимостей.
Что построил Уиллисон
Проект bun-webview-json-api — JSON-API для two вещей: исполнения JavaScript на загруженной странице и получения скриншотов. Willison вдохновился своим CLI-инструментом shot-scraper javascript — но хотел HTTP-API вместо одного CLI-вызова.
Эндпоинты
POST /javascript {"url": "...", "javascript": "...", "wait_ms": 500}
→ {"ok": true, "result": <json>}
POST /screenshot {"url": "...", "width": 1280, "height": 800,
"format": "png|jpeg|webp", "quality": 80,
"javascript": "...", "b64": false}
→ image bytes (или base64 JSON)
GET /healthz → {"ok": true}
Каждый запрос создаёт свежий WebView (= вкладку Chrome), открывает страницу, делает работу и закрывает вкладку. Это делает сервис concurrency-safe по умолчанию: evaluate() допускает только один параллельный вызов на WebView, а WebView создаются на запрос.
Пример вызова
curl -X POST localhost:8044/javascript -d '{
"url": "https://example.com/",
"javascript": "new Promise(done => done({title: document.title, h2: document.querySelector(\"h2\")?.textContent}))"
}'
{
"ok": true,
"result": {
"title": "Example Domain",
"h2": "Example Domain"
}
}
Запуск
# macOS — без переменных окружения (системный WebKit)
bun server.ts
# Linux — указываем Chrome
BUN_CHROME_PATH=/usr/bin/chromium CHROME_EXTRA_ARGS="--no-sandbox" bun server.ts
Как устроен код
Весь сервер — один файл server.ts на 139 строк (128 lines of code). Ключевые части:
Фабрика WebView:
function makeView(width = 1280, height = 800) {
const backend =
process.platform === "darwin" && !process.env.BUN_CHROME_PATH
? undefined // системный WebKit
: ({
type: "chrome",
...(process.env.BUN_CHROME_PATH ? { path: process.env.BUN_CHROME_PATH } : {}),
argv: extraArgs,
} as const);
return new Bun.WebView({ backend, width, height } as any);
}
Обёртка для запроса с гарантированным закрытием вкладки:
async function withView<T>(
body: JsBody,
fn: (view: InstanceType<typeof Bun.WebView>) => Promise<T>,
): Promise<T> {
const view = makeView(body.width ?? 1280, body.height ?? 800);
try {
await view.navigate(body.url);
if (body.wait_ms) await Bun.sleep(body.wait_ms);
return await fn(view);
} finally {
view.close();
}
}
HTTP-сервер через Bun.serve() с маршрутизацией: "/" — справочник, "/healthz" — проверка работоспособности (открывает about:blank, считает 1 + 1), "/javascript" — выполнение JS, "/screenshot" — скриншот.
Интерфейс JsBody объединяет параметры всех эндпоинтов:
interface JsBody {
url: string;
javascript?: string;
wait_ms?: number;
width?: number;
height?: number;
format?: "png" | "jpeg" | "webp";
quality?: number;
b64?: boolean;
}
Зависимости — ноль. Bun.serve() встроенный, Bun.WebView встроенный, типы определены в файле. Единственный внешний бинарник — сам Chrome, но это системная зависимость, а не npm-пакет.
Потребление RAM: сколько нужно контейнеру
Willison провёл бенчмарки через cgroup с жёстким memory.limit_in_bytes (без swap — при превышении OOM kill). Методology: бинарный поиск минимального лимита, при котором смешанная нагрузка (выполнение JS на лёгких и тяжёлых страницах + скриншоты 1280×800 со страницы с 500 DOM-узлами, canvas и массивом в 100k элементов) проходит без падений.
| Конфигурация | Мин. RAM | Не работает |
|---|---|---|
| Полный Chromium, JS + скриншоты | 168 MB | 160 MB |
| Chromium headless_shell, JS + скриншоты | 104 MB | 96 MB |
| headless_shell + флаги¹, JS + скриншоты | 88 MB | 80 MB |
| headless_shell + флаги¹, только JS | 56 MB | 48 MB |
¹ Флаги: --no-zygote --renderer-process-limit=1 --js-flags=--max-old-space-size=32 --disable-dev-shm-usage
Ключевые наблюдения:
- Bun-процесс сам потребляет только ~27 MB PSS — всё остальное съедает Chrome.
- Playwright-сборка headless_shell — главный выигрыш: она пропускает GPU-процесс, UI-зависимости и большинство утилитных процессов (~103 MB пика вместо ~176 MB у полного Chrome).
- Для продакшена: контейнер на 128 MB спокойно тянет headless_shell со скриншотами для лёгких страниц; для тяжёлых реальных сайтов стоит выделить 192–256 MB.
Производительность
На контейнере (16 ГБ RAM, shared CPU) — замеры по одному запросу, включая создание вкладки / навигацию / закрытие:
- /javascript — ~64 мс
- /screenshot тяжёлая страница — ~308 мс
- 8 параллельных JS-запросов — 193 мс суммарно
- 8 параллельных скриншотов — 1.7 с суммарно
Модель «один Chrome, вкладка на запрос» хорошо масштабируется: выделение WebView — дешёвая операция (Target.createTarget), параллелизм идёт на уровне вкладок, а не процессов.
Подводные камни
Willison честно разложил проблемы, с которыми столкнулся:
-
Root без
--no-sandbox— Chrome сразу падает: «Chrome process closed the pipe». В контейнерах и CI это стандартная проблема. -
TLS-интерцептирующие прокси — прокси в песочнице не могли распарсить TLS 1.3 ClientHello от Chrome (постквантовый X25519MLKEM768 key share, ~1.7 КБ). Решение для полного Chrome —
--ssl-version-max=tls1.2. headless_shell не поддерживает эту настройку — бенчмарки шли через localhost. -
evaluate()принимает выражение, а не инструкцию — голыйthrow— syntax error. Ошибки страницы отдаются как reject с текстом ошибки. -
API экспериментальный — имена методов и структура параметров могут измениться в следующих версиях.
-
Puppeteer/Playwright не используются — нет драйверов, нет API-слоя над CDP. Всё на прямом
Bun.WebView, поэтому при проблемах с специфичными CDP-методами обойтись нечем.
Реальные российские реалии
Bun — Open Source (MIT), ставится через npm, brew, docker или curl | bash. В России работает без ограничений, репозиторий oven-sh/bun доступен. Проблем с установкой или npm-реестром нет — npm install -g bun проходит штатно.
Chrome / Chromium — для Linux-серверов головной болью становится не сам браузер (он ставится через apt или yum), а его потребление RAM. В контейнерах с лимитами памяти (typical для VPS из 512–1024 MB) даже headless_shell на 88–104 MB — существенная доля. На дешёвых VPS без swap OOM-killer убьёт процесс при сложной странице. Практический совет: для продакшена выделить отдельный контейнер с лимитом 192–256 MB, если планируется работа со скриншотами.
Claude Code for web — доступен из РФ через VPN. Anthropic не принимает российские карты, оплата — через иностранные посредники, крипту или виртуальные карты (см. наш гид по установке Claude Code). Бесплатный тариф есть, но с лимитами; для серьёзных задач нужна подписка или API-ключ.
Русскоязычные материалы по Bun в целом скудны. Тема обсуждается в Telegram-чатах (Bun Russia), на vc.ru и Tproger — но глубоких разборов Bun.WebView на русском на момент публикации нет. Для фактчекинга стоит смотреть на блог Bun и GitHub oven-sh/bun.
Где это применимо
API, как его задумал Willison, — это lightweight-альтернатива Puppeteer/Playwright для:
- Скриншотов сайтов по API — получить PNG-изображение любой страницы по HTTP-запросу, без запуска отдельного процесса Puppeteer на каждый снимок.
- Парсинг JavaScript-рендеринга — выполнять JS на странице и получать результат в JSON, как делает shot-scraper, но через HTTP.
- Сервисы мониторинга — проверять, что страница рендерится корректно, элементы на месте, заголовки совпадают.
- CI/CD-пайплайны — получать скриншоты без установки Playwright (нужен только Chromium headless).
Не подходит, если нужна полная автоматизация (form submission, мультистраничные сценарии, куки-менеджмент) — тут Puppeteer/Playwright всё ещё стандарт. Буновский WebView — про одну операцию на страницу: открыл → сделал → закрыл.
Ограничения
- API экспериментальный — стабильность и совместимость не гарантированы.
Bun.WebViewпривязан к Bun 1.4+ — нельзя использовать в Node.js или Deno.- На macOS используется WebKit, на Linux — Chrome; поведение может различаться (движки рендеринга разные).
- Нет стейт-менеджмента между запросами: каждый запрос — свежий контейнер без куки, без авторизации, без истории.
- Memory consumption определяется страницей, а не сервисом: тяжёлый SPA на React может потребовать больше, чем замеры на лёгких тестовых страницах.
- Willison отмечает: minimum RAM замерялся на простых тестовых страницах; реальные сайты с тяжёлым JS, аналитикой и трекерами будут есть больше.
Вывод
Simon Willison показал, что Bun 1.4 + Bun.WebView дают минимальный headless-браузер API за 139 строк TypeScript без зависимостей. Claude Code for web справился с написанием всего сервера за один вечер. RAM-профиль (56–168 MB в зависимости от конфигурации) делает это жизнеспособным для контейнерных деплоев. Пока API экспериментальный и не заменяет Playwright для сложных сценариев — но для простых задач (скриншоты, исполнение JS, проверка рендеринга) вариант уже рабочий.
