mandrock-files / dev

Blockly

Бібліотека блокових редакторів, яка сама нічого не виконує — і чому саме це в ній головне для мого стека.

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» у голові все ще жива, а питання «хто це супроводжує і куди воно рухається» — окрема вісь ризику, не рядок у футері.

Що всередині

Ядро, коротко, бо це і так задокументовано краще за мене:

Плагіни живуть окремо, у 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 — лише один приклад.

Де воно ламається (чесно, не списком-рекламою)

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-шим, але я на нього не спираюсь.

Далі — короткий «як не зробити»:


P.S. Півгодини я був певен, що зламав inject — редактор вантажився порожній, блоки не з'являлись. Виявилось, msg/en.js я підключив перед blockly_compressed.js, і бібліотека мовчки стартувала без мови, як людина, яку розбудили й одразу попросили щось порахувати. Blockly навчить дитину програмувати перетягуванням кубиків, але спершу навчить тебе читати порядок <script>-тегів. Виховна, зараза.

MANDROCK.DEV · TELEGRAMЩе більше про код, агентів і те, що ламається красиво.Зазирнути в канал →