@fictjs/runtime 0.3.0 → 0.5.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 +10 -8
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +4 -3
- package/dist/advanced.d.ts +4 -3
- package/dist/advanced.js +10 -8
- package/dist/advanced.js.map +1 -1
- package/dist/{chunk-TWELIZRY.js → chunk-5AA7HP4S.js} +5 -3
- package/dist/{chunk-TWELIZRY.js.map → chunk-5AA7HP4S.js.map} +1 -1
- package/dist/chunk-6SOPF5LZ.cjs +2363 -0
- package/dist/chunk-6SOPF5LZ.cjs.map +1 -0
- package/dist/{chunk-SO6X7G5S.js → chunk-BQG7VEBY.js} +501 -1880
- package/dist/chunk-BQG7VEBY.js.map +1 -0
- package/dist/chunk-FKDMDAUR.js +2363 -0
- package/dist/chunk-FKDMDAUR.js.map +1 -0
- package/dist/{chunk-L4DIV3RC.cjs → chunk-GHUV2FLD.cjs} +9 -7
- package/dist/chunk-GHUV2FLD.cjs.map +1 -0
- package/dist/{chunk-XLIZJMMJ.js → chunk-KKKYW54Z.js} +8 -6
- package/dist/{chunk-XLIZJMMJ.js.map → chunk-KKKYW54Z.js.map} +1 -1
- package/dist/{chunk-M2TSXZ4C.cjs → chunk-KYLNC4CD.cjs} +18 -16
- package/dist/chunk-KYLNC4CD.cjs.map +1 -0
- package/dist/chunk-TKWN42TA.cjs +2259 -0
- package/dist/chunk-TKWN42TA.cjs.map +1 -0
- package/dist/{context-B25xyQrJ.d.cts → context-CTBE00S_.d.cts} +1 -1
- package/dist/{context-CGdP7_Jb.d.ts → context-lkLhbkFJ.d.ts} +1 -1
- package/dist/{effect-D6kaLM2-.d.cts → effect-BpSNEJJz.d.cts} +7 -67
- package/dist/{effect-D6kaLM2-.d.ts → effect-BpSNEJJz.d.ts} +7 -67
- package/dist/index.cjs +40 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.dev.js +92 -4
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +19 -17
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +189 -202
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +13 -23
- package/dist/internal.d.ts +13 -23
- package/dist/internal.js +195 -208
- package/dist/internal.js.map +1 -1
- package/dist/loader.cjs +280 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.cts +57 -0
- package/dist/loader.d.ts +57 -0
- package/dist/loader.js +280 -0
- package/dist/loader.js.map +1 -0
- package/dist/{props-BIfromL0.d.cts → props-XTHYD19o.d.cts} +13 -2
- package/dist/{props-BEgIVMRx.d.ts → props-x-HbI-jX.d.ts} +13 -2
- package/dist/resume-BrAkmSTY.d.cts +79 -0
- package/dist/resume-Dx8_l72o.d.ts +79 -0
- package/dist/{scope-CzNkn587.d.ts → scope-CdbGmsFf.d.ts} +1 -1
- package/dist/{scope-Cx_3CjIZ.d.cts → scope-DfcP9I-A.d.cts} +1 -1
- package/dist/signal-C4ISF17w.d.cts +66 -0
- package/dist/signal-C4ISF17w.d.ts +66 -0
- package/package.json +8 -3
- package/src/binding.ts +254 -5
- package/src/dom.ts +103 -5
- package/src/hooks.ts +15 -2
- package/src/hydration.ts +75 -0
- package/src/internal.ts +34 -2
- package/src/list-helpers.ts +113 -12
- package/src/loader.ts +437 -0
- package/src/node-ops.ts +65 -0
- package/src/resume.ts +517 -0
- package/src/store.ts +8 -0
- package/dist/chunk-ID3WBWNO.cjs +0 -3638
- package/dist/chunk-ID3WBWNO.cjs.map +0 -1
- package/dist/chunk-L4DIV3RC.cjs.map +0 -1
- package/dist/chunk-M2TSXZ4C.cjs.map +0 -1
- package/dist/chunk-SO6X7G5S.js.map +0 -1
package/src/hydration.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface HydrationContext {
|
|
2
|
+
cursor: Node | null
|
|
3
|
+
boundary: Node | null
|
|
4
|
+
owner: Document
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const hydrationStack: HydrationContext[] = []
|
|
8
|
+
|
|
9
|
+
export function withHydration(root: ParentNode & Node, fn: () => void): void {
|
|
10
|
+
const owner = root.ownerDocument ?? document
|
|
11
|
+
hydrationStack.push({
|
|
12
|
+
cursor: root.firstChild,
|
|
13
|
+
boundary: null,
|
|
14
|
+
owner,
|
|
15
|
+
})
|
|
16
|
+
try {
|
|
17
|
+
fn()
|
|
18
|
+
} finally {
|
|
19
|
+
hydrationStack.pop()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function withHydrationRange(
|
|
24
|
+
start: Node | null,
|
|
25
|
+
end: Node | null,
|
|
26
|
+
owner: Document,
|
|
27
|
+
fn: () => void,
|
|
28
|
+
): void {
|
|
29
|
+
hydrationStack.push({
|
|
30
|
+
cursor: start,
|
|
31
|
+
boundary: end,
|
|
32
|
+
owner,
|
|
33
|
+
})
|
|
34
|
+
try {
|
|
35
|
+
fn()
|
|
36
|
+
} finally {
|
|
37
|
+
hydrationStack.pop()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function claimNodes(templateRoot: Node, fallback: () => Node): Node {
|
|
42
|
+
const ctx = hydrationStack[hydrationStack.length - 1]
|
|
43
|
+
if (!ctx || !ctx.cursor) {
|
|
44
|
+
return fallback()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const count = templateRoot.nodeType === 11 ? templateRoot.childNodes.length : 1
|
|
48
|
+
if (count === 0) return fallback()
|
|
49
|
+
|
|
50
|
+
const claimed: Node[] = []
|
|
51
|
+
let cursor: Node | null = ctx.cursor
|
|
52
|
+
for (let i = 0; i < count; i++) {
|
|
53
|
+
if (!cursor || cursor === ctx.boundary) {
|
|
54
|
+
return fallback()
|
|
55
|
+
}
|
|
56
|
+
claimed.push(cursor)
|
|
57
|
+
cursor = cursor.nextSibling
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ctx.cursor = cursor
|
|
61
|
+
|
|
62
|
+
if (claimed.length === 1) {
|
|
63
|
+
return claimed[0]!
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const frag = ctx.owner.createDocumentFragment()
|
|
67
|
+
for (const node of claimed) {
|
|
68
|
+
frag.appendChild(node)
|
|
69
|
+
}
|
|
70
|
+
return frag
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isHydratingActive(): boolean {
|
|
74
|
+
return hydrationStack.length > 0
|
|
75
|
+
}
|
package/src/internal.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
15
|
export { createSignal, createSelector, __resetReactiveState } from './signal'
|
|
16
|
-
export { createStore, type Store } from './store'
|
|
16
|
+
export { createStore, type Store, isStoreProxy, unwrapStore } from './store'
|
|
17
17
|
export { createMemo } from './memo'
|
|
18
18
|
export { createEffect } from './effect'
|
|
19
19
|
export { Fragment } from './jsx'
|
|
@@ -31,8 +31,38 @@ export {
|
|
|
31
31
|
__fictUseEffect,
|
|
32
32
|
__fictRender,
|
|
33
33
|
__fictResetContext,
|
|
34
|
+
__fictPrepareContext,
|
|
34
35
|
} from './hooks'
|
|
35
36
|
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// SSR / Resumability (Internal)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
__fictEnableSSR,
|
|
43
|
+
__fictDisableSSR,
|
|
44
|
+
__fictIsSSR,
|
|
45
|
+
__fictEnableResumable,
|
|
46
|
+
__fictDisableResumable,
|
|
47
|
+
__fictIsResumable,
|
|
48
|
+
__fictEnterHydration,
|
|
49
|
+
__fictExitHydration,
|
|
50
|
+
__fictIsHydrating,
|
|
51
|
+
__fictRegisterScope,
|
|
52
|
+
__fictGetScopeRegistry,
|
|
53
|
+
__fictSerializeSSRState,
|
|
54
|
+
__fictSetSSRState,
|
|
55
|
+
__fictGetSSRScope,
|
|
56
|
+
__fictEnsureScope,
|
|
57
|
+
__fictUseLexicalScope,
|
|
58
|
+
__fictGetScopeProps,
|
|
59
|
+
__fictQrl,
|
|
60
|
+
__fictRegisterResume,
|
|
61
|
+
__fictGetResume,
|
|
62
|
+
serializeValue,
|
|
63
|
+
deserializeValue,
|
|
64
|
+
} from './resume'
|
|
65
|
+
|
|
36
66
|
// ============================================================================
|
|
37
67
|
// Props Helpers (Compiler-generated code)
|
|
38
68
|
// ============================================================================
|
|
@@ -53,6 +83,7 @@ export {
|
|
|
53
83
|
bindProperty,
|
|
54
84
|
bindRef,
|
|
55
85
|
insert,
|
|
86
|
+
insertBetween,
|
|
56
87
|
createConditional,
|
|
57
88
|
createPortal,
|
|
58
89
|
spread,
|
|
@@ -61,6 +92,7 @@ export {
|
|
|
61
92
|
isReactive,
|
|
62
93
|
unwrap,
|
|
63
94
|
} from './binding'
|
|
95
|
+
export { resolvePath, getSlotEnd } from './node-ops'
|
|
64
96
|
|
|
65
97
|
// ============================================================================
|
|
66
98
|
// Event Delegation (Compiler-generated code)
|
|
@@ -86,7 +118,7 @@ export {
|
|
|
86
118
|
// DOM Creation (Compiler-generated code)
|
|
87
119
|
// ============================================================================
|
|
88
120
|
|
|
89
|
-
export { createElement, template } from './dom'
|
|
121
|
+
export { createElement, template, render, hydrateComponent } from './dom'
|
|
90
122
|
export { createRenderEffect } from './effect'
|
|
91
123
|
|
|
92
124
|
// ============================================================================
|
package/src/list-helpers.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { createElement } from './dom'
|
|
9
9
|
import { createRenderEffect } from './effect'
|
|
10
|
+
import { isHydratingActive, withHydrationRange } from './hydration'
|
|
10
11
|
import {
|
|
11
12
|
createRootContext,
|
|
12
13
|
destroyRoot,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from './lifecycle'
|
|
19
20
|
import { insertNodesBefore, removeNodes, toNodeArray } from './node-ops'
|
|
20
21
|
import reconcileArrays from './reconcile'
|
|
22
|
+
import { __fictIsHydrating, __fictIsSSR } from './resume'
|
|
21
23
|
import { batch } from './scheduler'
|
|
22
24
|
import { createSignal, effectScope, flush, setActiveSub, type Signal } from './signal'
|
|
23
25
|
import type { FictNode } from './types'
|
|
@@ -85,7 +87,7 @@ interface KeyedListContainer<T = unknown> {
|
|
|
85
87
|
*/
|
|
86
88
|
export interface KeyedListBinding {
|
|
87
89
|
/** Document fragment placeholder inserted by the compiler/runtime */
|
|
88
|
-
marker: DocumentFragment
|
|
90
|
+
marker: Comment | DocumentFragment
|
|
89
91
|
/** Start marker comment node */
|
|
90
92
|
startMarker: Comment
|
|
91
93
|
/** End marker comment node */
|
|
@@ -195,9 +197,12 @@ export function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {
|
|
|
195
197
|
*
|
|
196
198
|
* @returns Container object with markers, blocks map, and dispose function
|
|
197
199
|
*/
|
|
198
|
-
function createKeyedListContainer<T = unknown>(
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
function createKeyedListContainer<T = unknown>(
|
|
201
|
+
startOverride?: Comment,
|
|
202
|
+
endOverride?: Comment,
|
|
203
|
+
): KeyedListContainer<T> {
|
|
204
|
+
const startMarker = startOverride ?? document.createComment('fict:list:start')
|
|
205
|
+
const endMarker = endOverride ?? document.createComment('fict:list:end')
|
|
201
206
|
|
|
202
207
|
const dispose = () => {
|
|
203
208
|
// Clean up all blocks
|
|
@@ -464,32 +469,57 @@ function reorderByLIS<T>(
|
|
|
464
469
|
* @param renderItem - Function that creates DOM nodes for each item
|
|
465
470
|
* @returns Binding handle with markers and dispose function
|
|
466
471
|
*/
|
|
467
|
-
export function createKeyedList<T>(
|
|
472
|
+
export function createKeyedList<T extends object>(
|
|
468
473
|
getItems: () => T[],
|
|
469
474
|
keyFn: (item: T, index: number) => string | number,
|
|
470
475
|
renderItem: FineGrainedRenderItem<T>,
|
|
471
476
|
needsIndex?: boolean,
|
|
477
|
+
startMarker?: Comment,
|
|
478
|
+
endMarker?: Comment,
|
|
472
479
|
): KeyedListBinding {
|
|
473
480
|
const resolvedNeedsIndex =
|
|
474
481
|
arguments.length >= 4 ? !!needsIndex : renderItem.length > 1 /* has index param */
|
|
475
|
-
return createFineGrainedKeyedList(
|
|
482
|
+
return createFineGrainedKeyedList(
|
|
483
|
+
getItems,
|
|
484
|
+
keyFn,
|
|
485
|
+
renderItem,
|
|
486
|
+
resolvedNeedsIndex,
|
|
487
|
+
startMarker,
|
|
488
|
+
endMarker,
|
|
489
|
+
)
|
|
476
490
|
}
|
|
477
491
|
|
|
478
|
-
function createFineGrainedKeyedList<T>(
|
|
492
|
+
function createFineGrainedKeyedList<T extends object>(
|
|
479
493
|
getItems: () => T[],
|
|
480
494
|
keyFn: (item: T, index: number) => string | number,
|
|
481
495
|
renderItem: FineGrainedRenderItem<T>,
|
|
482
496
|
needsIndex: boolean,
|
|
497
|
+
startOverride?: Comment,
|
|
498
|
+
endOverride?: Comment,
|
|
483
499
|
): KeyedListBinding {
|
|
484
|
-
const container = createKeyedListContainer<T>()
|
|
500
|
+
const container = createKeyedListContainer<T>(startOverride, endOverride)
|
|
485
501
|
const hostRoot = getCurrentRoot()
|
|
486
|
-
const
|
|
487
|
-
fragment
|
|
502
|
+
const useProvided = !!(startOverride && endOverride)
|
|
503
|
+
const fragment = useProvided ? container.startMarker : document.createDocumentFragment()
|
|
504
|
+
if (!useProvided) {
|
|
505
|
+
;(fragment as DocumentFragment).append(container.startMarker, container.endMarker)
|
|
506
|
+
}
|
|
488
507
|
let disposed = false
|
|
489
508
|
let effectDispose: (() => void) | undefined
|
|
490
509
|
let connectObserver: MutationObserver | null = null
|
|
491
510
|
let effectStarted = false
|
|
492
511
|
let startScheduled = false
|
|
512
|
+
let initialHydrating = __fictIsHydrating()
|
|
513
|
+
|
|
514
|
+
const collectBetween = (): Node[] => {
|
|
515
|
+
const nodes: Node[] = []
|
|
516
|
+
let cursor = container.startMarker.nextSibling
|
|
517
|
+
while (cursor && cursor !== container.endMarker) {
|
|
518
|
+
nodes.push(cursor)
|
|
519
|
+
cursor = cursor.nextSibling
|
|
520
|
+
}
|
|
521
|
+
return nodes
|
|
522
|
+
}
|
|
493
523
|
|
|
494
524
|
const getConnectedParent = (): (ParentNode & Node) | null => {
|
|
495
525
|
const endParent = container.endMarker.parentNode
|
|
@@ -509,7 +539,11 @@ function createFineGrainedKeyedList<T>(
|
|
|
509
539
|
|
|
510
540
|
const performDiff = () => {
|
|
511
541
|
if (disposed) return
|
|
512
|
-
|
|
542
|
+
// During SSR, render synchronously without waiting for DOM connection
|
|
543
|
+
const isSSR = __fictIsSSR()
|
|
544
|
+
const parent = isSSR
|
|
545
|
+
? (container.startMarker.parentNode as (ParentNode & Node) | null)
|
|
546
|
+
: getConnectedParent()
|
|
513
547
|
if (!parent) return
|
|
514
548
|
batch(() => {
|
|
515
549
|
const oldBlocks = container.blocks
|
|
@@ -519,6 +553,69 @@ function createFineGrainedKeyedList<T>(
|
|
|
519
553
|
const orderedIndexByKey = container.orderedIndexByKey
|
|
520
554
|
const newItems = getItems()
|
|
521
555
|
|
|
556
|
+
if (initialHydrating && isHydratingActive()) {
|
|
557
|
+
initialHydrating = false
|
|
558
|
+
newBlocks.clear()
|
|
559
|
+
nextOrderedBlocks.length = 0
|
|
560
|
+
orderedIndexByKey.clear()
|
|
561
|
+
|
|
562
|
+
if (newItems.length === 0) {
|
|
563
|
+
oldBlocks.clear()
|
|
564
|
+
prevOrderedBlocks.length = 0
|
|
565
|
+
container.currentNodes = [container.startMarker, container.endMarker]
|
|
566
|
+
container.nextNodes.length = 0
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const createdBlocks: KeyedBlock<T>[] = []
|
|
571
|
+
withHydrationRange(
|
|
572
|
+
container.startMarker.nextSibling,
|
|
573
|
+
container.endMarker,
|
|
574
|
+
parent.ownerDocument ?? document,
|
|
575
|
+
() => {
|
|
576
|
+
for (let index = 0; index < newItems.length; index++) {
|
|
577
|
+
const item = newItems[index]!
|
|
578
|
+
const key = keyFn(item, index)
|
|
579
|
+
if (newBlocks.has(key)) {
|
|
580
|
+
if (isDev) {
|
|
581
|
+
console.warn(
|
|
582
|
+
`[fict] Duplicate key "${String(key)}" detected in list hydration. ` +
|
|
583
|
+
`Each item should have a unique key.`,
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
const existing = newBlocks.get(key)
|
|
587
|
+
if (existing) {
|
|
588
|
+
destroyRoot(existing.root)
|
|
589
|
+
removeNodes(existing.nodes)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const block = createKeyedBlock(key, item, index, renderItem, needsIndex, hostRoot)
|
|
593
|
+
createdBlocks.push(block)
|
|
594
|
+
newBlocks.set(key, block)
|
|
595
|
+
orderedIndexByKey.set(key, nextOrderedBlocks.length)
|
|
596
|
+
nextOrderedBlocks.push(block)
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
container.blocks = newBlocks
|
|
602
|
+
container.nextBlocks = oldBlocks
|
|
603
|
+
container.orderedBlocks = nextOrderedBlocks
|
|
604
|
+
container.nextOrderedBlocks = prevOrderedBlocks
|
|
605
|
+
oldBlocks.clear()
|
|
606
|
+
prevOrderedBlocks.length = 0
|
|
607
|
+
container.currentNodes = [container.startMarker, ...collectBetween(), container.endMarker]
|
|
608
|
+
container.nextNodes.length = 0
|
|
609
|
+
|
|
610
|
+
for (const block of createdBlocks) {
|
|
611
|
+
if (newBlocks.get(block.key) === block) {
|
|
612
|
+
flushOnMount(block.root)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
|
|
522
619
|
if (newItems.length === 0) {
|
|
523
620
|
if (oldBlocks.size > 0) {
|
|
524
621
|
// Destroy all block roots first
|
|
@@ -803,7 +900,11 @@ function createFineGrainedKeyedList<T>(
|
|
|
803
900
|
|
|
804
901
|
const ensureEffectStarted = (): boolean => {
|
|
805
902
|
if (disposed || effectStarted) return effectStarted
|
|
806
|
-
|
|
903
|
+
// During SSR, render synchronously without waiting for DOM connection
|
|
904
|
+
const isSSR = __fictIsSSR()
|
|
905
|
+
const parent = isSSR
|
|
906
|
+
? (container.startMarker.parentNode as (ParentNode & Node) | null)
|
|
907
|
+
: getConnectedParent()
|
|
807
908
|
if (!parent) return false
|
|
808
909
|
const start = () => {
|
|
809
910
|
effectDispose = createRenderEffect(performDiff)
|