neutralinojs / neutralino.js

JavaScript API for Neutralinojs

Home Page:https://neutralino.js.org/docs/api/overview

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Suggestion] Alternative script for patching the user's html file

smac89 opened this issue · comments

I didn't really like the idea of having neutralino patching my html file, so I decided to create this script, which I put in my public folder:

'use strict';

const script = '<script id="neutralino" src="http://localhost:0/neutralino.js"></script>';
const injectScriptOnLoad = () => {
    const existing = document.head.querySelector('#neutralino');
    if (existing) {
        document.head.removeChild(existing);
    }
    // https://stackoverflow.com/a/72639251/2089675
    const scriptFragment = document.createRange().createContextualFragment(script);
    document.body.appendChild(scriptFragment);
};

if (document.readyState !== 'loading') {
    injectScriptOnLoad();
}

// https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState#readystatechange_as_an_alternative_to_load_event
document.addEventListener('readystatechange', event => {
    if (event.target.readyState === 'interactive') {
        injectScriptOnLoad();
    }
});

I am using react (cra) as my frontend, so somewhere in my index.html file, I placed the following:

<% if (process.env.NODE_ENV === 'production') { %>
  <script id="neutralino" src="%PUBLIC_URL%/neutralino.js"></script>
<% } else { %>
  <script src="%PUBLIC_URL%/neutralino.patch.js"></script>
<% } %>

Problems immediately solved

  • Patch file is not part of code, and will not affect written code

Potential future solution

Include this script as part of the neu create myapp process. I suggest changing the meaning of frontendLibrary.patchFile from being the file that neutralino will patch, to meaning the location where neutralino will place the above patch script:

"cli": {
  ...
  "frontendLibrary": {
    "patchFile": "/www/public/neutralino.patch.js",
    "devUrl": "http://localhost:3000"
  }
}
    const script = '<script id="neutralino" src="http://localhost:0/neutralino.js"></script>';
    
    // https://stackoverflow.com/a/72639251/2089675
    const scriptFragment = document.createRange().createContextualFragment(script);
    document.body.appendChild(scriptFragment);

FWIW, this is probably more subtle/advanced than it needs to be. Instead of creating a document fragment from a string using a range, it should be possible to simply create a script element DOM object with document.createElement('script'), assign its attributes, and use appendChild with the script element DOM object. Similar to the other answer in the same Stack Exchange question (except you'd set the src attribute instead of textContent).

@ScottFreeCode the problem with that idea is that most browsers will not execute or try to download that script, unless it is added the way I did. See the stackoverflow link

From that Stack Overflow question https://stackoverflow.com/q/57209520 :

document['body'].insertAdjacentHTML('afterbegin', ${json});

From the accepted answer on the same question https://stackoverflow.com/a/57211379 :

From the official HTML5 spec:

script elements inserted using innerHTML do not execute when they are inserted.

Inserting the adjacent HTML is the same as modifying innerHTML of a specific DOM element.

… Then, on the client side, you can create a script tag and inline your logic to it:

var script = document.createElement('script');
script.textContent = data.js;
document.body.appendChild(script);

The difference is use of appendChild instead of insertAdjacentHTML. Although if the JavaScript code is itself loaded from a file or server, I recommend using src to get the script tag itself to do the load, rather than using textContent.

This is also used in long-established web frameworks that predate ES Modules and as far as I know work cross-browser, and which still work today (I used it with Chromium just a couple weeks ago): e.g. https://requirejs.org/ (and there's a good discussion of possible implementations here https://requirejs.org/docs/why.html )

That said, I just tried ES Modules and they seem to work fine in a page served by Neu. If you don't specifically want to embed scripts complete with text content right in the page source rather than from JS files, or to load scripts synchronously for some reason, then ES Modules is probably the way to go. It's been a long time since they were experimental and everyone was bugging various frameworks and libraries to support them even though runtimes like Node didn't yet; I'd expect them to be pretty much the go-to solution now for anything with a server (even if the server is really just a local app!)