@aaqu/fromcubes-portal-react 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nodes/portal-react.js +63 -27
- package/package.json +1 -1
package/nodes/portal-react.js
CHANGED
|
@@ -71,11 +71,18 @@ module.exports = function (RED) {
|
|
|
71
71
|
}
|
|
72
72
|
const compNameOwners = RED.settings.fcCompNameOwners;
|
|
73
73
|
|
|
74
|
+
// Per-portal config signature — detects no-op Full-deploy reconstructions so unchanged
|
|
75
|
+
// portals skip rebuild entirely (Node-RED closes/reopens every node on Full deploy).
|
|
76
|
+
if (!RED.settings.fcPortalSig) {
|
|
77
|
+
RED.settings.fcPortalSig = {};
|
|
78
|
+
}
|
|
79
|
+
const portalSig = RED.settings.fcPortalSig;
|
|
80
|
+
|
|
74
81
|
// Debounced selective rebuild: coalesces multiple component changes into one pass.
|
|
75
82
|
// Yields event loop between builds so HTTP server stays responsive.
|
|
76
83
|
let _rebuildTimer = null;
|
|
77
84
|
const _dirtyComps = new Set();
|
|
78
|
-
|
|
85
|
+
const _dirtyPortals = new Set();
|
|
79
86
|
|
|
80
87
|
// Startup gate: on first process start, Node-RED constructs portal/component nodes
|
|
81
88
|
// sequentially over a window longer than the 50ms debounce. Without gating, an early
|
|
@@ -86,7 +93,7 @@ module.exports = function (RED) {
|
|
|
86
93
|
function _endStartupPhase() {
|
|
87
94
|
if (!_startupPhase) return;
|
|
88
95
|
_startupPhase = false;
|
|
89
|
-
if (
|
|
96
|
+
if (_dirtyPortals.size > 0 || _dirtyComps.size > 0) {
|
|
90
97
|
if (_rebuildTimer) { clearTimeout(_rebuildTimer); _rebuildTimer = null; }
|
|
91
98
|
_flushRebuild();
|
|
92
99
|
}
|
|
@@ -104,8 +111,9 @@ module.exports = function (RED) {
|
|
|
104
111
|
if (_rebuildTimer) clearTimeout(_rebuildTimer);
|
|
105
112
|
_rebuildTimer = setTimeout(_flushRebuild, 50);
|
|
106
113
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
114
|
+
function scheduleRebuildSelf(nodeId) {
|
|
115
|
+
if (!nodeId) return;
|
|
116
|
+
_dirtyPortals.add(nodeId);
|
|
109
117
|
_armRebuild();
|
|
110
118
|
}
|
|
111
119
|
function scheduleRebuildUsing(compName) {
|
|
@@ -115,20 +123,22 @@ module.exports = function (RED) {
|
|
|
115
123
|
}
|
|
116
124
|
function _flushRebuild() {
|
|
117
125
|
_rebuildTimer = null;
|
|
118
|
-
const all = _rebuildAllPending;
|
|
119
126
|
const dirty = new Set(_dirtyComps);
|
|
120
|
-
|
|
127
|
+
const selfIds = new Set(_dirtyPortals);
|
|
121
128
|
_dirtyComps.clear();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
_dirtyPortals.clear();
|
|
130
|
+
|
|
131
|
+
const targetIds = new Set(selfIds);
|
|
132
|
+
if (dirty.size > 0) {
|
|
133
|
+
for (const nodeId of Object.keys(rebuildCallbacks)) {
|
|
134
|
+
if (targetIds.has(nodeId)) continue;
|
|
135
|
+
const used = portalNeeded[nodeId];
|
|
136
|
+
const raw = portalCode[nodeId] || "";
|
|
137
|
+
for (const name of dirty) {
|
|
138
|
+
if ((used && used.has(name)) || raw.includes(name)) {
|
|
139
|
+
targetIds.add(nodeId);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
144
|
}
|
|
@@ -301,6 +311,16 @@ module.exports = function (RED) {
|
|
|
301
311
|
|
|
302
312
|
const wsPath = nodeRoot + endpoint + "/_ws";
|
|
303
313
|
|
|
314
|
+
function updateStatus() {
|
|
315
|
+
if (isClosing) return;
|
|
316
|
+
const n = clients.size;
|
|
317
|
+
node.status({
|
|
318
|
+
fill: n > 0 ? "green" : "grey",
|
|
319
|
+
shape: n > 0 ? "dot" : "ring",
|
|
320
|
+
text: `${endpoint} [${n} client${n !== 1 ? "s" : ""}]`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
304
324
|
// ── Rebuild: transpile JSX + update page state ────────────
|
|
305
325
|
|
|
306
326
|
function rebuild() {
|
|
@@ -582,8 +602,32 @@ module.exports = function (RED) {
|
|
|
582
602
|
// Remember raw user JSX so selective rebuild can detect references to new components
|
|
583
603
|
portalCode[nodeId] = componentCode;
|
|
584
604
|
|
|
585
|
-
//
|
|
586
|
-
|
|
605
|
+
// No-op redeploy detection: if nothing in the portal's config changed AND a valid
|
|
606
|
+
// build already exists for this endpoint, skip rebuild. Node-RED Full deploy
|
|
607
|
+
// reconstructs every node even when unchanged — without this check every portal
|
|
608
|
+
// would rebuild on every Full deploy.
|
|
609
|
+
const sig = hash(
|
|
610
|
+
[
|
|
611
|
+
componentCode,
|
|
612
|
+
JSON.stringify(libs),
|
|
613
|
+
pageTitle,
|
|
614
|
+
customHead,
|
|
615
|
+
String(portalAuth),
|
|
616
|
+
String(showWsStatus),
|
|
617
|
+
].join("\0"),
|
|
618
|
+
);
|
|
619
|
+
const prevSig = portalSig[nodeId];
|
|
620
|
+
const existing = pageState[endpoint];
|
|
621
|
+
const hasValidBuild =
|
|
622
|
+
!!existing && !existing.building && !existing.compiled?.error;
|
|
623
|
+
portalSig[nodeId] = sig;
|
|
624
|
+
|
|
625
|
+
if (prevSig !== sig || !hasValidBuild) {
|
|
626
|
+
scheduleRebuildSelf(nodeId);
|
|
627
|
+
} else {
|
|
628
|
+
node.log(`[${nodeId}] unchanged — skipping rebuild`);
|
|
629
|
+
node.status({ fill: "grey", shape: "ring", text: `${endpoint} [0 clients]` });
|
|
630
|
+
}
|
|
587
631
|
setImmediate(() => {
|
|
588
632
|
// Register route only once per endpoint (persists across deploys)
|
|
589
633
|
if (!registeredRoutes[endpoint]) {
|
|
@@ -849,6 +893,7 @@ module.exports = function (RED) {
|
|
|
849
893
|
// redeploy we keep it so reconnecting clients still recover.
|
|
850
894
|
if (removed) {
|
|
851
895
|
lastBroadcastCache.delete(endpoint);
|
|
896
|
+
delete portalSig[nodeId];
|
|
852
897
|
}
|
|
853
898
|
|
|
854
899
|
// Clear the userIndex — WS clients are already closed above, but
|
|
@@ -877,15 +922,6 @@ module.exports = function (RED) {
|
|
|
877
922
|
} catch (e) { RED.log.trace("[portal-react] wsSend: " + e.message); }
|
|
878
923
|
}
|
|
879
924
|
|
|
880
|
-
function updateStatus() {
|
|
881
|
-
if (isClosing) return;
|
|
882
|
-
const n = clients.size;
|
|
883
|
-
node.status({
|
|
884
|
-
fill: n > 0 ? "green" : "grey",
|
|
885
|
-
shape: n > 0 ? "dot" : "ring",
|
|
886
|
-
text: `${endpoint} [${n} client${n !== 1 ? "s" : ""}]`,
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
925
|
}); // end setImmediate
|
|
890
926
|
}
|
|
891
927
|
|