Перейти к содержимому

Кейс: Bun 1.4 + Claude Code for web — headless browser API за вечер

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
Схема: архитектура Bun.WebView JSON API
Как устроен запрос: клиент → Bun.serve() → Bun.WebView → Chrome/WebKit. На macOS используется встроенный WKWebView, на Linux — Chrome через CDP. Своя схема по данным репозитория simonw/research.

Как устроен код

Весь сервер — один файл 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.
Диаграмма: потребление RAM при разных конфигурациях
Замеры через cgroup: полный Chromium — 168 MB, headless_shell — 104 MB, с флагами — 88 MB, только JS — 56 MB. Источник: simonw/research.

Производительность

На контейнере (16 ГБ RAM, shared CPU) — замеры по одному запросу, включая создание вкладки / навигацию / закрытие:

  • /javascript — ~64 мс
  • /screenshot тяжёлая страница — ~308 мс
  • 8 параллельных JS-запросов — 193 мс суммарно
  • 8 параллельных скриншотов — 1.7 с суммарно

Модель «один Chrome, вкладка на запрос» хорошо масштабируется: выделение WebView — дешёвая операция (Target.createTarget), параллелизм идёт на уровне вкладок, а не процессов.

Подводные камни

Willison честно разложил проблемы, с которыми столкнулся:

  1. Root без --no-sandbox — Chrome сразу падает: «Chrome process closed the pipe». В контейнерах и CI это стандартная проблема.

  2. TLS-интерцептирующие прокси — прокси в песочнице не могли распарсить TLS 1.3 ClientHello от Chrome (постквантовый X25519MLKEM768 key share, ~1.7 КБ). Решение для полного Chrome — --ssl-version-max=tls1.2. headless_shell не поддерживает эту настройку — бенчмарки шли через localhost.

  3. evaluate() принимает выражение, а не инструкцию — голый throw — syntax error. Ошибки страницы отдаются как reject с текстом ошибки.

  4. API экспериментальный — имена методов и структура параметров могут измениться в следующих версиях.

  5. 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, проверка рендеринга) вариант уже рабочий.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка / 5. Количество оценок:

Оценок пока нет. Поставьте оценку первым.

Добавить комментарий

Adblock
detector