kiru 1.4.1 → 1.5.1
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/components/derive.d.ts +1 -1
- package/dist/components/derive.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/devtools.d.ts +1 -1
- package/dist/devtools.d.ts.map +1 -1
- package/dist/dom/nodes.d.ts +1 -1
- package/dist/dom/nodes.d.ts.map +1 -1
- package/dist/dom/nodes.js.map +1 -1
- package/dist/dom/props.js.map +1 -1
- package/dist/globalContext.d.ts +2 -2
- package/dist/globalContext.d.ts.map +1 -1
- package/dist/headlessRender.d.ts.map +1 -1
- package/dist/headlessRender.js +3 -0
- package/dist/headlessRender.js.map +1 -1
- package/dist/hooks/onCleanup.d.ts.map +1 -1
- package/dist/hooks/onCleanup.js +3 -1
- package/dist/hooks/onCleanup.js.map +1 -1
- package/dist/hooks/setup.d.ts.map +1 -1
- package/dist/hooks/setup.js +11 -28
- package/dist/hooks/setup.js.map +1 -1
- package/dist/hooks/utils.d.ts.map +1 -1
- package/dist/hooks/utils.js +2 -1
- package/dist/hooks/utils.js.map +1 -1
- package/dist/hydration.d.ts +1 -1
- package/dist/hydration.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/profiling.d.ts +1 -1
- package/dist/profiling.d.ts.map +1 -1
- package/dist/reconciler.d.ts.map +1 -1
- package/dist/reconciler.js +38 -6
- package/dist/reconciler.js.map +1 -1
- package/dist/resource.d.ts +11 -5
- package/dist/resource.d.ts.map +1 -1
- package/dist/resource.js +90 -40
- package/dist/resource.js.map +1 -1
- package/dist/router/client/index.d.ts +1 -1
- package/dist/router/client/index.d.ts.map +1 -1
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +1 -1
- package/dist/router/fileRouterController.js.map +1 -1
- package/dist/router/globals.d.ts +2 -2
- package/dist/router/globals.d.ts.map +1 -1
- package/dist/router/link.d.ts +1 -1
- package/dist/router/link.d.ts.map +1 -1
- package/dist/router/pageConfig.d.ts +1 -1
- package/dist/router/pageConfig.d.ts.map +1 -1
- package/dist/router/types.d.ts +3 -3
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.internal.d.ts +2 -2
- package/dist/router/types.internal.d.ts.map +1 -1
- package/dist/router/utils/index.d.ts +2 -2
- package/dist/router/utils/index.d.ts.map +1 -1
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +15 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/signals/base.d.ts +1 -0
- package/dist/signals/base.d.ts.map +1 -1
- package/dist/signals/base.js +14 -2
- package/dist/signals/base.js.map +1 -1
- package/dist/signals/effect.d.ts.map +1 -1
- package/dist/signals/effect.js +8 -0
- package/dist/signals/effect.js.map +1 -1
- package/dist/signals/tracking.d.ts +1 -1
- package/dist/signals/tracking.d.ts.map +1 -1
- package/dist/signals/tracking.js +14 -15
- package/dist/signals/tracking.js.map +1 -1
- package/dist/ssr/client.d.ts +1 -1
- package/dist/ssr/client.d.ts.map +1 -1
- package/dist/types.d.ts +31 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.dom.d.ts +190 -2
- package/dist/types.dom.d.ts.map +1 -1
- package/dist/types.utils.d.ts +28 -21
- package/dist/types.utils.d.ts.map +1 -1
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +5 -2
- package/dist/utils/vdom.js.map +1 -1
- package/package.json +1 -1
- package/src/components/derive.ts +1 -1
- package/src/constants.ts +2 -0
- package/src/devtools.ts +1 -1
- package/src/dom/commit.ts +1 -1
- package/src/dom/nodes.ts +8 -3
- package/src/dom/props.ts +6 -6
- package/src/globalContext.ts +2 -2
- package/src/headlessRender.ts +4 -1
- package/src/hooks/onCleanup.ts +3 -1
- package/src/hooks/setup.ts +17 -31
- package/src/hooks/utils.ts +2 -1
- package/src/hydration.ts +1 -1
- package/src/index.ts +1 -1
- package/src/profiling.ts +1 -1
- package/src/reconciler.ts +51 -9
- package/src/resource.ts +119 -45
- package/src/router/client/index.ts +2 -2
- package/src/router/fileRouterController.ts +5 -5
- package/src/router/globals.ts +2 -2
- package/src/router/link.ts +1 -1
- package/src/router/pageConfig.ts +1 -1
- package/src/router/types.internal.ts +2 -2
- package/src/router/types.ts +3 -3
- package/src/router/utils/index.ts +1 -1
- package/src/scheduler.ts +20 -3
- package/src/signals/base.ts +20 -2
- package/src/signals/effect.ts +8 -0
- package/src/signals/tracking.ts +18 -16
- package/src/ssr/client.ts +1 -1
- package/src/types.dom.ts +270 -53
- package/src/types.ts +36 -32
- package/src/types.utils.ts +56 -22
- package/src/utils/vdom.ts +7 -1
package/src/dom/props.ts
CHANGED
|
@@ -733,8 +733,8 @@ function setStyleProp(
|
|
|
733
733
|
// Avoid Set allocation for the common case where prevStyle is empty
|
|
734
734
|
if (prevKeys.length === 0) {
|
|
735
735
|
for (let i = 0; i < nextKeys.length; i++) {
|
|
736
|
-
const k = nextKeys[i]
|
|
737
|
-
const rawNext = nextStyle[k]
|
|
736
|
+
const k = nextKeys[i]
|
|
737
|
+
const rawNext = nextStyle[k as keyof StyleObject]
|
|
738
738
|
const nextVal = unwrap(rawNext)
|
|
739
739
|
if (trackSignals && Signal.isSignal(rawNext)) {
|
|
740
740
|
styleKeyToSignal.set(k, rawNext)
|
|
@@ -751,7 +751,7 @@ function setStyleProp(
|
|
|
751
751
|
// Full merge path: iterate prevKeys for removals, nextKeys for additions/changes
|
|
752
752
|
const nextStyleKeys = new Set(nextKeys)
|
|
753
753
|
for (let i = 0; i < prevKeys.length; i++) {
|
|
754
|
-
const k = prevKeys[i]
|
|
754
|
+
const k = prevKeys[i]
|
|
755
755
|
if (!nextStyleKeys.has(k)) {
|
|
756
756
|
// Property was removed
|
|
757
757
|
if ((k as string).startsWith("--")) {
|
|
@@ -763,9 +763,9 @@ function setStyleProp(
|
|
|
763
763
|
}
|
|
764
764
|
|
|
765
765
|
for (let i = 0; i < nextKeys.length; i++) {
|
|
766
|
-
const k = nextKeys[i]
|
|
767
|
-
const rawNext = nextStyle[k]
|
|
768
|
-
const prevVal = unwrap(prevStyle[k])
|
|
766
|
+
const k = nextKeys[i]
|
|
767
|
+
const rawNext = nextStyle[k as keyof StyleObject]
|
|
768
|
+
const prevVal = unwrap(prevStyle[k as keyof StyleObject])
|
|
769
769
|
const nextVal = unwrap(rawNext)
|
|
770
770
|
if (trackSignals && Signal.isSignal(rawNext)) {
|
|
771
771
|
styleKeyToSignal.set(k, rawNext)
|
package/src/globalContext.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { __DEV__ } from "./env.js"
|
|
|
2
2
|
import { createHmrContext } from "./hmr.js"
|
|
3
3
|
import { createProfilingContext } from "./profiling.js"
|
|
4
4
|
import { fileRouterInstance } from "./router/globals.js"
|
|
5
|
-
import type { FileRouterController } from "./router/fileRouterController"
|
|
6
|
-
import type { AppHandle } from "./appHandle"
|
|
5
|
+
import type { FileRouterController } from "./router/fileRouterController.js"
|
|
6
|
+
import type { AppHandle } from "./appHandle.js"
|
|
7
7
|
|
|
8
8
|
export { createKiruGlobalContext, type GlobalKiruEvent, type KiruGlobalContext }
|
|
9
9
|
|
package/src/headlessRender.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { Signal } from "./signals/base.js"
|
|
13
13
|
import { $ERROR_BOUNDARY, voidElements, $STREAM_DATA } from "./constants.js"
|
|
14
14
|
import { __DEV__ } from "./env.js"
|
|
15
|
-
import type { ErrorBoundaryNode } from "./types.utils"
|
|
15
|
+
import type { ErrorBoundaryNode } from "./types.utils.js"
|
|
16
16
|
|
|
17
17
|
export interface HeadlessRenderContext {
|
|
18
18
|
write(chunk: string): void
|
|
@@ -37,6 +37,9 @@ export function headlessRender(
|
|
|
37
37
|
if (el instanceof Array) {
|
|
38
38
|
return el.forEach((c, i) => headlessRender(ctx, c, parent, i))
|
|
39
39
|
}
|
|
40
|
+
if (typeof el === "function") {
|
|
41
|
+
return headlessRender(ctx, el(), parent, idx)
|
|
42
|
+
}
|
|
40
43
|
if (Signal.isSignal(el)) {
|
|
41
44
|
const value = el.peek()
|
|
42
45
|
if (!isPrimitiveChild(value)) {
|
package/src/hooks/onCleanup.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { $INLINE_FN } from "../constants.js"
|
|
2
|
+
import { __DEV__ } from "../env.js"
|
|
1
3
|
import { node } from "../globals.js"
|
|
2
4
|
import {
|
|
3
5
|
generateRandomID,
|
|
@@ -14,7 +16,7 @@ import {
|
|
|
14
16
|
export function onCleanup(fn: () => void): void {
|
|
15
17
|
if (!sideEffectsEnabled()) return
|
|
16
18
|
const vNode = node.current!
|
|
17
|
-
if (!vNode) {
|
|
19
|
+
if (!vNode || (__DEV__ && vNode.type === $INLINE_FN)) {
|
|
18
20
|
throw new Error("Cannot queue onCleanup effect outside of a component")
|
|
19
21
|
}
|
|
20
22
|
registerVNodeCleanup(vNode, generateRandomID(10), fn)
|
package/src/hooks/setup.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { signal, Signal } from "../signals/base.js"
|
|
2
2
|
import { createVNodeId, isVNodeDeleted } from "../utils/vdom.js"
|
|
3
|
+
import { $INLINE_FN } from "../constants.js"
|
|
3
4
|
import { __DEV__ } from "../env.js"
|
|
4
5
|
import { node, setups } from "../globals.js"
|
|
5
|
-
import {
|
|
6
|
-
tracking,
|
|
7
|
-
type TrackingStackObservations,
|
|
8
|
-
} from "../signals/tracking.js"
|
|
6
|
+
import { executeWithTracking } from "../signals/tracking.js"
|
|
9
7
|
import { registerVNodeCleanup } from "../utils/index.js"
|
|
10
8
|
|
|
11
9
|
let currentAccessedPaths: Set<string[]> | null = null
|
|
@@ -29,7 +27,7 @@ export interface Setup<Props extends {}> {
|
|
|
29
27
|
export function setup<Props extends {}>(): Setup<Props> {
|
|
30
28
|
const vNode = node.current!
|
|
31
29
|
if (__DEV__) {
|
|
32
|
-
if (!vNode) {
|
|
30
|
+
if (!vNode || vNode.type === $INLINE_FN) {
|
|
33
31
|
throw new Error("setup() must be called inside a Kiru component")
|
|
34
32
|
}
|
|
35
33
|
if (vNode.render) {
|
|
@@ -94,34 +92,19 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
94
92
|
|
|
95
93
|
function sync() {
|
|
96
94
|
accessedPaths.clear()
|
|
95
|
+
currentAccessedPaths = accessedPaths
|
|
96
|
+
|
|
97
97
|
const propsProxy = createProxy(
|
|
98
98
|
currentProps.current as Record<string, unknown>
|
|
99
99
|
) as InferredProps
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
|
|
101
|
+
resultSig.value = executeWithTracking({
|
|
102
|
+
id: Signal.id(resultSig),
|
|
103
|
+
fn: () => selector(propsProxy),
|
|
104
|
+
onDepChanged: sync,
|
|
105
|
+
subs: unsubs,
|
|
106
|
+
})
|
|
104
107
|
currentAccessedPaths = null
|
|
105
|
-
tracking.stack.pop()
|
|
106
|
-
// Always assign and notify so the component re-renders when the derived value changes
|
|
107
|
-
// (e.g. when parent passes a different signal ref like toggle switching count/double).
|
|
108
|
-
resultSig.value = value
|
|
109
|
-
|
|
110
|
-
for (const [sid, unsub] of unsubs) {
|
|
111
|
-
if (!observations.has(sid)) {
|
|
112
|
-
unsub()
|
|
113
|
-
unsubs.delete(sid)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
for (const [sid, observedSig] of observations) {
|
|
117
|
-
if (!unsubs.has(sid)) {
|
|
118
|
-
try {
|
|
119
|
-
unsubs.set(sid, observedSig.subscribe(sync))
|
|
120
|
-
} catch {
|
|
121
|
-
// Signal may be disposed after HMR; skip subscribing
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
108
|
}
|
|
126
109
|
|
|
127
110
|
sync()
|
|
@@ -143,8 +126,11 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
143
126
|
return id
|
|
144
127
|
}
|
|
145
128
|
if (node.current !== vNode) {
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
registerVNodeCleanup(
|
|
130
|
+
vNode,
|
|
131
|
+
Signal.id(id),
|
|
132
|
+
Signal.dispose.bind(null, id)
|
|
133
|
+
)
|
|
148
134
|
}
|
|
149
135
|
prevIndex = vNode.index
|
|
150
136
|
propSyncs.push(() => {
|
package/src/hooks/utils.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { $INLINE_FN } from "../constants.js"
|
|
1
2
|
import { node } from "../globals.js"
|
|
2
3
|
|
|
3
4
|
export function getVNodeLifecycleHooks(): null | NonNullable<
|
|
4
5
|
Kiru.VNode["hooks"]
|
|
5
6
|
> {
|
|
6
7
|
const vNode = node.current!
|
|
7
|
-
if (!vNode) return null
|
|
8
|
+
if (!vNode || vNode.type === $INLINE_FN) return null
|
|
8
9
|
|
|
9
10
|
return (vNode.hooks ??= {
|
|
10
11
|
pre: [],
|
package/src/hydration.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createKiruGlobalContext } from "./globalContext.js"
|
|
2
2
|
import { isBrowser } from "./env.js"
|
|
3
3
|
|
|
4
|
-
export type * from "./types"
|
|
4
|
+
export type * from "./types.js"
|
|
5
5
|
export * from "./signals/index.js"
|
|
6
6
|
export * from "./action.js"
|
|
7
7
|
export * from "./appHandle.js"
|
package/src/profiling.ts
CHANGED
package/src/reconciler.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
$FRAGMENT,
|
|
3
|
+
$INLINE_FN,
|
|
4
|
+
FLAG_PLACEMENT,
|
|
5
|
+
FLAG_UPDATE,
|
|
6
|
+
} from "./constants.js"
|
|
2
7
|
import {
|
|
3
8
|
getVNodeApp,
|
|
4
9
|
isElement,
|
|
@@ -189,6 +194,10 @@ function updateSlot(
|
|
|
189
194
|
}
|
|
190
195
|
return updateFragment(parent, oldChild, child)
|
|
191
196
|
}
|
|
197
|
+
if (typeof child === "function") {
|
|
198
|
+
if (key !== null) return null
|
|
199
|
+
return updateInlineFnChild(parent, oldChild, child)
|
|
200
|
+
}
|
|
192
201
|
return null
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -263,6 +272,23 @@ function updateFragment(
|
|
|
263
272
|
return oldChild
|
|
264
273
|
}
|
|
265
274
|
|
|
275
|
+
function updateInlineFnChild(
|
|
276
|
+
parent: VNode,
|
|
277
|
+
oldChild: VNode | null,
|
|
278
|
+
expr: Function
|
|
279
|
+
) {
|
|
280
|
+
if (oldChild === null || oldChild.type !== $INLINE_FN) {
|
|
281
|
+
return createVNode(parent, $INLINE_FN, { expr })
|
|
282
|
+
}
|
|
283
|
+
if (__DEV__) {
|
|
284
|
+
dev_emitUpdateNode()
|
|
285
|
+
}
|
|
286
|
+
oldChild.props = { expr }
|
|
287
|
+
oldChild.flags |= FLAG_UPDATE
|
|
288
|
+
oldChild.sibling = null
|
|
289
|
+
return oldChild
|
|
290
|
+
}
|
|
291
|
+
|
|
266
292
|
function createChild(parent: VNode, child: unknown): VNode | null {
|
|
267
293
|
if (isValidTextChild(child)) {
|
|
268
294
|
return createVNode(parent, "#text", { nodeValue: "" + child })
|
|
@@ -283,6 +309,10 @@ function createChild(parent: VNode, child: unknown): VNode | null {
|
|
|
283
309
|
return createVNode(parent, $FRAGMENT, { children: child })
|
|
284
310
|
}
|
|
285
311
|
|
|
312
|
+
if (typeof child === "function") {
|
|
313
|
+
return createVNode(parent, $INLINE_FN, { expr: child })
|
|
314
|
+
}
|
|
315
|
+
|
|
286
316
|
return null
|
|
287
317
|
}
|
|
288
318
|
|
|
@@ -316,14 +346,11 @@ function updateFromMap(
|
|
|
316
346
|
const isSig = Signal.isSignal(child)
|
|
317
347
|
if (isSig || isValidTextChild(child)) {
|
|
318
348
|
const oldChild = existingChildren.get(index)
|
|
319
|
-
if (oldChild) {
|
|
349
|
+
if (oldChild?.type === "#text") {
|
|
320
350
|
if (oldChild.props.nodeValue === child) {
|
|
321
351
|
return oldChild
|
|
322
352
|
}
|
|
323
|
-
if (
|
|
324
|
-
oldChild.type === "#text" &&
|
|
325
|
-
Signal.isSignal(oldChild.props.nodeValue)
|
|
326
|
-
) {
|
|
353
|
+
if (Signal.isSignal(oldChild.props.nodeValue)) {
|
|
327
354
|
oldChild.cleanups?.["nodeValue"]?.()
|
|
328
355
|
}
|
|
329
356
|
}
|
|
@@ -356,11 +383,11 @@ function updateFromMap(
|
|
|
356
383
|
|
|
357
384
|
if (Array.isArray(child)) {
|
|
358
385
|
const props = { children: child }
|
|
359
|
-
const oldChild = existingChildren.get(index)
|
|
360
386
|
if (__DEV__) {
|
|
361
387
|
markListChild(child)
|
|
362
388
|
}
|
|
363
|
-
|
|
389
|
+
const oldChild = existingChildren.get(index)
|
|
390
|
+
if (oldChild?.type === $FRAGMENT) {
|
|
364
391
|
if (__DEV__) {
|
|
365
392
|
dev_emitUpdateNode()
|
|
366
393
|
}
|
|
@@ -372,6 +399,21 @@ function updateFromMap(
|
|
|
372
399
|
return createVNode(parent, $FRAGMENT, props, null, index)
|
|
373
400
|
}
|
|
374
401
|
|
|
402
|
+
if (typeof child === "function") {
|
|
403
|
+
const props = { expr: child }
|
|
404
|
+
const oldChild = existingChildren.get(index)
|
|
405
|
+
if (oldChild?.type === $INLINE_FN) {
|
|
406
|
+
if (__DEV__) {
|
|
407
|
+
dev_emitUpdateNode()
|
|
408
|
+
}
|
|
409
|
+
oldChild.flags |= FLAG_UPDATE
|
|
410
|
+
oldChild.props = props
|
|
411
|
+
return oldChild
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return createVNode(parent, $INLINE_FN, props, null, index)
|
|
415
|
+
}
|
|
416
|
+
|
|
375
417
|
return null
|
|
376
418
|
}
|
|
377
419
|
|
|
@@ -472,7 +514,7 @@ function getNearestParentFcTag(vNode: VNode) {
|
|
|
472
514
|
function createVNode(
|
|
473
515
|
parent: VNode,
|
|
474
516
|
type: VNode["type"],
|
|
475
|
-
props
|
|
517
|
+
props?: VNode["props"],
|
|
476
518
|
key: VNode["key"] = null,
|
|
477
519
|
index = 0
|
|
478
520
|
): VNode {
|
package/src/resource.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { $HMR_ACCEPT, STREAMED_DATA_EVENT } from "./constants.js"
|
|
2
2
|
import { hydrationMode, node, renderMode } from "./globals.js"
|
|
3
3
|
import { Signal, signal } from "./signals/base.js"
|
|
4
|
+
import { executeWithTracking } from "./signals/tracking.js"
|
|
4
5
|
import { createVNodeId, registerVNodeCleanup } from "./utils/vdom.js"
|
|
5
6
|
import { generateRandomID } from "./utils/generateId.js"
|
|
6
7
|
import { __DEV__, isBrowser } from "./env.js"
|
|
7
8
|
import { GenericHMRAcceptor, performHmrAccept } from "./hmr.js"
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
* Returns true if the value is a {@link Resource}
|
|
11
|
-
*/
|
|
12
|
-
export function isResource(thing: unknown): thing is Resource<unknown> {
|
|
13
|
-
return (
|
|
14
|
-
Signal.isSignal(thing) &&
|
|
15
|
-
"promise" in thing &&
|
|
16
|
-
thing["promise"] instanceof Promise
|
|
17
|
-
)
|
|
18
|
-
}
|
|
10
|
+
export type ResourceSource = Record<string, Signal<unknown>> | Signal<unknown>
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
type InnerOf<T> = T extends Kiru.Signal<infer V> ? V : never
|
|
13
|
+
|
|
14
|
+
type UnwrapResourceSource<T extends ResourceSource> =
|
|
15
|
+
T extends Kiru.Signal<unknown>
|
|
16
|
+
? InnerOf<T>
|
|
17
|
+
: { [K in keyof T]: InnerOf<T[K]> }
|
|
21
18
|
|
|
22
19
|
interface ResourceState<T> {
|
|
23
20
|
error: Signal<Error | null>
|
|
@@ -32,14 +29,31 @@ export interface ResourceLoaderContext {
|
|
|
32
29
|
signal: AbortSignal
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
const resourceMeta = new WeakMap<Kiru.VNode, { id: string; index: number }>()
|
|
33
|
+
|
|
34
|
+
export function resource<T>(
|
|
35
|
+
callback: (ctx: ResourceLoaderContext) => Promise<T>
|
|
36
|
+
): Resource<T>
|
|
37
|
+
export function resource<T, Source extends ResourceSource>(
|
|
38
|
+
source: Source,
|
|
39
|
+
callback: (
|
|
40
|
+
source: UnwrapResourceSource<Source>,
|
|
41
|
+
ctx: ResourceLoaderContext
|
|
42
|
+
) => Promise<T>
|
|
43
|
+
): Resource<T>
|
|
44
|
+
export function resource<T, Source extends ResourceSource>(
|
|
45
|
+
callbackOrSource: Source | ((ctx: ResourceLoaderContext) => Promise<T>),
|
|
46
|
+
callback?: (
|
|
47
|
+
source: UnwrapResourceSource<Source>,
|
|
48
|
+
ctx: ResourceLoaderContext
|
|
49
|
+
) => Promise<T>
|
|
38
50
|
): Resource<T> {
|
|
39
51
|
const data = signal(void 0 as T)
|
|
40
52
|
const error = signal<Error | null>(null)
|
|
41
53
|
const isPending = signal(true)
|
|
42
54
|
|
|
55
|
+
let controller = new AbortController()
|
|
56
|
+
|
|
43
57
|
let promiseId = ""
|
|
44
58
|
const vNode = node.current
|
|
45
59
|
if (!vNode) {
|
|
@@ -62,30 +76,53 @@ export function resource<T, Source>(
|
|
|
62
76
|
promiseId = generateRandomID()
|
|
63
77
|
}
|
|
64
78
|
|
|
65
|
-
const
|
|
66
|
-
resource.promise = createPromise(
|
|
79
|
+
const updateResource = () => {
|
|
80
|
+
resource.promise = createPromise()
|
|
67
81
|
resource.notify()
|
|
68
|
-
}
|
|
82
|
+
}
|
|
69
83
|
|
|
70
|
-
let
|
|
84
|
+
let unsubFromSource: (() => void) | undefined
|
|
85
|
+
if (typeof callbackOrSource === "object") {
|
|
86
|
+
if (Signal.isSignal(callbackOrSource)) {
|
|
87
|
+
unsubFromSource = callbackOrSource.subscribe(updateResource)
|
|
88
|
+
} else {
|
|
89
|
+
const unsubs: (() => void)[] = []
|
|
90
|
+
for (const key in callbackOrSource) {
|
|
91
|
+
if (!Signal.isSignal(callbackOrSource[key])) continue
|
|
92
|
+
unsubs.push(callbackOrSource[key].subscribe(updateResource))
|
|
93
|
+
}
|
|
94
|
+
unsubFromSource = () => {
|
|
95
|
+
unsubs.forEach((unsub) => unsub())
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const observedSignalUnsubs = new Map<string, () => void>()
|
|
71
101
|
const dispose = () => {
|
|
72
102
|
if (!controller.signal.aborted) controller.abort()
|
|
73
103
|
Signal.dispose(data)
|
|
74
104
|
Signal.dispose(isPending)
|
|
75
|
-
unsub()
|
|
105
|
+
observedSignalUnsubs.forEach((unsub) => unsub())
|
|
106
|
+
unsubFromSource?.()
|
|
76
107
|
}
|
|
77
108
|
|
|
78
109
|
if (vNode) {
|
|
79
110
|
registerVNodeCleanup(vNode, promiseId, dispose)
|
|
80
111
|
}
|
|
81
112
|
|
|
113
|
+
let promise: Kiru.StatefulPromise<T>
|
|
82
114
|
const resource: Resource<T> = Object.assign(data, {
|
|
83
115
|
error,
|
|
84
116
|
isPending,
|
|
85
|
-
promise
|
|
117
|
+
get promise() {
|
|
118
|
+
return (promise ??= createPromise())
|
|
119
|
+
},
|
|
120
|
+
set promise(newPromise) {
|
|
121
|
+
promise = newPromise
|
|
122
|
+
},
|
|
86
123
|
refetch() {
|
|
87
124
|
data.value = void 0 as T
|
|
88
|
-
|
|
125
|
+
resource.promise = createPromise()
|
|
89
126
|
},
|
|
90
127
|
dispose,
|
|
91
128
|
})
|
|
@@ -111,33 +148,38 @@ export function resource<T, Source>(
|
|
|
111
148
|
}
|
|
112
149
|
}
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
queueMicrotask(() => {
|
|
116
|
-
resource.promise = createPromise(source.peek())
|
|
117
|
-
})
|
|
118
|
-
} else {
|
|
119
|
-
resource.promise = createPromise(source.peek())
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function createPromise(source: Source): Kiru.StatefulPromise<T> {
|
|
151
|
+
function createPromise(): Kiru.StatefulPromise<T> {
|
|
123
152
|
controller.abort()
|
|
124
153
|
const ctrl = (controller = new AbortController())
|
|
125
154
|
isPending.value = true
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
const newPromise = executeWithTracking({
|
|
156
|
+
fn: () => {
|
|
157
|
+
let promise: Promise<T>
|
|
158
|
+
if (renderMode.current === "string") {
|
|
159
|
+
// if we're rendering to a string, there's no need to fire the callback
|
|
160
|
+
promise = Promise.resolve() as Promise<T>
|
|
161
|
+
} else if (
|
|
162
|
+
renderMode.current === "hydrate" &&
|
|
163
|
+
hydrationMode.current === "dynamic"
|
|
164
|
+
) {
|
|
165
|
+
// if we're hydrating and the hydration mode is not static,
|
|
166
|
+
// we need to resolve the promise from cache/event
|
|
167
|
+
promise = resolveDeferredPromise<T>(promiseId, ctrl.signal)
|
|
168
|
+
} else {
|
|
169
|
+
// stream / dom / (hydrate + static)
|
|
170
|
+
if (typeof callbackOrSource === "function") {
|
|
171
|
+
promise = callbackOrSource({ signal: ctrl.signal })
|
|
172
|
+
} else {
|
|
173
|
+
const source = unwrapResourceSource(callbackOrSource)
|
|
174
|
+
promise = callback!(source, { signal: ctrl.signal })
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return promise
|
|
178
|
+
},
|
|
179
|
+
id: Signal.id(data),
|
|
180
|
+
onDepChanged: updateResource,
|
|
181
|
+
subs: observedSignalUnsubs,
|
|
182
|
+
})
|
|
141
183
|
|
|
142
184
|
const statefulPromise: Kiru.StatefulPromise<T> = Object.assign(newPromise, {
|
|
143
185
|
id: promiseId,
|
|
@@ -162,6 +204,12 @@ export function resource<T, Source>(
|
|
|
162
204
|
return statefulPromise
|
|
163
205
|
}
|
|
164
206
|
|
|
207
|
+
if (__DEV__ && isBrowser && window.__kiru.HMRContext?.isReplacement()) {
|
|
208
|
+
queueMicrotask(() => (resource.promise = createPromise()))
|
|
209
|
+
} else {
|
|
210
|
+
resource.promise ??= createPromise()
|
|
211
|
+
}
|
|
212
|
+
|
|
165
213
|
return resource
|
|
166
214
|
}
|
|
167
215
|
|
|
@@ -205,3 +253,29 @@ function resolveDeferredPromise<T>(
|
|
|
205
253
|
})
|
|
206
254
|
})
|
|
207
255
|
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Returns true if the value is a {@link Resource}
|
|
259
|
+
*/
|
|
260
|
+
export function isResource(thing: unknown): thing is Resource<unknown> {
|
|
261
|
+
return (
|
|
262
|
+
Signal.isSignal(thing) &&
|
|
263
|
+
"promise" in thing &&
|
|
264
|
+
thing["promise"] instanceof Promise
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function unwrapResourceSource<T extends ResourceSource>(
|
|
269
|
+
source: T
|
|
270
|
+
): UnwrapResourceSource<T> {
|
|
271
|
+
if (Signal.isSignal(source)) {
|
|
272
|
+
return source.peek() as UnwrapResourceSource<T>
|
|
273
|
+
}
|
|
274
|
+
const out: Record<string, unknown> = {}
|
|
275
|
+
for (const key in source) {
|
|
276
|
+
if (Signal.isSignal(source[key])) {
|
|
277
|
+
out[key] = source[key].peek()
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return out as UnwrapResourceSource<T>
|
|
281
|
+
}
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
match404Route,
|
|
10
10
|
parseQuery,
|
|
11
11
|
} from "../utils/index.js"
|
|
12
|
-
import type { FormattedViteImportMap, PageModule } from "../types.internal"
|
|
13
|
-
import type { FileRouterConfig, FileRouterPreloadConfig } from "../types"
|
|
12
|
+
import type { FormattedViteImportMap, PageModule } from "../types.internal.js"
|
|
13
|
+
import type { FileRouterConfig, FileRouterPreloadConfig } from "../types.js"
|
|
14
14
|
import { fileRouterInstance, fileRouterRoute, routerCache } from "../globals.js"
|
|
15
15
|
import { FileRouterController } from "../fileRouterController.js"
|
|
16
16
|
import { FileRouterDataLoadError } from "../errors.js"
|
|
@@ -2,7 +2,7 @@ import { signal, Signal } from "../signals/base.js"
|
|
|
2
2
|
import { effect } from "../signals/effect.js"
|
|
3
3
|
import { __DEV__ } from "../env.js"
|
|
4
4
|
import { nextIdle } from "../scheduler.js"
|
|
5
|
-
import {
|
|
5
|
+
import { type FileRouterContextType } from "./context.js"
|
|
6
6
|
import { FileRouterDataLoadError } from "./errors.js"
|
|
7
7
|
import { fileRouterInstance, fileRouterRoute, routerCache } from "./globals.js"
|
|
8
8
|
import type {
|
|
@@ -521,7 +521,7 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
521
521
|
data: null,
|
|
522
522
|
error: new FileRouterDataLoadError(error),
|
|
523
523
|
loading: false,
|
|
524
|
-
} satisfies PageProps<PageConfig<unknown>>
|
|
524
|
+
}) satisfies PageProps<PageConfig<unknown>>
|
|
525
525
|
)
|
|
526
526
|
.then((state) => {
|
|
527
527
|
if (context.signal.aborted) return
|
|
@@ -661,13 +661,13 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
661
661
|
)
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
|
-
private createContextValue() {
|
|
664
|
+
private createContextValue(): FileRouterContextType {
|
|
665
665
|
const __this = this
|
|
666
666
|
return {
|
|
667
667
|
get baseUrl() {
|
|
668
668
|
return __this.baseUrl
|
|
669
669
|
},
|
|
670
|
-
invalidate: async (...paths
|
|
670
|
+
invalidate: async (...paths) => {
|
|
671
671
|
if (this.invalidate(...paths)) {
|
|
672
672
|
return this.loadRoute(void 0, void 0, true)
|
|
673
673
|
}
|
|
@@ -677,7 +677,7 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
677
677
|
},
|
|
678
678
|
navigate: this.navigate.bind(this),
|
|
679
679
|
prefetchRouteModules: this.prefetchRouteModules.bind(this),
|
|
680
|
-
reload:
|
|
680
|
+
reload: (options) => {
|
|
681
681
|
if (options?.invalidate ?? true) {
|
|
682
682
|
this.invalidate(this.state.pathname.peek())
|
|
683
683
|
}
|
package/src/router/globals.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RouterCache } from "./cache"
|
|
2
|
-
import type { FileRouterController } from "./fileRouterController"
|
|
1
|
+
import type { RouterCache } from "./cache.js"
|
|
2
|
+
import type { FileRouterController } from "./fileRouterController.js"
|
|
3
3
|
|
|
4
4
|
export const fileRouterInstance = {
|
|
5
5
|
current: null as FileRouterController | null,
|
package/src/router/link.ts
CHANGED
package/src/router/pageConfig.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __DEV__, isBrowser } from "../env.js"
|
|
2
2
|
import { fileRouterInstance } from "./globals.js"
|
|
3
|
-
import type { PageConfig } from "./types"
|
|
3
|
+
import type { PageConfig } from "./types.js"
|
|
4
4
|
|
|
5
5
|
export function definePageConfig<T>(config: PageConfig<T>): PageConfig<T> {
|
|
6
6
|
if (__DEV__ && isBrowser) {
|
package/src/router/types.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { AsyncTaskState } from "../types.utils"
|
|
2
|
-
import type { FileRouterDataLoadError } from "./errors"
|
|
1
|
+
import type { AsyncTaskState } from "../types.utils.js"
|
|
2
|
+
import type { FileRouterDataLoadError } from "./errors.js"
|
|
3
3
|
import type {
|
|
4
4
|
DefaultComponentModule,
|
|
5
5
|
FormattedViteImportMap,
|
|
6
6
|
PageModule,
|
|
7
|
-
} from "./types.internal"
|
|
7
|
+
} from "./types.internal.js"
|
|
8
8
|
|
|
9
9
|
export interface FileRouterPreloadConfig {
|
|
10
10
|
pages: FormattedViteImportMap
|