← Усі статті

Voodoo.js: архітектура середовища виконання без компілятора, Virtual DOM і eval

Карта шарів Voodoo.js: обхідник, області видимості, реактивність, ціна відмови від збірки, розмір пакета, роздування можливостей і HTML як артефакт для ШІ-кодування.

Voodoo.js: архітектура середовища виконання без компілятора, Virtual DOM і eval
Зміст

Чи можна побудувати достатньо потужне сучасне середовище виконання клієнтської частини, відмовившись від обов’язкового компілятора й ланцюжка збирання? 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»:

  1. застосунок зі звичайного HTML без обов’язкової збірки;
  2. вирази без eval / new Function;
  3. без Virtual DOM;
  4. без залежностей часу виконання.

Спрощений ланцюжок:

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? Власна ієрархія областей видимості (rootv-datav-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?

Занадто мало — каркас незручний. Занадто багато — другий час виконання JS усередині HTML. Розумний гібрид: справжній JavaScript для логіки й API, вирази Voodoo.js — для прив’язок у розмітці.

Цей борг також організаційний. Хтось має володіти крайовими випадками, коли вираз майже працює. Хтось має документувати, які можливості мови навмисні, а які випадкові. Хтось має вирішити, чи варто вигадувати типи TypeScript для виразів у розмітці. Зрілі каркаси амортизують цю роботу роками й компаніями. Молоде середовище виконання амортизує її ночами й запитами на злиття. Якщо ви берете Voodoo.js не лише для пісочниці, закладіть супровід поверхні виразів так, ніби супроводжуєте невелику мову — бо саме це вона і є.

Часті питання

Чим ця стаття відрізняється від розбору розбірника?

Коротка відповідь: розбірник — вертикальне глибоке занурення в конвеєр виразів; тут — горизонтальна карта архітектури, порівняльні тести, розмір і стратегія продукту.

Читайте обидва: інтерпретатор + цей матеріал.

Чи потрібен Virtual DOM, якщо є дрібнозернисті ефекти?

Коротка відповідь: для точкових прив’язок у цій моделі — ні; для складних списків узгодження все одно з’являється.

Суперечка «віртуальний DOM поганий назавжди» тут не ставиться.

Чому повний пакет такий великий?

Коротка відповідь: тому що повний пакет — це вже платформа служб, не лише реактивне ядро.

Беріть core / вузькі точки входу, якщо бюджет байтів критичний.

Чи це добра ціль для генерації ШІ?

Коротка відповідь: так для однофайлових мікрозастосунків; ні як сліпа заміна корпоративного стеку React.

Менше конфігів — коротший шлях «інструкція моделі → клікабельний HTML», вищий ризик простині без меж.

Чи варто вчити команду Voodoo.js замість Svelte/Solid?

Коротка відповідь: як розширення кругозору й інструмент пісочниць — так; як єдину компетенцію продуктової команди — ні.

Ринок і екосистема досі обертаються навколо зрілих каркасів.

Де першоджерела?

Коротка відповідь: репозиторій, ARCHITECTURE.md, порівняльні тести й документація на GitHub; короткий анонс — новинний допис.

Посилання: GitHub, ARCHITECTURE.md, порівняльні тести.

Що досліджувати далі

  1. src/parser/ — наскільки інтерпретатор наблизився до підмножини JavaScript.
  2. src/reactivity/ — порівняння Proxy/ефектів/планувальника з Vue 3 і Solid.
  3. runtime/walker.ts — динамічний DOM і крайові випадки HTML.
  4. v-for — чи може каркас лише часу виконання тримати великі списки проти систем часу компіляції.
  5. Бюджети пакета: які точки входу реально потрібні внутрішнім інструментам.

Висновок

Voodoo.js не доводить, що React, Vue чи Svelte застаріли. Він доводить інше:

сучасну клієнтську частину можна архітектурно побудувати геть інакше.

Найточніша формула на сьогодні:

Не революція, яка вже перемогла. А експеримент, який показує: обов’язковий компілятор — не закон природи для кожного класу застосунків.

Наступний етап галузі, можливо, звучатиме не «який каркас краще компілює», а «чи потрібен компілятор цьому класу інтерфейсів». Voodoo.js — одна з найцікавіших експериментальних відповідей на це питання.

Практичний крок: порівняйте на одній внутрішній задачі три артефакти — каркас Vite+React, один HTML на ядрі Voodoo.js і сторінку htmx — за часом до першого екрана й за розміром того, що їде в git. Цифри скажуть більше за гасла.

Лабораторна рамка: зафіксуйте інваріант експерименту (байти, політика безпеки вмісту або зручність без збірки) — інакше архітектурний конспект не зводиться в рішення.