Blockly
Перше, у що впираєшся з Blockly, — це не «як зробити блоки», а «а хто це тепер запускає». Ти перетягнув гарні кубики, склав програму, натиснув уявний «run» — і не сталося нічого, бо запускати нема кому. Blockly віддає рядок. Виконання рядка — вже не його клопіт, а твій, і про це в туторіалах згадують останнім абзацом, коли ти вже вклав вечір у toolbox.
Отже від межі назад до норми. Blockly — JS-бібліотека, з якої будують блокові редактори: ту саму scratch-подібну поверхню, де програму збирають перетягуванням. Apache 2.0, стартувала 2011 усередині Google, під нею живуть Scratch (частково), MIT App Inventor і половина освітніх «візуальних мов», які ти бачив. Вона малює workspace, тримає toolbox, знає, які блоки в які пази стають, і на виході дає текст. Текст — і крапка. Це не «обмеження, яке пізніше приберуть», це архітектурне рішення, і решта статті крутиться навколо нього.
Друге дно: чий це тепер проєкт
Найважливіше про Blockly у 2026-му ти не знайдеш у списку фіч. З 10.11.2025 стюард бібліотеки — Raspberry Pi Foundation, не Google. Анонс лежить у двох місцях: у Google Open Source Blog під заголовком «Blockly graduates from Google» (28.10.2025) і в блозі raspberrypi.org («new home for Blockly»). Репозиторій переїхав google/blockly → RaspberryPiFoundation/blockly, старі GitHub-посилання редіректяться, доки поки що їдуть на blockly.com, а developers.google.com/blockly ще віддає сторінки з банером про переїзд.
Ліцензія та сама, API не зламали, існуючі проєкти правити не треба — на папері нічого не сталося. А тепер друге дно: сам факт, що Google випустив річ із рук через чотирнадцять років, — це діагноз, а не новина про логотип. І ще прозоріший діагноз — те, що RPF першим пунктом на рік уперед поставив accessibility: screen reader і keyboard navigation. Тобто нова команда перше, за що взялась, — це те, що стара чотирнадцять років не лагодила. Вендор-наступник ненавмисно підтвердив, де в проєкті було найтонше. Для мене це не формальність: назва «Google Blocks» у голові все ще жива, а питання «хто це супроводжує і куди воно рухається» — окрема вісь ризику, не рядок у футері.
Що всередині
Ядро, коротко, бо це і так задокументовано краще за мене:
- workspace — полотно й модель водночас; блоки — це дерево, не картинки;
- toolbox — палітра блоків, описується XML або (сучасно) JSON;
- визначення блоків — JSON або JS;
- генератори коду — JavaScript, Python, Lua, Dart, PHP із коробки;
- серіалізація — сучасна в JSON, стара у XML (
Blockly.Xml), і це два різні світи, до яких я ще повернусь; - плюс events, themes, renderers та i18n з RTL.
Плагіни живуть окремо, у blockly-samples, під скоупом @blockly/*: workspace-search, backpack, minimap, continuous-toolbox, toolbox-search, typed-variable-modal і ще пара десятків. npm-пакет — blockly; остання версія на момент, коли я дивився (22.09.2026, сторінка npm), — 13.3.0, опублікована приблизно на початку серпня 2026. Плагіни цього циклу отримали спільний номер 13.0.0 — щоб з першого погляду було видно, що вони під це ядро, а не під попереднє. Число 13.3.0 я не виміряв сам, я його прочитав на npm — тому воно тут із підписом, а не як факт із неба.
Головне: воно віддає текст, і це не баг
Повторю, бо на цьому все тримається. Blockly не виконує нічого. Генератор — це функція, яка обходить дерево блоків і склеює рядок. Що ти зробиш із рядком — твоя справа, і тут дві дороги.
Перша, наївна: генеруєш JavaScript і виконуєш його в браузері через JS-Interpreter (окремий пісочничний інтерпретатор, щоб не пускати згенероване в eval). Працює, але ти щойно взяв на себе цілий рантайм чужої мови заради того, щоб покрутити три блоки.
Друга, правильніша: не генеруй цільову мову взагалі. Генеруй свій JSON або DSL і віддавай його власному рантайму. Блоки стають не «кодом», а декларативним описом наміру, а що з тим наміром робити — вирішуєш ти, не бібліотека.
Це не теорія з чужого блогу. У мене на mandrock-files.duckdns.org/esp32/ уже стоїть діючий Blockly-фронт, і він зроблений рівно за другою схемою (перевірено на диску 22.09.2026). Toolbox — чотири категорії: Board, Peripherals, Events, Actions. Блоків десять, я порахував: board_init, wifi_config, gpio_pin, i2c_device, on_boot, on_interval, on_gpio_change, set_pin, log_msg, http_post. Blockly підтягнутий з unpkg. І генератор, хоч і висить на Blockly.JavaScript['board_init'], віддає не JavaScript, а рядок JSON:
Blockly.JavaScript['board_init'] = function(block) {
return JSON.stringify({type:'board', chip: block.getFieldValue('CHIP')}) + ',\n';
};
Ці рядки злипаються у spec.json, а далі бекенд esp32-api (pm2, express) з роутами /spec, /generate, /prompts/:id.md, /build, /flash/usb, /ota/push, /version бере spec і будує з нього не прошивку, а промпт для LLM, яка вже пише код. Тобто ланцюг «блоки → декларативний spec → код пише вже модель/рантайм» я прогнав руками, а не уявив. Чесно про стан: build і flash/usb там — стаби, ESP-IDF на VPS не стоїть, теки data/builds і data/logs порожні, у data/specs лежить один-єдиний файл. Тобто патерн доведено до spec.json, а не до залізної плати. Але сам патерн — факт, і саме на ньому я тут стою. Ця стаття, до речі, не про esp32; esp32 — лише один приклад.
Де воно ламається (чесно, не списком-рекламою)
- Генератори — це конкатенація рядків. Ніякого AST, ніякого semantic layer. Значить екранування — на тобі (у прикладі вище esp32 рятує
JSON.stringify, і це не випадковість), і пріоритети операцій — теж на тобі: згенеруєшa + b * cбез дужок — отримаєшa + b * c, а не те, що юзер малював у голові. - Connection checks типізовані рядками. Коли ти кажеш, що блок віддає тип
"Number", а пази приймають"Number", — це домовленість двох рядків, а не типізація. Одрук у рядку, і пази мовчки перестають стикуватись. Компілятор тобі про це не скаже, бо компілятора нема. - Великі workspace просідають. Рендер — це SVG-дерево, і на сотнях блоків воно починає думати. Точних чисел не наводжу, бо не міряв на своєму — скажу лише, що межу видно оком раніше, ніж хотілося б.
- Мобільні жести посередні. Тач працює, але «посередньо» — це якраз слово; для touch-first речі це болить.
- Мажорні релізи ламають API і тягнуть за собою плагіни. Тому й спільний номер 13.0.0 на плагінах — це не маркетинг, це попередження: підняв ядро на мажор — готуйся піднімати весь
@blockly/*, який ти тягнеш. - Історично слабка accessibility. Та сама, яку RPF пішов лагодити. Якщо тобі треба screen reader сьогодні — рахуй, що його ще нема.
- Міграція XML→JSON. Старі проєкти серіалізували workspace у XML; сучасні — у JSON. Обидва живі, але це два формати, і якщо в тебе на диску лежить старий XML — конвертувати доведеться свідомо, а не «воно саме».
Getting started (копіпастний)
Мінімальний робочий редактор — один файл, без збірки. Підключення з CDN, div, toolbox у JSON, один кастомний блок через defineBlocksWithJsonArray, свій генератор, серіалізація в localStorage і кнопка виводу:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/blockly/blockly_compressed.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/javascript_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/en.js"></script>
<style>
html, body { margin: 0; height: 100%; }
#app { height: 70vh; }
#out { white-space: pre; font-family: monospace; padding: 1em; }
</style>
</head>
<body>
<div id="app"></div>
<button id="run">показати вихід</button>
<pre id="out"></pre>
<script>
// 1. один кастомний блок — JSON-опис, не імперативний Blockly.Blocks[...]
Blockly.common.defineBlocksWithJsonArray([{
type: 'say_text',
message0: 'say %1',
args0: [{ type: 'field_input', name: 'TXT', text: 'hello' }],
previousStatement: null,
nextStatement: null,
colour: 230,
}]);
// 2. генератор до нього. forBlock — реєстрація з v10;
// екранування на мені, тому рядок роблю через JSON.stringify, а не руками
Blockly.JavaScript.forBlock['say_text'] = function(block, generator) {
const txt = block.getFieldValue('TXT');
return 'console.log(' + JSON.stringify(txt) + ');\n';
};
// 3. toolbox як JSON, не XML
const toolbox = {
kind: 'flyoutToolbox',
contents: [{ kind: 'block', type: 'say_text' }],
};
// 4. inject
const ws = Blockly.inject('app', { toolbox });
// 5. серіалізація в localStorage — сучасний JSON API, не Blockly.Xml
const KEY = 'blockly-demo';
const saved = localStorage.getItem(KEY);
if (saved) Blockly.serialization.workspaces.load(JSON.parse(saved), ws);
ws.addChangeListener(function () {
const state = Blockly.serialization.workspaces.save(ws);
localStorage.setItem(KEY, JSON.stringify(state));
});
// 6. кнопка «показати вихід»
document.getElementById('run').addEventListener('click', function () {
document.getElementById('out').textContent =
Blockly.JavaScript.workspaceToCode(ws);
});
</script>
</body>
</html>
Чесна межа: цей файл написаний під задокументований API 11–13 (defineBlocksWithJsonArray, forBlock, serialization.workspaces, workspaceToCode — усе стабільне з v10/v11), але я не звірив його прогоном на встановленій 13.3 — blockly як пакет на цьому боксі не стоїть, фронт тягне ядро з CDN. Тобто це «має працювати за докою», а не «прогнав і бачив вивід». Рядок Blockly.JavaScript.forBlock['say_text'] = fn — сучасна форма; старий стиль Blockly.JavaScript['say_text'] = fn (той, що на моєму esp32-фронті) досі живий через deprecation-шим, але я на нього не спираюсь.
Далі — короткий «як не зробити»:
- Не тримай стан програми в блоках. Блоки — це редактор наміру, не база. Хочеш «поточне значення лічильника» — воно живе в рантаймі, а не в полі блоку.
- Не генеруй одразу цільову мову. Згенеруй свій spec/JSON, а переклад у мову (чи в промпт) зроби окремим кроком. Інакше кожна зміна цільового синтаксису — це правка генератора.
- Не покладайся на порядок блоків замість явних полів. «Верхній on_boot спрацює першим» — крихка домовленість. Якщо порядок важить — зроби його явним полем, а не наслідком верстки на полотні.
P.S. Півгодини я був певен, що зламав inject — редактор вантажився порожній, блоки не з'являлись. Виявилось, msg/en.js я підключив перед blockly_compressed.js, і бібліотека мовчки стартувала без мови, як людина, яку розбудили й одразу попросили щось порахувати. Blockly навчить дитину програмувати перетягуванням кубиків, але спершу навчить тебе читати порядок <script>-тегів. Виховна, зараза.