Blockly自定义块开发全攻略:扩展编辑器功能
引言:为什么需要自定义块?
你是否曾在使用Blockly(可视化编程编辑器)时遇到内置块无法满足特定业务需求的困境?作为Web-based visual programming editor,Blockly的核心优势在于其高度可扩展性,而自定义块(Custom Blocks)正是扩展其功能的关键所在。本文将系统讲解从块定义、字段配置到代码生成的完整开发流程,帮助你掌握自定义块开发的核心技术。
读完本文后,你将能够:
- 理解Blockly块的JSON结构与TypeScript定义规范
- 掌握基础型、输入型和嵌套型三种块的开发方法
- 实现自定义字段验证与动态交互逻辑
- 配置代码生成器将块转换为目标语言
- 解决块开发中的常见问题与性能优化
Blockly架构与自定义块原理
Blockly核心组件架构
Blockly编辑器由五大核心模块构成,自定义块开发主要涉及块定义系统和代码生成器:
自定义块开发流程
自定义块开发遵循标准化流程,从定义到集成通常需要完成六个关键步骤:
开发环境搭建
环境准备与项目结构
使用国内GitCode仓库地址克隆项目:
git clone https://gitcode.com/gh_mirrors/bloc/blockly.git
cd blockly
npm install
自定义块开发主要涉及以下目录结构:
blockly/
├── blocks/ # 块定义目录
│ ├── custom/ # 推荐的自定义块存放位置
│ ├── math.ts # 数学块示例
│ └── blocks.ts # 块导出配置
├── generators/ # 代码生成器目录
│ ├── javascript/ # JS生成器
│ └── python/ # Python生成器
└── demos/ # 测试演示页面
调试工具与技术
推荐使用以下调试工具组合:
- Blockly Developer Tools:内置的块调试面板
- VS Code + TypeScript:类型检查与智能提示
- Chrome DevTools:DOM检查与事件调试
启动开发服务器:
npm run start
访问http://localhost:8080/demos/code/index.html即可看到默认编辑器界面,我们将在后续步骤中把自定义块添加到该环境。
块定义详解
块结构JSON规范
Blockly块定义遵循严格的JSON结构,每个块包含基本信息、参数配置和行为规则三大要素。以下是一个完整的基础块定义示例:
{
"type": "math_custom_calculator", // 唯一块类型ID
"message0": "%1 %2 %3 = %4", // 块显示模板
"args0": [ // 参数配置数组
{
"type": "input_value", // 输入值类型
"name": "A", // 引用名称
"check": "Number" // 类型检查规则
},
{
"type": "field_dropdown", // 下拉字段类型
"name": "OPERATOR", // 引用名称
"options": [ // 选项配置
["+", "ADD"],
["-", "SUBTRACT"],
["×", "MULTIPLY"],
["÷", "DIVIDE"]
]
},
{
"type": "input_value",
"name": "B",
"check": "Number"
},
{
"type": "field_label_serializable", // 可序列化标签
"name": "RESULT",
"value": "?" // 默认值
}
],
"inputsInline": true, // 输入是否内联显示
"previousStatement": null, // 前连接点类型
"nextStatement": null, // 后连接点类型
"output": null, // 输出类型
"style": "math_blocks", // 样式分类
"tooltip": "自定义计算器块,支持加减乘除运算", // 提示信息
"helpUrl": "", // 帮助文档URL
"extensions": ["dynamic_result"] // 扩展功能
}
TypeScript定义方式
对于复杂块,推荐使用TypeScript类定义,以获得更好的类型检查和逻辑组织能力。以数学块为例:
import { BlockDefinition, defineBlocks } from '../core/common.js';
import { FieldNumber } from '../core/field_number.js';
export const blocks: {[key: string]: BlockDefinition} = {
math_custom_calculator: {
init: function() {
this.jsonInit({
"type": "math_custom_calculator",
"message0": "%1 %2 %3 = %4",
// ... 其他JSON配置与前文相同
});
// 添加自定义字段验证器
const operatorField = this.getField('OPERATOR');
operatorField.setValidator(function(newValue) {
// 验证逻辑
return newValue;
});
}
}
};
defineBlocks(blocks);
三种基础块类型开发实例
1. 基础型块:颜色选择块
需求:创建一个选择颜色并返回十六进制值的块。
步骤1:定义块结构
创建blocks/custom/color_picker.ts:
import { createBlockDefinitionsFromJsonArray, defineBlocks } from '../../core/common.js';
export const blocks = createBlockDefinitionsFromJsonArray([
{
"type": "color_picker",
"message0": "颜色 %1",
"args0": [
{
"type": "field_colour",
"name": "COLOUR",
"value": "#ff0000" // 默认红色
}
],
"output": "Colour",
"style": "colour_blocks",
"tooltip": "选择颜色并返回十六进制值",
"helpUrl": ""
}
]);
defineBlocks(blocks);
步骤2:配置代码生成器
创建generators/javascript/color_picker.ts:
import * as Blockly from '../../core/blockly.js';
Blockly.JavaScript['color_picker'] = function(block) {
const colour = block.getFieldValue('COLOUR');
const code = `"${colour}"`; // 返回字符串形式的十六进制值
return [code, Blockly.JavaScript.ORDER_ATOMIC];
};
步骤3:注册到系统
在blocks/blocks.ts中添加导出:
import * as colorPicker from './custom/color_picker.js';
export {
// ... 其他导出
colorPicker
};
在工具箱配置中添加:
<category name="自定义工具" colour="#5C8AFF">
<block type="color_picker"></block>
</category>
2. 输入型块:温度转换块
需求:创建一个支持摄氏度与华氏度互转的块,包含输入值和单位选择。
块定义关键代码:
{
"type": "temperature_converter",
"message0": "转换 %1 %2 到 %3",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
},
{
"type": "field_dropdown",
"name": "FROM_UNIT",
"options": [
["摄氏度", "C"],
["华氏度", "F"]
]
},
{
"type": "field_dropdown",
"name": "TO_UNIT",
"options": [
["摄氏度", "C"],
["华氏度", "F"]
]
}
],
"output": "Number",
"style": "math_blocks",
"tooltip": "温度单位转换"
}
代码生成器实现:
Blockly.JavaScript['temperature_converter'] = function(block) {
const value = Blockly.JavaScript.valueToCode(block, 'VALUE', Blockly.JavaScript.ORDER_ATOMIC);
const fromUnit = block.getFieldValue('FROM_UNIT');
const toUnit = block.getFieldValue('TO_UNIT');
let code;
if (fromUnit === toUnit) {
code = value; // 单位相同,直接返回原值
} else if (fromUnit === 'C' && toUnit === 'F') {
// 摄氏度转华氏度公式: F = C * 9/5 + 32
code = `${value} * 9 / 5 + 32`;
} else {
// 华氏度转摄氏度公式: C = (F - 32) * 5/9
code = `(${value} - 32) * 5 / 9`;
}
return [code, Blockly.JavaScript.ORDER_MATHEMATICAL];
};
3. 嵌套型块:条件执行块
需求:创建一个支持条件判断的嵌套块,当条件满足时执行指定语句。
块定义关键代码:
{
"type": "custom_if",
"message0": "如果 %1 则执行",
"args0": [
{
"type": "input_value",
"name": "CONDITION",
"check": "Boolean"
}
],
"message1": "%1",
"args1": [
{
"type": "input_statement",
"name": "DO"
}
],
"previousStatement": null,
"nextStatement": null,
"style": "logic_blocks",
"tooltip": "条件满足时执行语句",
"mutator": "custom_if_mutator" // 用于后续扩展多分支
}
代码生成器实现:
Blockly.JavaScript['custom_if'] = function(block) {
const condition = Blockly.JavaScript.valueToCode(
block, 'CONDITION', Blockly.JavaScript.ORDER_LOGICAL_NOT);
const statementsDo = Blockly.JavaScript.statementToCode(block, 'DO');
const code = `if (${condition}) {\n${statementsDo}}\n`;
return code;
};
高级功能实现
动态字段与验证器
自定义字段验证器可以确保用户输入符合预期格式,以下是一个邮箱验证器示例:
// 在块的init方法中添加
const emailField = new Blockly.FieldTextInput('user@example.com');
emailField.setValidator(function(text) {
// 简单邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(text)) {
return null; // 验证失败,保留原值
}
return text; // 验证通过,接受新值
});
this.appendField(emailField, 'EMAIL');
块的动态形状变化
某些场景下需要根据用户选择动态改变块的形状,例如"数学运算"块根据选择的运算符显示不同输入:
// 定义mutator
const MATH_OPERATOR_MUTATOR_MIXIN = {
updateShape_: function(operator) {
// 清除现有输入
this.removeInput('ARG2');
// 对于需要两个参数的运算符添加第二个输入
if (['ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE'].includes(operator)) {
this.appendValueInput('ARG2')
.setCheck('Number')
.appendField('参数2');
}
}
};
// 注册mutator
Extensions.registerMutator(
'math_operator_mutator',
MATH_OPERATOR_MUTATOR_MIXIN,
function() {
// 初始化逻辑
this.getField('OPERATOR').setValidator((value) => {
this.updateShape_(value);
return value;
});
}
);
自定义渲染样式
通过CSS自定义块的视觉样式,创建media/custom_blocks.css:
/* 自定义颜色块样式 */
.blocklyStyleColourBlocks .blocklyPathLight {
fill: #5C8AFF;
}
/* 自定义突出显示效果 */
.customHighlight .blocklyPath {
stroke: #ffd700;
stroke-width: 2px;
}
在块定义中引用样式:
{
"style": "colour_blocks customHighlight",
// ... 其他配置
}
代码生成器深度配置
JavaScript生成器高级技巧
代码生成器不仅能转换块为代码,还能实现优化逻辑。以下是一个具有结果缓存功能的生成器示例:
Blockly.JavaScript['fibonacci'] = function(block) {
const n = Blockly.JavaScript.valueToCode(block, 'N', Blockly.JavaScript.ORDER_ATOMIC);
// 添加缓存逻辑
const funcName = Blockly.JavaScript.provideFunction_(
'fibonacci',
[
'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(n) {',
' if (n <= 1) return n;',
' if (!' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '.cache) {',
' ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '.cache = {};',
' }',
' if (n in ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '.cache) {',
' return ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '.cache[n];',
' }',
' const result = ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(n-1) + ' +
Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(n-2);',
' ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '.cache[n] = result;',
' return result;',
'}'
]);
const code = `${funcName}(${n})`;
return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
};
Python生成器配置示例
为自定义块添加Python代码生成支持:
// generators/python/color_picker.ts
import * as Blockly from '../../core/blockly.js';
Blockly.Python['color_picker'] = function(block) {
const colour = block.getFieldValue('COLOUR');
return [`'${colour}'`, Blockly.Python.ORDER_ATOMIC];
};
Blockly.Python['temperature_converter'] = function(block) {
const value = Blockly.Python.valueToCode(block, 'VALUE', Blockly.Python.ORDER_ATOMIC);
const fromUnit = block.getFieldValue('FROM_UNIT');
const toUnit = block.getFieldValue('TO_UNIT');
if (fromUnit === toUnit) {
return [value, Blockly.Python.ORDER_ATOMIC];
}
let code;
if (fromUnit === 'C' && toUnit === 'F') {
code = f"({value} * 9/5) + 32";
} else {
code = f"({value} - 32) * 5/9";
}
return [code, Blockly.Python.ORDER_MATHEMATICAL];
};
测试与调试策略
单元测试与集成测试
为自定义块编写单元测试,创建tests/blocks/custom/color_picker_test.js:
describe('Color picker block', function() {
it('should return correct hex value', function() {
// 初始化工作区
const workspace = new Blockly.Workspace();
// 创建测试块
const block = workspace.newBlock('color_picker');
block.setFieldValue('#00ff00', 'COLOUR');
block.initSvg();
block.render();
// 验证代码生成
const code = Blockly.JavaScript.blockToCode(block);
chai.assert.equal(code[0], '"#00ff00"');
});
});
常见问题排查
| 问题类型 | 可能原因 | 解决方案 |
|---|---|---|
| 块不显示在工具箱 | 1. 未在blocks.ts中导出 2. 工具箱XML配置错误 3. 块类型ID不匹配 | 1. 检查export语句 2. 验证XML中的type属性 3. 确保块定义与注册名称一致 |
| 代码生成错误 | 1. 生成器函数未注册 2. 块字段名称不匹配 3. 缺少必要的输入值处理 | 1. 确认生成器注册代码 2. 检查getFieldValue参数 3. 使用valueToCode时处理null情况 |
| 动态形状不更新 | 1. mutator未正确注册 2. 验证器未调用updateShape_ 3. 输入连接规则冲突 | 1. 检查mutator注册代码 2. 在字段验证器中显式调用 3. 简化连接规则避免冲突 |
| 性能问题 | 1. 复杂逻辑在渲染时执行 2. 过多DOM操作 3. 递归调用未优化 | 1. 将逻辑移至事件处理而非渲染 2. 使用DocumentFragment批量更新 3. 添加缓存机制减少重复计算 |
最佳实践与性能优化
块设计原则
- 单一职责:每个块只实现一个功能,复杂逻辑通过块组合实现
- 接口一致性:遵循内置块的交互模式,减少用户学习成本
- 渐进式复杂度:基础功能保持简单,通过mutator实现高级特性
- 类型安全:严格定义输入/输出类型检查规则
性能优化技巧
- 减少DOM操作:使用
Blockly.utils.dom工具类批量处理DOM - 延迟计算:复杂计算推迟到代码生成阶段而非块交互时
- 事件节流:对于高频事件(如拖拽)实现节流控制
- 样式优化:使用CSS类而非内联样式,减少渲染阻塞
// 优化前:多次DOM操作
this.appendField(new Blockly.FieldTextInput(''), 'FIELD1');
this.appendField(new Blockly.FieldTextInput(''), 'FIELD2');
// 优化后:批量处理
const fragment = document.createDocumentFragment();
const field1 = new Blockly.FieldTextInput('');
const field2 = new Blockly.FieldTextInput('');
fragment.appendChild(field1.createDom());
fragment.appendChild(field2.createDom());
this.svgGroup_.appendChild(fragment);
总结与进阶学习
核心知识点回顾
本文系统讲解了Blockly自定义块开发的完整流程,包括:
- 块的JSON结构与TypeScript定义方法
- 三种基础块类型(基础型、输入型、嵌套型)的实现
- 高级功能(动态形状、自定义渲染、代码生成优化)的开发
- 测试策略与性能优化技巧
进阶学习路径
掌握基础开发后,可进一步探索以下高级主题:
- 自定义字段类型:开发滑块、日期选择器等特殊输入控件
- 块库管理:实现块的按需加载与版本控制
- 国际化支持:多语言块名称与提示文本
- 协作编辑:基于事件系统实现多用户协作功能
实用资源推荐
- 官方文档:Blockly Developer Site(包含API参考与教程)
- 示例库:blockly-samples仓库中的custom-blocks示例
- 社区工具:Blockly Developer Tools(在线块定义生成器)
希望本文能帮助你顺利开展Blockly自定义块开发工作。如有任何问题或建议,欢迎在评论区留言讨论。别忘了点赞、收藏本文,关注作者获取更多Blockly进阶教程!
下期预告:《Blockly代码生成器深度定制:从模板到优化》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



