2、自定义节点开发(上):节点结构、HTML定义、JavaScript逻辑、package.json配置

说实话,很多Node-RED用户用了一两年,还是在拖拽现成节点。但真正让Node-RED发挥威力的,恰恰是自定义节点开发。我自己带团队做物联网项目时,经常遇到标准节点搞不定的场景——比如对接某个私有协议的传感器,或者需要做复杂的边缘计算逻辑。这时候,写一个自定义节点,往往比拼凑十几个标准节点要优雅得多。

这一章,咱们就聊聊自定义节点的骨架。嗯,说白了就是四个部分:节点结构长什么样、HTML怎么定义界面、JavaScript怎么写逻辑、package.json怎么配。搞懂这些,你就能写出第一个真正属于自己的节点了。

核心要点: 一个自定义节点 = HTML(界面) + JavaScript(逻辑) + package.json(元信息)。三者缺一不可。

2.1 节点结构:从文件到文件夹

我刚开始写自定义节点时,犯过一个低级错误——把所有代码塞进一个文件里。后来项目大了,维护起来简直想哭。你想想看,一个节点既要处理UI,又要处理业务逻辑,还要管配置,全挤在一起,改个颜色都要翻半天。

正确的做法是这样的:

my-custom-node/
├── package.json          # 节点元信息
├── my-node.html          # 节点界面定义
├── my-node.js            # 节点逻辑代码
└── icons/                # 图标文件夹(可选)
    └── my-icon.png

我个人习惯把HTML和JS分开。虽然Node-RED官方允许你把它们写在一个文件里,但分开后,前端和后端的逻辑边界更清晰。特别是当你需要多人协作时,前端工程师改HTML,后端工程师改JS,互不干扰。

文件 作用 谁负责
package.json 声明节点名称、版本、依赖 项目初始化时配置
my-node.html 定义节点在编辑器中的外观、配置面板 前端开发者
my-node.js 实现节点的运行时逻辑 后端/全栈开发者
小技巧: 文件名最好和节点类型名保持一致。比如节点类型叫"my-sensor",文件就叫my-sensor.html和my-sensor.js。这样找起来一目了然。

2.2 HTML定义:节点长什么样

HTML文件定义了节点在编辑器里的样子。说白了,就是三个部分:节点图标、配置对话框、帮助文本。

来看一个最简单的例子:

<script type="text/html" data-template-name="my-sensor">
  <div class="form-row">
    <label for="node-input-name">名称<i class="fa fa-asterisk"></i></label>
    <input type="text" id="node-input-name" placeholder="传感器名称">
  </div>
  <div class="form-row">
    <label for="node-input-interval">采集间隔(秒)</label>
    <input type="number" id="node-input-interval" min="1" value="10">
  </div>
</script>

<script type="text/html" data-help-name="my-sensor">
  <p>这是一个自定义传感器节点,用于采集环境数据。</p>
  <h3>配置说明</h3>
  <ul>
    <li><b>名称</b>:给节点起个易识别的名字</li>
    <li><b>采集间隔</b>:每隔多少秒采集一次数据</li>
  </ul>
</script>

<script type="text/javascript">
  RED.nodes.registerType('my-sensor', {
    category: 'input',
    color: '#a6bbcf',
    defaults: {
      name: { value: '' },
      interval: { value: 10, required: true }
    },
    inputs: 0,
    outputs: 1,
    icon: 'my-icon.png',
    label: function() {
      return this.name || 'my-sensor';
    }
  });
</script>

这里有几个关键点,我得提醒你注意:

  • data-template-name:这个属性值必须和节点类型名一致。我曾经因为拼写错误,折腾了半小时找不到节点——编辑器里死活不显示配置面板。
  • data-help-name:帮助文档。很多人忽略这个,但团队里新人上手时,帮助文档能省不少事。
  • RED.nodes.registerType:注册节点的核心函数。第二个参数里的defaults定义了节点的默认配置,这些配置会保存到流程文件中。
注意: defaults里的每个属性,都会自动映射到HTML中对应id的元素。比如name属性对应node-input-name。命名规则是"node-input-属性名"。搞错了,配置就存不进去。

2.3 JavaScript逻辑:节点怎么干活

JS文件是节点的灵魂。它定义了节点接收到消息后做什么,以及如何输出消息。

来看对应的JS代码:

module.exports = function(RED) {
  function MySensorNode(config) {
    RED.nodes.createNode(this, config);
    
    // 保存配置
    this.name = config.name;
    this.interval = config.interval || 10;
    
    // 节点实例化时的逻辑
    var node = this;
    
    // 定时采集
    var timer = setInterval(function() {
      var msg = {
        payload: {
          temperature: Math.random() * 30 + 10,
          humidity: Math.random() * 50 + 30,
          timestamp: Date.now()
        }
      };
      node.send(msg);
    }, this.interval * 1000);
    
    // 清理资源
    node.on('close', function() {
      clearInterval(timer);
    });
  }
  
  RED.nodes.registerType('my-sensor', MySensorNode);
};

这段代码做了几件事:

  1. 构造函数:MySensorNode接收config参数,里面包含了用户在配置面板里填的值。
  2. 创建节点:RED.nodes.createNode(this, config)是必须调用的,它初始化了节点的生命周期。
  3. 业务逻辑:这里模拟了一个定时采集传感器数据的场景。实际项目中,你可能需要调用硬件API或者读取串口数据。
  4. 清理资源:node.on('close')里释放定时器。这个很重要——不清理的话,节点删除了定时器还在跑,内存泄漏就来了。
经验之谈: 我在一个工厂项目中,就因为忘了在close事件里清理数据库连接,导致Node-RED运行三天后内存爆满。从那以后,我养成了习惯:所有外部资源(定时器、文件句柄、网络连接)都在close里释放。

2.4 package.json配置:节点的身份证

package.json告诉Node-RED:这个包里有哪些节点,叫什么名字,依赖什么库。

一个标准的配置长这样:

{
  "name": "node-red-contrib-my-sensor",
  "version": "1.0.0",
  "description": "自定义传感器节点",
  "node-red": {
    "nodes": {
      "my-sensor": "my-sensor.js"
    }
  },
  "keywords": ["node-red", "sensor", "iot"],
  "dependencies": {
    "serialport": "^10.0.0"
  }
}

这里有个命名规范,我建议你遵守:

  • 包名以node-red-contrib-开头。这是社区约定,方便别人搜索。
  • node-red.nodes字段里,键是节点类型名,值是JS文件路径。
  • 如果节点依赖第三方库(比如串口通信的serialport),写在dependencies里。
字段 说明 示例
name 包名,全局唯一 node-red-contrib-my-sensor
node-red.nodes 声明节点类型和入口文件 {"my-sensor": "my-sensor.js"}
keywords 方便在Node-RED库中搜索 ["node-red", "sensor"]
提示: 如果你有多个节点类型,可以在nodes里列出多个。比如:{"sensor-a": "sensor-a.js", "sensor-b": "sensor-b.js"}。每个JS文件里可以注册多个节点类型,但建议一个文件只负责一个类型,保持清晰。

2.5 知识体系总览

说了这么多,咱们用一张图把整个结构串起来。这张图是我自己画课时常用的,能帮你快速建立整体认知:

自定义节点开发核心结构 HTML 定义 JavaScript 逻辑 package.json 配置 配置面板 (data-template-name) 帮助文档 (data-help-name) 注册类型 (registerType) 构造函数 + createNode 业务逻辑 (send/on) 资源清理 (close事件) 包名 + 版本号 node-red.nodes 声明 依赖管理 (dependencies) 三者协作关系 HTML定义界面 → 用户配置参数 → 传给JS构造函数 package.json告诉Node-RED如何加载和识别节点

这张图把三个模块的关系讲得很清楚了。HTML负责"长什么样",JS负责"怎么干活",package.json负责"我是谁"。三者通过registerType这个纽带连接在一起。

总结一下: 写自定义节点,就是先搭架子(package.json),再画皮(HTML),最后注入灵魂(JS)。顺序可以调,但三个部分一个都不能少。

嗯,这一章的内容就到这儿。记住,自定义节点开发是Node-RED进阶的分水岭。跨过这道坎,你就不再是"只会拖拽的用户",而是"能造轮子的开发者"了。

可可精品资料库