Blockly 代码生成:用内置生成器还是自定义?看完这篇不纠结
【免费下载链接】blocklyThe web-based visual programming editor.项目地址: https://gitcode.com/gh_mirrors/bloc/blockly
你的积木拼好了,现在要把它变成能跑的代码,结果在"直接调 Blockly 自带的代码生成器"和"自己写一套"之间来回纠结,选错后面返工很麻烦。
这篇文章先给你一张选型表,再按投入从低到高分三档讲清楚:每一档适合谁、改哪几行代码、要付什么代价。前两张表看完,你就能做决定了。
选型速查:先对号入座
| 对比维度 | 直接用内置生成器 | 自定义代码生成 |
|---|---|---|
| 上手成本 | 引一个模块 + 一次函数调用 | 建类、补保留字、实现所有积木函数 |
| 维护负担 | 版本升级即修复 | 每种语言各维护一份实现 |
| 语言选择 | 锁定 JavaScript / Python / Lua / PHP / Dart 五种 | 任意目标语言 |
| 输出控制 | 缩进、括号、注释格式由内置决定 | 完全按你的规范来 |
30 秒决策:目标语言就在这五种之内、又不需要改输出格式,直接走内置生成器;语言不在列表里,或者输出要完全贴你的工程规范,才值得上自定义。
五分钟跑通内置代码生成
内置生成器是仓库generators/目录下五个现成的实现:入口文件分别是javascript.ts、python.ts、lua.ts、php.ts、dart.ts,每个入口在实例化生成器后把 lists、logic、loops、math 等模块的积木函数批量注册进去,你拿到手的就是一个能直接用的成品。
谁适合:目标语言是这五种之一的项目,绝大多数场景停在这档就够了。
怎么配:
- 安装依赖:
npm install blockly,包体积和构建配置不用你操心; - 按
package.json的 exports 映射(第 52-100 行),用对应子路径引出生成器; - 拼积木的界面用
core/inject.ts的inject注入到一个空div; - 生成时调生成器实例的
workspaceToCode。
app.js里只需要这几行:
import { javascriptGenerator } from 'blockly/javascript'; import { pythonGenerator } from 'blockly/python'; import { inject } from 'blockly/core'; const workspace = inject(document.getElementById('blocklyDiv')); const code = javascriptGenerator.workspaceToCode(workspace); console.log(code);代价:
- 注意:语言被锁死在那五种,想换语言只能再引一个内置实例,没有中间档;
- 注意:输出格式内置说了算,缩进、注释、变量命名规则你插不上手;
- 注意:内置积木的生成逻辑升级跟随版本走,你的业务积木类型仍要自己补(下一档的事)。
不满足于上一档的话,往下走。
给自定义积木注册生成函数
在内置生成器上注册函数,本质是往生成器的forBlock字典里加条目:积木类型映射到(block, generator)函数。基类core/generator.ts的blockToCode(第 257 行)按block.type查这个字典,查到就执行,查不到就抛错。
谁适合:目标语言还在五种之内,但你有自定义积木,或想微调某个内置积木的输出。
怎么配:
- 复用内置生成器实例,不新建类;
- 在
forBlock上补你的积木类型; - 值积木返回
[代码, 优先级]二元组,语句积木返回字符串。
import { javascriptGenerator } from 'blockly/javascript'; javascriptGenerator.forBlock['my_sensor_read'] = function (block, generator) { const pin = generator.valueToCode(block, 'PIN', 0) || '0'; return [ `read(${pin})`, javascriptGenerator.Order.ATOMIC, ]; };代价:
- 注意:每个自定义积木类型都要在用的每种语言上各注册一次,五个语言就是五份;
- 注意:返回值约定错了(值积木返回字符串、或语句积木返回二元组)会在生成时抛
TypeError,见core/generator.ts第 272-274 行的检查; - 注意:内置积木的生成函数你看得到、但不建议在实例上覆盖,升级时容易覆盖丢。
继承基类自建一个完整生成器
自建生成器 = 继承core/generator.ts的CodeGenerator(第 40 行),只重写它给过的钩子:init、INDENT、Order枚举、各积木的生成函数,基类负责遍历顶层积木、拼接、前导定义注入。
谁适合:要生成 Go、Rust 这类不在五种里的语言,或输出必须严格贴自家 lint 规范的平台型产品。
怎么配:
- 子类里定义该语言的
Order优先级枚举(照抄generators/python/python_generator.ts第 27 行起的写法); - 重写
init,给nameDB_建Names实例,传入保留字; - 按目标语言设
INDENT; - 把积木函数按
generators/lua/lua_generator.ts的模块结构分批挂载。
import { CodeGenerator } from 'blockly/core'; import { Names } from 'blockly/core'; class GoGenerator extends CodeGenerator { INDENT = '\t'; init(workspace) { this.nameDB_ = new Names('func,if,for,range,var'); this.nameDB_.setVariableMap(workspace.getVariableMap()); } } const goGenerator = new GoGenerator('go');代价:
- 注意:每种语言都是一份独立维护,字符串转义、换行约定、定义前置这些细节基类不代劳;
- 注意:
init里不初始化nameDB_,取变量名时直接抛错,见core/generator.ts第 540-547 行的getName; - 注意:积木类型覆盖不全的代价在这里被放大——
forBlock缺一个类型就整个工作区生成失败。
从零到跑通:完整落地路径
按时间顺序走一遍,以"内置 JavaScript 生成器 + 一个自定义积木"为例。
- 装依赖:
npm install blockly。本仓库可离线 clone 查看源码:git clone https://gitcode.com/gh_mirrors/bloc/blockly; - 准备注入点:页面放一个
id="blocklyDiv"的div,尺寸自己定; - 注入编辑器:
app.js里调inject(document.getElementById('blocklyDiv')),返回主workspace; - 注册自定义积木:往
javascriptGenerator.forBlock挂my_sensor_read(代码见上一节); - 接生成按钮:把"生成"按钮的点击事件绑到
workspaceToCode(workspace),结果填进一个textarea。
关键改动就这一处,app.js:
import { javascriptGenerator } from 'blockly/javascript'; import { inject } from 'blockly/core'; const workspace = inject(document.getElementById('blocklyDiv')); document.getElementById('runBtn').addEventListener('click', () => { const code = javascriptGenerator.workspaceToCode(workspace); document.getElementById('output').value = code; });怎么确认成功了?拼一个"循环 + 变量"的积木组,点生成,输出应是带缩进的for循环且变量名不撞保留字。想核对标准答案,仓库里tests/generators/golden/generated.js和generated.py就是固定积木组合的基准输出,对照一眼就知道对不对;跑npm run test:generators(对应package.json第 47 行)可以过一遍仓库自带的生成器测试。
踩坑预警
报 "does not know how to generate code for block type"
现象:一调workspaceToCode就抛错,工作区里明明没坏积木。原因:blockToCode在forBlock里查不到该积木类型的函数(core/generator.ts第 258-263 行)。解法:到generators/javascript/对应模块里确认这个类型本就有内置函数;如果是你自定义的积木,按上一节的格式补注册;如果你用的是 UMD 压缩包而不是 npm 子路径,生成器可能挂在Blockly.JavaScript下,别和模块入口混着拿。
变量名和保留字撞了
现象:用户把变量命名为if或func,生成的代码直接语法错误。原因:名字映射走Names,但你的init没传保留字表,或压根没初始化nameDB_。解法:在init(workspace)里给nameDB_赋new Names('保留字,逗号,分隔')并setVariableMap,参考generators/lua/lua_generator.ts的init写法。
缩进或括号位置不对
现象:Python 缩进错一行、比较表达式少一层括号。原因:INDENT仍是基类默认两空格(core/generator.ts第 97 行),或生成函数没返回优先级、返回了 0。解法:自建生成器按语言重写INDENT;所有值积木函数返回二元组时,第二项必须用Order枚举里的正确档位,照抄generators/python/python_generator.ts第 27 行起的枚举对照着填。
生成的代码能跑但和 golden 文件对不上
现象:手动跑的输出和tests/generators/golden/下的基准差在换行或空格上。原因:你本地装的是另一个版本的 blockly,内置生成逻辑已经变了。解法:锁住package.json里的 blockly 版本,别用*;升级前先跑一遍生成器测试再换。
按场景对号入座
| 你的场景 | 走哪条路 |
|---|---|
| 教学网站、儿童编程工具 | 内置生成器,五分钟完事 |
| 行业工具,有几十个自定义积木 | 内置生成器 +forBlock补注册 |
| 平台型产品,要出五种之外的语言 | 自建生成器,长期维护 |
先跑通内置生成器,再往forBlock上加自定义积木,确认这条路撑不住了再动继承基类的主意。每一档的投入都比上一档大一个数量级,按需升级比一步到位省事得多。
【免费下载链接】blocklyThe web-based visual programming editor.项目地址: https://gitcode.com/gh_mirrors/bloc/blockly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考