Include JS in a html custom module

I am writing my very first module and want to do the most basic thing, include a js in HTML or JS file of module. This is what I want to do

my-node.html

<script type="text/javascript">
    RED.nodes.registerType('create-issue', {
        .
        .
        .
        oneditprepare: function() {
            // some kind of require here
            myVeryLargeFunction();
        }
        .
        .
        .
    });
</script>

my-very-large-function.js

function myVeryLargeFunction() {
    // Do stuff here
}

So could anyone help me, tks

Use import or require in your js file as you would any other project.

example...

const fs = require('fs');

module.exports = function(RED) {
    "use strict";
    //use fs
}
1 Like

For an example see the getscript part of the geofence html file. https://github.com/hardillb/node-red-node-geofence/blob/master/geofence.html

Steve, this is browser side. You can’t just require as you would in node

2 Likes

Yes of course I missed that.

I got getScript function, tks alot. I will test now

i was working on this for last few days as well... the most important part is not the getScript function, but rather making the static folder available with:

RED.httpAdmin.get('/enocean-js/*', function(req, res){
        var options = {
            root: __dirname + '/static/',
            dotfiles: 'deny'
        };
        res.sendFile(req.params[0], options);
    });

in your nodes .js file

with that in place, you can just include your external scrips like you would normally do in your nodes .html file:

<scritp src="enocean-js/my-very-large-function.js"></script>

looking at the geofence repo lead me to the right path, though...

as advised on github i will add my request here...

while i was working on this, i realized that you can not load
<script type="module"></script>

currently i overcome this by injecting the script tag myself with

var st = document.createElement("script")
st.setAttribute("type","module")
st.setAttribute("src","enocean-js/test.js")
document.body.appendChild(st)

that seems a bit hackish to me, so i would really like to get this working cleanly.

I would like to write a PR for getting this to work. But as i'm still quite new to node-red it would be great if someone can give me a pointer in the right direction where loading the nodes .html file happens in the core.

not sure why that wouldn't work ? you haven't added the src in the <script version but I assume that is just a bad cut/paste ? What errors do you get ? Main thing is as you point out previously to ensure the server endpoint is in place.

<edit>oh yeah - on the client side we already have specific bits of script tags we look for so you can't have script inside script. Is that the problem ? where are you injecting your tag ? in oneditprepare ?

You don't need script inside script. The core debug node uses a script tag to load another file: https://github.com/node-red/node-red/blob/c7587960fbfe77a1609e7c3c29705bc1d5918dc3/packages/node_modules/%40node-red/nodes/core/core/58-debug.html#L34

@Holger-Will I'm not clear what you are asking for here that you cannot already do?

What i'm trying to do is using esm modules

Test 1

<script type="text/javascript">
    RED.nodes.registerType(...bla bla bla)
    console.log("one")
</script>
<script type="text/javascript">
    console.log("two")
</script>

logs: "one" and "two"

Test 2

<script type="text/javascript">
    RED.nodes.registerType(...bla bla bla)
    console.log("one")
</script>
<script type="module">
    console.log("two")
</script>

this only logs "one"

Test 3

using an external file ("enocean-js/test.js")

import { foo } from 'bar'
console.log("three")
<script type="text/javascript">
    RED.nodes.registerType(...bla bla bla)
    console.log("one")
</script>
<script type="module" src="enocean-js/test.js"></script>

i get: Uncaught SyntaxError: Unexpected token {
that's the same error you get when you try to use esm modules without type="module"

Test 4

agian using the external file ("enocean-js/test.js")

import { foo } from 'bar'
console.log("three")
<script type="text/javascript">
    RED.nodes.registerType(...bla bla bla)
    console.log("one")
    var st = document.createElement("script")
    st.setAttribute("type","module")
    st.setAttribute("src","enocean-js/test.js")
    document.body.appendChild(st)
</script>

logs "one" and "three", and esm modules work as expected, no error

conclusion

from these test it seems that the attribute type="module" gets lost somewhere during registration

obsolete... see next reply

for Test 3 i have a fix in my local branch now... it's pretty easy:
after line 45 in packages/node_modules/@node-red/editor-client/src/js/red.js

if( $(el).attr('type') === "module"){
  newScript.type = "module"
}

i could create a PR if you like.

Edit

Test 2 is caused by a bug in jQuerys append function. it's fixed in jQuery 3.x, so i'm not going to fix it. I guess you have a reason to stick with 1.x...

Edit 2

fiddling around a bit more, i came up with a solution for Test 2 (without changing jQuery version).

The problem is, that jQuery uses .innerHTML in it's append() function. The fact that scripts not getting executed when inserted by .innerHTML is overcome by jQuery by evaling the textContent of the script elements. jQuery does this based on script type (which is absolutely correct, as there are script types out there that are not evalable) But at the time jQuery was written, there was no type="module" :frowning:

So the script elements with type="module" do get inserted but not executed.

Knowing all this, the solution is again quite simple: get all scripts with type="module", create a new script element, copy the textContent over, and replace the old script with the new script.

after line 64 ($("body").append(nodeConfigEls);)

nodeConfigEls[ 0 ].querySelectorAll( 'script[type="module"]' ).forEach( function ( item )  {
  var newScript = document.createElement( 'script' )
  newScript.appendChild( document.createTextNode( item.textContent ) )
  newScript.setAttribute( 'type',  'module' )
  item.parentNode.replaceChild( newScript, item )
})

This allows script type="module" in node configs everywhere. That means to use esm modules one can simply import them from the config script:

<script type="module">
    import { foo } from 'bar'
    RED.nodes.registerType(...bla bla bla)
</script>

Edit 4

the simplest would be to use

document.body.appendChild(nodeConfigEls[0])

but that does not work... i have to investigate further...

1 Like

Hi @Holger-Will - great work.

By coincidence, upgrading to jQuery 3.x is in the plan for Node-RED 1.0 - and I've spent the morning on that - https://github.com/node-red/node-red/pull/2153 - so your workaround for that part wouldn't be needed. It isn't merged yet, but should be this week.

If you wanted to PR the other parts, we can review that. Please target the dev branch and not master.

1 Like