Multiple Nodes In One Module/Package?

How does one configure the package.json, and respective js and html files to have multiple nodes defined in one module/package?

I took a crack at it, and I am getting a really, at least to me odd error in the manage palette view. It states the package has 9 nodes, i.e. 3 copies of each of my 3 nodes? No clue why or how that is happening.

All my nodes work, and I see them fine in the editor, I have something definitely wrong in my configuration.

package.json...

{
  "name": "node-red-contrib-raspberry-pi-hardware",
  "version": "0.0.1",
  "description": "",
  "main": "pi.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "node-red": {
    "nodes": {
      "pi-processor": "pi.js",
      "pi-configuration": "pi.js",
      "pi-frequency": "pi.js"
    }
  }
}

pi.js...

module.exports = function(RED) {
    function PiConfigurationNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            msg.payload = 'pi-configuration node under construction';
            node.send(msg);
        });
    }
    RED.nodes.registerType("pi-configuration",PiConfigurationNode);

    function PiFrequencyNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            msg.payload = 'pi-frequency node under construction';
            node.send(msg);
        });
    }
    RED.nodes.registerType("pi-frequency",PiFrequencyNode);

    function PiProcessorNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            msg.payload = 'pi-processor node under construction';
            node.send(msg);
        });
    }
    RED.nodes.registerType("pi-processor",PiProcessorNode);
}

pi.html...

<script type="text/javascript">
    RED.nodes.registerType('pi-configuration',{
        category: 'function',
        color: '#a6bbcf',
        defaults: {
            name: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"pi-configuration";
        }
    });
</script>

<script type="text/html" data-template-name="pi-configuration">
    <div class="form-row">
        <label for="node-input-name"><i class="icon-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
</script>

<script type="text/html" data-help-name="pi-configuration">
    <p>A simple node that provides visibility to the raspberry pi start configuration</p>
</script>

<script type="text/javascript">
    RED.nodes.registerType('pi-frequency',{
        category: 'function',
        color: '#a6bbcf',
        defaults: {
            name: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"pi-frequency";
        }
    });
</script>

<script type="text/html" data-template-name="pi-frequency">
    <div class="form-row">
        <label for="node-input-name"><i class="icon-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
</script>

<script type="text/html" data-help-name="pi-frequency">
    <p>A simple node that provides visibility to the raspberry pi processor frequency state</p>
</script>

<script type="text/javascript">
    RED.nodes.registerType('pi-processor',{
        category: 'function',
        color: '#a6bbcf',
        defaults: {
            name: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"pi-processor";
        }
    });
</script>
<script type="text/javascript">
    RED.nodes.registerType('pi-processor',{
        category: 'function',
        color: '#a6bbcf',
        defaults: {
            name: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"pi-processor";
        }
    });
</script>

<script type="text/html" data-template-name="pi-processor">
    <div class="form-row">
        <label for="node-input-name"><i class="icon-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
</script>

<script type="text/html" data-help-name="pi-processor">
    <p>A simple node that provides visibility to the raspberry pi processor information</p>
</script>

Oh, the node code is in a single js file because the nodes will have common requirements and functions. I know there is some discussion on split things out to multiple js files, but for this question, can we limit the discussion to how to do things correct in a single js file for now, thanks.

And... how does one define a section in the node palette? I notice that some packages place nodes under function section, some have their own, I guess, custom sections, if you will?

"node-red": {
    "nodes": {
      "pi-processor": "pi.js"
    }
  }

is enough

And with category: 'function', you can define your own category, just use something else than function

Tip: Read the documentation carefully, i.e. every page about creating nodes and also look at existing nodes

Thanks for the reply. Interesting... got the idea of referencing each node... in the package.json from this forum. Oh, wait, the example in this forum... was when some split the code files? Now it makes sense, with what the log file was reporting too. That nodes were already defined. So if I understand this correctly, the way I have it now, the entire package really is being (re)dloaded each time for each reference in the nodes list. As for reading... I have read about 20 sources about packaging for node red, and all are extreme light weight examples, which is expected a of course. No one seems, that I have found thus, to have written a really deep example.

What would you consider a deep example? What sorts of things would you like to see it cover?

True, that is the question... An example that includes the following, IMHO:

  1. How to split code files as a best practice, explain when and why this should be considered, this not specific to node-red, but nodejs in general, so node-red would not have to reinvent this per se.

2: How use more of the frequently used features or settings in the package.json. Looking at examples in github the vary greatly and significantly, but no one comments their json files, so why they vary is a real mystery. For example, use of private: true I found only once, yet it is important while developing packages to avoid publishing in error.

  1. Explanation of what to look for in the logs, what some of the common issues are, and maybe steps to fix some of the more common issues.

  2. Explain what and when npn audit reports at least at some level.

Of course, of all the above would not be appropriate to a simple beginner example, but the examples, I have found, never go beyond a quick blurb on the subject.

If the beginner example is standing at the edge of a cliff. And, jumping into the vast expanse of the official documentation is like hitting the ground off said cliff. IMHO, we need some steps in the cliff face, to let developers walk into the official documentation, not have to jump off the cliff, from the top edge.

Above said, once I have my project done, I am going to make a point to do extensive explanation of why my package does what it does in reference to the package.json, why I did or did not split code files for the given project, as well reference of the code design questions that may be applicable, suggesting some standardized best practices that may want to be considered. For example, why running npm init --yes is really not a good idea except for the very first learning example.

Another example is better design goal questions on when a this.on close handler is really required with a complete example showing how it is applicable, explained well. Most references state use close event for async tasking clean up in a configuration node... a beginner is already asking what is a configuration node. The intermediate step, example, might be to explain what is special about a configuration node, versus simple function node.

Maybe one more issue, error handling is all over the map, some use try/catch, some just drop out reporting err via done(), many examples still just use node.warn with no reference to done(). This is understandable given so many packages for node-red seem to be working but abandoned creations? What can we do to encourage developers to bring their efforts current? We should have some way to support and promote concurrency in development.

You make some valid points here. However, one also has to distinguish between facts that are specific to Node-RED and other things that apply to javascript/nodejs/npm in general.
It cannot be the goal of the Node-RED project to explain every detail of npm, the package.json or javascript in its documentation.

I agree that an explanation what the node-red keyword in the package.json is doing exactly is missing in the docs and therefore caused confusion on your side:

Along with the usual entries, the package.json file must contain a node-red entry that identifies what nodes the module provides, along with a pointer to their .js files.

Is indeed confusing.

As you say, this is nothing specific to Node-RED and if you discuss that with 5 software engineers, you get 5 different answers.

The done mechanism has only been introduced in 1.0, therefore, not all nodes have been updated and it probably will take a long time. Furthermore, there are situations where you want to report a warning or an error without telling the runtime that the node has finished handling the message. Therefore, node.warn and node.error still have there use case.
And try/catch is a mechanism of javascript to catch errors, nothing specific to Node-RED.

nothing specific to node-red?

I agree, closing nodes properly is not always straightforward.

How I learned about writing nodes was really looking at the implementation of the core nodes, read them in detail, try things out with a lot of debug output.

... JSON does not allow comments so no-one can comment their json files...

1 Like

@dceejay
Right specific to current JSON standard, no comments. But that does NOT stop someone adding a file, say package.JSON.notes, that is not the JSON package file used by node red, but can be a reference to explain the content of the package JSON file. Maybe the readme file should comment on the JSON file as applicable. The goal is to get clarity of what and why something was done in the example of note. Not violate standards.

@cinhcet
Yes, there is overlap with nodejs methods and practices and node-red of course. The node-red development examples run the gambit of the issues in nodejs development, right. That said, there is no reason a detailed example cannot address some of the common pit falls, qualify why they are of significance to node-red. For example, many node-red development examples do not follow nodejs best practices for error handling. Why? Does node-red development get a pass of error handling best practice? Of course not.

Getting past solutions compliant to 1.0 standards, if you will, should be a strategic goal of the entire node-red community. Not sure how we do it, but clearly we need to take some steps in this direction. Maybe adding code to our packages that blocks their use for below 1.0 installations is an idea to consider. I plan to do it, to flat out code only for 1.0 and later. I know this will not always be popular, but it is what I plan to do for now.

@everyone
Another example of something that I can't seem to find a good reference for so far via google.

Where are the icon files? Which ones are core to the node-red basic instal? I want to use rpi.png. It is referenced by other pi specific nodes html files. But when I search the entire file system for rpi.png, no hits. When I look at the source files of other pi specific node packages I see the same reference to icons in html file, that I used, icon: rpi.png. But no actual rpi.png file in the package sources? Why can't I find the file in the file system? This something I want to clearly explain in my package documentation, source.

what do you mean exactly?

If you read
https://nodered.org/docs/creating-nodes/appearance
and the icons are now svgs.
Your raspberry icons are here node-red/packages/node_modules/@node-red/nodes/icons at master · node-red/node-red · GitHub
as rpi.svg

oh, use of try and catch appropriately for example, strongly suggesting try/catch method versus if/then often used. Not that if/then technically wrong, but again, trying to encourage consistency. Of course, I am also referring to those developers that never do any serious error handling should have their toes stepped on. I have a bias... I did code review for years, even though I was NOT a programmer per se, because I happened to be able to break code often and well... the corporate programming teams hated me, in a professional sense, because I was always pushing for robust, responsive code design... handle issues, anticipate issues and code for the likely expected ones, now, not later, type of thing. One programmer was told, hey, that guy broke your app in 30 seconds... a new record.... the programmer replied... NOT THAT GUY AGAIN. :slight_smile: Said programmer became a good friend of mine over time... His code got better, I had a harder time breaking it as often. Not to brag, but I got corporate recognition repeatly for my QA efforts, saved a lot $$$, in a fortune 50 firm.

Oh, and, thanks for the info on the icons... weird in html icon reference is icon.png, but file system svg. Ok I guess. Bottom line is, apparently, said icon is core to node-red so I don't need to handle it as an explicit dependency in my package.

Yup - just a quick pull-request away from being fixed.

LOL! Love it.

can you please give examples? Because try/catch is not always the solution....

Yes, I will in my current project, once I get it more flushed out. One key good practice is to make sure each node function has at least one try/catch to trap everything not already handled. This is parallel to the idea of an uncaught exception trap concept that is best practice for nodejs in general. I always establish that enclosing try/catch as a starting point. At times I use if then to decide if I handle the error in a sub try/catch, or throw the error to the default handler as noted above. Of course throwing to the default handler in context of the node-red function is when the issue is fatal. Whereas any other errors should be handled in graceful manner to allow node to respond appropriately. All this has to do with correctly supporting the (new) ** done ** model as well.

You claimed that " node-red development examples do not follow nodejs best practices for error handling" and I wanted to so examples of that :slight_smile:

Further, just putting a try/catch around everything is pretty much useless in javascript if you do something with async callbacks etc. In addition, you may even want that the complete process terminates if there is an uncaught exception, since it can lead to undefined behavior if you just have a big try catch around everything.

Anyway, I don't want to argue too much about that.
I just don't like if people have too strong claims about "other implementations do not follow the rules", when neither "the rules" can not even be defined objectively or no examples are given.

I hope I could help you a bit and if you have further questions, don't hesitate to ask, I think you spotted a good weakness of the documentation about the node-red keyword in the package.json, which should be improved.

1 Like

What you mean be a 'node function'?

Node function... the functional task the given node does. I guess node task is a better way to explain it. For example:

Input->Do Something->Send->Done

I always make sure I have an enclosing try/catch for the entire sequence, so if something odd happens, I ways get to a known state.

So for a fatal issue...
try->
Input->Do Something->Send->Done
catch->
Handle Fatal Issue->Done

For non-fatal...
try->
Input->Do Something->Handle Error If Any->Send If Applicable->Done
catch->
Handle Fatal Issue->Done

Since my current project is not Async or Daemon type of scenario, simple to implement an uncaught exception handler compliant design concept. Of course if you have multiple outputs it is a bit more complex, but I think above examples, in the simple case, illustrate the goal of the design.

What do you do if you get an exception that you don't expect?