Naming convention regarding custom nodes

I've been playing around with building custom nodes and so far it's been a great experience.

However, I'd love to know the naming convention surrounding the js/html and key functions. I haven't fathomed the do's and don'ts of those areas.

Here are my assumptions to be confirmed by folks here:

1. custom node module folders

If custom node modules X and Y are in two different parent folders, then their immediate module folders could share the same name, e.g.,
* /path/to/my_node_x/node (module folder holding package.json)
* /path/to/my_node_y/node (module folder holding package.json)

are two valid paths for nodes, correct?

2. filenames

The "node-red" section in package.json could be named this way

  "node-red": {
    "nodes": {
      "my_node_x": "node.js"
    }
  },

This means that

  • The JS/HTML filenames are unimportant from node to node, as long as they share the same basename, e.g. in this case, node.js, and node.html are good enough for all the custom nodes.
  • The name of the main script of package.json is unimportant.

Are these correct?

3. function names

With node.js, the name of the node function to register is unimportant.

e.g., it could look like this

module.exports = function(RED) {
    function Node(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        // gets project path
        node.on('input', function(msg) {
            var inMsg = msg.payload;
            ...
            node.send(msg);
        });
    }
    RED.nodes.registerType("my_node_x", Node);

}

And I could name my_node_y 's node function and all the other nodes' functions the exact same way, i.e., having the same funciton name Node to register with.

Are these correct?

Thanks in advance!

Correct.

Yes, but it is good idea to give it a bit more of a meaningful name. For example, if you are working on multiple nodes at the same time, having lots of node.js files open in your editor won't make your life any easier.

For typical nodes, you do not need to have any main entry in the package.json at all.

Yes, technically that is right. But again, you would do yourself no favours by using generic names like that when it comes to debugging/developing the node. I would always recommend trying to use meaningful names.

Personally, I try to have a single name for my nodes registration function (nodeInstance). Having recently been thinking hard about how to best structure a complex node (uibuilder), and with input from a couple of other recent threads, I stumbled upon what I think is a better and clearer structure for a node's .js file as in the example below.

There are a number of advantages to this approach. The use of named functions throughout can make debugging easier. Creating a master reference to the RED object simplies use across the separated functions. The separated functions make it easier to follow the execution of your node and its instances and make it more obvious when code is executed and in what context. It also eliminates the need for a separate node local object which at least one person found, led to some memory leakage.

I've also gone back to building the node's .html file from 4 component files. A master template and 1 file for each of the 3 sections (JavaScript, config panel and help panel). I use Gulp scripts to automatically build the output which has the same file name as the .js file. This lets me more easily use linting and multiple editor windows. This is only worth-while for more complex nodes of course.

I always make sure that the name of the node is reflected in the node's file names and in the npm package name (where possible) otherwise it can get terribly confusing (for my simple brain, your milage may differ :grinning: )

Example restructured .js file:

/** A simple template for defining custom nodes
 * Destructured to make for easier and more consistent logic.
 */
'use strict'

/** --- Type Defs ---
 * @typedef {import('../typedefs.js').runtimeRED} runtimeRED
 * @typedef {import('../typedefs.js').runtimeNodeConfig} runtimeNodeConfig
 * @typedef {import('../typedefs.js').runtimeNode} runtimeNode
 * typedef {import('../typedefs.js').myNode} myNode
 */

//#region ----- Module level variables ---- //

/** Main (module) variables - acts as a configuration object
 *  that can easily be passed around.
 */
const mod = {
    /** @type {runtimeRED} Reference to the master RED instance */
    RED: undefined,
    /** @type {string} Custom Node Name - has to match with html file and package.json `red` section */
    nodeName: 'jktest2',

    // Add anything else here that you may wish
    // to access from any function.
    // Having one object also makes it much easier
    // to pass this to external modules as needed.
}

//#endregion ----- Module level variables ---- //

//#region ----- Module-level support functions ----- //

/** 1a) Runs once when Node-RED (re)starts or when the node package is first added */
function moduleSetup() {
    // As a module-level named function, it will inherit `mod` and other module-level variables
    //const RED = mod.RED

    console.log('>>>=[1a]=>>> [moduleSetup] Startng')

    // Do stuff here that only needs doing once
    // Don't forget to push anything that might be needed by other functions and modules
    // into the `mod` variable so that it is easily accessible and can be passed on.
}

/** 3) Run whenever a node instance receives a new input msg
 * NOTE: `this` context is still the parent (nodeInstance).
 * See https://nodered.org/blog/2019/09/20/node-done 
 * @param {object} msg The msg object received.
 * @param {Function} send Per msg send function, node-red v1+
 * @param {Function} done Per msg finish function, node-red v1+
 */
function inputMsgHandler(msg, send, done) {
    // As a module-level named function, it will inherit `mod` and other module-level variables

    // If you need it - or just use mod.RED if you prefer:
    //const RED = mod.RED

    console.log('>>>=[3]=>>> [inputMsgHandler] Startng', msg) //, this)
    send(msg)
    done()
}

/** 2) This is run when an actual instance of our node is committed to a flow
 * @param {runtimeNodeConfig} config The Node-RED node instance config object
 */
function nodeInstance(config) {
    // As a module-level named function, it will inherit `mod` and other module-level variables

    // If you need it - which you will here - or just use mod.RED if you prefer:
    const RED = mod.RED

    console.log('>>>=[2]=>>> [nodeInstance] Startng')
    //console.log('>>>=[2a]=>>>', config)

    // Create the node instance - `this` can only be referenced AFTER here
    RED.nodes.createNode(this, config) 

    // Transfer config items from the Editor panel to the runtime
    this.name = config.name
    this.topic = config.topic

    /** Handle incoming msg's - note that the handler fn inherits `this`
     *  The inputMsgHandler function is executed every time this instance
     *  of the node receives a msg in a flow.
     */
    this.on('input', inputMsgHandler)


    /** Put things here if you need to do anything when a node instance is removed
     * Or if Node-RED is shutting down.
     * Note the use of an arrow function, ensures that the function keeps the
     * same `this` context and so has access to all of the node instance properties.
     */
    this.on('close', (removed, done) => { 
        console.log('>>>=[4]=>>> [nodeInstance:close] Closing. Removed?: ', removed)
        

        // Give Node-RED a clue when you have finished (more important if your shutdown
        // process includes an async task, make sure done() is executed when the async
        // task completes, not when this function ends).
        done()
    })

    /** Properties of `this`
     * Methods: updateWires(wires), context(), on(event,callback), emit(event,...args), removeListener(name,listener), removeAllListeners(name), close(removed)
     *          send(msg), receive(msg), log(msg), warn(msg), error(logMessage,msg), debug(msg), trace(msg), metric(eventname, msg, metricValue), status(status)
     * Other: credentials, id, type, z, wires, x, y
     * + any props added manually from config, typically at least name and topic
     */
    //console.log('>>>=[2b]=>>>', this)
}

//#endregion ----- Module-level support functions ----- //

/** 1) Complete module definition for our Node. This is where things actually start.
 * @param {runtimeRED} RED The Node-RED runtime object
 */
function Test2(RED) {
    // As a module-level named function, it will inherit `mod` and other module-level variables

    // Save a reference to the RED runtime for convenience
    // This allows you to access it from any other function
    // defined above.
    mod.RED = RED

    // Add function calls here for setup that only needs doing
    // once. Node-RED loads this once no matter how many instances
    // of you node you add to flows.
    moduleSetup() // (1a)

    // Register a new instance of the specified node type (2)
    RED.nodes.registerType(mod.nodeName, nodeInstance)
}

// Export the module definition (1), this is consumed by Node-RED on startup.
module.exports = Test2

//EOF

Wow, such a valuable share!
Makes a lot of sense.
Thanks a bunch @TotallyInformation

No problem. I like to share these things to see if there is any feedback or improvements. :grinning:

You could, of course, go further and put the whole thing in a CLASS but though I'm using classes for other modules in uibuilder, I didn't see much benefit in the case of the main node js file.