Содержание
Можно ли построить достаточно мощную современную среду выполнения клиентской части, отказавшись от обязательного компилятора и цепочки сборки? Voodoo.js отвечает «да» — но только если принять четыре жёстких ограничения: HTML без обязательного шага сборки, без eval / new Function, без Virtual DOM и без сторонних зависимостей времени выполнения. Смысл идеи мы разбирали в обзоре-сигнале, конвейер выражений — в разборе разборщика и интерпретатора. Здесь — архитектурная карта целиком: слои, компромиссы, сравнительные тесты, размер пакета и вопрос, не превращается ли «один script» снова в большой каркас.
Ключевые выводы
Цепочка важнее набора директив. Интерес проекта — не каталог v-*, а путь HTML → обходчик → выражения → область видимости → Proxy/эффекты → прямая запись в DOM.
Отказ от Virtual DOM — следствие модели, а не лозунг. Если среда выполнения знает, какой узел зависит от какого значения, сравнивать два дерева не нужно. Исключение — списки (v-for), где локальное согласование всё равно есть.
Компилятор переносит работу на время сборки; Voodoo.js — обратно во время выполнения. Отсюда честная позиция в сравнительных тестах: не «убийца React», а сосед по миллисекундам при другой цене входа.
Размер «одного файла» — серьёзный минус полной сборки. Ядро заметно легче полного пакета; сравнивать voodoo.full.min.js с крошечным Preact несимметрично, но факт остаётся: это не микроскопическая среда выполнения.
Главный риск зрелости — расползание возможностей. Маршрутизатор, HTTP, интерфейс, диаграммы, локализация… Если каркас раздуется до «всего сразу», исходное преимущество простоты может исчезнуть. Зато модель «HTML + среда выполнения» особенно сильна для генерации микроприложений с помощью ИИ.
Четыре ограничения и одна цепочка
Архитектура вырастает из ограничений, а не из желания «сделать как Vue, но в HTML»:
- приложение из обычного HTML без обязательной сборки;
- выражения без
eval/new Function; - без
Virtual DOM; - без зависимостей времени выполнения.
Упрощённая цепочка:
HTML
→ DOM Walker (директивы / компоненты / текст)
→ Lexer → Pratt Parser → AST
→ Interpreter
→ Scope lookup
→ Reactive Proxy / Effects
→ Direct DOM write
Детали восстановления вставок в духе JSX из «порезанного» браузером DOM и устройство интерпретатора разобраны в сопутствующем материале про разборщик. Здесь важно другое: это уже не маленький шаблонизатор, а стек слоёв с чёткими границами (ядро не знает про document; браузерные точки входа — единственная точка старта страницы, по ARCHITECTURE.md).
Эту границу легко пропустить, если читать только маркетинговые формулировки. Многие HTML-первые библиотеки смешивают «добавить поведение к узлам» и «владеть языком и реактивным графом». Voodoo.js владеет обоими. Обходчик — не удобный цикл; это заменитель компилятора. Область видимости — не мешок глобалов; это модель безопасности и поиска имён. Эффекты — не вспомогательный API; это единица работы, которая для обычных привязок заменяет сравнение деревьев.
HTML-разборщик браузера как промежуточное представление
Обычный JSX нельзя «просто вставить» в HTML: браузер не исполняет {user} как JavaScript. Voodoo.js использует след после HTML-разбора — текстовые узлы и элементы — как обратимое представление выражения, восстанавливает строку и гоняет её через собственный языковой конвейер.
JSX compiler: source → compiler → JS → DOM
Voodoo: source → browser HTML parser → DOM → runtime parser → AST → DOM
Компилятор обычно заранее превращает разметку в createElement или прямые операции с DOM. Voodoo.js делает эквивалент во время работы страницы. Фундаментальный компромисс:
Компилятор переносит работу из времени выполнения во время сборки.
Voodoo.jsпереносит её обратно во время выполнения.
Поэтому каркас не может одновременно быть самым простым и самым быстрым во всех сценариях. Рядом по духу «вернуть выразительность разметке» — htmx и HTML поверх WebSockets, но там чаще ось «сервер двигает HTML», а не клиентский язык выражений в документе.
Почему вообще называть это промежуточным представлением, а не хаком? Потому что на нём держится замысел. Если браузер нормализует фрагмент иначе, чем ожидает сканер восстановления, строка выражения уже неверна до старта лексера. Это не сноска; это цена использования платформенного HTML-разборщика как бесплатного входа в собственный язык. Командам, оценивающим Voodoo.js, стоит трактовать странные краевые случаи HTML — таблицы, пустые элементы, собственные элементы, неожиданные пробелы — как риски первого порядка, а не как «документацию позже».
Область видимости, реактивность и почему Virtual DOM «не нужен»
После разборщика возникает вопрос: где искать user? Собственная иерархия областей видимости (root → v-data → v-for → компонент) плюс «магии» и allowedGlobals даёт контролируемый поиск без превращения страницы в один глобальный контекст JavaScript. Это же — кусок модели безопасности без eval.
Обновление при count++ идёт не через перерендер всего компонента и сравнение деревьев, а через Proxy.set → срабатывание → эффекты, читавшие count → очередь → микрозадача → вычисление → запись в конкретный узел. Граф зависимостей концептуально:
WeakMap(target → Map(key → Set<ReactiveEffect>))
Отсюда отсутствие Virtual DOM как задачи: если эффект привязан к <strong>{count}</strong>, менять нужно textContent, а не согласовывать новое дерево со старым. Цена — много мелких эффектов и учёт зависимостей при каждом чтении; на огромных деревьях с тысячами привязок это может стать заметным. Архитектура проекта сама фиксирует: в отдельных формах очень больших списков Virtual DOM иногда выигрывает, а мелкозернистая модель — там, где обновляются только реально зависимые узлы.
Планировщик схлопывает пачку синхронных мутаций в один сброс; послесбросовый этап обслуживает жизненный цикл. Для списков честность требует оговорки: v-for всё равно мирит реальные блоки DOM (журнал мутаций, ключи, наибольшая возрастающая подпоследовательность) — см. глубокий разбор в статье про интерпретатор.
Для архитектурных обзоров именно этот слайд важен на встрече по дизайну. Менеджеры продукта слышат «без Virtual DOM» и слышат «волшебная скорость». Инженеры должны слышать «другая единица работы». Эффекты локализуют обновления; они также умножают учёт. Если ваш интерфейс — таблица на десять тысяч ячеек с пересекающимися производными значениями, вы не убегаете от сложности — вы выбираете, где она живёт. Voodoo.js кладёт эту сложность в реактивный граф и согласователь списков, а не в шаг компиляции.
Обходчик DOM, директивы и «чистый» DOM
runtime/walker.ts — двигатель: обойти узлы, собрать директивы, сначала решить терминальные v-for / v-if, создать область видимости, выполнить поведение, очистить служебные атрибуты, спуститься к детям. Порядок критичен: нельзя привязывать {item.name}, пока нет области итерации.
Принцип архитектуры: каждое декларативное поведение в HTML — директива. Приоритеты (FOR/IF/DATA/COMPONENT/…) позволяют наращивать возможности без переписывания обходчика. После обработки v-* / @ / : могут исчезнуть из видимой разметки, а исходники жить в WeakMap — DOM в инспекторе «чистый», метаданные — сбоку. MutationObserver (при autoDiscover) оживляет узлы, вставленные снаружи, и гасит эффекты при удалении; EffectScope связывает жизнь реактивного графа с жизнью поддерева.
Компонент здесь — не функция рендера, возвращающая виртуальное дерево, а оживлённая область существующего DOM с состоянием, свойствами и жизненным циклом. Свойства считаются в родительской области видимости. Это другая ментальная модель, чем React, и ближе к «документу с островами поведения».
Эта модель меняет представление о владении. В типичном одностраничном приложении дерево компонентов владеет DOM. В Voodoo.js документ владеет структурой; среда выполнения — накладками поведения. Поэтому очищенный вид в инспекторе — больше чем косметический трюк: он рекламирует контракт. То, что вы видите в DevTools после установки директив, ближе к тому, что дизайнер или редактор системы управления содержимым понимает как «страницу». То, что остаётся в памяти, — программа. Команды, живущие в каталогах компонентов вроде Storybook, почувствуют трение; команды, выкладывающие статические страницы с островами интерактивности, — облегчение.
Полная схема слоёв
Собранная карта:
HTML (source of truth)
→ DOM Walker + MutationObserver
→ Directives / Components / Text
→ Scope (лексическая иерархия)
→ Lexer → Parser → AST → Interpreter
→ Reactive Proxy (tracking)
→ Effects + microtask scheduler
→ Real DOM (text / style / attrs)
Сверху в реальном проекте ещё платформенные службы (HTTP, хранилище, маршрутизатор, локализация, интерфейс…) — и именно они питают парадокс «простого скрипта», о котором ниже.
Сравнение жизненных циклов:
| React (упрощённо) | Voodoo.js |
|
|---|---|---|
| Вопрос | Как получить правильный DOM из нового состояния? | Какие узлы зависят от изменившегося состояния? |
| Путь | состояние → рендер → виртуальное дерево → сравнение → латка | состояние → Proxy → граф → эффект → интерпретатор → запись в DOM |
| Svelte | сложный компилятор → простая среда выполнения | простой конвейер → более сложная среда выполнения |
По мелкозернистым обновлениям ближе к Solid, но Solid получает JSX через компилятор, Voodoo.js — через DOM + интерпретатор.
Читать таблицу как контрольный список — ошибка. React не «ошибается», спрашивая про всё дерево; этот вопрос подходит командам, которые хотят одну функцию рендера как единый источник истины. Voodoo.js задаёт более узкий вопрос, потому что разметка уже существует. Svelte платит во время компиляции, чтобы доставляемая среда выполнения оставалась тонкой. Voodoo.js платит во время выполнения, чтобы путь автора оставался «открыть файл». Ни один из этих ответов не бесплатен. Архитектура — это выбор, какой налог платить и когда.
Сравнительные тесты: что покупает отсутствие сборки
Опубликованные замеры автора (список из 1000 элементов, медиана; версия линии около 0.13 / состояние репозитория сентябрь 2026):
| Каркас | Создание 1k | Обновление каждого 10-го | Очистка 1k |
|---|---|---|---|
| Ванильный JS | 39.51 ms | 6.65 ms | 20.04 ms |
Preact |
71.03 ms | 2.73 ms | 30.68 ms |
Voodoo.js |
77.47 ms | 4.69 ms | 30.14 ms |
| Vue | 78.72 ms | 14.29 ms | 32.84 ms |
Solid |
80.13 ms | 0.90 ms | 21.85 ms |
| React | 81.22 ms | 4.65 ms | 33.55 ms |
Alpine |
157.06 ms | 111.29 ms | 32.76 ms |
Чтение: не уничтожает конкурентов; заметно быстрее Alpine на обновлении; близок к React в части сценариев; далеко от ванильного JS и от Solid на обновлении; при этом не требует компилятора JSX. Формула:
отсутствие шага сборки куплено измеримой ценой времени выполнения.
Про стоимость тяжёлых цепочек сборки в «обычном» мире см. также фрагментацию Turbopack.
Как команде пользоваться этой таблицей? Как вето на лозунги, а не как балл закупки. Если боль продукта — сложность холодного старта для внутренних инструментов, несколько десятков миллисекунд на создание могут быть приемлемы. Если боль — обновление плотных панелей шестьдесят раз в секунду, колонка обновления у Solid должна держать вас честными. Тезис Voodoo.js — не «победить в каждой ячейке». Это «оставаться достаточно конкурентоспособным, удалив обязательный инструментарий». Это утверждение о продукте не меньшей мере, чем о производительности.
Размер среды выполнения: неприятная правда
В опубликованных цифрах примерно:
voodoo.core.min.js ~141 KB min / ~48 KB gzip
voodoo.min.js ~265 KB min / ~85 KB gzip
voodoo.full.min.js ~442 KB min / ~134 KB gzip
Полный пакет тянет HTTP, формы, проверку, маршрутизатор, интерфейс, диаграммы, локализацию, анимацию и др. Сравнивать полный пакет с минимальным Alpine/Preact нечестно — но и говорить «просто один крошечный скрипт» тоже нельзя. Для внутренних панелей 50–130 KB после gzip часто приемлемы; для маркетинговой посадочной с жёстким бюджетом — уже предмет выбора точки входа (core против full) и трезвого аудита.
Размер тонко взаимодействует с архитектурной историей. Вес ядра частично — это стек языка и реактивности: цена работы компилятора в браузере. Вес полной сборки частично — продуктовая амбиция: желание быть платформой, а не библиотекой «присыпать поведение». Критики, атакующие только размер после сжатия, не разделяя эти две истории, спорят мимо сопровождающих. Защитники, лишь говорящие «берите ядро», не признавая, что многие руководства тянут людей к полному пакету, спорят мимо пользователей. Честный архитектурный обзор называет оба слоя.
Расползание возможностей и формат артефакта для ИИ
Стартовая мечта: <script src="voodoo.js"> и всё. Затем неизбежно появляются хранилища, маршрутизатор, HTTP, формы, интерфейс, диаграммы, перетаскивание, WebSocket, инструменты разработчика, командная строка… Экосистема растёт — и возникает вопрос: не съест ли полнота исходную простоту?
Здесь же — самый сильный аргумент «за» в эпоху ИИ-кодинга. Запрос «панель с таблицей, поиском и CRUD» сегодня часто рождает дерево Vite/React. Альтернатива:
index.html + одна среда выполнения
Документ снова переносим: отправить файлом, открыть локально, встроить, сгенерировать моделью, править без сервера разработки. Это меньше «новый React», больше новый формат артефакта — HTML-родной реактивный документ. Радикальный горизонт: не победа одного каркаса, а класс технологий «HTML + реактивная среда выполнения» вместо обязательной башни JSX/TS/компилятор/сборщик.
Где я бы поставил Voodoo.js сегодня (совпадает с вердиктом столбового материала):
| Сценарий | Оценка |
|---|---|
| Прототип / внутренний инструмент / микроприложение с ИИ | Сильно |
| Статическая страница с умеренной динамикой | Хорошо |
| Ядро крупного продукта, найм под React, жёсткая производительность/SEO | Рано / обычно нет |
Замена Solid/Svelte «потому что без сборки» |
Плохая мотивация |
Для рабочих процессов с ИИ именно формат артефакта важнее любой отдельной директивы. Модели уже умеют выдавать цельный HTML. Они плохо любят хрупкие графы конфигурации. Если критерий успеха сгенерированного интерфейса — «открыть этот файл и кликнуть», стеки в духе Voodoo.js сокращают расстояние между генерацией и проверкой. Это не отменяет ревью, доступность или дисциплину границ данных. Это меняет типичную форму результата с «репозиторий» на «документ». Командам, строящим конвейеры агентов, стоит трактовать эту форму как проектный выбор первого порядка, а не как ностальгию по двухтысячным.
Главный технический долг: свой язык выражений
Плюс безопасности собственного интерпретатора (белый список глобалов, политика безопасности содержимого без unsafe-eval) зеркально становится долгом: нужно сопровождать разбор, приоритеты операторов, массивы, объекты, функции, стрелки, вставки в духе JSX, ошибки, области видимости, границы безопасности. Каждое расширение языка приближает к вопросу:
Насколько далеко можно развить предметно-ориентированный язык, прежде чем он станет плохой копией JavaScript?
Слишком мало — каркас неудобен. Слишком много — вторая среда выполнения JavaScript внутри HTML. Разумный гибрид: настоящий JavaScript для логики и API, выражения Voodoo.js — для привязок в разметке.
Этот долг также организационный. Кто-то должен владеть краевыми случаями, когда выражение почти работает. Кто-то должен документировать, какие возможности языка намеренны, а какие случайны. Кто-то должен решить, стоит ли придумывать типы TypeScript для выражений в разметке. Зрелые каркасы амортизируют эту работу годами и компаниями. Молодая среда выполнения амортизирует её ночами и запросами на слияние. Если вы берёте Voodoo.js не только для песочницы, заложите сопровождение поверхности выражений так, будто сопровождаете небольшой язык — потому что именно это она и есть.
Частые вопросы
Чем эта статья отличается от разбора разборщика?
Короткий ответ: разборщик — вертикальное глубокое погружение в конвейер выражений; здесь — горизонтальная карта архитектуры, сравнительные тесты, размер и стратегия продукта.
Читайте оба: интерпретатор + этот материал.
Нужен ли Virtual DOM, если есть мелкозернистые эффекты?
Короткий ответ: для точечных привязок в этой модели — нет; для сложных списков согласование всё равно появляется.
Спор «виртуальный DOM плох навсегда» здесь не ставится.
Почему полный пакет такой большой?
Короткий ответ: потому что полный пакет — это уже платформа служб, не только реактивное ядро.
Берите core / узкие точки входа, если бюджет байт критичен.
Это хорошая цель для генерации ИИ?
Короткий ответ: да для однофайловых микроприложений; нет как слепая замена корпоративного стека React.
Меньше конфигов — короче путь «инструкция модели → кликабельный HTML», выше риск простыни без границ.
Стоит ли учить команду Voodoo.js вместо Svelte/Solid?
Короткий ответ: как расширение кругозора и инструмент песочниц — да; как единственную компетенцию команды продукта — нет.
Рынок и экосистема по-прежнему вокруг зрелых каркасов.
Где первоисточники?
Короткий ответ: репозиторий, ARCHITECTURE.md, сравнительные тесты и документация на GitHub; краткий анонс — новостной пост.
Ссылки: GitHub, ARCHITECTURE.md, сравнительные тесты.
Дальнейшее чтение
Что исследовать дальше
src/parser/— насколько интерпретатор приблизился к подмножеству JavaScript.src/reactivity/— сравнениеProxy/эффектов/планировщика с Vue 3 иSolid.runtime/walker.ts— динамический DOM и краевые случаи HTML.v-for— может ли каркас только времени выполнения держать большие списки против систем времени компиляции.- Бюджеты пакета: какие точки входа реально нужны внутренним инструментам.
Заключение
Voodoo.js не доказывает, что React, Vue или Svelte устарели. Он доказывает другое:
современную клиентскую часть можно архитектурно построить совершенно иначе.
Наиболее точная формула на сегодня:
Не революция, которая уже победила. А эксперимент, который показывает: обязательный компилятор — не закон природы для каждого класса приложений.
Следующий этап отрасли, возможно, будет звучать не «какой каркас лучше компилирует», а «нужен ли компилятор этому классу интерфейсов». Voodoo.js — один из самых интересных экспериментальных ответов на этот вопрос.
Практический шаг: сравните на одной внутренней задаче три артефакта — каркас Vite+React, один HTML на ядре Voodoo.js и страницу htmx — по времени до первого экрана и по размеру того, что уезжает в git. Цифры скажут больше слоганов.
Лабораторная рамка: зафиксируйте инвариант эксперимента (байты, политика безопасности содержимого или удобство без сборки) — иначе архитектурный конспект не сходится в решение.

