@fictjs/runtime 0.9.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/dist/advanced.cjs +9 -9
- package/dist/advanced.d.cts +4 -4
- package/dist/advanced.d.ts +4 -4
- package/dist/advanced.js +4 -4
- package/dist/{binding-BWchH3Kp.d.ts → binding-DUEukRxl.d.cts} +4 -2
- package/dist/{binding-BWchH3Kp.d.cts → binding-DqxS9ZQf.d.ts} +4 -2
- package/dist/{chunk-JVYH76ZX.js → chunk-2JRPPCG7.js} +3 -3
- package/dist/{chunk-FVX77557.js → chunk-DKA2I6ET.js} +3 -3
- package/dist/{chunk-UBFDB6OL.cjs → chunk-EQ5E4WOV.cjs} +216 -50
- package/dist/chunk-EQ5E4WOV.cjs.map +1 -0
- package/dist/{chunk-DXG3TARY.js → chunk-F4RVNXOL.js} +196 -30
- package/dist/chunk-F4RVNXOL.js.map +1 -0
- package/dist/{chunk-OAM7HABA.cjs → chunk-I4GKKAAY.cjs} +226 -182
- package/dist/chunk-I4GKKAAY.cjs.map +1 -0
- package/dist/{chunk-PG4QX2I2.cjs → chunk-K3DH5SD5.cjs} +17 -17
- package/dist/{chunk-PG4QX2I2.cjs.map → chunk-K3DH5SD5.cjs.map} +1 -1
- package/dist/{chunk-N6ODUM2Y.js → chunk-P4TZLFV6.js} +3 -3
- package/dist/{chunk-T2LNV5Q5.js → chunk-R6FINS25.js} +50 -6
- package/dist/chunk-R6FINS25.js.map +1 -0
- package/dist/{chunk-LBE6DC3V.cjs → chunk-SZLJCQFZ.cjs} +40 -40
- package/dist/{chunk-LBE6DC3V.cjs.map → chunk-SZLJCQFZ.cjs.map} +1 -1
- package/dist/{chunk-PD6IQY2Y.cjs → chunk-V7BC64W2.cjs} +8 -8
- package/dist/{chunk-PD6IQY2Y.cjs.map → chunk-V7BC64W2.cjs.map} +1 -1
- package/dist/{devtools-5AipK9CX.d.cts → devtools-C4Hgfa-S.d.ts} +14 -2
- package/dist/{devtools-BDp76luf.d.ts → devtools-CMxlJUTx.d.cts} +14 -2
- 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 +230 -25
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/internal-list.cjs +4 -4
- package/dist/internal-list.d.cts +2 -2
- package/dist/internal-list.d.ts +2 -2
- package/dist/internal-list.js +3 -3
- package/dist/internal.cjs +5 -5
- package/dist/internal.d.cts +6 -6
- package/dist/internal.d.ts +6 -6
- package/dist/internal.js +4 -4
- 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-DL5DOFcO.d.ts → list-BBzsJhrm.d.ts} +1 -1
- package/dist/{list-hP7hQ9Vk.d.cts → list-_NJCcjl1.d.cts} +1 -1
- package/dist/loader.cjs +24 -20
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +2 -2
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +7 -3
- package/dist/loader.js.map +1 -1
- package/dist/{props-BpZz0AOq.d.cts → props--zJ4ebbT.d.cts} +2 -2
- package/dist/{props-CjLH0JE-.d.ts → props-BAGR7j-j.d.ts} +2 -2
- package/dist/{resume-BJ4oHLi_.d.cts → resume-C5IKAIdh.d.ts} +2 -2
- package/dist/{resume-CuyJWXP_.d.ts → resume-DPZxmA95.d.cts} +2 -2
- package/dist/{scope-jPt5DHRT.d.ts → scope-CuImnvh1.d.ts} +1 -1
- package/dist/{scope-BJCtq8hJ.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 +2 -2
- 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/lifecycle.ts +41 -3
- package/src/loader.ts +10 -4
- package/src/signal.ts +191 -18
- package/src/transition.ts +9 -3
- package/dist/chunk-DXG3TARY.js.map +0 -1
- package/dist/chunk-OAM7HABA.cjs.map +0 -1
- package/dist/chunk-T2LNV5Q5.js.map +0 -1
- package/dist/chunk-UBFDB6OL.cjs.map +0 -1
- /package/dist/{chunk-JVYH76ZX.js.map → chunk-2JRPPCG7.js.map} +0 -0
- /package/dist/{chunk-FVX77557.js.map → chunk-DKA2I6ET.js.map} +0 -0
- /package/dist/{chunk-N6ODUM2Y.js.map → chunk-P4TZLFV6.js.map} +0 -0
package/src/dom.ts
CHANGED
|
@@ -64,6 +64,46 @@ const isDev =
|
|
|
64
64
|
|
|
65
65
|
let nextComponentId = 1
|
|
66
66
|
|
|
67
|
+
type DevtoolsAnnotatedElement = HTMLElement & {
|
|
68
|
+
__fict_component_id__?: number
|
|
69
|
+
__fict_component_name__?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function collectComponentMountElements(node: Node): HTMLElement[] {
|
|
73
|
+
if (node instanceof DocumentFragment) {
|
|
74
|
+
return Array.from(node.childNodes).filter(
|
|
75
|
+
(child): child is HTMLElement => child instanceof HTMLElement,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (node instanceof HTMLElement) {
|
|
80
|
+
// Resumable hosts use display: contents; surface concrete child elements for inspection.
|
|
81
|
+
if (node.hasAttribute('data-fict-host')) {
|
|
82
|
+
const children = Array.from(node.children).filter(
|
|
83
|
+
(child): child is HTMLElement => child instanceof HTMLElement,
|
|
84
|
+
)
|
|
85
|
+
if (children.length > 0) return children
|
|
86
|
+
}
|
|
87
|
+
return [node]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return []
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function annotateComponentElements(
|
|
94
|
+
elements: HTMLElement[],
|
|
95
|
+
componentId: number,
|
|
96
|
+
componentName: string,
|
|
97
|
+
): void {
|
|
98
|
+
for (const element of elements) {
|
|
99
|
+
element.setAttribute('data-fict-component', componentName)
|
|
100
|
+
element.setAttribute('data-fict-component-id', String(componentId))
|
|
101
|
+
const annotated = element as DevtoolsAnnotatedElement
|
|
102
|
+
annotated.__fict_component_id__ = componentId
|
|
103
|
+
annotated.__fict_component_name__ = componentName
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
67
107
|
// ============================================================================
|
|
68
108
|
// Main Render Function
|
|
69
109
|
// ============================================================================
|
|
@@ -282,12 +322,13 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
282
322
|
// Create a fresh hook context for this component instance.
|
|
283
323
|
// This preserves slot state across re-renders driven by __fictRender.
|
|
284
324
|
const hook = isDev ? getDevtoolsHook() : undefined
|
|
325
|
+
const componentName = vnode.type.name || 'Anonymous'
|
|
285
326
|
const parentId = hook ? __fictGetCurrentComponentId() : undefined
|
|
286
327
|
const componentId = hook ? nextComponentId++ : undefined
|
|
287
328
|
|
|
288
329
|
// Register component
|
|
289
330
|
if (hook?.registerComponent && componentId !== undefined) {
|
|
290
|
-
hook.registerComponent(componentId,
|
|
331
|
+
hook.registerComponent(componentId, componentName, parentId)
|
|
291
332
|
}
|
|
292
333
|
|
|
293
334
|
const ctx = __fictPushContext()
|
|
@@ -300,11 +341,16 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
300
341
|
|
|
301
342
|
try {
|
|
302
343
|
const rendered = vnode.type(props)
|
|
344
|
+
let mountElements: HTMLElement[] | undefined
|
|
345
|
+
|
|
346
|
+
if (hook && componentId !== undefined) {
|
|
347
|
+
hook.componentRender?.(componentId)
|
|
348
|
+
}
|
|
303
349
|
|
|
304
350
|
// Register lifecycle hooks
|
|
305
351
|
if (hook && componentId !== undefined) {
|
|
306
352
|
onMount(() => {
|
|
307
|
-
hook.componentMount?.(componentId)
|
|
353
|
+
hook.componentMount?.(componentId, mountElements)
|
|
308
354
|
})
|
|
309
355
|
onCleanup(() => hook.componentUnmount?.(componentId))
|
|
310
356
|
}
|
|
@@ -332,10 +378,18 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
332
378
|
} else {
|
|
333
379
|
host.appendChild(content)
|
|
334
380
|
}
|
|
381
|
+
if (hook && componentId !== undefined) {
|
|
382
|
+
mountElements = collectComponentMountElements(host)
|
|
383
|
+
annotateComponentElements(mountElements, componentId, componentName)
|
|
384
|
+
}
|
|
335
385
|
return host as DOMElement
|
|
336
386
|
}
|
|
337
|
-
|
|
338
|
-
|
|
387
|
+
const componentRoot = createElementWithContext(rendered as FictNode, namespace)
|
|
388
|
+
if (hook && componentId !== undefined) {
|
|
389
|
+
mountElements = collectComponentMountElements(componentRoot)
|
|
390
|
+
annotateComponentElements(mountElements, componentId, componentName)
|
|
391
|
+
}
|
|
392
|
+
return componentRoot
|
|
339
393
|
} catch (err) {
|
|
340
394
|
if (handleSuspend(err as any)) {
|
|
341
395
|
return document.createComment('fict:suspend')
|
package/src/effect.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
runCleanupList,
|
|
7
7
|
withEffectCleanups,
|
|
8
8
|
} from './lifecycle'
|
|
9
|
-
import { effectWithCleanup } from './signal'
|
|
9
|
+
import { effectWithCleanup, type EffectOptions } from './signal'
|
|
10
10
|
import type { Cleanup } from './types'
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -15,7 +15,7 @@ import type { Cleanup } from './types'
|
|
|
15
15
|
*/
|
|
16
16
|
export type Effect = () => void | Cleanup
|
|
17
17
|
|
|
18
|
-
export function createEffect(fn: Effect): () => void {
|
|
18
|
+
export function createEffect(fn: Effect, options?: EffectOptions): () => void {
|
|
19
19
|
let cleanups: Cleanup[] = []
|
|
20
20
|
const rootForError = getCurrentRoot()
|
|
21
21
|
|
|
@@ -47,7 +47,7 @@ export function createEffect(fn: Effect): () => void {
|
|
|
47
47
|
cleanups = bucket
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const disposeEffect = effectWithCleanup(run, doCleanup, rootForError)
|
|
50
|
+
const disposeEffect = effectWithCleanup(run, doCleanup, rootForError, options)
|
|
51
51
|
const teardown = () => {
|
|
52
52
|
runCleanupList(cleanups)
|
|
53
53
|
disposeEffect()
|
|
@@ -60,7 +60,7 @@ export function createEffect(fn: Effect): () => void {
|
|
|
60
60
|
|
|
61
61
|
export const $effect = createEffect
|
|
62
62
|
|
|
63
|
-
export function createRenderEffect(fn: Effect): () => void {
|
|
63
|
+
export function createRenderEffect(fn: Effect, options?: EffectOptions): () => void {
|
|
64
64
|
let cleanup: Cleanup | undefined
|
|
65
65
|
const rootForError = getCurrentRoot()
|
|
66
66
|
|
|
@@ -91,7 +91,7 @@ export function createRenderEffect(fn: Effect): () => void {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const disposeEffect = effectWithCleanup(run, doCleanup, rootForError)
|
|
94
|
+
const disposeEffect = effectWithCleanup(run, doCleanup, rootForError, options)
|
|
95
95
|
const teardown = () => {
|
|
96
96
|
if (cleanup) {
|
|
97
97
|
cleanup()
|
package/src/hooks.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createSignal,
|
|
5
5
|
type SignalAccessor,
|
|
6
6
|
type ComputedAccessor,
|
|
7
|
+
type EffectOptions,
|
|
7
8
|
type MemoOptions,
|
|
8
9
|
type SignalOptions,
|
|
9
10
|
} from './signal'
|
|
@@ -118,17 +119,24 @@ export function __fictUseMemo<T>(
|
|
|
118
119
|
return ctx.slots[index] as ComputedAccessor<T>
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
export function __fictUseEffect(
|
|
122
|
+
export function __fictUseEffect(
|
|
123
|
+
ctx: HookContext,
|
|
124
|
+
fn: () => void,
|
|
125
|
+
optionsOrSlot?: number | EffectOptions,
|
|
126
|
+
slot?: number,
|
|
127
|
+
): void {
|
|
128
|
+
const options = typeof optionsOrSlot === 'number' ? undefined : optionsOrSlot
|
|
129
|
+
const resolvedSlot = typeof optionsOrSlot === 'number' ? optionsOrSlot : slot
|
|
122
130
|
// fix: When a slot number is provided, we trust the compiler has allocated this slot.
|
|
123
131
|
// This allows effects inside conditional callbacks to work even outside render context.
|
|
124
132
|
// The slot number proves this is a known, statically-allocated effect location.
|
|
125
|
-
if (
|
|
126
|
-
if (ctx.slots[
|
|
133
|
+
if (resolvedSlot !== undefined) {
|
|
134
|
+
if (ctx.slots[resolvedSlot]) {
|
|
127
135
|
// Effect already exists, nothing to do
|
|
128
136
|
return
|
|
129
137
|
}
|
|
130
138
|
// Create the effect even outside render context - the slot number proves validity
|
|
131
|
-
ctx.slots[
|
|
139
|
+
ctx.slots[resolvedSlot] = createEffect(fn, options)
|
|
132
140
|
return
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -136,7 +144,7 @@ export function __fictUseEffect(ctx: HookContext, fn: () => void, slot?: number)
|
|
|
136
144
|
assertRenderContext(ctx, '__fictUseEffect')
|
|
137
145
|
const index = ctx.cursor++
|
|
138
146
|
if (!ctx.slots[index]) {
|
|
139
|
-
ctx.slots[index] = createEffect(fn)
|
|
147
|
+
ctx.slots[index] = createEffect(fn, options)
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { enterRootGuard, exitRootGuard } from './cycle-guard'
|
|
2
|
+
import { getDevtoolsHook } from './devtools'
|
|
2
3
|
import type { Cleanup, ErrorInfo, SuspenseToken } from './types'
|
|
3
4
|
|
|
4
5
|
const isDev =
|
|
@@ -29,9 +30,39 @@ let currentRoot: RootContext | undefined
|
|
|
29
30
|
let currentEffectCleanups: Cleanup[] | undefined
|
|
30
31
|
const globalErrorHandlers = new WeakMap<RootContext, ErrorHandler[]>()
|
|
31
32
|
const globalSuspenseHandlers = new WeakMap<RootContext, SuspenseHandler[]>()
|
|
33
|
+
const rootDevtoolsIds = new WeakMap<RootContext, number>()
|
|
34
|
+
let nextRootDevtoolsId = 0
|
|
35
|
+
|
|
36
|
+
function registerRootDevtools(root: RootContext): void {
|
|
37
|
+
if (!isDev) return
|
|
38
|
+
const hook = getDevtoolsHook()
|
|
39
|
+
if (!hook?.registerRoot) return
|
|
40
|
+
const id = ++nextRootDevtoolsId
|
|
41
|
+
rootDevtoolsIds.set(root, id)
|
|
42
|
+
hook.registerRoot(id)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function disposeRootDevtools(root: RootContext): void {
|
|
46
|
+
if (!isDev) return
|
|
47
|
+
const id = rootDevtoolsIds.get(root)
|
|
48
|
+
if (id === undefined) return
|
|
49
|
+
const hook = getDevtoolsHook()
|
|
50
|
+
hook?.disposeRoot?.(id)
|
|
51
|
+
rootDevtoolsIds.delete(root)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setRootSuspendDevtools(root: RootContext, suspended: boolean): void {
|
|
55
|
+
if (!isDev) return
|
|
56
|
+
const id = rootDevtoolsIds.get(root)
|
|
57
|
+
if (id === undefined) return
|
|
58
|
+
const hook = getDevtoolsHook()
|
|
59
|
+
hook?.rootSuspend?.(id, suspended)
|
|
60
|
+
}
|
|
32
61
|
|
|
33
62
|
export function createRootContext(parent?: RootContext): RootContext {
|
|
34
|
-
|
|
63
|
+
const root = { parent, cleanups: [], destroyCallbacks: [], suspended: false }
|
|
64
|
+
registerRootDevtools(root)
|
|
65
|
+
return root
|
|
35
66
|
}
|
|
36
67
|
|
|
37
68
|
export function pushRoot(root: RootContext): RootContext | undefined {
|
|
@@ -122,6 +153,7 @@ export function destroyRoot(root: RootContext): void {
|
|
|
122
153
|
if (globalSuspenseHandlers.has(root)) {
|
|
123
154
|
globalSuspenseHandlers.delete(root)
|
|
124
155
|
}
|
|
156
|
+
disposeRootDevtools(root)
|
|
125
157
|
}
|
|
126
158
|
|
|
127
159
|
export function createRoot<T>(
|
|
@@ -285,7 +317,10 @@ export function handleSuspend(
|
|
|
285
317
|
const handled = handler(token)
|
|
286
318
|
if (handled !== false) {
|
|
287
319
|
// Only set suspended = true when a handler actually handles the token
|
|
288
|
-
if (originRoot)
|
|
320
|
+
if (originRoot) {
|
|
321
|
+
originRoot.suspended = true
|
|
322
|
+
setRootSuspendDevtools(originRoot, true)
|
|
323
|
+
}
|
|
289
324
|
return true
|
|
290
325
|
}
|
|
291
326
|
}
|
|
@@ -304,7 +339,10 @@ export function handleSuspend(
|
|
|
304
339
|
const handled = handler(token)
|
|
305
340
|
if (handled !== false) {
|
|
306
341
|
// Only set suspended = true when a handler actually handles the token
|
|
307
|
-
if (originRoot)
|
|
342
|
+
if (originRoot) {
|
|
343
|
+
originRoot.suspended = true
|
|
344
|
+
setRootSuspendDevtools(originRoot, true)
|
|
345
|
+
}
|
|
308
346
|
return true
|
|
309
347
|
}
|
|
310
348
|
}
|
package/src/loader.ts
CHANGED
|
@@ -507,9 +507,15 @@ function prefetchQrl(qrl: string): void {
|
|
|
507
507
|
function handleResumableEvent(event: Event): void {
|
|
508
508
|
const promise = handleResumableEventAsync(event)
|
|
509
509
|
pendingHandlers.add(promise)
|
|
510
|
-
promise
|
|
511
|
-
|
|
512
|
-
|
|
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
|
+
})
|
|
513
519
|
}
|
|
514
520
|
|
|
515
521
|
async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
@@ -535,7 +541,7 @@ async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
|
535
541
|
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
536
542
|
scopeId,
|
|
537
543
|
})
|
|
538
|
-
|
|
544
|
+
continue
|
|
539
545
|
}
|
|
540
546
|
__fictEnsureScope(scopeId, host, snapshot)
|
|
541
547
|
|