@alloy-js/core 0.23.0-dev.8 → 0.23.0-dev.9
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/src/components/Prose.js +2 -2
- package/dist/src/components/Prose.js.map +1 -1
- package/dist/src/components/Scope.d.ts.map +1 -1
- package/dist/src/components/Scope.js +2 -0
- package/dist/src/components/Scope.js.map +1 -1
- package/dist/src/components/SourceDirectory.d.ts.map +1 -1
- package/dist/src/components/SourceDirectory.js +1 -2
- package/dist/src/components/SourceDirectory.js.map +1 -1
- package/dist/src/content-slot.js +2 -2
- package/dist/src/content-slot.js.map +1 -1
- package/dist/src/context.js +2 -2
- package/dist/src/context.js.map +1 -1
- package/dist/src/debug/effects.d.ts +4 -0
- package/dist/src/debug/effects.d.ts.map +1 -1
- package/dist/src/debug/effects.js.map +1 -1
- package/dist/src/debug/effects.test.js +22 -24
- package/dist/src/debug/effects.test.js.map +1 -1
- package/dist/src/debug/index.d.ts +2 -1
- package/dist/src/debug/index.d.ts.map +1 -1
- package/dist/src/debug/index.js +2 -1
- package/dist/src/debug/index.js.map +1 -1
- package/dist/src/debug/symbols.d.ts +6 -0
- package/dist/src/debug/symbols.d.ts.map +1 -1
- package/dist/src/debug/symbols.js +9 -0
- package/dist/src/debug/symbols.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/reactive-union-set.d.ts.map +1 -1
- package/dist/src/reactive-union-set.js +13 -3
- package/dist/src/reactive-union-set.js.map +1 -1
- package/dist/src/reactivity.d.ts +34 -6
- package/dist/src/reactivity.d.ts.map +1 -1
- package/dist/src/reactivity.js +161 -123
- package/dist/src/reactivity.js.map +1 -1
- package/dist/src/render-stack.d.ts +1 -0
- package/dist/src/render-stack.d.ts.map +1 -1
- package/dist/src/render-stack.js +4 -0
- package/dist/src/render-stack.js.map +1 -1
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +15 -13
- package/dist/src/render.js.map +1 -1
- package/dist/src/scheduler.d.ts +5 -0
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +24 -1
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/symbols/output-scope.d.ts.map +1 -1
- package/dist/src/symbols/output-scope.js +2 -2
- package/dist/src/symbols/output-scope.js.map +1 -1
- package/dist/src/symbols/output-symbol.d.ts.map +1 -1
- package/dist/src/symbols/output-symbol.js +2 -2
- package/dist/src/symbols/output-symbol.js.map +1 -1
- package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
- package/dist/src/symbols/symbol-flow.js +2 -2
- package/dist/src/symbols/symbol-flow.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +2 -5
- package/dist/src/utils.js.map +1 -1
- package/dist/test/lazy-isempty.test.d.ts +2 -0
- package/dist/test/lazy-isempty.test.d.ts.map +1 -0
- package/dist/test/lazy-isempty.test.js +89 -0
- package/dist/test/lazy-isempty.test.js.map +1 -0
- package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
- package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
- package/dist/test/reactive-union-set-disposers.test.js +98 -0
- package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
- package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
- package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
- package/dist/test/reactivity/shallow-reactive.test.js +52 -0
- package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
- package/dist/test/scheduler-extended.test.d.ts +2 -0
- package/dist/test/scheduler-extended.test.d.ts.map +1 -0
- package/dist/test/scheduler-extended.test.js +96 -0
- package/dist/test/scheduler-extended.test.js.map +1 -0
- package/dist/test/scheduler.test.d.ts +2 -0
- package/dist/test/scheduler.test.d.ts.map +1 -0
- package/dist/test/scheduler.test.js +46 -0
- package/dist/test/scheduler.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/Prose.tsx +1 -1
- package/src/components/Scope.tsx +2 -0
- package/src/components/SourceDirectory.tsx +1 -2
- package/src/content-slot.tsx +2 -2
- package/src/context.ts +3 -3
- package/src/debug/effects.test.tsx +24 -31
- package/src/debug/effects.ts +4 -0
- package/src/debug/index.ts +2 -0
- package/src/debug/symbols.ts +9 -0
- package/src/index.ts +0 -1
- package/src/reactive-union-set.ts +14 -3
- package/src/reactivity.ts +189 -130
- package/src/render-stack.ts +5 -0
- package/src/render.ts +16 -14
- package/src/scheduler.ts +25 -1
- package/src/symbols/output-scope.ts +1 -2
- package/src/symbols/output-symbol.ts +1 -2
- package/src/symbols/symbol-flow.ts +8 -2
- package/src/utils.tsx +2 -4
- package/temp/api.json +425 -14
- package/test/lazy-isempty.test.tsx +106 -0
- package/test/reactive-union-set-disposers.test.tsx +112 -0
- package/test/reactivity/shallow-reactive.test.tsx +56 -0
- package/test/scheduler-extended.test.tsx +122 -0
- package/test/scheduler.test.tsx +50 -0
package/src/reactivity.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
computed as vueComputed,
|
|
10
10
|
effect as vueEffect,
|
|
11
11
|
ref as vueRef,
|
|
12
|
+
shallowReactive as vueShallowReactive,
|
|
12
13
|
shallowRef as vueShallowRef,
|
|
13
14
|
toRef as vueToRef,
|
|
14
15
|
toRefs as vueToRefs,
|
|
@@ -23,40 +24,6 @@ import { Children, ComponentCreator } from "./runtime/component.js";
|
|
|
23
24
|
import { scheduler } from "./scheduler.js";
|
|
24
25
|
import type { OutputSymbol } from "./symbols/output-symbol.js";
|
|
25
26
|
|
|
26
|
-
function attachEffectWriteDebug(refValue: Ref<unknown>, kind: string) {
|
|
27
|
-
if (!isDevtoolsEnabled()) return;
|
|
28
|
-
const descriptor =
|
|
29
|
-
Object.getOwnPropertyDescriptor(refValue, "value") ??
|
|
30
|
-
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(refValue), "value");
|
|
31
|
-
if (!descriptor?.get || !descriptor?.set) return;
|
|
32
|
-
if ((refValue as any).__alloyDebugWrapped) return;
|
|
33
|
-
Object.defineProperty(refValue, "value", {
|
|
34
|
-
get: descriptor.get,
|
|
35
|
-
set(value: unknown) {
|
|
36
|
-
descriptor.set!.call(this, value);
|
|
37
|
-
const effectId = globalContext?.meta?.effectId;
|
|
38
|
-
if (effectId !== undefined && effectId !== -1) {
|
|
39
|
-
const id = refId(refValue);
|
|
40
|
-
debug.effect.ensureRef({ id, kind });
|
|
41
|
-
debug.effect.trigger({
|
|
42
|
-
effectId,
|
|
43
|
-
target: refValue,
|
|
44
|
-
refId: id,
|
|
45
|
-
location: captureSourceLocation(),
|
|
46
|
-
kind: "trigger",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
enumerable: descriptor.enumerable ?? true,
|
|
51
|
-
configurable: true,
|
|
52
|
-
});
|
|
53
|
-
Object.defineProperty(refValue, "__alloyDebugWrapped", {
|
|
54
|
-
value: true,
|
|
55
|
-
enumerable: false,
|
|
56
|
-
configurable: false,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
27
|
if ((globalThis as any).__ALLOY__) {
|
|
61
28
|
throw new Error(
|
|
62
29
|
"Multiple versions of Alloy are loaded for this project. This will likely cause undesirable behavior.",
|
|
@@ -65,7 +32,8 @@ if ((globalThis as any).__ALLOY__) {
|
|
|
65
32
|
(globalThis as any).__ALLOY__ = true;
|
|
66
33
|
|
|
67
34
|
export function getElementCache() {
|
|
68
|
-
|
|
35
|
+
const ctx = getContext()!;
|
|
36
|
+
return (ctx.elementCache ??= new Map());
|
|
69
37
|
}
|
|
70
38
|
|
|
71
39
|
export type ElementCacheKey =
|
|
@@ -79,8 +47,12 @@ export interface Disposable {
|
|
|
79
47
|
(): void;
|
|
80
48
|
}
|
|
81
49
|
|
|
50
|
+
let contextIdCounter = 0;
|
|
51
|
+
|
|
82
52
|
export interface Context {
|
|
83
|
-
|
|
53
|
+
/** Monotonic numeric ID for trace/debug correlation. */
|
|
54
|
+
id: number;
|
|
55
|
+
disposables?: Disposable[];
|
|
84
56
|
owner: Context | null;
|
|
85
57
|
|
|
86
58
|
// context providers
|
|
@@ -93,7 +65,7 @@ export interface Context {
|
|
|
93
65
|
* A cache of RenderTextTree nodes created within this context,
|
|
94
66
|
* indexed by the component or function which created them.
|
|
95
67
|
*/
|
|
96
|
-
elementCache
|
|
68
|
+
elementCache?: ElementCache;
|
|
97
69
|
/**
|
|
98
70
|
* When this context was created by a component, this will
|
|
99
71
|
* be the component that created it.
|
|
@@ -118,9 +90,17 @@ export interface Context {
|
|
|
118
90
|
|
|
119
91
|
/**
|
|
120
92
|
* A ref that indicates whether the component is empty.
|
|
93
|
+
* Only allocated when reactively observed (ContentSlot, mapJoin).
|
|
121
94
|
*/
|
|
122
95
|
isEmpty?: Ref<boolean>;
|
|
123
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Cheap boolean tracking the last propagated empty state.
|
|
99
|
+
* Used by notifyContentState() for early-return optimization
|
|
100
|
+
* without requiring a reactive ref on every context.
|
|
101
|
+
*/
|
|
102
|
+
_lastEmpty: boolean;
|
|
103
|
+
|
|
124
104
|
/**
|
|
125
105
|
* Whether this context is a root context
|
|
126
106
|
*/
|
|
@@ -132,21 +112,46 @@ export function getContext() {
|
|
|
132
112
|
return globalContext;
|
|
133
113
|
}
|
|
134
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Walk up the owner chain to find the nearest ancestor context that
|
|
117
|
+
* corresponds to an effect (has meta.effectId). This bridges non-effect
|
|
118
|
+
* scopes (like createRoot iterations in For) so the owner chain always
|
|
119
|
+
* connects effect-to-effect.
|
|
120
|
+
*/
|
|
121
|
+
function resolveOwnerEffectContextId(context: Context): number | null {
|
|
122
|
+
let owner = context.owner;
|
|
123
|
+
while (owner) {
|
|
124
|
+
if (owner.meta?.effectId !== undefined) {
|
|
125
|
+
return owner.id;
|
|
126
|
+
}
|
|
127
|
+
owner = owner.owner;
|
|
128
|
+
}
|
|
129
|
+
return context.owner?.id ?? null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Ensure that a context has an isEmpty ref, creating one if needed.
|
|
134
|
+
* Only call this when you need to reactively observe isEmpty (e.g.,
|
|
135
|
+
* ContentSlot, mapJoin). Most contexts don't need an isEmpty ref.
|
|
136
|
+
*/
|
|
137
|
+
export function ensureIsEmpty(context: Context): Ref<boolean> {
|
|
138
|
+
context.isEmpty ??= ref(context.childrenWithContent === 0);
|
|
139
|
+
return context.isEmpty;
|
|
140
|
+
}
|
|
141
|
+
|
|
135
142
|
export interface RootOptions {
|
|
136
143
|
componentOwner?: ComponentCreator<any>;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
|
|
140
147
|
const context: Context = {
|
|
148
|
+
id: contextIdCounter++,
|
|
141
149
|
componentOwner: options?.componentOwner,
|
|
142
|
-
disposables: [],
|
|
143
150
|
owner: globalContext,
|
|
144
|
-
context: {},
|
|
145
|
-
elementCache: new Map(),
|
|
146
151
|
takesSymbols: false,
|
|
147
152
|
takenSymbols: undefined,
|
|
148
153
|
childrenWithContent: 0,
|
|
149
|
-
|
|
154
|
+
_lastEmpty: true,
|
|
150
155
|
isRoot: true,
|
|
151
156
|
};
|
|
152
157
|
|
|
@@ -155,7 +160,7 @@ export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
|
|
|
155
160
|
try {
|
|
156
161
|
ret = untrack(() =>
|
|
157
162
|
fn(() => {
|
|
158
|
-
for (const d of context!.disposables) {
|
|
163
|
+
for (const d of context!.disposables ?? []) {
|
|
159
164
|
untrack(d);
|
|
160
165
|
}
|
|
161
166
|
}),
|
|
@@ -183,7 +188,21 @@ export function untrack<T>(fn: () => T): T {
|
|
|
183
188
|
return v;
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Walk up the context owner chain to find the nearest effect ID.
|
|
193
|
+
* Used to attribute reactive mutations to the effect that caused them.
|
|
194
|
+
*/
|
|
195
|
+
export function findCurrentEffectId(): number | undefined {
|
|
196
|
+
let ctx = globalContext;
|
|
197
|
+
while (ctx) {
|
|
198
|
+
const id = ctx.meta?.effectId;
|
|
199
|
+
if (id !== undefined && id !== -1) return id;
|
|
200
|
+
ctx = ctx.owner;
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function memo<T>(fn: () => T, equal?: boolean, name?: string): () => T {
|
|
187
206
|
const o = shallowRef<T>();
|
|
188
207
|
effect(
|
|
189
208
|
(prev) => {
|
|
@@ -194,10 +213,14 @@ export function memo<T>(fn: () => T, equal?: boolean): () => T {
|
|
|
194
213
|
},
|
|
195
214
|
undefined as T,
|
|
196
215
|
{
|
|
197
|
-
debug: { name: "memo" },
|
|
216
|
+
debug: { name: name ? `memo:${name}` : "memo" },
|
|
198
217
|
},
|
|
199
218
|
);
|
|
200
|
-
|
|
219
|
+
const getter = (() => o.value as T) as () => T;
|
|
220
|
+
if (name) {
|
|
221
|
+
Object.defineProperty(getter, "name", { value: name, configurable: true });
|
|
222
|
+
}
|
|
223
|
+
return getter;
|
|
201
224
|
}
|
|
202
225
|
|
|
203
226
|
export function effect<T>(
|
|
@@ -206,13 +229,12 @@ export function effect<T>(
|
|
|
206
229
|
options?: EffectOptions,
|
|
207
230
|
) {
|
|
208
231
|
const context: Context = {
|
|
209
|
-
|
|
210
|
-
disposables: [] as (() => void)[],
|
|
232
|
+
id: contextIdCounter++,
|
|
211
233
|
owner: globalContext,
|
|
212
|
-
elementCache: new Map(),
|
|
213
234
|
takesSymbols: false,
|
|
214
235
|
takenSymbols: undefined,
|
|
215
236
|
childrenWithContent: 0,
|
|
237
|
+
_lastEmpty: true,
|
|
216
238
|
isRoot: false,
|
|
217
239
|
};
|
|
218
240
|
|
|
@@ -221,94 +243,93 @@ export function effect<T>(
|
|
|
221
243
|
name: debugInfo?.name ?? fn.name,
|
|
222
244
|
type: debugInfo?.type,
|
|
223
245
|
createdAt: captureSourceLocation(),
|
|
246
|
+
contextId: context.id,
|
|
247
|
+
ownerContextId: resolveOwnerEffectContextId(context),
|
|
224
248
|
});
|
|
225
249
|
|
|
226
|
-
context.meta ??= {};
|
|
227
250
|
if (effectId !== -1) {
|
|
251
|
+
context.meta ??= {};
|
|
228
252
|
context.meta.effectId = effectId;
|
|
229
253
|
}
|
|
230
254
|
|
|
231
255
|
const cleanupFn = (final: boolean) => {
|
|
232
256
|
const d = context.disposables;
|
|
233
|
-
context.disposables =
|
|
234
|
-
|
|
257
|
+
context.disposables = undefined;
|
|
258
|
+
if (d) {
|
|
259
|
+
for (let k = 0, len = d.length; k < len; k++) untrack(d[k]);
|
|
260
|
+
}
|
|
235
261
|
|
|
236
262
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
237
263
|
final && stop(runner);
|
|
238
264
|
};
|
|
239
265
|
|
|
240
266
|
onCleanup(() => cleanupFn(true));
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
267
|
+
const effectOpts: Record<string, unknown> = {
|
|
268
|
+
// allow recursive effects with 32, 1 and 4 are default flags
|
|
269
|
+
flags: 1 | 4 | 32,
|
|
270
|
+
scheduler: scheduler(),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (effectId !== -1) {
|
|
274
|
+
effectOpts.onTrack = (event: any) => {
|
|
275
|
+
const targetKey =
|
|
276
|
+
typeof event.key === "symbol" ? event.key.toString() : event.key;
|
|
277
|
+
if (isRef(event.target)) {
|
|
278
|
+
const id = refId(event.target);
|
|
279
|
+
debug.effect.ensureRef({ id, kind: "ref" });
|
|
280
|
+
debug.effect.track({
|
|
281
|
+
effectId,
|
|
282
|
+
target: event.target,
|
|
283
|
+
refId: id,
|
|
284
|
+
targetKey,
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
debug.effect.track({
|
|
288
|
+
effectId,
|
|
289
|
+
target: event.target,
|
|
290
|
+
targetKey,
|
|
291
|
+
});
|
|
251
292
|
}
|
|
252
|
-
}
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
target: event.target,
|
|
293
|
-
refId: id,
|
|
294
|
-
targetKey,
|
|
295
|
-
location: captureSourceLocation(),
|
|
296
|
-
kind: "triggered-by",
|
|
297
|
-
});
|
|
298
|
-
} else {
|
|
299
|
-
debug.effect.trigger({
|
|
300
|
-
effectId,
|
|
301
|
-
target: event.target,
|
|
302
|
-
targetKey,
|
|
303
|
-
location: captureSourceLocation(),
|
|
304
|
-
kind: "triggered-by",
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
// trigger edge emitted via recordEffectTrigger
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
);
|
|
293
|
+
};
|
|
294
|
+
effectOpts.onTrigger = (event: any) => {
|
|
295
|
+
const targetKey =
|
|
296
|
+
typeof event.key === "symbol" ? event.key.toString() : event.key;
|
|
297
|
+
if (isRef(event.target)) {
|
|
298
|
+
const id = refId(event.target);
|
|
299
|
+
debug.effect.ensureRef({ id, kind: "ref" });
|
|
300
|
+
debug.effect.trigger({
|
|
301
|
+
effectId,
|
|
302
|
+
target: event.target,
|
|
303
|
+
refId: id,
|
|
304
|
+
targetKey,
|
|
305
|
+
kind: "triggered-by",
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
debug.effect.trigger({
|
|
309
|
+
effectId,
|
|
310
|
+
target: event.target,
|
|
311
|
+
targetKey,
|
|
312
|
+
kind: "triggered-by",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const runner: ReactiveEffectRunner<void> = vueEffect(() => {
|
|
319
|
+
cleanupFn(false);
|
|
320
|
+
|
|
321
|
+
const oldContext = globalContext;
|
|
322
|
+
globalContext = context;
|
|
323
|
+
try {
|
|
324
|
+
current = fn(current);
|
|
325
|
+
} finally {
|
|
326
|
+
globalContext = oldContext;
|
|
327
|
+
}
|
|
328
|
+
}, effectOpts as any);
|
|
329
|
+
|
|
330
|
+
if (effectId !== -1) {
|
|
331
|
+
effectIdMap.set(runner.effect, effectId);
|
|
332
|
+
}
|
|
312
333
|
}
|
|
313
334
|
|
|
314
335
|
/**
|
|
@@ -331,7 +352,7 @@ export function effect<T>(
|
|
|
331
352
|
*/
|
|
332
353
|
export function onCleanup(fn: Disposable) {
|
|
333
354
|
if (globalContext != null) {
|
|
334
|
-
globalContext.disposables.push(fn);
|
|
355
|
+
(globalContext.disposables ??= []).push(fn);
|
|
335
356
|
}
|
|
336
357
|
}
|
|
337
358
|
|
|
@@ -366,21 +387,45 @@ export function isCustomContext(child: Children): child is CustomContext {
|
|
|
366
387
|
);
|
|
367
388
|
}
|
|
368
389
|
|
|
369
|
-
export function ref<T>(
|
|
390
|
+
export function ref<T>(
|
|
391
|
+
value?: T,
|
|
392
|
+
options?: { isInfrastructure?: boolean },
|
|
393
|
+
): Ref<T> {
|
|
370
394
|
const result = vueRef(value) as Ref<T>;
|
|
371
|
-
attachEffectWriteDebug(result, "ref");
|
|
372
395
|
debug.effect.registerRef({
|
|
373
|
-
id: refId(result),
|
|
396
|
+
id: refId(result, options?.isInfrastructure),
|
|
374
397
|
kind: "ref",
|
|
375
398
|
createdAt: captureSourceLocation(),
|
|
376
399
|
createdByEffectId: globalContext?.meta?.effectId,
|
|
400
|
+
isInfrastructure: options?.isInfrastructure,
|
|
377
401
|
});
|
|
378
402
|
return result;
|
|
379
403
|
}
|
|
380
404
|
|
|
405
|
+
// Stores creation location for shallowReactive objects so registerNonRefTarget
|
|
406
|
+
// can look it up later (since targets are lazily registered on first track/trigger).
|
|
407
|
+
const reactiveCreationLocations = new WeakMap<
|
|
408
|
+
object,
|
|
409
|
+
ReturnType<typeof captureSourceLocation>
|
|
410
|
+
>();
|
|
411
|
+
|
|
412
|
+
export function getReactiveCreationLocation(target: object) {
|
|
413
|
+
return reactiveCreationLocations.get(target);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function shallowReactive<T extends object>(
|
|
417
|
+
target: T,
|
|
418
|
+
): ShallowReactive<T> {
|
|
419
|
+
const result = vueShallowReactive(target);
|
|
420
|
+
if (isDevtoolsEnabled()) {
|
|
421
|
+
// Store by raw target — Vue's onTrack/onTrigger events pass the raw object, not the proxy.
|
|
422
|
+
reactiveCreationLocations.set(target, captureSourceLocation());
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
381
427
|
export function shallowRef<T>(value?: T): Ref<T> {
|
|
382
428
|
const result = vueShallowRef(value) as Ref<T>;
|
|
383
|
-
attachEffectWriteDebug(result, "shallowRef");
|
|
384
429
|
debug.effect.registerRef({
|
|
385
430
|
id: refId(result),
|
|
386
431
|
kind: "shallowRef",
|
|
@@ -410,7 +455,6 @@ export function toRef<T extends object, K extends keyof T>(
|
|
|
410
455
|
defaultValue === undefined ?
|
|
411
456
|
(vueToRef(object, key) as Ref<T[K]>)
|
|
412
457
|
: (vueToRef(object, key, defaultValue) as Ref<T[K]>);
|
|
413
|
-
attachEffectWriteDebug(result, "toRef");
|
|
414
458
|
debug.effect.registerRef({
|
|
415
459
|
id: refId(result),
|
|
416
460
|
kind: "toRef",
|
|
@@ -425,7 +469,6 @@ export function toRefs<T extends object>(
|
|
|
425
469
|
): { [K in keyof T]: Ref<T[K]> } {
|
|
426
470
|
const result = vueToRefs(object) as { [K in keyof T]: Ref<T[K]> };
|
|
427
471
|
for (const refValue of Object.values(result) as Ref<unknown>[]) {
|
|
428
|
-
attachEffectWriteDebug(refValue, "toRef");
|
|
429
472
|
debug.effect.registerRef({
|
|
430
473
|
id: refId(refValue),
|
|
431
474
|
kind: "toRef",
|
|
@@ -438,12 +481,28 @@ export function toRefs<T extends object>(
|
|
|
438
481
|
|
|
439
482
|
const seenRefs = new WeakMap<Ref<unknown>, number>();
|
|
440
483
|
let refIdCounter = 1;
|
|
484
|
+
let infraRefIdCounter = -1;
|
|
485
|
+
const effectIdMap = new WeakMap<object, number>();
|
|
441
486
|
|
|
442
|
-
export function refId(ref: Ref<unknown
|
|
487
|
+
export function refId(ref: Ref<unknown>, isInfrastructure?: boolean): number {
|
|
443
488
|
let id = seenRefs.get(ref);
|
|
444
489
|
if (id === undefined) {
|
|
445
|
-
id = refIdCounter++;
|
|
490
|
+
id = isInfrastructure ? infraRefIdCounter-- : refIdCounter++;
|
|
446
491
|
seenRefs.set(ref, id);
|
|
447
492
|
}
|
|
448
493
|
return id;
|
|
449
494
|
}
|
|
495
|
+
|
|
496
|
+
/** Allocate a unique reactive target ID from the same counter space as ref IDs. */
|
|
497
|
+
export function nextReactiveId(): number {
|
|
498
|
+
return refIdCounter++;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function resetRefIdCounter(): void {
|
|
502
|
+
refIdCounter = 1;
|
|
503
|
+
infraRefIdCounter = -1;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function getEffectDebugId(effect: object): number | undefined {
|
|
507
|
+
return effectIdMap.get(effect);
|
|
508
|
+
}
|
package/src/render-stack.ts
CHANGED
|
@@ -33,6 +33,11 @@ export function popStack() {
|
|
|
33
33
|
renderStack.pop();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function currentComponentName(): string | undefined {
|
|
37
|
+
const entry = renderStack[renderStack.length - 1];
|
|
38
|
+
return entry?.component.name || undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
export function clearRenderStack() {
|
|
37
42
|
renderStack.length = 0;
|
|
38
43
|
}
|
package/src/render.ts
CHANGED
|
@@ -498,11 +498,12 @@ export function notifyContentState() {
|
|
|
498
498
|
const startContext = getContext()!;
|
|
499
499
|
|
|
500
500
|
if (startContext.childrenWithContent === 0) {
|
|
501
|
-
if (startContext.
|
|
501
|
+
if (startContext._lastEmpty) {
|
|
502
502
|
// it was already empty, no work to do.
|
|
503
503
|
return;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
startContext._lastEmpty = true;
|
|
506
507
|
if (startContext.isEmpty) {
|
|
507
508
|
startContext.isEmpty.value = true;
|
|
508
509
|
}
|
|
@@ -518,18 +519,20 @@ export function notifyContentState() {
|
|
|
518
519
|
// This isn't the last content so we have no work to do
|
|
519
520
|
break;
|
|
520
521
|
}
|
|
522
|
+
current._lastEmpty = true;
|
|
521
523
|
if (current.isEmpty) {
|
|
522
524
|
current.isEmpty.value = true;
|
|
523
525
|
}
|
|
524
526
|
current = current.owner;
|
|
525
527
|
}
|
|
526
528
|
} else {
|
|
527
|
-
if (startContext.
|
|
529
|
+
if (!startContext._lastEmpty) {
|
|
528
530
|
// it was already non-empty, no work to do.
|
|
529
531
|
return;
|
|
530
532
|
}
|
|
531
533
|
|
|
532
|
-
|
|
534
|
+
startContext._lastEmpty = false;
|
|
535
|
+
if (startContext.isEmpty) {
|
|
533
536
|
startContext.isEmpty.value = false;
|
|
534
537
|
}
|
|
535
538
|
|
|
@@ -542,7 +545,8 @@ export function notifyContentState() {
|
|
|
542
545
|
break;
|
|
543
546
|
}
|
|
544
547
|
|
|
545
|
-
|
|
548
|
+
current._lastEmpty = false;
|
|
549
|
+
if (current.isEmpty) {
|
|
546
550
|
current.isEmpty.value = false;
|
|
547
551
|
}
|
|
548
552
|
|
|
@@ -757,16 +761,15 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
757
761
|
}
|
|
758
762
|
} else if (isComponentCreator(child)) {
|
|
759
763
|
const index = node.length;
|
|
760
|
-
const rerenderToken = ref(0);
|
|
761
|
-
const breakNext = ref(false);
|
|
764
|
+
const rerenderToken = isDevtoolsEnabled() ? ref(0) : undefined;
|
|
765
|
+
const breakNext = isDevtoolsEnabled() ? ref(false) : undefined;
|
|
762
766
|
// todo: remove this effect (only needed for context, not needed for anything else)
|
|
763
767
|
effect(
|
|
764
768
|
() => {
|
|
765
769
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
766
|
-
rerenderToken
|
|
770
|
+
rerenderToken?.value;
|
|
767
771
|
const context = getContext();
|
|
768
772
|
context!.childrenWithContent = 0;
|
|
769
|
-
context!.isEmpty ??= ref(true);
|
|
770
773
|
|
|
771
774
|
if (context) context.componentOwner = child;
|
|
772
775
|
const existing = node[index];
|
|
@@ -788,12 +791,12 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
788
791
|
actions: {
|
|
789
792
|
rerender: () => {
|
|
790
793
|
lastRenderError = null;
|
|
791
|
-
rerenderToken.value++;
|
|
794
|
+
if (rerenderToken) rerenderToken.value++;
|
|
792
795
|
},
|
|
793
796
|
rerenderAndBreak: () => {
|
|
794
797
|
lastRenderError = null;
|
|
795
|
-
breakNext.value = true;
|
|
796
|
-
rerenderToken.value++;
|
|
798
|
+
if (breakNext) breakNext.value = true;
|
|
799
|
+
if (rerenderToken) rerenderToken.value++;
|
|
797
800
|
},
|
|
798
801
|
},
|
|
799
802
|
});
|
|
@@ -806,9 +809,9 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
806
809
|
let childResult: Children | undefined;
|
|
807
810
|
try {
|
|
808
811
|
childResult = untrack(() => {
|
|
809
|
-
const shouldBreak = breakNext
|
|
812
|
+
const shouldBreak = breakNext?.value;
|
|
810
813
|
if (shouldBreak) {
|
|
811
|
-
breakNext
|
|
814
|
+
breakNext!.value = false;
|
|
812
815
|
// eslint-disable-next-line no-debugger
|
|
813
816
|
debugger;
|
|
814
817
|
}
|
|
@@ -880,7 +883,6 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
880
883
|
}
|
|
881
884
|
const context = getContext();
|
|
882
885
|
context!.childrenWithContent = 0;
|
|
883
|
-
context!.isEmpty ??= ref(true);
|
|
884
886
|
|
|
885
887
|
const existing = node[index];
|
|
886
888
|
const memoNode: RenderedTextTree =
|
package/src/scheduler.ts
CHANGED
|
@@ -6,17 +6,39 @@ export interface QueueJob {
|
|
|
6
6
|
}
|
|
7
7
|
const immediateQueue = new Set<QueueJob>();
|
|
8
8
|
const queue = new Set<QueueJob>();
|
|
9
|
+
function isJobActive(job: QueueJob): boolean {
|
|
10
|
+
// ReactiveEffect uses bit 0 (flags & 1) as the ACTIVE flag.
|
|
11
|
+
// Skip effects that were stopped after being queued.
|
|
12
|
+
const flags = (job as any).flags;
|
|
13
|
+
return flags === undefined || (flags & 1) !== 0;
|
|
14
|
+
}
|
|
9
15
|
const pendingPromises = new Set<Promise<any>>();
|
|
10
16
|
let waitForSignalPromise: Promise<void> | null = null;
|
|
11
17
|
let resolveWaitForSignal: (() => void) | null = null;
|
|
12
18
|
let jobSignalPromise: Promise<void> | null = null;
|
|
13
19
|
let resolveJobSignal: (() => void) | null = null;
|
|
14
20
|
|
|
21
|
+
// Maps effect debug IDs to the ref that most recently triggered them
|
|
22
|
+
const lastTriggerRef = new Map<number, number>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Record which ref triggered an effect re-run.
|
|
26
|
+
* Called from the onTrigger debug hook before the effect is scheduled.
|
|
27
|
+
*/
|
|
28
|
+
export function setLastTriggerRef(effectDebugId: number, refId: number): void {
|
|
29
|
+
lastTriggerRef.set(effectDebugId, refId);
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
export function scheduler(immediate = false) {
|
|
33
|
+
if (!immediate) return defaultScheduler;
|
|
16
34
|
return function (this: ReactiveEffect) {
|
|
17
|
-
queueJob(this,
|
|
35
|
+
queueJob(this, true);
|
|
18
36
|
};
|
|
19
37
|
}
|
|
38
|
+
|
|
39
|
+
const defaultScheduler = function (this: ReactiveEffect) {
|
|
40
|
+
queueJob(this, false);
|
|
41
|
+
};
|
|
20
42
|
export function queueJob(job: QueueJob | (() => void), immediate = false) {
|
|
21
43
|
// if we have an immediate job, we don't need to queue the normal job.
|
|
22
44
|
// the set is serving an important purpose here in deduping the effects we run
|
|
@@ -52,6 +74,7 @@ export function flushJobs() {
|
|
|
52
74
|
// First, run all synchronous jobs
|
|
53
75
|
let job;
|
|
54
76
|
while ((job = takeJob()) !== null) {
|
|
77
|
+
if (!isJobActive(job)) continue;
|
|
55
78
|
job.run();
|
|
56
79
|
}
|
|
57
80
|
|
|
@@ -96,6 +119,7 @@ export async function flushJobsAsync() {
|
|
|
96
119
|
// First, run all synchronous jobs
|
|
97
120
|
let job;
|
|
98
121
|
while ((job = takeJob()) !== null) {
|
|
122
|
+
if (!isJobActive(job)) continue;
|
|
99
123
|
job.run();
|
|
100
124
|
}
|
|
101
125
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
reactive,
|
|
3
3
|
ReactiveFlags,
|
|
4
|
-
shallowReactive,
|
|
5
4
|
track,
|
|
6
5
|
TrackOpTypes,
|
|
7
6
|
trigger,
|
|
@@ -10,7 +9,7 @@ import {
|
|
|
10
9
|
import type { Binder } from "../binder.js";
|
|
11
10
|
import { useBinder } from "../context/binder.js";
|
|
12
11
|
import { inspect } from "../inspect.js";
|
|
13
|
-
import { effect, untrack } from "../reactivity.js";
|
|
12
|
+
import { effect, shallowReactive, untrack } from "../reactivity.js";
|
|
14
13
|
import { OutputDeclarationSpace, OutputSpace } from "./output-space.js";
|
|
15
14
|
import { OutputSymbol } from "./output-symbol.js";
|
|
16
15
|
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
reactive,
|
|
4
4
|
ReactiveFlags,
|
|
5
5
|
Ref,
|
|
6
|
-
shallowReactive,
|
|
7
6
|
track,
|
|
8
7
|
TrackOpTypes,
|
|
9
8
|
trigger,
|
|
@@ -15,7 +14,7 @@ import { useBinder } from "../context/binder.js";
|
|
|
15
14
|
import { debug, TracePhase } from "../debug/index.js";
|
|
16
15
|
import { inspect } from "../inspect.js";
|
|
17
16
|
import { NamePolicyGetter } from "../name-policy.js";
|
|
18
|
-
import { untrack } from "../reactivity.js";
|
|
17
|
+
import { shallowReactive, untrack } from "../reactivity.js";
|
|
19
18
|
import {
|
|
20
19
|
isMemberRefkey,
|
|
21
20
|
isNamekey,
|