kiru 0.51.6 → 0.51.8
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/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/globalContext.d.ts +4 -0
- package/dist/globalContext.d.ts.map +1 -1
- package/dist/globalContext.js +2 -0
- package/dist/globalContext.js.map +1 -1
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +6 -22
- package/dist/hmr.js.map +1 -1
- package/dist/router/context.d.ts +13 -0
- package/dist/router/context.d.ts.map +1 -1
- package/dist/router/context.js.map +1 -1
- package/dist/router/fileRouterController.d.ts +4 -1
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +113 -93
- package/dist/router/fileRouterController.js.map +1 -1
- package/dist/router/link.js +1 -1
- package/dist/router/link.js.map +1 -1
- package/dist/router/pageConfig.js +1 -1
- package/dist/router/pageConfig.js.map +1 -1
- package/dist/router/server/index.js +4 -4
- package/dist/router/server/index.js.map +1 -1
- package/dist/router/types.internal.d.ts +17 -2
- package/dist/router/types.internal.d.ts.map +1 -1
- package/dist/router/utils/index.d.ts.map +1 -1
- package/dist/router/utils/index.js +17 -4
- package/dist/router/utils/index.js.map +1 -1
- package/dist/signals/watch.d.ts +0 -3
- package/dist/signals/watch.d.ts.map +1 -1
- package/dist/signals/watch.js +0 -12
- package/dist/signals/watch.js.map +1 -1
- package/dist/types.dom.d.ts +3 -2
- package/dist/types.dom.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +2 -0
- package/src/globalContext.ts +6 -0
- package/src/hmr.ts +7 -23
- package/src/router/context.ts +17 -1
- package/src/router/fileRouterController.ts +144 -128
- package/src/router/link.ts +1 -1
- package/src/router/pageConfig.ts +1 -1
- package/src/router/server/index.ts +4 -4
- package/src/router/types.internal.ts +21 -2
- package/src/router/utils/index.ts +21 -4
- package/src/signals/watch.ts +0 -13
- package/src/types.dom.ts +3 -1
package/src/constants.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
$MEMO,
|
|
9
9
|
$ERROR_BOUNDARY,
|
|
10
10
|
$SUSPENSE_THROW,
|
|
11
|
+
$DEV_FILE_LINK,
|
|
11
12
|
CONSECUTIVE_DIRTY_LIMIT,
|
|
12
13
|
PREFETCHED_DATA_EVENT,
|
|
13
14
|
EVENT_PREFIX_REGEX,
|
|
@@ -31,6 +32,7 @@ const $HMR_ACCEPT = Symbol.for("kiru.hmrAccept")
|
|
|
31
32
|
const $MEMO = Symbol.for("kiru.memo")
|
|
32
33
|
const $ERROR_BOUNDARY = Symbol.for("kiru.errorBoundary")
|
|
33
34
|
const $SUSPENSE_THROW = Symbol.for("kiru.suspenseThrow")
|
|
35
|
+
const $DEV_FILE_LINK = Symbol.for("kiru.devFileLink")
|
|
34
36
|
|
|
35
37
|
const CONSECUTIVE_DIRTY_LIMIT = 50
|
|
36
38
|
const PREFETCHED_DATA_EVENT = "kiru:prefetched"
|
package/src/globalContext.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { __DEV__ } from "./env.js"
|
|
2
2
|
import { createHMRContext } from "./hmr.js"
|
|
3
3
|
import { createProfilingContext } from "./profiling.js"
|
|
4
|
+
import { fileRouterInstance } from "./router/globals.js"
|
|
5
|
+
import type { FileRouterController } from "./router/fileRouterController"
|
|
4
6
|
import type { AppContext } from "./appContext"
|
|
5
7
|
import type { Store } from "./store"
|
|
6
8
|
import type { SWRCache } from "./swr"
|
|
@@ -86,6 +88,9 @@ interface KiruGlobalContext {
|
|
|
86
88
|
HMRContext?: ReturnType<typeof createHMRContext>
|
|
87
89
|
profilingContext?: ReturnType<typeof createProfilingContext>
|
|
88
90
|
SWRGlobalCache?: SWRCache
|
|
91
|
+
fileRouterInstance?: {
|
|
92
|
+
current: FileRouterController | null
|
|
93
|
+
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function createKiruGlobalContext(): KiruGlobalContext {
|
|
@@ -136,6 +141,7 @@ function createKiruGlobalContext(): KiruGlobalContext {
|
|
|
136
141
|
globalContext.HMRContext = createHMRContext()
|
|
137
142
|
globalContext.profilingContext = createProfilingContext()
|
|
138
143
|
globalContext.stores = createReactiveMap()
|
|
144
|
+
globalContext.fileRouterInstance = fileRouterInstance
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
return globalContext
|
package/src/hmr.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $HMR_ACCEPT } from "./constants.js"
|
|
1
|
+
import { $HMR_ACCEPT, $DEV_FILE_LINK } from "./constants.js"
|
|
2
2
|
import { __DEV__ } from "./env.js"
|
|
3
3
|
import { traverseApply } from "./utils/index.js"
|
|
4
4
|
import { requestUpdate } from "./scheduler.js"
|
|
@@ -56,7 +56,6 @@ export function createHMRContext() {
|
|
|
56
56
|
let isModuleReplacementExecution = false
|
|
57
57
|
const isReplacement = () => isModuleReplacementExecution
|
|
58
58
|
let isWaitingForNextWatchCall = false
|
|
59
|
-
let tmpUnnamedWatchers: WatchEffect[] = []
|
|
60
59
|
|
|
61
60
|
const onHmrCallbacks: Array<() => void> = []
|
|
62
61
|
const onHmr = (callback: () => void) => {
|
|
@@ -74,7 +73,11 @@ export function createHMRContext() {
|
|
|
74
73
|
moduleMap.set(filePath, mod)
|
|
75
74
|
} else {
|
|
76
75
|
while (onHmrCallbacks.length) onHmrCallbacks.shift()!()
|
|
76
|
+
for (const prevWatcher of mod.unnamedWatchers.splice(0)) {
|
|
77
|
+
prevWatcher.stop()
|
|
78
|
+
}
|
|
77
79
|
}
|
|
80
|
+
|
|
78
81
|
currentModuleMemory = mod!
|
|
79
82
|
currentModuleFilePath = filePath
|
|
80
83
|
}
|
|
@@ -90,7 +93,7 @@ export function createHMRContext() {
|
|
|
90
93
|
const oldEntry = currentModuleMemory.hotVars.get(name)
|
|
91
94
|
|
|
92
95
|
// @ts-ignore - this is how we tell devtools what file the hotvar is from
|
|
93
|
-
newEntry.value
|
|
96
|
+
newEntry.value[$DEV_FILE_LINK] = newEntry.link
|
|
94
97
|
|
|
95
98
|
if (typeof newEntry.value === "function") {
|
|
96
99
|
if (oldEntry?.value) {
|
|
@@ -134,25 +137,6 @@ export function createHMRContext() {
|
|
|
134
137
|
dirtiedApps.forEach((ctx) => ctx.rootNode && requestUpdate(ctx.rootNode))
|
|
135
138
|
isModuleReplacementExecution = false
|
|
136
139
|
|
|
137
|
-
if (tmpUnnamedWatchers.length) {
|
|
138
|
-
let i = 0
|
|
139
|
-
for (; i < tmpUnnamedWatchers.length; i++) {
|
|
140
|
-
const newWatcher = tmpUnnamedWatchers[i]
|
|
141
|
-
const oldWatcher = currentModuleMemory.unnamedWatchers[i]
|
|
142
|
-
if (oldWatcher) {
|
|
143
|
-
newWatcher[$HMR_ACCEPT]!.inject(oldWatcher[$HMR_ACCEPT]!.provide())
|
|
144
|
-
oldWatcher[$HMR_ACCEPT]!.destroy()
|
|
145
|
-
}
|
|
146
|
-
currentModuleMemory.unnamedWatchers[i] = newWatcher
|
|
147
|
-
}
|
|
148
|
-
for (; i < currentModuleMemory.unnamedWatchers.length; i++) {
|
|
149
|
-
const oldWatcher = currentModuleMemory.unnamedWatchers[i]
|
|
150
|
-
oldWatcher[$HMR_ACCEPT]!.destroy()
|
|
151
|
-
}
|
|
152
|
-
currentModuleMemory.unnamedWatchers.length = tmpUnnamedWatchers.length
|
|
153
|
-
tmpUnnamedWatchers.length = 0
|
|
154
|
-
}
|
|
155
|
-
|
|
156
140
|
currentModuleMemory = null
|
|
157
141
|
currentModuleFilePath = null
|
|
158
142
|
}
|
|
@@ -165,7 +149,7 @@ export function createHMRContext() {
|
|
|
165
149
|
return isWaitingForNextWatchCall
|
|
166
150
|
},
|
|
167
151
|
pushWatch(watch: WatchEffect) {
|
|
168
|
-
|
|
152
|
+
currentModuleMemory!.unnamedWatchers.push(watch)
|
|
169
153
|
isWaitingForNextWatchCall = false
|
|
170
154
|
},
|
|
171
155
|
}
|
package/src/router/context.ts
CHANGED
|
@@ -3,6 +3,19 @@ import { __DEV__ } from "../env.js"
|
|
|
3
3
|
import { useContext } from "../hooks/index.js"
|
|
4
4
|
import type { RouteQuery, RouterState } from "./types.js"
|
|
5
5
|
|
|
6
|
+
export interface ReloadOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Trigger a view transition (overrides transition from config)
|
|
9
|
+
* @default false
|
|
10
|
+
*/
|
|
11
|
+
transition?: boolean
|
|
12
|
+
/**
|
|
13
|
+
* Invalidate the cache for the current route
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
invalidate?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
export interface FileRouterContextType {
|
|
7
20
|
/**
|
|
8
21
|
* Invalidate cached loader data for the given paths
|
|
@@ -41,7 +54,10 @@ export interface FileRouterContextType {
|
|
|
41
54
|
/**
|
|
42
55
|
* Reload the current route, optionally triggering a view transition
|
|
43
56
|
*/
|
|
44
|
-
reload: (options?: {
|
|
57
|
+
reload: (options?: {
|
|
58
|
+
transition?: boolean
|
|
59
|
+
invalidate?: boolean
|
|
60
|
+
}) => Promise<void>
|
|
45
61
|
|
|
46
62
|
/**
|
|
47
63
|
* Set the current query parameters
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Signal } from "../signals/base.js"
|
|
2
2
|
import { flushSync, nextIdle } from "../scheduler.js"
|
|
3
3
|
import { __DEV__ } from "../env.js"
|
|
4
|
-
import { type FileRouterContextType } from "./context.js"
|
|
4
|
+
import { ReloadOptions, type FileRouterContextType } from "./context.js"
|
|
5
5
|
import { FileRouterDataLoadError } from "./errors.js"
|
|
6
6
|
import { fileRouterInstance, fileRouterRoute, routerCache } from "./globals.js"
|
|
7
7
|
import type {
|
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
RouterState,
|
|
14
14
|
} from "./types.js"
|
|
15
15
|
import type {
|
|
16
|
+
CurrentPage,
|
|
17
|
+
DevtoolsInterface,
|
|
16
18
|
FormattedViteImportMap,
|
|
17
19
|
PageModule,
|
|
18
20
|
ViteImportMap,
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
wrapWithLayouts,
|
|
28
30
|
} from "./utils/index.js"
|
|
29
31
|
import { RouterCache, type CacheKey } from "./cache.js"
|
|
32
|
+
import { watch } from "../signals/watch.js"
|
|
30
33
|
|
|
31
34
|
interface PageConfigWithLoader<T = unknown> extends PageConfig {
|
|
32
35
|
loader: PageDataLoaderConfig<T>
|
|
@@ -38,11 +41,7 @@ export class FileRouterController {
|
|
|
38
41
|
private pages: FormattedViteImportMap<PageModule>
|
|
39
42
|
private layouts: FormattedViteImportMap
|
|
40
43
|
private abortController: AbortController
|
|
41
|
-
private currentPage: Signal<
|
|
42
|
-
component: Kiru.FC<any>
|
|
43
|
-
config?: PageConfig
|
|
44
|
-
route: string
|
|
45
|
-
} | null>
|
|
44
|
+
private currentPage: Signal<CurrentPage | null>
|
|
46
45
|
private currentPageProps: Signal<Record<string, unknown>>
|
|
47
46
|
private currentLayouts: Signal<Kiru.FC[]>
|
|
48
47
|
private state: RouterState
|
|
@@ -52,6 +51,11 @@ export class FileRouterController {
|
|
|
52
51
|
{ route: string; config: PageConfig }
|
|
53
52
|
>
|
|
54
53
|
private pageRouteToConfig?: Map<string, PageConfig>
|
|
54
|
+
public dev_onPageConfigDefined?: <T extends PageConfig<any>>(
|
|
55
|
+
fp: string,
|
|
56
|
+
config: T
|
|
57
|
+
) => void
|
|
58
|
+
public devtools?: DevtoolsInterface
|
|
55
59
|
|
|
56
60
|
constructor() {
|
|
57
61
|
routerCache.current ??= new RouterCache()
|
|
@@ -71,7 +75,9 @@ export class FileRouterController {
|
|
|
71
75
|
}
|
|
72
76
|
const __this = this
|
|
73
77
|
this.contextValue = {
|
|
74
|
-
invalidate:
|
|
78
|
+
invalidate: (...paths: string[]) => {
|
|
79
|
+
this.invalidate(...paths)
|
|
80
|
+
},
|
|
75
81
|
get state() {
|
|
76
82
|
return {
|
|
77
83
|
...__this.state,
|
|
@@ -87,38 +93,113 @@ export class FileRouterController {
|
|
|
87
93
|
},
|
|
88
94
|
navigate: this.navigate.bind(this),
|
|
89
95
|
prefetchRouteModules: this.prefetchRouteModules.bind(this),
|
|
90
|
-
reload: (options?:
|
|
91
|
-
|
|
96
|
+
reload: (options?: ReloadOptions) => {
|
|
97
|
+
if (
|
|
98
|
+
(options?.invalidate ?? true) &&
|
|
99
|
+
this.invalidate(this.state.pathname)
|
|
100
|
+
) {
|
|
101
|
+
return Promise.resolve() // invalidate triggered a reload
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this.loadRoute(void 0, void 0, options?.transition)
|
|
105
|
+
},
|
|
92
106
|
setQuery: this.setQuery.bind(this),
|
|
93
107
|
}
|
|
94
108
|
if (__DEV__) {
|
|
95
109
|
this.filePathToPageRoute = new Map()
|
|
96
110
|
this.pageRouteToConfig = new Map()
|
|
111
|
+
this.dev_onPageConfigDefined = (fp, config) => {
|
|
112
|
+
const existing = this.filePathToPageRoute?.get(fp)
|
|
113
|
+
if (existing === undefined) {
|
|
114
|
+
const route = fileRouterRoute.current
|
|
115
|
+
if (!route) return
|
|
116
|
+
this.filePathToPageRoute?.set(fp, { route, config })
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
const curPage = this.currentPage.value
|
|
120
|
+
const loader = config.loader
|
|
121
|
+
if (curPage?.route === existing.route && loader) {
|
|
122
|
+
const p = this.currentPageProps.value
|
|
123
|
+
const transition =
|
|
124
|
+
(loader.mode !== "static" && loader.transition) ??
|
|
125
|
+
this.enableTransitions
|
|
126
|
+
|
|
127
|
+
// Check cache first if caching is enabled
|
|
128
|
+
let cachedData = null
|
|
129
|
+
if (loader.mode !== "static" && loader.cache) {
|
|
130
|
+
const cacheKey: CacheKey = {
|
|
131
|
+
path: this.state.pathname,
|
|
132
|
+
params: this.state.params,
|
|
133
|
+
query: this.state.query,
|
|
134
|
+
}
|
|
135
|
+
cachedData = routerCache.current!.get(cacheKey, loader.cache)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (cachedData !== null) {
|
|
139
|
+
// Use cached data immediately - no loading state needed
|
|
140
|
+
const props = {
|
|
141
|
+
...p,
|
|
142
|
+
data: cachedData.value,
|
|
143
|
+
error: null,
|
|
144
|
+
loading: false,
|
|
145
|
+
}
|
|
146
|
+
handleStateTransition(this.state.signal, transition, () => {
|
|
147
|
+
this.currentPageProps.value = props
|
|
148
|
+
})
|
|
149
|
+
} else {
|
|
150
|
+
// No cached data - show loading state and load data
|
|
151
|
+
const props = {
|
|
152
|
+
...p,
|
|
153
|
+
loading: true,
|
|
154
|
+
data: null,
|
|
155
|
+
error: null,
|
|
156
|
+
}
|
|
157
|
+
handleStateTransition(this.state.signal, transition, () => {
|
|
158
|
+
this.currentPageProps.value = props
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
this.loadRouteData(
|
|
162
|
+
config as PageConfigWithLoader,
|
|
163
|
+
props,
|
|
164
|
+
this.state,
|
|
165
|
+
transition
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.pageRouteToConfig?.set(existing.route, config)
|
|
171
|
+
}
|
|
172
|
+
this.devtools = {
|
|
173
|
+
getPages: () => this.pages,
|
|
174
|
+
invalidate: this.invalidate.bind(this),
|
|
175
|
+
navigate: this.navigate.bind(this),
|
|
176
|
+
reload: () => {
|
|
177
|
+
if (this.invalidate(this.state.pathname)) {
|
|
178
|
+
return Promise.resolve() // invalidate triggered a reload
|
|
179
|
+
}
|
|
180
|
+
return this.loadRoute()
|
|
181
|
+
},
|
|
182
|
+
subscribe: (callback) => {
|
|
183
|
+
const watcher = watch(
|
|
184
|
+
[this.currentPage, this.currentPageProps],
|
|
185
|
+
callback
|
|
186
|
+
)
|
|
187
|
+
return () => watcher.stop()
|
|
188
|
+
},
|
|
189
|
+
}
|
|
97
190
|
}
|
|
98
191
|
|
|
99
|
-
const handlePopState = () => this.loadRoute()
|
|
100
192
|
window.addEventListener("popstate", (e) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
}
|
|
193
|
+
e.preventDefault()
|
|
194
|
+
const { pathname: prevPath, hash: prevHash } = this.state
|
|
195
|
+
const { pathname: nextPath, hash: nextHash } = window.location
|
|
106
196
|
|
|
107
197
|
this.loadRoute().then(() => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
window.location.hash = state.nextHash
|
|
112
|
-
}
|
|
113
|
-
if (!state.nextHash) {
|
|
114
|
-
window.scrollTo(0, 0)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
198
|
+
if (prevHash !== nextHash || prevPath !== nextPath) {
|
|
199
|
+
this.queueScrollManagement(nextHash)
|
|
200
|
+
}
|
|
117
201
|
})
|
|
118
202
|
})
|
|
119
|
-
this.cleanups.push(() =>
|
|
120
|
-
window.removeEventListener("popstate", handlePopState)
|
|
121
|
-
)
|
|
122
203
|
}
|
|
123
204
|
|
|
124
205
|
public init(config: FileRouterConfig) {
|
|
@@ -166,14 +247,17 @@ export class FileRouterController {
|
|
|
166
247
|
this.layouts = layouts
|
|
167
248
|
if (__DEV__) {
|
|
168
249
|
if (page.config) {
|
|
169
|
-
this.
|
|
250
|
+
this.dev_onPageConfigDefined!(route, page.config)
|
|
170
251
|
}
|
|
171
252
|
}
|
|
172
253
|
if (__DEV__) {
|
|
173
254
|
validateRoutes(this.pages)
|
|
174
255
|
}
|
|
175
256
|
const loader = page.config?.loader
|
|
176
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
loader &&
|
|
259
|
+
((loader.mode !== "static" && pageProps.loading === true) || __DEV__)
|
|
260
|
+
) {
|
|
177
261
|
if (cacheData === null) {
|
|
178
262
|
this.loadRouteData(
|
|
179
263
|
page.config as PageConfigWithLoader,
|
|
@@ -188,6 +272,7 @@ export class FileRouterController {
|
|
|
188
272
|
error: null,
|
|
189
273
|
loading: false,
|
|
190
274
|
}
|
|
275
|
+
// @ts-ignore
|
|
191
276
|
const transition = loader.transition ?? this.enableTransitions
|
|
192
277
|
handleStateTransition(this.state.signal, transition, () => {
|
|
193
278
|
this.currentPageProps.value = props
|
|
@@ -195,6 +280,7 @@ export class FileRouterController {
|
|
|
195
280
|
})
|
|
196
281
|
}
|
|
197
282
|
}
|
|
283
|
+
// window.history.scrollRestoration = "manual"
|
|
198
284
|
} else {
|
|
199
285
|
this.pages = formatViteImportMap(
|
|
200
286
|
pages as ViteImportMap,
|
|
@@ -210,72 +296,11 @@ export class FileRouterController {
|
|
|
210
296
|
if (__DEV__) {
|
|
211
297
|
validateRoutes(this.pages)
|
|
212
298
|
}
|
|
299
|
+
//window.history.scrollRestoration = "manual"
|
|
213
300
|
this.loadRoute()
|
|
214
301
|
}
|
|
215
302
|
}
|
|
216
303
|
|
|
217
|
-
public onPageConfigDefined<T extends PageConfig<any>>(fp: string, config: T) {
|
|
218
|
-
const existing = this.filePathToPageRoute?.get(fp)
|
|
219
|
-
if (existing === undefined) {
|
|
220
|
-
const route = fileRouterRoute.current
|
|
221
|
-
if (!route) return
|
|
222
|
-
this.filePathToPageRoute?.set(fp, { route, config })
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
const curPage = this.currentPage.value
|
|
226
|
-
const loader = config.loader
|
|
227
|
-
if (curPage?.route === existing.route && loader) {
|
|
228
|
-
const p = this.currentPageProps.value
|
|
229
|
-
const transition =
|
|
230
|
-
(loader.mode !== "static" && loader.transition) ??
|
|
231
|
-
this.enableTransitions
|
|
232
|
-
|
|
233
|
-
// Check cache first if caching is enabled
|
|
234
|
-
let cachedData = null
|
|
235
|
-
if (loader.mode !== "static" && loader.cache) {
|
|
236
|
-
const cacheKey: CacheKey = {
|
|
237
|
-
path: this.state.pathname,
|
|
238
|
-
params: this.state.params,
|
|
239
|
-
query: this.state.query,
|
|
240
|
-
}
|
|
241
|
-
cachedData = routerCache.current!.get(cacheKey, loader.cache)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (cachedData !== null) {
|
|
245
|
-
// Use cached data immediately - no loading state needed
|
|
246
|
-
const props = {
|
|
247
|
-
...p,
|
|
248
|
-
data: cachedData.value,
|
|
249
|
-
error: null,
|
|
250
|
-
loading: false,
|
|
251
|
-
}
|
|
252
|
-
handleStateTransition(this.state.signal, transition, () => {
|
|
253
|
-
this.currentPageProps.value = props
|
|
254
|
-
})
|
|
255
|
-
} else {
|
|
256
|
-
// No cached data - show loading state and load data
|
|
257
|
-
const props = {
|
|
258
|
-
...p,
|
|
259
|
-
loading: true,
|
|
260
|
-
data: null,
|
|
261
|
-
error: null,
|
|
262
|
-
}
|
|
263
|
-
handleStateTransition(this.state.signal, transition, () => {
|
|
264
|
-
this.currentPageProps.value = props
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
this.loadRouteData(
|
|
268
|
-
config as PageConfigWithLoader,
|
|
269
|
-
props,
|
|
270
|
-
this.state,
|
|
271
|
-
transition
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
this.pageRouteToConfig?.set(existing.route, config)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
304
|
public getChildren() {
|
|
280
305
|
const page = this.currentPage.value
|
|
281
306
|
if (!page) return null
|
|
@@ -502,8 +527,10 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
502
527
|
|
|
503
528
|
if (shouldRefresh) {
|
|
504
529
|
// Refresh the current page to get fresh data
|
|
505
|
-
this.loadRoute(
|
|
530
|
+
this.loadRoute()
|
|
531
|
+
return true
|
|
506
532
|
}
|
|
533
|
+
return false
|
|
507
534
|
}
|
|
508
535
|
|
|
509
536
|
private async navigate(
|
|
@@ -513,27 +540,36 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
513
540
|
transition?: boolean
|
|
514
541
|
}
|
|
515
542
|
) {
|
|
516
|
-
const { pathname: prevPath, hash: prevHash } = window.location
|
|
517
|
-
|
|
518
543
|
const url = new URL(path, "http://localhost")
|
|
519
|
-
const {
|
|
544
|
+
const { hash: prevHash, pathname: prevPath } = this.state
|
|
545
|
+
const { hash: nextHash, pathname: nextPath } = url
|
|
546
|
+
|
|
520
547
|
if (options?.replace) {
|
|
521
548
|
window.history.replaceState({}, "", path)
|
|
522
549
|
} else {
|
|
523
550
|
window.history.pushState({}, "", path)
|
|
524
551
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
552
|
+
|
|
553
|
+
this.loadRoute(
|
|
554
|
+
void 0,
|
|
555
|
+
void 0,
|
|
556
|
+
options?.transition ?? this.enableTransitions
|
|
557
|
+
).then(() => {
|
|
558
|
+
if (prevHash !== nextHash || prevPath !== nextPath) {
|
|
559
|
+
this.queueScrollManagement(nextHash)
|
|
560
|
+
}
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
private queueScrollManagement(nextHash: string) {
|
|
565
|
+
nextIdle(() => {
|
|
566
|
+
let nextEl: HTMLElement | null = null
|
|
567
|
+
if (nextHash && (nextEl = document.getElementById(nextHash.slice(1)))) {
|
|
568
|
+
nextEl.scrollIntoView()
|
|
569
|
+
} else {
|
|
570
|
+
window.scrollTo(0, 0)
|
|
571
|
+
}
|
|
572
|
+
})
|
|
537
573
|
}
|
|
538
574
|
|
|
539
575
|
private async prefetchRouteModules(path: string) {
|
|
@@ -623,7 +659,7 @@ function validateRoutes(pageMap: FormattedViteImportMap) {
|
|
|
623
659
|
let warning = "[kiru/router]: Route conflicts detected:\n"
|
|
624
660
|
warning += routeConflicts
|
|
625
661
|
.map(([route1, route2]) => {
|
|
626
|
-
return ` - "${route1.
|
|
662
|
+
return ` - "${route1.filePath}" conflicts with "${route2.filePath}"\n`
|
|
627
663
|
})
|
|
628
664
|
.join("")
|
|
629
665
|
warning += "Routes are ordered by specificity (higher specificity wins)"
|
|
@@ -678,23 +714,3 @@ function routesConflict(route1: string, route2: string): boolean {
|
|
|
678
714
|
|
|
679
715
|
return true
|
|
680
716
|
}
|
|
681
|
-
|
|
682
|
-
interface CustomNavigationState {
|
|
683
|
-
["kiru-router-event"]: true
|
|
684
|
-
prevHash: string
|
|
685
|
-
nextHash: string
|
|
686
|
-
prevPath: string
|
|
687
|
-
nextPath: string
|
|
688
|
-
transition: boolean
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function isCustomNavigationState(
|
|
692
|
-
state: unknown
|
|
693
|
-
): state is CustomNavigationState {
|
|
694
|
-
return (
|
|
695
|
-
typeof state === "object" &&
|
|
696
|
-
state !== null &&
|
|
697
|
-
"kiru-router-event" in state &&
|
|
698
|
-
state["kiru-router-event"] === true
|
|
699
|
-
)
|
|
700
|
-
}
|
package/src/router/link.ts
CHANGED
package/src/router/pageConfig.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function definePageConfig<T>(config: PageConfig<T>): PageConfig<T> {
|
|
|
7
7
|
const filePath = window.__kiru?.HMRContext?.getCurrentFilePath()
|
|
8
8
|
const fileRouter = fileRouterInstance.current
|
|
9
9
|
if (filePath && fileRouter) {
|
|
10
|
-
fileRouter.
|
|
10
|
+
fileRouter.dev_onPageConfigDefined!(filePath, config)
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -67,7 +67,7 @@ export async function render(
|
|
|
67
67
|
const layoutEntries = matchLayouts(ctx.layouts, routeSegments)
|
|
68
68
|
|
|
69
69
|
;[pageEntry, ...layoutEntries].forEach((e) => {
|
|
70
|
-
ctx.registerModule(e.
|
|
70
|
+
ctx.registerModule(e.filePath)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
const [page, ...layouts] = await Promise.all([
|
|
@@ -133,7 +133,7 @@ export async function render(
|
|
|
133
133
|
!documentShell.includes("<kiru-body-outlet>")
|
|
134
134
|
) {
|
|
135
135
|
throw new Error(
|
|
136
|
-
"[kiru/router]: Document is expected to contain a <Body.Outlet> element. See https://kirujs.dev/docs/api/file-router#
|
|
136
|
+
"[kiru/router]: Document is expected to contain a <Body.Outlet> element. See https://kirujs.dev/docs/api/file-router#general-usage"
|
|
137
137
|
)
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -198,7 +198,7 @@ export async function generateStaticPaths(pages: FormattedViteImportMap) {
|
|
|
198
198
|
|
|
199
199
|
const hasDynamic = urlSegments.some((s) => s.startsWith(":"))
|
|
200
200
|
if (!hasDynamic) {
|
|
201
|
-
results[basePath === "" ? "/" : basePath] = entry.
|
|
201
|
+
results[basePath === "" ? "/" : basePath] = entry.filePath
|
|
202
202
|
continue
|
|
203
203
|
}
|
|
204
204
|
try {
|
|
@@ -214,7 +214,7 @@ export async function generateStaticPaths(pages: FormattedViteImportMap) {
|
|
|
214
214
|
const value = params[key]
|
|
215
215
|
p = p.replace(`:${key}*`, value).replace(`:${key}`, value)
|
|
216
216
|
}
|
|
217
|
-
results[p] = entry.
|
|
217
|
+
results[p] = entry.filePath
|
|
218
218
|
}
|
|
219
219
|
} catch {}
|
|
220
220
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { FileRouterContextType } from "./context"
|
|
1
2
|
import type { PageConfig } from "./types"
|
|
2
3
|
|
|
4
|
+
export interface CurrentPage {
|
|
5
|
+
component: Kiru.FC<any>
|
|
6
|
+
config?: PageConfig
|
|
7
|
+
route: string
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
export interface DefaultComponentModule {
|
|
4
11
|
default: Kiru.FC
|
|
5
12
|
}
|
|
@@ -18,10 +25,12 @@ export interface ViteImportMap {
|
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export interface FormattedViteImportMapEntry<T = DefaultComponentModule> {
|
|
28
|
+
filePath: string
|
|
21
29
|
load: () => Promise<T>
|
|
22
|
-
|
|
30
|
+
params: string[]
|
|
31
|
+
route: string
|
|
23
32
|
segments: string[]
|
|
24
|
-
|
|
33
|
+
specificity: number
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export interface FormattedViteImportMap<T = DefaultComponentModule> {
|
|
@@ -34,3 +43,13 @@ export interface RouteMatch {
|
|
|
34
43
|
params: Record<string, string>
|
|
35
44
|
routeSegments: string[]
|
|
36
45
|
}
|
|
46
|
+
|
|
47
|
+
export interface DevtoolsInterface {
|
|
48
|
+
getPages: () => FormattedViteImportMap<PageModule>
|
|
49
|
+
invalidate: FileRouterContextType["invalidate"]
|
|
50
|
+
navigate: FileRouterContextType["navigate"]
|
|
51
|
+
reload: FileRouterContextType["reload"]
|
|
52
|
+
subscribe: (
|
|
53
|
+
cb: (page: CurrentPage | null, props: Record<string, unknown>) => void
|
|
54
|
+
) => () => void
|
|
55
|
+
}
|
|
@@ -39,6 +39,7 @@ function formatViteImportMap(
|
|
|
39
39
|
}
|
|
40
40
|
const segments: string[] = []
|
|
41
41
|
const parts = k.split("/").slice(0, -1)
|
|
42
|
+
const params = new Set<string>()
|
|
42
43
|
|
|
43
44
|
for (let i = 0; i < parts.length; i++) {
|
|
44
45
|
const part = parts[i]
|
|
@@ -48,12 +49,26 @@ function formatViteImportMap(
|
|
|
48
49
|
`[kiru/router]: Catchall must be the folder name. Got "${key}"`
|
|
49
50
|
)
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
const param = part.slice(4, -1)
|
|
53
|
+
if (params.has(param)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[kiru/router]: Duplicate parameter "${param}" in "${key}"`
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
params.add(param)
|
|
59
|
+
segments.push(`:${param}*`)
|
|
52
60
|
specificity += 1
|
|
53
61
|
break
|
|
54
62
|
}
|
|
55
63
|
if (part.startsWith("[") && part.endsWith("]")) {
|
|
56
|
-
|
|
64
|
+
const param = part.slice(1, -1)
|
|
65
|
+
if (params.has(param)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`[kiru/router]: Duplicate parameter "${param}" in "${key}"`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
params.add(param)
|
|
71
|
+
segments.push(`:${param}`)
|
|
57
72
|
specificity += 10
|
|
58
73
|
continue
|
|
59
74
|
}
|
|
@@ -62,10 +77,12 @@ function formatViteImportMap(
|
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
const value: FormattedViteImportMap[string] = {
|
|
80
|
+
filePath: key,
|
|
65
81
|
load: map[key],
|
|
66
|
-
|
|
82
|
+
params: Array.from(params),
|
|
83
|
+
route: "/" + parts.join("/"),
|
|
67
84
|
segments,
|
|
68
|
-
|
|
85
|
+
specificity,
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
return {
|