How do Node-RED plugins work?

Is there any documentation for how to write a Node-RED runtime plugin? E.g.,

RED.plugins.registerPlugin()

Or any examples?
I'm probably using the wrong search keys....

Update: never mind, I was missing an essentially empty flexdash-plugin.html file. Awesome error message...

Why can't plugins have nodes? If I try something like this in package.json:

  "node-red": {
    "plugins": {
      "flexdash": "flexdash-plugin.js"
    },
    "nodes": {
      "flexdash dashboard": "flexdash-dashboard.js"
    }
  }

I get:

6 Apr 11:06:34 - [error] Failed to start server:
6 Apr 11:06:34 - [error] TypeError: Cannot read properties of undefined (reading 'forEach')
    at Object.addModule (/usr/src/node-red/node_modules/@node-red/registry/lib/registry.js:195:27)
    at /usr/src/node-red/node_modules/@node-red/registry/lib/loader.js:138:34

at the point of the error, if I print set I get:

set flexdash dashboard: {
  "file": "/usr/src/node-red/node_modules/@flexdash/node-red-flexdash/flexdash-dashboard.js",
  "module": "@flexdash/node-red-flexdash",
  "name": "flexdash dashboard",
  "version": "0.4.3",
  "local": false
}

There's no .id nor .type...

This error keeps plaguing me:

6 Apr 23:29:24 - [error] TypeError: Cannot read properties of undefined (reading 'forEach')
    at Object.addModule (/usr/src/node-red/node_modules/@node-red/registry/lib/registry.js:195:27)
    at /usr/src/node-red/node_modules/@node-red/registry/lib/loader.js:138:34

It seems there is a race condition between loading the plugins and the nodes of a module. I just restarted NR 3x without making any changes whatsoever. The first 2x I got the error, the 3rd it started up. Not sure how to provide troubleshooting info.

A detailed description of what you are actually doing would probably help. What is your plugin doing?

I can't produce a simple example to reproduce the problem. However, I did spend some time debugging. Here's what I see:

  • if I have a package that has plugins and modules, both are loaded asynchronously at the same time: node-red/loader.js at master · node-red/node-red · GitHub
  • then the loader waits for the plugins to be done loading on line 129
  • and proceeds to process the nodes of the package on line 138
  • that's where things fail randomly because the loading of nodes may or may not be complete, it's awaited further down on line 145

I'm now wondering what a work-around might be (other than moving the plugin portion into a separate module).

For reference, the node-red packages.json section is:

  "node-red": {
    "version": "^2.0.6",
    "plugins": {
      "flexdash": "flexdash-plugin.js"
    },
    "nodes": {
      "flexdash dashboard": "flexdash-dashboard.js",
      "flexdash container": "flexdash-container.js",
      "flexdash tab": "flexdash-tab.js",
      "flexdash dev server": "flexdash-dev-server.js"
    }
  },

I didn't ask for a test case/example, just an overview of what you are trying to achieve here.

How do the nodes/plugin interact?

From the symptoms you've described, I assume there is some interaction between the too where one expects the other to have finished loading before it tries to do something. If you can describe what that it is then we may be able to offer an alternative or maybe come up with a fix for the core.

Plugins are a relatively new feature, so it is always possible that there will be code paths that have not been exercised enough to shake out all the possible errors. This might be the first tightly coupled set of plugin/nodes.

The use-case is the FlexDash dashboard. I have a number of config nodes (grids, panels, tabs, dashboard) and I want a simple way for users to initialize dashboard nodes (similar to UI nodes). For that, I'd like a global so the code reads something like

<global>.initDashboardNode(this, config)

and by creating a plugin this can indeed be done and looks like

RED.plugins.get('flexdash').initDashboardNode(this, config)

In initDashboardNode I can then work out which dashboard config node this belongs to based on the config (unlike the UI dashboard, FlexDash supports multiple dashboards).

:question: I described the bug in detail in my previous message. All that happens before any of my code executes. It's all in the loader when it's just reading files and concocting some metadata about them, it's not executing any of them.

Just calling require on the .js runs code (which is happening here), and it's not uncommon for nodes/plugins to do some initialisation upfront before the module.exports of the Node-RED initialisation function for nodes/plugins (and will become relevant a little later on).

But anyway, I have managed to write a simple test case with a basically empty plugin and node bundled together in the same package.json that triggers the error, your earlier analysis of a race condition looks to be correct. It is an intermittent failure but I think I can force the window wider, so it fails more often making it easier to test potential fixes, in a number of ways

  1. Having more nodes in the node-red.nodes section of the package.json, Just repeating the node with id changes so it doesn't clash with it's self
  2. Having the loading of any given node in the node-red.node section take longer, adding a bunch of extra require statements at the beginning of the a node's .js file should force the initial load to take longer.

Now we have a test case we can open an issue (Race condition when loading module with both Plugin and Nodes · Issue #3523 · node-red/node-red · GitHub) to try and get this fixed.

But as I mentioned earlier it appears you are the first person to try and bundle a plugin and a set of nodes in a single package.

2 Likes

Hi Ben, do you have a rough idea of where the problem lies? I can (try to) pick this up next week but won't if you are going to work on it.

As far as I can tell, the relevant requires are in loadPlugin and loadModule, which are called from loadNodeSetList, which is called after the bug happens.

Yup, that should do it.

That's very surprising given the ample documentation about plugins! /end-sarcasm :wink:

I can't speak for Ben, but I thought my description made it pretty obvious. You can't call addModule node-red/loader.js at master · node-red/node-red · GitHub before awaiting the promise node-red/loader.js at master · node-red/node-red · GitHub that signals that the data needed by addModule is available.