Blockly自定义块开发全攻略:扩展编辑器功能

Blockly自定义块开发全攻略:扩展编辑器功能

【免费下载链接】blockly The web-based visual programming editor. 【免费下载链接】blockly 项目地址: https://gitcode.com/gh_mirrors/bloc/blockly

引言:为什么需要自定义块?

你是否曾在使用Blockly(可视化编程编辑器)时遇到内置块无法满足特定业务需求的困境?作为Web-based visual programming editor,Blockly的核心优势在于其高度可扩展性,而自定义块(Custom Blocks)正是扩展其功能的关键所在。本文将系统讲解从块定义、字段配置到代码生成的完整开发流程,帮助你掌握自定义块开发的核心技术。

读完本文后,你将能够:

  • 理解Blockly块的JSON结构与TypeScript定义规范
  • 掌握基础型、输入型和嵌套型三种块的开发方法
  • 实现自定义字段验证与动态交互逻辑
  • 配置代码生成器将块转换为目标语言
  • 解决块开发中的常见问题与性能优化

Blockly架构与自定义块原理

Blockly核心组件架构

Blockly编辑器由五大核心模块构成,自定义块开发主要涉及块定义系统代码生成器

mermaid

自定义块开发流程

自定义块开发遵循标准化流程,从定义到集成通常需要完成六个关键步骤:

mermaid

开发环境搭建

环境准备与项目结构

使用国内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. 添加缓存机制减少重复计算

最佳实践与性能优化

块设计原则

  1. 单一职责:每个块只实现一个功能,复杂逻辑通过块组合实现
  2. 接口一致性:遵循内置块的交互模式,减少用户学习成本
  3. 渐进式复杂度:基础功能保持简单,通过mutator实现高级特性
  4. 类型安全:严格定义输入/输出类型检查规则

性能优化技巧

  1. 减少DOM操作:使用Blockly.utils.dom工具类批量处理DOM
  2. 延迟计算:复杂计算推迟到代码生成阶段而非块交互时
  3. 事件节流:对于高频事件(如拖拽)实现节流控制
  4. 样式优化:使用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定义方法
  • 三种基础块类型(基础型、输入型、嵌套型)的实现
  • 高级功能(动态形状、自定义渲染、代码生成优化)的开发
  • 测试策略与性能优化技巧

进阶学习路径

掌握基础开发后,可进一步探索以下高级主题:

  1. 自定义字段类型:开发滑块、日期选择器等特殊输入控件
  2. 块库管理:实现块的按需加载与版本控制
  3. 国际化支持:多语言块名称与提示文本
  4. 协作编辑:基于事件系统实现多用户协作功能

实用资源推荐

  • 官方文档:Blockly Developer Site(包含API参考与教程)
  • 示例库:blockly-samples仓库中的custom-blocks示例
  • 社区工具:Blockly Developer Tools(在线块定义生成器)

希望本文能帮助你顺利开展Blockly自定义块开发工作。如有任何问题或建议,欢迎在评论区留言讨论。别忘了点赞、收藏本文,关注作者获取更多Blockly进阶教程!

下期预告:《Blockly代码生成器深度定制:从模板到优化》

【免费下载链接】blockly The web-based visual programming editor. 【免费下载链接】blockly 项目地址: https://gitcode.com/gh_mirrors/bloc/blockly

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值