@fictjs/runtime 0.8.0 → 0.10.0
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/README.md +46 -0
- package/dist/advanced.cjs +9 -9
- package/dist/advanced.d.cts +5 -5
- package/dist/advanced.d.ts +5 -5
- package/dist/advanced.js +4 -4
- package/dist/{effect-DAzpH7Mm.d.ts → binding-DUEukRxl.d.cts} +35 -24
- package/dist/{effect-DAzpH7Mm.d.cts → binding-DqxS9ZQf.d.ts} +35 -24
- package/dist/{chunk-WRU3IZOA.js → chunk-2JRPPCG7.js} +3 -3
- package/dist/{chunk-TLDT76RV.js → chunk-DKA2I6ET.js} +3 -3
- package/dist/{chunk-FSCBL7RI.cjs → chunk-EQ5E4WOV.cjs} +702 -534
- package/dist/chunk-EQ5E4WOV.cjs.map +1 -0
- package/dist/{chunk-7YQK3XKY.js → chunk-F4RVNXOL.js} +687 -519
- package/dist/chunk-F4RVNXOL.js.map +1 -0
- package/dist/{chunk-PRF4QG73.cjs → chunk-I4GKKAAY.cjs} +469 -248
- package/dist/chunk-I4GKKAAY.cjs.map +1 -0
- package/dist/{chunk-HHDHQGJY.cjs → chunk-K3DH5SD5.cjs} +17 -17
- package/dist/{chunk-HHDHQGJY.cjs.map → chunk-K3DH5SD5.cjs.map} +1 -1
- package/dist/chunk-P4TZLFV6.js +768 -0
- package/dist/chunk-P4TZLFV6.js.map +1 -0
- package/dist/{chunk-4LCHQ7U4.js → chunk-R6FINS25.js} +318 -97
- package/dist/chunk-R6FINS25.js.map +1 -0
- package/dist/chunk-SZLJCQFZ.cjs +768 -0
- package/dist/chunk-SZLJCQFZ.cjs.map +1 -0
- package/dist/{chunk-CEV6TO5U.cjs → chunk-V7BC64W2.cjs} +8 -8
- package/dist/{chunk-CEV6TO5U.cjs.map → chunk-V7BC64W2.cjs.map} +1 -1
- package/dist/{context-BFbHf9nC.d.cts → devtools-C4Hgfa-S.d.ts} +47 -35
- package/dist/{context-C4vBQbb4.d.ts → devtools-CMxlJUTx.d.cts} +47 -35
- package/dist/index.cjs +42 -42
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.dev.js +233 -28
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/internal-list.cjs +12 -0
- package/dist/internal-list.cjs.map +1 -0
- package/dist/internal-list.d.cts +2 -0
- package/dist/internal-list.d.ts +2 -0
- package/dist/internal-list.js +12 -0
- package/dist/internal-list.js.map +1 -0
- package/dist/internal.cjs +6 -746
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +7 -75
- package/dist/internal.d.ts +7 -75
- package/dist/internal.js +12 -752
- package/dist/internal.js.map +1 -1
- package/dist/jsx-dev-runtime.d.cts +671 -0
- package/dist/jsx-dev-runtime.d.ts +671 -0
- package/dist/jsx-runtime.d.cts +671 -0
- package/dist/jsx-runtime.d.ts +671 -0
- package/dist/list-BBzsJhrm.d.ts +71 -0
- package/dist/list-_NJCcjl1.d.cts +71 -0
- package/dist/loader.cjs +99 -16
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +17 -3
- package/dist/loader.d.ts +17 -3
- package/dist/loader.js +92 -9
- package/dist/loader.js.map +1 -1
- package/dist/{props-84UJeWO8.d.cts → props--zJ4ebbT.d.cts} +3 -3
- package/dist/{props-BRhFK50f.d.ts → props-BAGR7j-j.d.ts} +3 -3
- package/dist/{resume-i-A3EFox.d.cts → resume-C5IKAIdh.d.ts} +5 -3
- package/dist/{resume-CqeQ3v_q.d.ts → resume-DPZxmA95.d.cts} +5 -3
- package/dist/{scope-D3DpsfoG.d.ts → scope-CuImnvh1.d.ts} +1 -1
- package/dist/{scope-DlCBL1Ft.d.cts → scope-Dq5hOu7c.d.cts} +1 -1
- package/dist/{signal-C4ISF17w.d.cts → signal-Z4KkDk9h.d.cts} +12 -1
- package/dist/{signal-C4ISF17w.d.ts → signal-Z4KkDk9h.d.ts} +12 -1
- package/package.json +9 -2
- package/src/binding.ts +113 -36
- package/src/cycle-guard.ts +3 -3
- package/src/devtools.ts +19 -2
- package/src/dom.ts +58 -4
- package/src/effect.ts +5 -5
- package/src/hooks.ts +13 -5
- package/src/internal/list.ts +7 -0
- package/src/internal.ts +1 -0
- package/src/lifecycle.ts +41 -3
- package/src/list-helpers.ts +1 -1
- package/src/loader.ts +128 -12
- package/src/resume.ts +6 -3
- package/src/signal.ts +200 -20
- package/src/transition.ts +9 -3
- package/dist/chunk-4LCHQ7U4.js.map +0 -1
- package/dist/chunk-7YQK3XKY.js.map +0 -1
- package/dist/chunk-FSCBL7RI.cjs.map +0 -1
- package/dist/chunk-PRF4QG73.cjs.map +0 -1
- /package/dist/{chunk-WRU3IZOA.js.map → chunk-2JRPPCG7.js.map} +0 -0
- /package/dist/{chunk-TLDT76RV.js.map → chunk-DKA2I6ET.js.map} +0 -0
package/src/loader.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DelegatedEvents } from './constants'
|
|
2
2
|
import {
|
|
3
|
+
FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
4
|
+
type SSRState,
|
|
3
5
|
__fictEnableResumable,
|
|
4
6
|
__fictEnsureScope,
|
|
5
7
|
__fictGetResume,
|
|
@@ -67,6 +69,11 @@ export interface ResumableLoaderOptions {
|
|
|
67
69
|
document?: Document
|
|
68
70
|
snapshotScriptId?: string
|
|
69
71
|
events?: string[]
|
|
72
|
+
/**
|
|
73
|
+
* Receives structured snapshot/resume issues detected by the loader.
|
|
74
|
+
* Useful for telemetry and fail-safe fallback orchestration.
|
|
75
|
+
*/
|
|
76
|
+
onSnapshotIssue?: (issue: SnapshotIssue) => void
|
|
70
77
|
/**
|
|
71
78
|
* Prefetch strategy configuration.
|
|
72
79
|
* Set to false to disable all prefetching.
|
|
@@ -75,6 +82,21 @@ export interface ResumableLoaderOptions {
|
|
|
75
82
|
prefetch?: PrefetchStrategy | false
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
export type SnapshotIssueCode =
|
|
86
|
+
| 'snapshot_parse_error'
|
|
87
|
+
| 'snapshot_invalid_shape'
|
|
88
|
+
| 'snapshot_unsupported_version'
|
|
89
|
+
| 'scope_snapshot_missing'
|
|
90
|
+
|
|
91
|
+
export interface SnapshotIssue {
|
|
92
|
+
code: SnapshotIssueCode
|
|
93
|
+
message: string
|
|
94
|
+
source: string
|
|
95
|
+
expectedVersion: number
|
|
96
|
+
actualVersion?: number
|
|
97
|
+
scopeId?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
// ============================================================================
|
|
79
101
|
// State
|
|
80
102
|
// ============================================================================
|
|
@@ -85,6 +107,8 @@ let prefetchCleanup: (() => void) | null = null
|
|
|
85
107
|
let eventListenerCleanup: (() => void) | null = null
|
|
86
108
|
let snapshotObserver: MutationObserver | null = null
|
|
87
109
|
const processedSnapshots = new Set<HTMLScriptElement>()
|
|
110
|
+
let snapshotIssueHandler: ((issue: SnapshotIssue) => void) | null = null
|
|
111
|
+
const emittedIssueKeys = new Set<string>()
|
|
88
112
|
|
|
89
113
|
/**
|
|
90
114
|
* Reset the hydrated scopes set. Useful for testing.
|
|
@@ -130,11 +154,14 @@ export function cleanupEventListeners(): void {
|
|
|
130
154
|
export function installResumableLoader(options: ResumableLoaderOptions = {}): void {
|
|
131
155
|
const doc = options.document ?? window.document
|
|
132
156
|
const scriptId = options.snapshotScriptId ?? '__FICT_SNAPSHOT__'
|
|
157
|
+
snapshotIssueHandler = options.onSnapshotIssue ?? null
|
|
133
158
|
|
|
134
159
|
// Reset hydrated scopes for fresh loader installation
|
|
135
160
|
hydratedScopes.clear()
|
|
136
161
|
prefetchedUrls.clear()
|
|
137
162
|
processedSnapshots.clear()
|
|
163
|
+
emittedIssueKeys.clear()
|
|
164
|
+
__fictSetSSRState(null)
|
|
138
165
|
|
|
139
166
|
// Clean up previous event listeners
|
|
140
167
|
if (eventListenerCleanup) {
|
|
@@ -155,11 +182,9 @@ export function installResumableLoader(options: ResumableLoaderOptions = {}): vo
|
|
|
155
182
|
|
|
156
183
|
const snapshotEl = doc.getElementById(scriptId)
|
|
157
184
|
if (snapshotEl?.textContent) {
|
|
158
|
-
|
|
159
|
-
|
|
185
|
+
const state = parseSnapshotText(snapshotEl.textContent, `#${scriptId}`)
|
|
186
|
+
if (state) {
|
|
160
187
|
__fictSetSSRState(state)
|
|
161
|
-
} catch {
|
|
162
|
-
// Ignore parse errors
|
|
163
188
|
}
|
|
164
189
|
}
|
|
165
190
|
|
|
@@ -224,11 +249,88 @@ function parseSnapshotScript(script: HTMLScriptElement): void {
|
|
|
224
249
|
processedSnapshots.add(script)
|
|
225
250
|
const text = script.textContent
|
|
226
251
|
if (!text) return
|
|
227
|
-
|
|
228
|
-
|
|
252
|
+
const source = script.id ? `#${script.id}` : '<script[data-fict-snapshot]>'
|
|
253
|
+
const state = parseSnapshotText(text, source)
|
|
254
|
+
if (state) {
|
|
229
255
|
__fictMergeSSRState(state)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseSnapshotText(text: string, source: string): SSRState | null {
|
|
260
|
+
let parsed: unknown
|
|
261
|
+
try {
|
|
262
|
+
parsed = JSON.parse(text)
|
|
230
263
|
} catch {
|
|
231
|
-
|
|
264
|
+
emitSnapshotIssue({
|
|
265
|
+
code: 'snapshot_parse_error',
|
|
266
|
+
message: '[fict/loader] Failed to parse SSR snapshot JSON.',
|
|
267
|
+
source,
|
|
268
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
269
|
+
})
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return normalizeSnapshotState(parsed, source)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function normalizeSnapshotState(value: unknown, source: string): SSRState | null {
|
|
277
|
+
if (!isRecord(value)) {
|
|
278
|
+
emitSnapshotIssue({
|
|
279
|
+
code: 'snapshot_invalid_shape',
|
|
280
|
+
message: '[fict/loader] Snapshot payload must be an object.',
|
|
281
|
+
source,
|
|
282
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
283
|
+
})
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rawVersion = value.v
|
|
288
|
+
const version = rawVersion === undefined ? FICT_SSR_SNAPSHOT_SCHEMA_VERSION : rawVersion
|
|
289
|
+
if (!Number.isInteger(version) || version !== FICT_SSR_SNAPSHOT_SCHEMA_VERSION) {
|
|
290
|
+
const versionIssue: SnapshotIssue = {
|
|
291
|
+
code: 'snapshot_unsupported_version',
|
|
292
|
+
message: `[fict/loader] Snapshot schema version ${String(version)} is not supported by this runtime.`,
|
|
293
|
+
source,
|
|
294
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
295
|
+
}
|
|
296
|
+
if (typeof version === 'number') {
|
|
297
|
+
versionIssue.actualVersion = version
|
|
298
|
+
}
|
|
299
|
+
emitSnapshotIssue({
|
|
300
|
+
...versionIssue,
|
|
301
|
+
})
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const scopes = value.scopes
|
|
306
|
+
if (!isRecord(scopes)) {
|
|
307
|
+
emitSnapshotIssue({
|
|
308
|
+
code: 'snapshot_invalid_shape',
|
|
309
|
+
message: '[fict/loader] Snapshot payload is missing a valid `scopes` object.',
|
|
310
|
+
source,
|
|
311
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
312
|
+
})
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes: scopes as SSRState['scopes'] }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
320
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function emitSnapshotIssue(issue: SnapshotIssue): void {
|
|
324
|
+
const key =
|
|
325
|
+
`${issue.code}|${issue.source}|${issue.scopeId ?? ''}|` +
|
|
326
|
+
`${issue.actualVersion ?? ''}|${issue.expectedVersion}`
|
|
327
|
+
if (emittedIssueKeys.has(key)) return
|
|
328
|
+
emittedIssueKeys.add(key)
|
|
329
|
+
|
|
330
|
+
snapshotIssueHandler?.(issue)
|
|
331
|
+
|
|
332
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
333
|
+
console.warn(issue.message)
|
|
232
334
|
}
|
|
233
335
|
}
|
|
234
336
|
|
|
@@ -405,9 +507,15 @@ function prefetchQrl(qrl: string): void {
|
|
|
405
507
|
function handleResumableEvent(event: Event): void {
|
|
406
508
|
const promise = handleResumableEventAsync(event)
|
|
407
509
|
pendingHandlers.add(promise)
|
|
408
|
-
promise
|
|
409
|
-
|
|
410
|
-
|
|
510
|
+
void promise
|
|
511
|
+
.catch(error => {
|
|
512
|
+
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
|
513
|
+
console.error('[fict/loader] Failed to handle resumable event.', error)
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
.finally(() => {
|
|
517
|
+
pendingHandlers.delete(promise)
|
|
518
|
+
})
|
|
411
519
|
}
|
|
412
520
|
|
|
413
521
|
async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
@@ -425,9 +533,17 @@ async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
|
425
533
|
if (!scopeId) continue
|
|
426
534
|
|
|
427
535
|
const snapshot = __fictGetSSRScope(scopeId)
|
|
428
|
-
if (snapshot) {
|
|
429
|
-
|
|
536
|
+
if (!snapshot) {
|
|
537
|
+
emitSnapshotIssue({
|
|
538
|
+
code: 'scope_snapshot_missing',
|
|
539
|
+
message: `[fict/loader] Missing scope snapshot for ${scopeId}; skipping resumable handler execution.`,
|
|
540
|
+
source: 'event',
|
|
541
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
542
|
+
scopeId,
|
|
543
|
+
})
|
|
544
|
+
continue
|
|
430
545
|
}
|
|
546
|
+
__fictEnsureScope(scopeId, host, snapshot)
|
|
431
547
|
|
|
432
548
|
const { url, exportName } = parseQrl(qrl)
|
|
433
549
|
|
package/src/resume.ts
CHANGED
|
@@ -36,7 +36,10 @@ export interface ScopeSnapshot {
|
|
|
36
36
|
vars?: Record<string, number>
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export const FICT_SSR_SNAPSHOT_SCHEMA_VERSION = 1
|
|
40
|
+
|
|
39
41
|
export interface SSRState {
|
|
42
|
+
v: number
|
|
40
43
|
scopes: Record<string, ScopeSnapshot>
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -173,7 +176,7 @@ export function __fictSerializeSSRState(): SSRState {
|
|
|
173
176
|
scopes[id] = snapshot
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
return { scopes }
|
|
179
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes }
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
export function __fictSerializeSSRStateForScopes(scopeIds: Iterable<string>): SSRState {
|
|
@@ -198,7 +201,7 @@ export function __fictSerializeSSRStateForScopes(scopeIds: Iterable<string>): SS
|
|
|
198
201
|
scopes[id] = snapshot
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
return { scopes }
|
|
204
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes }
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
export function __fictSetSSRState(state: SSRState | null): void {
|
|
@@ -211,7 +214,7 @@ export function __fictSetSSRState(state: SSRState | null): void {
|
|
|
211
214
|
export function __fictMergeSSRState(state: SSRState | null): void {
|
|
212
215
|
if (!state) return
|
|
213
216
|
if (!snapshotState) {
|
|
214
|
-
snapshotState = { scopes: { ...state.scopes } }
|
|
217
|
+
snapshotState = { v: state.v, scopes: { ...state.scopes } }
|
|
215
218
|
return
|
|
216
219
|
}
|
|
217
220
|
Object.assign(snapshotState.scopes, state.scopes)
|
package/src/signal.ts
CHANGED
|
@@ -93,6 +93,18 @@ export interface MemoOptions<T> {
|
|
|
93
93
|
name?: string
|
|
94
94
|
/** Source location */
|
|
95
95
|
devToolsSource?: string
|
|
96
|
+
/** Internal memo created by compiler runtime plumbing (hidden from DevTools) */
|
|
97
|
+
internal?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options for creating an effect
|
|
102
|
+
*/
|
|
103
|
+
export interface EffectOptions {
|
|
104
|
+
/** Debug name */
|
|
105
|
+
name?: string
|
|
106
|
+
/** Source location */
|
|
107
|
+
devToolsSource?: string
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
/**
|
|
@@ -145,6 +157,8 @@ export interface ComputedNode<T = unknown> extends BaseNode {
|
|
|
145
157
|
name?: string
|
|
146
158
|
/** Source location */
|
|
147
159
|
devToolsSource?: string
|
|
160
|
+
/** Hide this computed from DevTools (used by compiler-internal memos) */
|
|
161
|
+
devToolsInternal?: boolean
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
/**
|
|
@@ -161,6 +175,10 @@ export interface EffectNode extends BaseNode {
|
|
|
161
175
|
runCleanup?: () => void
|
|
162
176
|
/** Root context for error/suspense handling */
|
|
163
177
|
root?: RootContext
|
|
178
|
+
/** Debug name */
|
|
179
|
+
name?: string
|
|
180
|
+
/** Source location */
|
|
181
|
+
devToolsSource?: string
|
|
164
182
|
/** Devtools ID */
|
|
165
183
|
__id?: number | undefined
|
|
166
184
|
}
|
|
@@ -788,6 +806,16 @@ function purgeDeps(sub: ReactiveNode): void {
|
|
|
788
806
|
* @param node - The node to dispose
|
|
789
807
|
*/
|
|
790
808
|
function disposeNode(node: ReactiveNode): void {
|
|
809
|
+
if (isDev) {
|
|
810
|
+
if ('fn' in node && typeof node.fn === 'function') {
|
|
811
|
+
disposeEffectDevtools(node as EffectNode)
|
|
812
|
+
} else if ('getter' in node && typeof node.getter === 'function') {
|
|
813
|
+
disposeComputedDevtools(node as ComputedNode)
|
|
814
|
+
} else if ('currentValue' in node) {
|
|
815
|
+
disposeSignalDevtools(node as SignalNode)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
791
819
|
node.depsTail = undefined
|
|
792
820
|
node.flags = 0
|
|
793
821
|
purgeDeps(node)
|
|
@@ -858,6 +886,7 @@ function runEffect(e: EffectNode): void {
|
|
|
858
886
|
const flags = e.flags
|
|
859
887
|
const runCleanup = () => {
|
|
860
888
|
if (!e.runCleanup) return
|
|
889
|
+
if (isDev) effectCleanupDevtools(e)
|
|
861
890
|
inCleanup = true
|
|
862
891
|
activeCleanupFlushId = currentFlushId
|
|
863
892
|
try {
|
|
@@ -871,7 +900,6 @@ function runEffect(e: EffectNode): void {
|
|
|
871
900
|
// Run cleanup before re-run; values are still the previous commit.
|
|
872
901
|
runCleanup()
|
|
873
902
|
++cycle
|
|
874
|
-
if (isDev) effectRunDevtools(e)
|
|
875
903
|
e.depsTail = undefined
|
|
876
904
|
e.flags = WatchingRunning
|
|
877
905
|
const prevSub = activeSub
|
|
@@ -913,7 +941,6 @@ function runEffect(e: EffectNode): void {
|
|
|
913
941
|
// Cleanup reads should observe previous values for this flush.
|
|
914
942
|
runCleanup()
|
|
915
943
|
++cycle
|
|
916
|
-
if (isDev) effectRunDevtools(e)
|
|
917
944
|
e.depsTail = undefined
|
|
918
945
|
e.flags = WatchingRunning
|
|
919
946
|
const prevSub = activeSub
|
|
@@ -956,20 +983,31 @@ export function scheduleFlush(): void {
|
|
|
956
983
|
*/
|
|
957
984
|
function flush(): void {
|
|
958
985
|
beginFlushGuard()
|
|
986
|
+
let flushReported = false
|
|
987
|
+
const finishFlush = () => {
|
|
988
|
+
if (flushReported && isDev) {
|
|
989
|
+
flushEndDevtools()
|
|
990
|
+
}
|
|
991
|
+
endFlushGuard()
|
|
992
|
+
}
|
|
959
993
|
if (batchDepth > 0) {
|
|
960
994
|
// If batching is active, defer until the batch completes
|
|
961
995
|
scheduleFlush()
|
|
962
|
-
|
|
996
|
+
finishFlush()
|
|
963
997
|
return
|
|
964
998
|
}
|
|
965
999
|
const hasWork = highPriorityQueue.length > 0 || lowPriorityQueue.length > 0
|
|
966
1000
|
if (!hasWork) {
|
|
967
1001
|
flushScheduled = false
|
|
968
|
-
|
|
1002
|
+
finishFlush()
|
|
969
1003
|
return
|
|
970
1004
|
}
|
|
971
1005
|
currentFlushId++
|
|
972
1006
|
flushScheduled = false
|
|
1007
|
+
if (isDev) {
|
|
1008
|
+
flushStartDevtools()
|
|
1009
|
+
flushReported = true
|
|
1010
|
+
}
|
|
973
1011
|
|
|
974
1012
|
// 1. Process all high-priority effects first
|
|
975
1013
|
let highIndex = 0
|
|
@@ -993,7 +1031,7 @@ function flush(): void {
|
|
|
993
1031
|
highPriorityQueue.length = 0
|
|
994
1032
|
lowPriorityQueue.length = 0
|
|
995
1033
|
flushScheduled = false
|
|
996
|
-
|
|
1034
|
+
finishFlush()
|
|
997
1035
|
return
|
|
998
1036
|
}
|
|
999
1037
|
highIndex++
|
|
@@ -1011,7 +1049,7 @@ function flush(): void {
|
|
|
1011
1049
|
lowPriorityQueue.length -= lowIndex
|
|
1012
1050
|
}
|
|
1013
1051
|
scheduleFlush()
|
|
1014
|
-
|
|
1052
|
+
finishFlush()
|
|
1015
1053
|
return
|
|
1016
1054
|
}
|
|
1017
1055
|
const e = lowPriorityQueue[lowIndex]!
|
|
@@ -1033,7 +1071,7 @@ function flush(): void {
|
|
|
1033
1071
|
highPriorityQueue.length = 0
|
|
1034
1072
|
lowPriorityQueue.length = 0
|
|
1035
1073
|
flushScheduled = false
|
|
1036
|
-
|
|
1074
|
+
finishFlush()
|
|
1037
1075
|
return
|
|
1038
1076
|
}
|
|
1039
1077
|
lowIndex++
|
|
@@ -1041,7 +1079,7 @@ function flush(): void {
|
|
|
1041
1079
|
}
|
|
1042
1080
|
lowPriorityQueue.length = 0
|
|
1043
1081
|
|
|
1044
|
-
|
|
1082
|
+
finishFlush()
|
|
1045
1083
|
}
|
|
1046
1084
|
// ============================================================================
|
|
1047
1085
|
// Signal - Inline optimized version
|
|
@@ -1138,6 +1176,7 @@ export function computed<T>(
|
|
|
1138
1176
|
if (options?.equals !== undefined) c.equals = options.equals
|
|
1139
1177
|
if (options?.name !== undefined) c.name = options.name
|
|
1140
1178
|
if (options?.devToolsSource !== undefined) c.devToolsSource = options.devToolsSource
|
|
1179
|
+
if (options?.internal === true) c.devToolsInternal = true
|
|
1141
1180
|
if (isDev) registerComputedDevtools(c)
|
|
1142
1181
|
const bound = (computedOper as (this: ComputedNode<T>) => T).bind(
|
|
1143
1182
|
c as any,
|
|
@@ -1201,9 +1240,10 @@ function computedOper<T>(this: ComputedNode<T>): T {
|
|
|
1201
1240
|
/**
|
|
1202
1241
|
* Create a reactive effect
|
|
1203
1242
|
* @param fn - The effect function
|
|
1243
|
+
* @param options - Effect options
|
|
1204
1244
|
* @returns An effect disposer function
|
|
1205
1245
|
*/
|
|
1206
|
-
export function effect(fn: () => void): EffectDisposer {
|
|
1246
|
+
export function effect(fn: () => void, options?: EffectOptions): EffectDisposer {
|
|
1207
1247
|
const e: EffectNode = {
|
|
1208
1248
|
fn,
|
|
1209
1249
|
subs: undefined,
|
|
@@ -1211,6 +1251,8 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1211
1251
|
deps: undefined,
|
|
1212
1252
|
depsTail: undefined,
|
|
1213
1253
|
flags: WatchingRunning,
|
|
1254
|
+
...(options?.name !== undefined ? { name: options.name } : {}),
|
|
1255
|
+
...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
|
|
1214
1256
|
__id: undefined as number | undefined,
|
|
1215
1257
|
}
|
|
1216
1258
|
const root = getCurrentRoot()
|
|
@@ -1219,6 +1261,7 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1219
1261
|
}
|
|
1220
1262
|
|
|
1221
1263
|
if (isDev) registerEffectDevtools(e)
|
|
1264
|
+
e.fn = wrapEffectFnWithDevtoolsTiming(e, fn)
|
|
1222
1265
|
|
|
1223
1266
|
const prevSub = activeSub
|
|
1224
1267
|
if (prevSub !== undefined) link(e, prevSub, 0)
|
|
@@ -1227,8 +1270,7 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1227
1270
|
let didThrow = false
|
|
1228
1271
|
let thrown: unknown
|
|
1229
1272
|
try {
|
|
1230
|
-
|
|
1231
|
-
fn()
|
|
1273
|
+
e.fn()
|
|
1232
1274
|
} catch (err) {
|
|
1233
1275
|
didThrow = true
|
|
1234
1276
|
thrown = err
|
|
@@ -1261,6 +1303,7 @@ export function effectWithCleanup(
|
|
|
1261
1303
|
fn: () => void,
|
|
1262
1304
|
cleanupRunner: () => void,
|
|
1263
1305
|
root?: RootContext,
|
|
1306
|
+
options?: EffectOptions,
|
|
1264
1307
|
): EffectDisposer {
|
|
1265
1308
|
const e: EffectNode = {
|
|
1266
1309
|
fn,
|
|
@@ -1270,6 +1313,8 @@ export function effectWithCleanup(
|
|
|
1270
1313
|
depsTail: undefined,
|
|
1271
1314
|
flags: WatchingRunning,
|
|
1272
1315
|
runCleanup: cleanupRunner,
|
|
1316
|
+
...(options?.name !== undefined ? { name: options.name } : {}),
|
|
1317
|
+
...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
|
|
1273
1318
|
__id: undefined as number | undefined,
|
|
1274
1319
|
}
|
|
1275
1320
|
const resolvedRoot = root ?? getCurrentRoot()
|
|
@@ -1278,6 +1323,7 @@ export function effectWithCleanup(
|
|
|
1278
1323
|
}
|
|
1279
1324
|
|
|
1280
1325
|
if (isDev) registerEffectDevtools(e)
|
|
1326
|
+
e.fn = wrapEffectFnWithDevtoolsTiming(e, fn)
|
|
1281
1327
|
|
|
1282
1328
|
const prevSub = activeSub
|
|
1283
1329
|
if (prevSub !== undefined) link(e, prevSub, 0)
|
|
@@ -1286,8 +1332,7 @@ export function effectWithCleanup(
|
|
|
1286
1332
|
let didThrow = false
|
|
1287
1333
|
let thrown: unknown
|
|
1288
1334
|
try {
|
|
1289
|
-
|
|
1290
|
-
fn()
|
|
1335
|
+
e.fn()
|
|
1291
1336
|
} catch (err) {
|
|
1292
1337
|
didThrow = true
|
|
1293
1338
|
thrown = err
|
|
@@ -1385,13 +1430,24 @@ export function trigger(fn: () => void): void {
|
|
|
1385
1430
|
* Start a batch of updates
|
|
1386
1431
|
*/
|
|
1387
1432
|
export function startBatch(): void {
|
|
1433
|
+
const enteringOuterBatch = batchDepth === 0
|
|
1388
1434
|
++batchDepth
|
|
1435
|
+
if (enteringOuterBatch && isDev) {
|
|
1436
|
+
batchStartDevtools()
|
|
1437
|
+
}
|
|
1389
1438
|
}
|
|
1390
1439
|
/**
|
|
1391
1440
|
* End a batch of updates and flush effects
|
|
1392
1441
|
*/
|
|
1393
1442
|
export function endBatch(): void {
|
|
1394
|
-
if (
|
|
1443
|
+
if (batchDepth === 0) return
|
|
1444
|
+
--batchDepth
|
|
1445
|
+
if (batchDepth === 0) {
|
|
1446
|
+
if (isDev) {
|
|
1447
|
+
batchEndDevtools()
|
|
1448
|
+
}
|
|
1449
|
+
flush()
|
|
1450
|
+
}
|
|
1395
1451
|
}
|
|
1396
1452
|
/**
|
|
1397
1453
|
* Execute a function in a batch
|
|
@@ -1399,7 +1455,11 @@ export function endBatch(): void {
|
|
|
1399
1455
|
* @returns The return value of the function
|
|
1400
1456
|
*/
|
|
1401
1457
|
export function batch<T>(fn: () => T): T {
|
|
1458
|
+
const enteringOuterBatch = batchDepth === 0
|
|
1402
1459
|
++batchDepth
|
|
1460
|
+
if (enteringOuterBatch && isDev) {
|
|
1461
|
+
batchStartDevtools()
|
|
1462
|
+
}
|
|
1403
1463
|
let result!: T
|
|
1404
1464
|
let error: unknown
|
|
1405
1465
|
try {
|
|
@@ -1409,6 +1469,9 @@ export function batch<T>(fn: () => T): T {
|
|
|
1409
1469
|
} finally {
|
|
1410
1470
|
--batchDepth
|
|
1411
1471
|
if (batchDepth === 0) {
|
|
1472
|
+
if (isDev) {
|
|
1473
|
+
batchEndDevtools()
|
|
1474
|
+
}
|
|
1412
1475
|
try {
|
|
1413
1476
|
flush()
|
|
1414
1477
|
} catch (flushErr) {
|
|
@@ -1463,6 +1526,7 @@ export function __resetReactiveState(): void {
|
|
|
1463
1526
|
cycle = 0
|
|
1464
1527
|
currentFlushId = 0
|
|
1465
1528
|
activeCleanupFlushId = 0
|
|
1529
|
+
clearDevtoolsSignalSetters()
|
|
1466
1530
|
}
|
|
1467
1531
|
/**
|
|
1468
1532
|
* Execute a function without tracking dependencies
|
|
@@ -1588,17 +1652,56 @@ interface DevtoolsIdentifiable {
|
|
|
1588
1652
|
|
|
1589
1653
|
let registerSignalDevtools: <T>(node: SignalNode<T>) => number | undefined = () => undefined
|
|
1590
1654
|
let updateSignalDevtools: <T>(node: SignalNode<T>, value: unknown) => void = () => {}
|
|
1655
|
+
let disposeSignalDevtools: <T>(node: SignalNode<T>) => void = () => {}
|
|
1591
1656
|
let registerComputedDevtools: <T>(node: ComputedNode<T>) => number | undefined = () => undefined
|
|
1592
1657
|
let updateComputedDevtools: <T>(node: ComputedNode<T>, value: unknown) => void = () => {}
|
|
1658
|
+
let disposeComputedDevtools: <T>(node: ComputedNode<T>) => void = () => {}
|
|
1593
1659
|
let registerEffectDevtools: (node: EffectNode) => number | undefined = () => undefined
|
|
1594
|
-
let effectRunDevtools: (node: EffectNode) => void = () => {}
|
|
1660
|
+
let effectRunDevtools: (node: EffectNode, duration?: number) => void = () => {}
|
|
1661
|
+
let wrapEffectFnWithDevtoolsTiming: (node: EffectNode, fn: () => void) => () => void = (
|
|
1662
|
+
_node,
|
|
1663
|
+
fn,
|
|
1664
|
+
) => fn
|
|
1665
|
+
let effectCleanupDevtools: (node: EffectNode) => void = () => {}
|
|
1666
|
+
let disposeEffectDevtools: (node: EffectNode) => void = () => {}
|
|
1595
1667
|
let trackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1596
1668
|
let untrackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1597
|
-
|
|
1598
|
-
|
|
1669
|
+
let batchStartDevtools: () => void = () => {}
|
|
1670
|
+
let batchEndDevtools: () => void = () => {}
|
|
1671
|
+
let flushStartDevtools: () => void = () => {}
|
|
1672
|
+
let flushEndDevtools: () => void = () => {}
|
|
1673
|
+
let clearDevtoolsSignalSetters: () => void = () => {}
|
|
1674
|
+
|
|
1675
|
+
// Keep this as a direct conditional expression (instead of `if (isDev)`) so
|
|
1676
|
+
// bundlers can eliminate the entire devtools setup block when `__DEV__` is
|
|
1677
|
+
// defined as `false` in production builds.
|
|
1678
|
+
if (
|
|
1679
|
+
typeof __DEV__ !== 'undefined'
|
|
1680
|
+
? __DEV__
|
|
1681
|
+
: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'
|
|
1682
|
+
) {
|
|
1599
1683
|
// Unified ID counter for all reactive nodes (signal/computed/effect)
|
|
1600
1684
|
// to prevent ID collisions when storing in single devtools maps
|
|
1601
1685
|
let nextDevtoolsId = 0
|
|
1686
|
+
const getSignalSetterMap = () => {
|
|
1687
|
+
if (typeof globalThis === 'undefined') return undefined
|
|
1688
|
+
const global = globalThis as typeof globalThis & {
|
|
1689
|
+
__FICT_DEVTOOLS_SIGNALS__?: Map<number, (value: unknown) => void>
|
|
1690
|
+
}
|
|
1691
|
+
if (!global.__FICT_DEVTOOLS_SIGNALS__) {
|
|
1692
|
+
global.__FICT_DEVTOOLS_SIGNALS__ = new Map<number, (value: unknown) => void>()
|
|
1693
|
+
}
|
|
1694
|
+
return global.__FICT_DEVTOOLS_SIGNALS__
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const getExistingSignalSetterMap = () => {
|
|
1698
|
+
if (typeof globalThis === 'undefined') return undefined
|
|
1699
|
+
return (
|
|
1700
|
+
globalThis as typeof globalThis & {
|
|
1701
|
+
__FICT_DEVTOOLS_SIGNALS__?: Map<number, (value: unknown) => void>
|
|
1702
|
+
}
|
|
1703
|
+
).__FICT_DEVTOOLS_SIGNALS__
|
|
1704
|
+
}
|
|
1602
1705
|
|
|
1603
1706
|
registerSignalDevtools = node => {
|
|
1604
1707
|
const hook = getDevtoolsHook()
|
|
@@ -1611,6 +1714,9 @@ if (isDev) {
|
|
|
1611
1714
|
if (ownerId !== undefined) (options as any).ownerId = ownerId
|
|
1612
1715
|
hook.registerSignal(id, node.currentValue, options)
|
|
1613
1716
|
;(node as SignalNode & DevtoolsIdentifiable).__id = id
|
|
1717
|
+
getSignalSetterMap()?.set(id, value => {
|
|
1718
|
+
signalOper.call(node as SignalNode<unknown>, value)
|
|
1719
|
+
})
|
|
1614
1720
|
return id
|
|
1615
1721
|
}
|
|
1616
1722
|
|
|
@@ -1621,9 +1727,20 @@ if (isDev) {
|
|
|
1621
1727
|
if (id) hook.updateSignal(id, value)
|
|
1622
1728
|
}
|
|
1623
1729
|
|
|
1730
|
+
disposeSignalDevtools = node => {
|
|
1731
|
+
const identifiable = node as SignalNode & DevtoolsIdentifiable
|
|
1732
|
+
const id = identifiable.__id
|
|
1733
|
+
if (!id) return
|
|
1734
|
+
const hook = getDevtoolsHook()
|
|
1735
|
+
hook?.disposeSignal?.(id)
|
|
1736
|
+
getExistingSignalSetterMap()?.delete(id)
|
|
1737
|
+
delete identifiable.__id
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1624
1740
|
registerComputedDevtools = node => {
|
|
1625
1741
|
const hook = getDevtoolsHook()
|
|
1626
1742
|
if (!hook) return undefined
|
|
1743
|
+
if (node.devToolsInternal) return undefined
|
|
1627
1744
|
const id = ++nextDevtoolsId
|
|
1628
1745
|
const options: { name?: string; source?: string } = {}
|
|
1629
1746
|
if (node.name !== undefined) options.name = node.name
|
|
@@ -1643,21 +1760,60 @@ if (isDev) {
|
|
|
1643
1760
|
if (id) hook.updateComputed(id, value)
|
|
1644
1761
|
}
|
|
1645
1762
|
|
|
1763
|
+
disposeComputedDevtools = node => {
|
|
1764
|
+
const identifiable = node as ComputedNode & DevtoolsIdentifiable
|
|
1765
|
+
const id = identifiable.__id
|
|
1766
|
+
if (!id) return
|
|
1767
|
+
const hook = getDevtoolsHook()
|
|
1768
|
+
hook?.disposeComputed?.(id)
|
|
1769
|
+
delete identifiable.__id
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1646
1772
|
registerEffectDevtools = node => {
|
|
1647
1773
|
const hook = getDevtoolsHook()
|
|
1648
1774
|
if (!hook) return undefined
|
|
1649
1775
|
const id = ++nextDevtoolsId
|
|
1776
|
+
const options: { ownerId?: number; source?: string } = {}
|
|
1650
1777
|
const ownerId = __fictGetCurrentComponentId()
|
|
1651
|
-
|
|
1778
|
+
if (ownerId !== undefined) options.ownerId = ownerId
|
|
1779
|
+
if (node.devToolsSource !== undefined) options.source = node.devToolsSource
|
|
1780
|
+
hook.registerEffect(id, Object.keys(options).length > 0 ? options : undefined)
|
|
1652
1781
|
;(node as EffectNode & DevtoolsIdentifiable).__id = id
|
|
1653
1782
|
return id
|
|
1654
1783
|
}
|
|
1655
1784
|
|
|
1656
|
-
effectRunDevtools = node => {
|
|
1785
|
+
effectRunDevtools = (node, duration) => {
|
|
1786
|
+
const hook = getDevtoolsHook()
|
|
1787
|
+
if (!hook) return
|
|
1788
|
+
const id = (node as EffectNode & DevtoolsIdentifiable).__id
|
|
1789
|
+
if (id) hook.effectRun(id, duration)
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
wrapEffectFnWithDevtoolsTiming = (node, fn) => {
|
|
1793
|
+
return () => {
|
|
1794
|
+
const startedAt = performance.now()
|
|
1795
|
+
try {
|
|
1796
|
+
fn()
|
|
1797
|
+
} finally {
|
|
1798
|
+
effectRunDevtools(node, performance.now() - startedAt)
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
effectCleanupDevtools = node => {
|
|
1657
1804
|
const hook = getDevtoolsHook()
|
|
1658
1805
|
if (!hook) return
|
|
1659
1806
|
const id = (node as EffectNode & DevtoolsIdentifiable).__id
|
|
1660
|
-
if (id) hook.
|
|
1807
|
+
if (id) hook.effectCleanup?.(id)
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
disposeEffectDevtools = node => {
|
|
1811
|
+
const identifiable = node as EffectNode & DevtoolsIdentifiable
|
|
1812
|
+
const id = identifiable.__id
|
|
1813
|
+
if (!id) return
|
|
1814
|
+
const hook = getDevtoolsHook()
|
|
1815
|
+
hook?.disposeEffect?.(id)
|
|
1816
|
+
delete identifiable.__id
|
|
1661
1817
|
}
|
|
1662
1818
|
|
|
1663
1819
|
trackDependencyDevtools = (dep, sub) => {
|
|
@@ -1675,6 +1831,30 @@ if (isDev) {
|
|
|
1675
1831
|
const subId = (sub as ReactiveNode & DevtoolsIdentifiable).__id
|
|
1676
1832
|
if (depId && subId) hook.untrackDependency(subId, depId)
|
|
1677
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
batchStartDevtools = () => {
|
|
1836
|
+
const hook = getDevtoolsHook()
|
|
1837
|
+
hook?.batchStart?.()
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
batchEndDevtools = () => {
|
|
1841
|
+
const hook = getDevtoolsHook()
|
|
1842
|
+
hook?.batchEnd?.()
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
flushStartDevtools = () => {
|
|
1846
|
+
const hook = getDevtoolsHook()
|
|
1847
|
+
hook?.flushStart?.()
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
flushEndDevtools = () => {
|
|
1851
|
+
const hook = getDevtoolsHook()
|
|
1852
|
+
hook?.flushEnd?.()
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
clearDevtoolsSignalSetters = () => {
|
|
1856
|
+
getExistingSignalSetterMap()?.clear()
|
|
1857
|
+
}
|
|
1678
1858
|
}
|
|
1679
1859
|
|
|
1680
1860
|
// ============================================================================
|