尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Blockly入门指南:从零搭建可视化积木编辑器与代码生成

Blockly入门指南:从零搭建可视化积木编辑器与代码生成 可视化编程这两年几乎成了所有“非专业用户也要写逻辑”的产品的标配而Google开源的Blockly就是这类方案里绕不开的一个名字。我第一次认真研究Blockly是因为要给一套工业设备做图形化控制页面当时对Scratch已经很熟但把它嵌进自己的产品里实在麻烦后来试到Blockly才发现网页里嵌一个积木编辑器可以做到这么顺。如果你第一次听到Blockly一句话理解是它是一个前端JavaScript库提供拖拽积木的编辑器同时能把积木翻译成JavaScript、Python、PHP等代码。换句话说它同时做了“搭积木”和“翻译代码”两件事。这是系列教程的第一篇不打算一上来啃源码只解决三个问题Blockly到底是什么、它大概怎么工作、怎么在15分钟内跑起一个能拖能运行的项目。这篇文章适合两类人一类是做编程教育、低代码平台、硬件图形化编程的开发者想评估Blockly能不能用在产品里另一类是纯粹想给自己的小项目加一个可视化编辑能力的个人开发者。读完之后你至少能有一个可以跑起来的原生Blockly页面也为后续自定义块打基础。1. 认识Blockly可视化编程的积木哲学1.1 Blockly到底是个什么Blockly于2012年由Google对外开源采用Apache 2.0协议简单说就是你拿到的是一套“积木编程界面”的标准实现而不是一个现成的编程平台。Scratch和Blockly经常被放在一起比较但两者的定位差异很大Scratch是一个完整的创作工具有舞台、角色、声音素材库而Blockly是一个可以嵌入Web应用的JavaScript库没有自带运行环境它的核心产出是“拖拽积木的编辑器 把积木转成文本代码的生成器”。你可以把Blockly理解成一块可以直接装进自己产品的引擎。由于可嵌入、可定制Blockly的实际应用范围比很多人想的要广。App Inventor早期版本的积木编程界面基于它构建微软MakeCode里的积木编辑同样离不开Blockly不少物联网平台、报表系统的自动化配置功能也用了Blockly作为可视化规则编辑器。在这些场景里开发者的普遍做法是用Blockly做“用户看得见、拖得动”的部分然后把自己定义好的块翻译成业务代码、脚本或接口调用。1.2 哪些场景真正需要Blockly不是所有地方都需要Blockly。如果你的产品只需要让用户从几个固定动作里做选择一个下拉框加几个参数表单就够了但如果你希望用户能自由组合判断、循环、变量甚至写出“像程序一样”的逻辑流程这时候再设计一套基础配置就很不划算。Blockly的优势在于它替你实现了编辑器的交互细节拖拽、连接、缩放、撤销也提供了一套相对通用的块体系你只需要关注怎么将积木映射到自己的业务逻辑。我从实际项目经验看三类需求最适合引入Blockly。第一类是教学类产品比如图形化编程入门、算法演示、代码生成展示第二类是硬件编程工具拖积木生成Arduino或MicroPython代码再刷进设备第三类是企业系统里的流程编排比如自动化任务、审批规则、数据加工流程用户不懂代码但需要灵活配置。如果你正在做这类功能用Blockly大概率会比造一个自定义可视化编辑器省下数周时间而且踩坑成本会低很多。1.3 一条比较顺的学习路径Blockly入门不需要一上来就读源码。我个人建议的顺序是先在官方demo里拖一拖感受编辑器交互然后照着官方示例搭一个最小页面搞清楚inject、toolbox、workspaceToCode这几个核心对象接着尝试改工具箱、加一个自定义块理解块定义与代码生成器的对应关系最后再把序列化保存、异步执行、后端翻译这些能力接进真实产品。整个过程中最重要的不是记住API而是建立“积木结构 - 生成代码”的心智模型。很多人学Blockly容易陷入一个误区看到文档里的Blockly.Blocks、generator、toolbox一大堆术语就害怕其实它们对应的就是积木长得什么样、积木拖上去之后变成什么代码、用户从哪里取出这些积木。只要把这几个问题串起来Blockly的主干就通了。接下来我会用第2章把这几个核心机制拆开讲这也是后续所有扩展的基础。2. 核心机制拆解块、连接器与代码生成2.1 块与连接器一切都在拼装逻辑Blockly的基本单元是块block。从外观上看块就是一块拼图不同块之间通过凹凸的接口连接。常见类型有三种语句块像一行代码通常有上一句和下一句连接口值块表示一个值可以嵌到其他块的输入槽里布尔块则是值块的一种特殊形态输出true或false。连接口的形状和“凸起”数代表数据的类型比如数字形状只能插进数字输入槽字符串形状同理。这个拼图机制保证了用户不能随便把“一个数字”塞进“一条语句”的位置。关于块的形状Blockly里有个很直观的设计上一语句是顶部凸起、下一语句是底部凹槽值块左侧是凸出、承载它的输入槽是凹进去的。当用户尝试将不匹配的块连接时编辑器会明显拒绝并弹出红色指示这其实是一套“类型检查”在起作用。理解连接器机制后再去写自定义块时就不容易把输入类型设错。2.2 代码生成器积木怎么变成可执行代码Blockly编辑器本身不执行用户搭好的逻辑——除非你自己实现一套解释器。常规用法是调用代码生成器把工作区里的积木树转换成代码字符串。比如Blockly.JavaScript.workspaceToCode(workspace)会返回一段JavaScript代码而Blockly.Python.workspaceToCode会返回Python代码。要做到这一点每个块都要注册一个对应的“生成函数”这个函数把块上的参数拼接成目标代码片段再逐层组合成完整程序。这也是很多人最开始没想明白的地方Blockly不是“运行”积木而是“生成文本代码”。所以即使你没有目标语言运行环境也可以让用户搭积木、导出代码到本地再执行。很多硬件编程工具就是这么做的——积木导出成Python文件用户拷贝到开发板上运行。理解“块-代码字符串-执行”这个链条遇到运行时报错时就不会一头雾水。2.3 工作区与序列化用户搭的流程怎么保存每个Blockly页面都有一个工作区workspace所有积木都在这里被拖拽、排布、连接。为了让用户下次打开还能看到自己搭的内容Blockly提供了序列化能力也就是将工作区的块结构转成XML或JSON。常见做法是用Blockly.Xml.workspaceToDom(workspace)取得XML结构转成字符串后存到数据库或localStorage重新打开时再通过domToWorkspace恢复到工作区里。除了块本身块的位置、展开状态、注释等也能一起保存。这看起来像是“存档”功能但实际使用中往往被低估。比如企业流程编排场景里每个用户定义的自动化流程本质就是一份工作区序列化数据后端甚至不需要理解积木长什么样只需要在用户编辑时保存XML、在运行前解析代码即可。这个方法配合版本管理还可以实现“方案模板”“历史版本”等功能是实现产品化绕不开的一步。3. 15分钟跑通第一个Blockly项目HTML页面搭建实操3.1 先准备一个最简HTML页面我习惯把Blockly的起步项目固定成同一个模板这样不管做demo还是搭建真实产品都能快速进入状态。先创建一个index.html然后在头部按顺序引入Blockly核心库、内置块定义、代码生成器和语言包。顺序错了或者漏掉某个文件后面多半会白屏或提示某个函数不存在所以这里我建议直接把依赖都列清楚。以下是我长期在本地练习时用到的最小模板基于Blockly 9.0版本版本其实很关键第5章我会专门讲!DOCTYPE html html head meta charsetutf-8 titleBlockly 第一个示例/title script srchttps://unpkg.com/blockly9.0.0/blockly.min.js/script script srchttps://unpkg.com/blockly9.0.0/blocks_compressed.js/script script srchttps://unpkg.com/blockly9.0.0/javascript_compressed.js/script script srchttps://unpkg.com/blockly9.0.0/msg/en.js/script /head body div idblocklyDiv styleheight: 400px; width: 600px;/div button onclickrunCode()运行/button pre idcodeArea/pre script var toolbox xml xmlnshttps://developers.google.com/blockly/xml block typecontrols_if/block block typelogic_compare/block block typemath_number/block block typetext_print/block /xml; var workspace Blockly.inject(blocklyDiv, { toolbox: toolbox }); function runCode() { var code Blockly.JavaScript.workspaceToCode(workspace); document.getElementById(codeArea).textContent code; try { eval(code); } catch (e) { console.error(e); } } /script /body /html这段代码里最核心的是两行Blockly.inject(blocklyDiv, { toolbox: toolbox })负责创建编辑器Blockly.JavaScript.workspaceToCode(workspace)负责把工作区里的块翻译成JavaScript。浏览器打开这个页面左侧会出现工具箱中间是工作区把积木拖到中间组合再点运行下方就会显示生成的代码并且直接执行。这个小闭环就足够支撑前期的学习和实验了。3.2 配置工具箱决定用户能看到哪些积木工具箱好比一个积木仓库里面放什么块用户就能用什么块。上面的示例用了很简单的XML字符串直接列出块类型。如果想分组展示让界面更贴近产品体验可以用category给积木分类。Blockly工具箱同时支持XML和JSON两种格式但刚入门我更推荐XML结构直观看到一个block就是一块积木。xml xmlnshttps://developers.google.com/blockly/xml category name我的积木 block typesayHello/block /category category name逻辑 block typecontrols_if/block block typelogic_compare/block /category category name数学 block typemath_number/block /category category name文本 block typetext_print/block /category /xml这样改完之后工具箱里会按分类展示积木。需要说明的是工具箱里定义的块必须在运行时已经注册。内置块在blocks_compressed.js里都有如果是自定义块就必须自己写块定义代码否则工具箱里只会出现一个灰色或者空白的占位。这个坑我见过不少新手踩大家要记住工具箱只是“引用”块真正定义块的是Blockly.Blocks里的条目。3.3 运行与调试从拖拽到看到输出跑通页面只是第一步接下来的调试习惯更重要。我的建议是不要在runCode函数里一上来就调eval而是先把生成的代码显示到页面上或者打印到控制台确认代码长什么样再执行。因为Blockly生成的代码如果不符合预期直接eval会报错你往往分不清到底是代码生成逻辑有问题还是运行环境有问题。实际操作时拖一个math_number和一个text_print块把它接起来生成代码会类似 console.log(123)执行之后浏览器控制台会出现123。这个过程中可以右键点击块会发现Blockly自带“帮助”和“复制”等菜单也可以在工作区空白处右键创建注释、添加变量。这些交互细节虽然不起眼但真正做产品的时候都会影响到最终用户体验值得多体验几遍。4. 自定义第一个积木块从零实现“Hello World”能力4.1 自定义块的三件套定义、工具箱、生成器当内置块不能满足业务时就该写自定义块了。自定义一个块通常需要三部分块定义Blockly.Blocks里注册块的样子、工具箱引用让用户能拖到工作区、代码生成器把块翻译成目标代码。三部分缺一不可。块定义决定这个块长什么样有哪些输入口、什么颜色、什么提示工具箱决定用户从哪里找到它生成器决定它最终生成什么代码。我见过很多人只写了生成器却忘了块定义结果拖不进工具箱这类问题非常好排查。块定义的基本形式是通过Blockly.Blocks给块名挂一个init方法。在init里可以追加字段比如一个文本标签、一个输入槽。输入槽分两种value输入嵌值块比如数字、字符串和statement输入嵌语句块比如if的条件体。理解了这两类输入的配置大部分自定义块都能写出来。4.2 用Blockly Developer Tools加速开发手写块定义代码对新手来说确实有点繁琐尤其是多输入、多类型叠加的块很容易漏掉一个字段。Google官方提供了一个叫Blockly Developer Tools的辅助页面它用可视化方式让你拖出一个块的样子然后自动生成块定义代码和生成器代码。你只需要在这个工具里设置颜色、字段、输入类型再把生成的代码复制到工程里体验非常像“用积木生成积木”。这个工具我认为是学习Blockly的隐藏福利。我第一次写带下拉选择、数值输入、语句嵌套的块时在开发者工具里拖了几分钟就把骨架搭好了再对照生成的代码去理解API效率比对着文档啃快很多。强烈建议新手用这种方式起步先让块跑起来再逐步手写复杂配置。4.3 案例做一个sayHello问候块我们用最经典的“Hello”逻辑串一遍。先定义一个名叫sayHello的块它有一个字符串文本字段“问候”一个接收字符串的值输入“名字”同时它可以放在语句块序列中因此设置了上一句和下一句连接口。完整的定义如下Blockly.Blocks[sayHello] { init: function() { this.appendDummyInput() .appendField(问候); this.appendValueInput(NAME) .setCheck(String) .appendField(名字); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(210); this.setTooltip(输出一句问候语); } }; Blockly.JavaScript[sayHello] function(block) { var name Blockly.JavaScript.valueToCode(block, NAME, Blockly.JavaScript.ORDER_ATOMIC); var code console.log(Hello, name );\n; return code; };这里有一个关键APIBlockly.JavaScript.valueToCode。它负责取出连接在“名字”输入槽上的块并递归生成对应的代码字符串。如果没有连接任何块返回的是默认空字符串。你在写自定义生成器的时候基本都要用这个函数去拿输入值再拼进最终代码。就像字符串模板一样先把参数取出来再拼装成一行真正能运行的代码。把这个sayHello块加入工具箱XML后拖到工作区给名字槽接一个文本块点运行控制台就会输出Hello, xxx。至此一个从界面到代码生成的自定义块闭环就完整了。理解了这个闭环以后无论做多复杂的块本质上都是同一个套路定义好积木的外形、确定输入输出、再写拼接代码的生成函数。5. 新手避坑清单5个常犯错误与排查方法5.1 版本不统一导致各种报错Blockly更新速度不算慢API变化也不少。网上教程默认版本五花八门很多报错其实不是代码写错而是引用的版本和API不匹配。比如老版本里注册JavaScript生成器习惯写Blockly.JavaScript[块名]新版里可能推荐javascriptGenerator.forBlock[块名]两者不能混用。所以我建议初学者锁定一个版本先跟着该版本的文档走跑通后再考虑升级。我在本地练习时通常直接用unpkg上的固定版本号比如blockly9.0.0这样至少一个月内不会因为CDN默认版本升级导致本地代码失效。如果你开发的是正式产品更建议把Blockly库文件下载到本地或自己的静态资源服务器上避免第三方CDN不稳定或者版本更新带来意外。5.2 工具箱里的块不显示或拖不进去工具箱配置了块但工作区里找不到大概率有两种原因。一是内置块文件没有引入比如你用了字符串处理块但没有引入对应的blocks文件二是自定义块的Blockly.Blocks定义没有正确执行可以打开控制台看是否报错。还有一种情况是块类型名拼写错误比如把text_print写成了textprint工具箱自然会显示空白。排查方法也很简单先在控制台打印Blockly.Blocks对象看看目标块名是否已经注册。我每次添加自定义块后都会习惯性地执行一下console.log(Blockly.Blocks.sayHello)确认不为undefined再继续能省下不少时间来猜问题。5.3 生成代码报错或没有输出如果你成功把块拖进去了但点运行没有反应先把workspaceToCode返回的code打印到页面上不要直接eval。常见问题包括生成器返回的代码存在语法错误比如拼接出的代码少了分号或引号或者自定义块的生成器没有注册完整导致Blockly生成空字符串。另外如果用eval执行代码块内部使用let或const声明变量时浏览器的直接eval作用域可能会有影响我在严格模式下遇到过变量在后续模块里访问不到的情况这就是为什么我建议先查看生成的代码再决定用什么方式执行。如果生成代码里出现了undefined通常说明某个输入槽没有连接块而生成器在拼接时又没有做空值判断。在这种情况下可以在自定义生成器里通过条件判断输出默认值以此提高容错性。下面是我整理的一份高频问题速查表适合在实际开发时对照使用现象常见原因排查方向页面提示Blockly未定义核心库没加载或CDN路径错误检查script标签顺序、版本号工具箱里没有内置块blocks_compressed.js缺失确认内置块文件已引入自定义块拖不进工作区块定义未注册或类型名写错控制台查看Blockly.Blocks点运行没反应生成器未注册或eval报错先打印生成的代码再执行生成代码出现undefined输入槽没有连接块且未做默认值处理生成器里补空值判断5.4 中文界面和语言包相关事项想让界面右下角提示、右键菜单变成中文需要引入对应的语言包文件例如msg/zh-hans.js。有一点要注意语言包必须在核心库之后加载而且不同版本语言包的文件名和变量名不一定完全一样老教程里写的方法可能在新版本中不适用。如果你只是想先跑通功能用英文界面完全不影响理解等正式做产品时再整理语言包也不迟。5.5 性能和体积优化放到后期Blockly核心库加上内置块和生成器体积并不小放在移动端或弱网环境下会比较明显。第一版产品只要功能能跑通不用着急做按需加载和代码分割完全可以等积木体系稳定下来后再根据实际用到的块类型裁剪资源。过早优化反而会让学习曲线变陡。我自己踩过最深的一个坑就是一开始想把所有能力全部做完结果报错都不知道去哪儿查。后来把“跑通最小可运行demo”放在第一步反而推进最快。如果你第一次接触Blockly建议从今天这个最简页面开始先拖几个块、点一下运行再回头研究自定义块。只要这个闭环跑通Blockly就等于变成你可以掌控的工具了。
返回列表