Sure! When you write code for the browser the standard process is that the code you write isn't what is actually sent to the browser, rather, it gets transformed first. Kind'a like a program in a compiled language gets compiled and linked before you get to run it. For browsers the process generally includes:
- convert typescript (or other languages and syntactic sugar, such as Vue's single-file-components) to pure javascript/html/css
- add polyfills (compatibility shims) for older browsers
- figure out which code is actually referenced and needs to be shipped to the browser
- group small files into larger ones to optimize load time
- minify & compress
There are several "bundlers" that perform the above steps, webpack and rollup are two of the more popular ones. But this bundling is a problem when you're developing because it can easily take several minutes to complete and you don't want to wait that long for each edit-test cycle.
So what Vite does is act as a web server that does the minimum necessary transformations on-the-fly as the browser is requesting stuff. So if the browser requests a .js
file vite may serve it up as-is, but if the browser requests a .vue
file then vite quickly splits the html, javascript and css portions and serves up something that pulls all three pieces in an appropriate way. You could think of this as being similar to just-in-time compilation vs. up-front compilation.
In addition to the transformations vite also watches your source files so if you change a source file it automatically retransforms it and sends the updated version to the browser. There a small piece of code swaps out the old code for the new code as seamlessly as possible. This is hot-module-reload: your modified code is running immediately without reloading the page. Of course this has limitations 'cause the new code may not work with the old data structures, but it's awesome for many small changes, in particular when you make small CSS and layout tweaks.
Yes and yes.
Yup, these are all valid points. I don't know either, so I'm leaving all options open. An argument in favor of having the 'widget props' tab is that it makes clear which settings can be overridden by a message's props
field. But for the auto-generated widgets having a node config
tab with just name and FD isn't very nice either.
Sure! I haven't pushed the updated code to github because too much is still in flux, so you can't see it yet. I also don't really know what the workflow should look like , I only know what some of the pieces in it probably should do...
Abstractly speaking, the automatic code generation produces two classes of artifacts: the content and the boilerplate glue. The content ones contain the information gathered from the widget: param names, types, defaults, help text. This is the stuff you don't want to manually keep in sync and want re-generated automagically. Then there's the boilerplate glue you may want to change so you can put everything together to your liking and you don't want those changes to be clobbered by auto re-generation.
Let me take a specific example: the gauge widget. The code generator produces two files:
resources/gauge-props.html
with the the content of the widget props fields
gauge.html
with the std Node-RED editor boilerplate
You want the first file to regenerate each time you change the props definitions in the .vue
file. But while you may want to use the second file as a starting point, you want to be able to customize it to your liking.
So, starting the example at the beginning... the gauge widget .vue
file is the source of everything and contains:
props: {
max: { type: Number, default: 100, tip: "maximum value" },
color: { type: String, default: 'green', tip: "color of filled segment" },
...
The generated resource file (see Loading extra resources in the editor) resources/gauge-props.html
contains:
<div class="form-row">
<label for="node-input-max">Max</label>
<input type="text" id="node-input-max" class="fd-typed-input" input-type="num"/>
<input type="hidden" id="node-input-max-type" />
<small class="fd-indent">Maximum value. Default: 100</small>
</div>
<div class="form-row">
<label for="node-input-color">Color</label>
<input type="text" id="node-input-color" class="fd-typed-input" input-type="str"/>
<input type="hidden" id="node-input-color-type" />
<small class="fd-indent">Color of filled segment. Default: "green"</small>
</div>
You may recognize that this is what you normally type manually for each node configuration parameter. Now comes the boilerplate, which is in gauge.html
. First we need an html element into which we insert the above form-row stuff:
<!-- tab for the widget params -->
<div id="fd-tab-props" style="display:none"></div>
and then we need to insert it dynamically, which we do in the oneditprepare
function. The insertion is a one-liner using the JQuery .load()
method, although the generated html expects the use of typedInput fields so there's a magic fdInitTypedInput
callback function to do that:
oneditprepare() {
const url = 'resources/@flexdash/node-red-fd-testnodes/gauge-props.html'
$('#fd-tab-props').load(url, fdInitTypedInput)
So where does that leave you? The generated gauge.html
file only contains boilerplate, you can take it as-is, or you can modify it, or you can completely replace it. In all cases, you decide whether and how you pull in the auto-generated gauge-props.html
with the individual form-row divs. For example, you can:
- disregard
gauge-props.html
and write your own form-row html
- modify
gauge.html
to remove the tabs yet still include the gauge-props.html
- pull in
gauge-props.html
and then hide some of the form-rows because you don't want the user to set those because your code in the node computes them
- pull in
gauge-props.html
and then tweak the types allowed by some of the typedInput fields
I didn't address it specifically, but my plan is for the node-red-fd-core
module with the "standard" widgets & nodes to be no different from any "contributed" module. There should be a separate node-red-fd-samples
repo with sample widgets that are suited to hack up into your own.
Something I don't understand yet is how exactly Node-RED loads modules and thus what my options are to hook into that process and generate stuff on-the-fly. As a result I don't know yet when what should get generated. The thing I do know is that I'll get it wrong and we'll have to figure out how best to do it.
Phew, I hope this long post was helpful...