@webjsdev/core 0.7.56 → 0.7.58
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/dist/webjs-core-browser.js +4 -4
- package/dist/webjs-core-browser.js.map +2 -2
- package/dist/webjs-core.js +5 -5
- package/dist/webjs-core.js.map +2 -2
- package/package.json +1 -1
- package/src/component/lifecycle.js +21 -12
- package/src/signal.js +17 -7
- package/src/webjs-config.d.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.58",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render as clientRender } from '../render-client.js';
|
|
2
2
|
import { readAttributeValue, resolveAttributeProperty } from '../attribute-reader.js';
|
|
3
|
-
import { setActiveActionSignal } from '../action-abort-client.js';
|
|
3
|
+
import { setActiveActionSignal, activeActionSignal } from '../action-abort-client.js';
|
|
4
4
|
import { carriesFunction } from '../form-action.js';
|
|
5
5
|
import { isCSS, adoptStyles } from '../css.js';
|
|
6
6
|
import { register, tagOf } from '../registry.js';
|
|
@@ -872,23 +872,18 @@ class WebComponentBase extends Base {
|
|
|
872
872
|
// by-sync case (#469); a shouldUpdate=false cycle never reaches here,
|
|
873
873
|
// so it does not invalidate an in-flight async render.
|
|
874
874
|
this.__renderToken = (this.__renderToken || 0) + 1;
|
|
875
|
-
// Abort the superseded render's in-flight action fetches (#492)
|
|
876
|
-
// fresh controller for this render
|
|
877
|
-
//
|
|
878
|
-
// AbortError is dropped by the render-token guard
|
|
875
|
+
// Abort the superseded render's in-flight action fetches (#492) and
|
|
876
|
+
// start a fresh controller for this render. update() binds its signal
|
|
877
|
+
// around render() alone (see there), never the DOM commit. A
|
|
878
|
+
// superseded fetch's AbortError is dropped by the render-token guard
|
|
879
|
+
// in _commitAsync.
|
|
879
880
|
if (this.__renderAbort) this.__renderAbort.abort();
|
|
880
881
|
this.__renderAbort = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
881
|
-
setActiveActionSignal(this.__renderAbort ? this.__renderAbort.signal : null);
|
|
882
882
|
try {
|
|
883
883
|
const r = this.update(changedProperties);
|
|
884
884
|
if (r && typeof r.then === 'function') pendingCommit = r;
|
|
885
885
|
} catch (error) {
|
|
886
886
|
this._handleRenderError(/** @type {Error} */ (error));
|
|
887
|
-
} finally {
|
|
888
|
-
// The synchronous portion of render() has run; actions invoked there
|
|
889
|
-
// already captured the signal. Clear it so a later event handler is
|
|
890
|
-
// not bound to a stale render's controller.
|
|
891
|
-
setActiveActionSignal(null);
|
|
892
887
|
}
|
|
893
888
|
|
|
894
889
|
if (!pendingCommit) {
|
|
@@ -1126,7 +1121,21 @@ class WebComponentBase extends Base {
|
|
|
1126
1121
|
});
|
|
1127
1122
|
}
|
|
1128
1123
|
let tpl;
|
|
1129
|
-
this
|
|
1124
|
+
// Bind this render's abort signal (#492) around the synchronous part of
|
|
1125
|
+
// render() ONLY, so the RPC stub ties an action called there to this
|
|
1126
|
+
// render and the next render cancels it. The commit below runs unbound:
|
|
1127
|
+
// it connects the child elements this template creates, and an action a
|
|
1128
|
+
// child starts in its connectedCallback belongs to the child, not to this
|
|
1129
|
+
// render, so the parent's next render must not cancel it (#1514). The
|
|
1130
|
+
// previous binding is restored rather than cleared, so a render nested
|
|
1131
|
+
// inside another's synchronous work leaves the outer one intact.
|
|
1132
|
+
const outer = activeActionSignal();
|
|
1133
|
+
setActiveActionSignal(this.__renderAbort ? this.__renderAbort.signal : null);
|
|
1134
|
+
try {
|
|
1135
|
+
this.__signalWatcher.observe(() => { tpl = this.render(); });
|
|
1136
|
+
} finally {
|
|
1137
|
+
setActiveActionSignal(outer || null);
|
|
1138
|
+
}
|
|
1130
1139
|
// Bare-await async render (#469): render() returned a promise. Use
|
|
1131
1140
|
// stale-while-revalidate: keep the current DOM (the SSR first paint on
|
|
1132
1141
|
// hydration, or the prior content on a re-fetch) visible until the new
|
package/src/signal.js
CHANGED
|
@@ -81,7 +81,7 @@ let batchDepth = 0;
|
|
|
81
81
|
* @property {boolean} dirty
|
|
82
82
|
* @property {ReactiveNode[]} producers
|
|
83
83
|
* @property {number[]} producerLastReadVersions
|
|
84
|
-
* @property {
|
|
84
|
+
* @property {Map<ReactiveNode, number>=} liveConsumers live consumer -> how many of its producer slots read this node
|
|
85
85
|
* @property {boolean} consumerAllowSignalWrites
|
|
86
86
|
* @property {boolean} consumerIsAlwaysLive
|
|
87
87
|
* @property {(this: ReactiveNode) => boolean} producerMustRecompute
|
|
@@ -105,11 +105,18 @@ const FALSE_FN = () => false;
|
|
|
105
105
|
* This is how source `set()` calls reach watchers across computed
|
|
106
106
|
* chains: the push side of the graph propagates upward only when
|
|
107
107
|
* something downstream is actually live.
|
|
108
|
+
*
|
|
109
|
+
* The edge is REFERENCE COUNTED, one count per producer slot (#1508). A
|
|
110
|
+
* consumer that reads the same signal twice in one run holds it in two slots,
|
|
111
|
+
* and every slot is added and removed on its own as runs shrink, grow or
|
|
112
|
+
* reorder. With a plain set the first removal dropped the edge outright, so a
|
|
113
|
+
* render that went from reading a signal twice to once (or swapped the order
|
|
114
|
+
* of two reads) stopped hearing about that signal while still depending on it.
|
|
108
115
|
*/
|
|
109
116
|
function producerAddLiveConsumer(node, consumer) {
|
|
110
|
-
if (!node.liveConsumers) node.liveConsumers = new
|
|
117
|
+
if (!node.liveConsumers) node.liveConsumers = new Map();
|
|
111
118
|
const wasEmpty = node.liveConsumers.size === 0;
|
|
112
|
-
node.liveConsumers.
|
|
119
|
+
node.liveConsumers.set(consumer, (node.liveConsumers.get(consumer) || 0) + 1);
|
|
113
120
|
if (wasEmpty) {
|
|
114
121
|
if (node.producers && node.producers.length > 0) {
|
|
115
122
|
for (const p of node.producers) producerAddLiveConsumer(p, node);
|
|
@@ -120,9 +127,12 @@ function producerAddLiveConsumer(node, consumer) {
|
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
/** Inverse of producerAddLiveConsumer. Cascades teardown upward. */
|
|
130
|
+
/** Inverse of producerAddLiveConsumer: drops one slot's count, and the edge with the last one. Cascades teardown upward. */
|
|
124
131
|
function producerRemoveLiveConsumer(node, consumer) {
|
|
125
132
|
if (!node.liveConsumers) return;
|
|
133
|
+
const n = node.liveConsumers.get(consumer);
|
|
134
|
+
if (n === undefined) return;
|
|
135
|
+
if (n > 1) { node.liveConsumers.set(consumer, n - 1); return; }
|
|
126
136
|
node.liveConsumers.delete(consumer);
|
|
127
137
|
if (node.liveConsumers.size === 0) {
|
|
128
138
|
if (node.producers && node.producers.length > 0) {
|
|
@@ -181,7 +191,7 @@ function producerNotifyConsumers(node) {
|
|
|
181
191
|
const prev = inNotificationPhase;
|
|
182
192
|
inNotificationPhase = true;
|
|
183
193
|
try {
|
|
184
|
-
for (const consumer of node.liveConsumers) {
|
|
194
|
+
for (const consumer of node.liveConsumers.keys()) {
|
|
185
195
|
if (!consumer.dirty) {
|
|
186
196
|
consumerMarkDirty(consumer);
|
|
187
197
|
}
|
|
@@ -316,7 +326,7 @@ function collectBatched(node) {
|
|
|
316
326
|
inNotificationPhase = true;
|
|
317
327
|
try {
|
|
318
328
|
if (!node.liveConsumers) return;
|
|
319
|
-
for (const consumer of node.liveConsumers) {
|
|
329
|
+
for (const consumer of node.liveConsumers.keys()) {
|
|
320
330
|
if (consumer.dirty) continue;
|
|
321
331
|
consumer.dirty = true;
|
|
322
332
|
if (consumer.__isWatcher) {
|
|
@@ -550,7 +560,7 @@ function introspectSources(sink) {
|
|
|
550
560
|
|
|
551
561
|
/** @param {SignalState | SignalComputed} producer */
|
|
552
562
|
function introspectSinks(producer) {
|
|
553
|
-
return producer.liveConsumers ? [...producer.liveConsumers] : [];
|
|
563
|
+
return producer.liveConsumers ? [...producer.liveConsumers.keys()] : [];
|
|
554
564
|
}
|
|
555
565
|
|
|
556
566
|
/** @param {SignalState | SignalComputed} producer */
|
package/src/webjs-config.d.ts
CHANGED
|
@@ -146,6 +146,14 @@ export interface WebjsDevTasks {
|
|
|
146
146
|
* one run. Ignored by `webjs start`.
|
|
147
147
|
*/
|
|
148
148
|
sourceLocations?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Live-reload idle close (#1507), DEV-ONLY, off by default. Seconds with no
|
|
151
|
+
* edit and no interaction after which the dev live-reload stream closes, so a
|
|
152
|
+
* host that counts an open request as activity can sleep with a preview tab
|
|
153
|
+
* open; interaction, a tab showing, or an embed-bridge host command reopens
|
|
154
|
+
* it. `WEBJS_DEV_RELOAD_IDLE` (seconds) overrides it. Ignored by `webjs start`.
|
|
155
|
+
*/
|
|
156
|
+
reloadIdle?: number;
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
/** Start task orchestration in `webjs.start` (#550). Read by the CLI, not the server. */
|