@benjavicente/router-core 1.168.9 → 1.169.2
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/cjs/Matches.cjs +1 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/config.cjs +1 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/defer.cjs +1 -1
- package/dist/cjs/defer.cjs.map +1 -1
- package/dist/cjs/hash-scroll.cjs +1 -1
- package/dist/cjs/hash-scroll.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.d.cts +3 -3
- package/dist/cjs/isServer/client.cjs +1 -1
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/development.cjs +1 -1
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/server.cjs +1 -1
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/link.cjs +1 -1
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/load-matches.cjs +26 -26
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/manifest.cjs +31 -0
- package/dist/cjs/manifest.cjs.map +1 -1
- package/dist/cjs/manifest.d.cts +8 -0
- package/dist/cjs/new-process-route-tree.cjs +55 -67
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.d.cts +4 -26
- package/dist/cjs/not-found.cjs +1 -1
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/path.cjs +1 -22
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/root.cjs +1 -1
- package/dist/cjs/root.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +6 -34
- package/dist/cjs/router.cjs +94 -80
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +6 -5
- package/dist/cjs/scroll-restoration-script/server.cjs +1 -1
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +7 -7
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +2 -2
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/constants.cjs +2 -2
- package/dist/cjs/ssr/constants.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.cjs +50 -41
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
- package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
- package/dist/cjs/ssr/ssr-client.cjs +9 -9
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +110 -22
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +4 -2
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +10 -7
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/stores.cjs +57 -57
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +16 -16
- package/dist/cjs/utils.cjs +12 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.js +1 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/defer.js +1 -1
- package/dist/esm/defer.js.map +1 -1
- package/dist/esm/hash-scroll.js +1 -1
- package/dist/esm/hash-scroll.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/isServer/client.js +1 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.js +1 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.js +1 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/link.js +1 -1
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-matches.js +26 -26
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/manifest.d.ts +8 -0
- package/dist/esm/manifest.js +28 -1
- package/dist/esm/manifest.js.map +1 -1
- package/dist/esm/new-process-route-tree.d.ts +4 -26
- package/dist/esm/new-process-route-tree.js +55 -68
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/not-found.js +1 -1
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/path.js +1 -22
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/root.js +1 -1
- package/dist/esm/root.js.map +1 -1
- package/dist/esm/route.d.ts +6 -34
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +6 -5
- package/dist/esm/router.js +96 -82
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration-script/server.js +1 -1
- package/dist/esm/scroll-restoration-script/server.js.map +1 -1
- package/dist/esm/scroll-restoration.js +7 -7
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +2 -2
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/constants.js +2 -2
- package/dist/esm/ssr/constants.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +2 -2
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
- package/dist/esm/ssr/serializer/RawStream.js +50 -41
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
- package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
- package/dist/esm/ssr/serializer/transformer.js +16 -14
- package/dist/esm/ssr/serializer/transformer.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +9 -9
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +4 -2
- package/dist/esm/ssr/ssr-server.js +110 -22
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.js +10 -7
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/stores.d.ts +16 -16
- package/dist/esm/stores.js +58 -58
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +12 -8
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -8
- package/skills/router-core/auth-and-guards/SKILL.md +41 -0
- package/skills/router-core/ssr/SKILL.md +59 -5
- package/skills/router-core/type-safety/SKILL.md +37 -42
- package/src/hash-scroll.ts +1 -1
- package/src/index.ts +9 -2
- package/src/load-matches.ts +14 -15
- package/src/manifest.ts +46 -0
- package/src/new-process-route-tree.ts +93 -106
- package/src/not-found.ts +1 -1
- package/src/path.ts +1 -27
- package/src/route.ts +10 -36
- package/src/router.ts +151 -140
- package/src/scroll-restoration.ts +1 -1
- package/src/ssr/createRequestHandler.ts +4 -5
- package/src/ssr/serializer/RawStream.ts +65 -56
- package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
- package/src/ssr/serializer/seroval-plugins.ts +2 -1
- package/src/ssr/serializer/transformer.ts +71 -76
- package/src/ssr/ssr-client.ts +8 -12
- package/src/ssr/ssr-server.ts +170 -14
- package/src/ssr/transformStreamWithRouter.ts +3 -0
- package/src/stores.ts +86 -86
- package/src/utils.ts +7 -0
- package/bin/intent.js +0 -25
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
|
|
2
2
|
import { invariant } from '../invariant'
|
|
3
|
+
import {
|
|
4
|
+
createInlineCssPlaceholderAsset,
|
|
5
|
+
createInlineCssStyleAsset,
|
|
6
|
+
getStylesheetHref,
|
|
7
|
+
isInlinableStylesheet,
|
|
8
|
+
} from '../manifest'
|
|
3
9
|
import { decodePath } from '../utils'
|
|
4
10
|
import { createLRUCache } from '../lru-cache'
|
|
11
|
+
import { rootRouteId } from '../root'
|
|
5
12
|
import minifiedTsrBootStrapScript from './tsrScript?script-string'
|
|
6
13
|
import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'
|
|
7
14
|
import { dehydrateSsrMatchId } from './ssr-match-id'
|
|
@@ -156,9 +163,11 @@ const isProd = process.env.NODE_ENV === 'production'
|
|
|
156
163
|
type FilteredRoutes = Manifest['routes']
|
|
157
164
|
|
|
158
165
|
type ManifestLRU = LRUCache<string, FilteredRoutes>
|
|
166
|
+
type InlineCssLRU = LRUCache<string, string>
|
|
159
167
|
|
|
160
168
|
const MANIFEST_CACHE_SIZE = 100
|
|
161
169
|
const manifestCaches = new WeakMap<Manifest, ManifestLRU>()
|
|
170
|
+
const inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()
|
|
162
171
|
|
|
163
172
|
function getManifestCache(manifest: Manifest): ManifestLRU {
|
|
164
173
|
const cache = manifestCaches.get(manifest)
|
|
@@ -168,15 +177,143 @@ function getManifestCache(manifest: Manifest): ManifestLRU {
|
|
|
168
177
|
return newCache
|
|
169
178
|
}
|
|
170
179
|
|
|
180
|
+
function getInlineCssCache(manifest: Manifest): InlineCssLRU {
|
|
181
|
+
const cache = inlineCssCaches.get(manifest)
|
|
182
|
+
if (cache) return cache
|
|
183
|
+
const newCache = createLRUCache<string, string>(MANIFEST_CACHE_SIZE)
|
|
184
|
+
inlineCssCaches.set(manifest, newCache)
|
|
185
|
+
return newCache
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getInlineCssHrefsForMatches(
|
|
189
|
+
manifest: Manifest | undefined,
|
|
190
|
+
matches: Array<AnyRouteMatch>,
|
|
191
|
+
) {
|
|
192
|
+
const styles = manifest?.inlineCss?.styles
|
|
193
|
+
if (!styles) return []
|
|
194
|
+
|
|
195
|
+
const seen = new Set<string>()
|
|
196
|
+
const hrefs: Array<string> = []
|
|
197
|
+
|
|
198
|
+
for (const match of matches) {
|
|
199
|
+
const assets = manifest?.routes[match.routeId]?.assets ?? []
|
|
200
|
+
for (const asset of assets) {
|
|
201
|
+
const href = getStylesheetHref(asset)
|
|
202
|
+
if (!href || seen.has(href) || styles[href] === undefined) {
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
seen.add(href)
|
|
206
|
+
hrefs.push(href)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return hrefs
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getInlineCssForHrefs(manifest: Manifest, hrefs: Array<string>) {
|
|
214
|
+
const styles = manifest.inlineCss?.styles
|
|
215
|
+
if (!styles || hrefs.length === 0) return undefined
|
|
216
|
+
|
|
217
|
+
const cacheKey = hrefs.join('\0')
|
|
218
|
+
if (isProd) {
|
|
219
|
+
const cached = getInlineCssCache(manifest).get(cacheKey)
|
|
220
|
+
if (cached !== undefined) return cached
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const css = hrefs.map((href) => styles[href]!).join('')
|
|
224
|
+
|
|
225
|
+
if (isProd) {
|
|
226
|
+
getInlineCssCache(manifest).set(cacheKey, css)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return css
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getInlineCssAssetForMatches(
|
|
233
|
+
manifest: Manifest | undefined,
|
|
234
|
+
matches: Array<AnyRouteMatch>,
|
|
235
|
+
) {
|
|
236
|
+
if (!manifest?.inlineCss) return undefined
|
|
237
|
+
|
|
238
|
+
const hrefs = getInlineCssHrefsForMatches(manifest, matches)
|
|
239
|
+
const css = getInlineCssForHrefs(manifest, hrefs)
|
|
240
|
+
|
|
241
|
+
return css === undefined ? undefined : createInlineCssStyleAsset(css)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stripInlinedStylesheetAssets(
|
|
245
|
+
manifest: Manifest,
|
|
246
|
+
routes: FilteredRoutes,
|
|
247
|
+
matches: Array<AnyRouteMatch>,
|
|
248
|
+
): FilteredRoutes {
|
|
249
|
+
if (!manifest.inlineCss) {
|
|
250
|
+
return routes
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const nextRoutes: FilteredRoutes = {}
|
|
254
|
+
|
|
255
|
+
for (const [routeId, route] of Object.entries(routes)) {
|
|
256
|
+
const assets = route.assets?.filter(
|
|
257
|
+
(asset) => !isInlinableStylesheet(manifest, asset),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const nextRoute = { ...route }
|
|
261
|
+
if (assets) {
|
|
262
|
+
if (assets.length > 0) {
|
|
263
|
+
nextRoute.assets = assets
|
|
264
|
+
} else {
|
|
265
|
+
delete nextRoute.assets
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
nextRoutes[routeId] = nextRoute
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (getInlineCssAssetForMatches(manifest, matches)) {
|
|
272
|
+
const rootRoute = nextRoutes[rootRouteId] ?? {}
|
|
273
|
+
nextRoutes[rootRouteId] = {
|
|
274
|
+
...rootRoute,
|
|
275
|
+
assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return nextRoutes
|
|
280
|
+
}
|
|
281
|
+
|
|
171
282
|
export function attachRouterServerSsrUtils({
|
|
172
283
|
router,
|
|
173
284
|
manifest,
|
|
285
|
+
getRequestAssets,
|
|
286
|
+
includeUnmatchedRouteAssets = true,
|
|
174
287
|
}: {
|
|
175
288
|
router: AnyRouter
|
|
176
289
|
manifest: Manifest | undefined
|
|
290
|
+
getRequestAssets?: () => Array<RouterManagedTag> | undefined
|
|
291
|
+
includeUnmatchedRouteAssets?: boolean
|
|
177
292
|
}) {
|
|
178
293
|
router.ssr = {
|
|
179
|
-
manifest
|
|
294
|
+
get manifest() {
|
|
295
|
+
const requestAssets = getRequestAssets?.()
|
|
296
|
+
const inlineCssAsset = getInlineCssAssetForMatches(
|
|
297
|
+
manifest,
|
|
298
|
+
router.stores.matches.get(),
|
|
299
|
+
)
|
|
300
|
+
if (!requestAssets?.length && !inlineCssAsset) return manifest
|
|
301
|
+
// Merge request-scoped assets into root route without mutating cached manifest
|
|
302
|
+
return {
|
|
303
|
+
...manifest,
|
|
304
|
+
routes: {
|
|
305
|
+
...manifest?.routes,
|
|
306
|
+
[rootRouteId]: {
|
|
307
|
+
...manifest?.routes?.[rootRouteId],
|
|
308
|
+
assets: [
|
|
309
|
+
...(requestAssets ?? []),
|
|
310
|
+
...(inlineCssAsset ? [inlineCssAsset] : []),
|
|
311
|
+
...(manifest?.routes?.[rootRouteId]?.assets ?? []),
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
},
|
|
180
317
|
}
|
|
181
318
|
let _dehydrated = false
|
|
182
319
|
let _serializationFinished = false
|
|
@@ -200,7 +337,7 @@ export function attachRouterServerSsrUtils({
|
|
|
200
337
|
const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
|
|
201
338
|
router.serverSsr!.injectHtml(html)
|
|
202
339
|
},
|
|
203
|
-
dehydrate: async () => {
|
|
340
|
+
dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {
|
|
204
341
|
if (_dehydrated) {
|
|
205
342
|
if (process.env.NODE_ENV !== 'production') {
|
|
206
343
|
throw new Error('Invariant failed: router is already dehydrated!')
|
|
@@ -208,7 +345,7 @@ export function attachRouterServerSsrUtils({
|
|
|
208
345
|
|
|
209
346
|
invariant()
|
|
210
347
|
}
|
|
211
|
-
let matchesToDehydrate = router.stores.
|
|
348
|
+
let matchesToDehydrate = router.stores.matches.get()
|
|
212
349
|
if (router.isShell()) {
|
|
213
350
|
// In SPA mode we only want to dehydrate the root match
|
|
214
351
|
matchesToDehydrate = matchesToDehydrate.slice(0, 1)
|
|
@@ -216,12 +353,14 @@ export function attachRouterServerSsrUtils({
|
|
|
216
353
|
const matches = matchesToDehydrate.map(dehydrateMatch)
|
|
217
354
|
|
|
218
355
|
let manifestToDehydrate: Manifest | undefined = undefined
|
|
219
|
-
// For currently matched routes, send full manifest (preloads + assets)
|
|
220
|
-
// For
|
|
356
|
+
// For currently matched routes, send full manifest (preloads + assets).
|
|
357
|
+
// For unmatched routes, include assets only when includeUnmatchedRouteAssets
|
|
358
|
+
// is true; otherwise omit them entirely. Preloads for unmatched routes are
|
|
359
|
+
// still excluded because they are handled via dynamic imports.
|
|
221
360
|
if (manifest) {
|
|
222
361
|
// Prod-only caching; in dev manifests may be replaced/updated (HMR)
|
|
223
362
|
const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)
|
|
224
|
-
const manifestCacheKey = currentRouteIdsList.join('\0')
|
|
363
|
+
const manifestCacheKey = `${currentRouteIdsList.join('\0')}\0includeUnmatchedRouteAssets=${includeUnmatchedRouteAssets}`
|
|
225
364
|
|
|
226
365
|
let filteredRoutes: FilteredRoutes | undefined
|
|
227
366
|
|
|
@@ -238,6 +377,7 @@ export function attachRouterServerSsrUtils({
|
|
|
238
377
|
if (currentRouteIds.has(routeId)) {
|
|
239
378
|
nextFilteredRoutes[routeId] = routeManifest
|
|
240
379
|
} else if (
|
|
380
|
+
includeUnmatchedRouteAssets &&
|
|
241
381
|
routeManifest.assets &&
|
|
242
382
|
routeManifest.assets.length > 0
|
|
243
383
|
) {
|
|
@@ -247,15 +387,28 @@ export function attachRouterServerSsrUtils({
|
|
|
247
387
|
}
|
|
248
388
|
}
|
|
249
389
|
|
|
390
|
+
filteredRoutes = stripInlinedStylesheetAssets(
|
|
391
|
+
manifest,
|
|
392
|
+
nextFilteredRoutes,
|
|
393
|
+
matchesToDehydrate,
|
|
394
|
+
)
|
|
395
|
+
|
|
250
396
|
if (isProd) {
|
|
251
|
-
getManifestCache(manifest).set(manifestCacheKey,
|
|
397
|
+
getManifestCache(manifest).set(manifestCacheKey, filteredRoutes)
|
|
252
398
|
}
|
|
253
|
-
|
|
254
|
-
filteredRoutes = nextFilteredRoutes
|
|
255
399
|
}
|
|
256
400
|
|
|
257
401
|
manifestToDehydrate = {
|
|
258
|
-
routes: filteredRoutes,
|
|
402
|
+
routes: { ...filteredRoutes },
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Merge request-scoped assets into root route (without mutating cached manifest)
|
|
406
|
+
if (opts?.requestAssets?.length) {
|
|
407
|
+
const existingRoot = manifestToDehydrate.routes[rootRouteId]
|
|
408
|
+
manifestToDehydrate.routes[rootRouteId] = {
|
|
409
|
+
...existingRoot,
|
|
410
|
+
assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],
|
|
411
|
+
}
|
|
259
412
|
}
|
|
260
413
|
}
|
|
261
414
|
const dehydratedRouter: DehydratedRouter = {
|
|
@@ -305,6 +458,13 @@ export function attachRouterServerSsrUtils({
|
|
|
305
458
|
}
|
|
306
459
|
scriptBuffer.enqueue(serialized)
|
|
307
460
|
},
|
|
461
|
+
onError: (err: unknown) => {
|
|
462
|
+
console.error('Serialization error:', err)
|
|
463
|
+
if (err && (err as any).stack) {
|
|
464
|
+
console.error((err as any).stack)
|
|
465
|
+
}
|
|
466
|
+
signalSerializationComplete()
|
|
467
|
+
},
|
|
308
468
|
scopeId: SCOPE_ID,
|
|
309
469
|
onDone: () => {
|
|
310
470
|
scriptBuffer.enqueue(GLOBAL_TSR + '.e()')
|
|
@@ -313,10 +473,6 @@ export function attachRouterServerSsrUtils({
|
|
|
313
473
|
scriptBuffer.flush()
|
|
314
474
|
signalSerializationComplete()
|
|
315
475
|
},
|
|
316
|
-
onError: (err) => {
|
|
317
|
-
console.error('Serialization error:', err)
|
|
318
|
-
signalSerializationComplete()
|
|
319
|
-
},
|
|
320
476
|
})
|
|
321
477
|
},
|
|
322
478
|
isDehydrated() {
|
|
@@ -366,6 +366,9 @@ export function transformStreamWithRouter(
|
|
|
366
366
|
if (isAppRendering || leftover || pendingClosingTags) {
|
|
367
367
|
appendRouterHtml(html)
|
|
368
368
|
} else {
|
|
369
|
+
// App is done rendering - flush any pending buffer first to maintain order,
|
|
370
|
+
// then write the new HTML directly
|
|
371
|
+
flushPendingRouterHtml()
|
|
369
372
|
safeEnqueue(html)
|
|
370
373
|
}
|
|
371
374
|
})
|
package/src/stores.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLRUCache } from './lru-cache'
|
|
2
|
-
import { arraysEqual } from './utils'
|
|
2
|
+
import { arraysEqual, functionalUpdate } from './utils'
|
|
3
3
|
|
|
4
4
|
import type { AnyRoute } from './route'
|
|
5
5
|
import type { RouterState } from './router'
|
|
@@ -9,13 +9,13 @@ import type { AnyRedirect } from './redirect'
|
|
|
9
9
|
import type { AnyRouteMatch } from './Matches'
|
|
10
10
|
|
|
11
11
|
export interface RouterReadableStore<TValue> {
|
|
12
|
-
|
|
12
|
+
get: () => TValue
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface RouterWritableStore<
|
|
16
16
|
TValue,
|
|
17
17
|
> extends RouterReadableStore<TValue> {
|
|
18
|
-
|
|
18
|
+
set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export type RouterBatchFn = (fn: () => void) => void
|
|
@@ -49,11 +49,11 @@ export function createNonReactiveMutableStore<TValue>(
|
|
|
49
49
|
let value = initialValue
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
|
-
get
|
|
52
|
+
get() {
|
|
53
53
|
return value
|
|
54
54
|
},
|
|
55
|
-
|
|
56
|
-
value =
|
|
55
|
+
set(nextOrUpdater: TValue | ((prev: TValue) => TValue)) {
|
|
56
|
+
value = functionalUpdate(nextOrUpdater, value)
|
|
57
57
|
},
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -63,7 +63,7 @@ export function createNonReactiveReadonlyStore<TValue>(
|
|
|
63
63
|
read: () => TValue,
|
|
64
64
|
): RouterReadableStore<TValue> {
|
|
65
65
|
return {
|
|
66
|
-
get
|
|
66
|
+
get() {
|
|
67
67
|
return read()
|
|
68
68
|
},
|
|
69
69
|
}
|
|
@@ -81,24 +81,24 @@ export interface RouterStores<in out TRouteTree extends AnyRoute> {
|
|
|
81
81
|
statusCode: RouterWritableStore<number>
|
|
82
82
|
redirect: RouterWritableStore<AnyRedirect | undefined>
|
|
83
83
|
matchesId: RouterWritableStore<Array<string>>
|
|
84
|
-
|
|
84
|
+
pendingIds: RouterWritableStore<Array<string>>
|
|
85
85
|
/** @internal */
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
cachedIds: RouterWritableStore<Array<string>>
|
|
87
|
+
matches: ReadableStore<Array<AnyRouteMatch>>
|
|
88
|
+
pendingMatches: ReadableStore<Array<AnyRouteMatch>>
|
|
89
|
+
cachedMatches: ReadableStore<Array<AnyRouteMatch>>
|
|
90
|
+
firstId: ReadableStore<string | undefined>
|
|
91
|
+
hasPending: ReadableStore<boolean>
|
|
92
|
+
matchRouteDeps: ReadableStore<{
|
|
93
93
|
locationHref: string
|
|
94
94
|
resolvedLocationHref: string | undefined
|
|
95
95
|
status: RouterState<TRouteTree>['status']
|
|
96
96
|
}>
|
|
97
97
|
__store: RouterReadableStore<RouterState<TRouteTree>>
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
matchStores: Map<string, MatchStore>
|
|
100
|
+
pendingMatchStores: Map<string, MatchStore>
|
|
101
|
+
cachedMatchStores: Map<string, MatchStore>
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Get a computed store that resolves a routeId to its current match state.
|
|
@@ -106,13 +106,13 @@ export interface RouterStores<in out TRouteTree extends AnyRoute> {
|
|
|
106
106
|
* The computed depends on matchesId + the individual match store, so
|
|
107
107
|
* subscribers are only notified when the resolved match state changes.
|
|
108
108
|
*/
|
|
109
|
-
|
|
109
|
+
getRouteMatchStore: (
|
|
110
110
|
routeId: string,
|
|
111
111
|
) => RouterReadableStore<AnyRouteMatch | undefined>
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
setMatches: (nextMatches: Array<AnyRouteMatch>) => void
|
|
114
|
+
setPending: (nextMatches: Array<AnyRouteMatch>) => void
|
|
115
|
+
setCached: (nextMatches: Array<AnyRouteMatch>) => void
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
@@ -122,9 +122,9 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
|
122
122
|
const { createMutableStore, createReadonlyStore, batch, init } = config
|
|
123
123
|
|
|
124
124
|
// non reactive utilities
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
125
|
+
const matchStores = new Map<string, MatchStore>()
|
|
126
|
+
const pendingMatchStores = new Map<string, MatchStore>()
|
|
127
|
+
const cachedMatchStores = new Map<string, MatchStore>()
|
|
128
128
|
|
|
129
129
|
// atoms
|
|
130
130
|
const status = createMutableStore(initialState.status)
|
|
@@ -136,43 +136,43 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
|
136
136
|
const statusCode = createMutableStore(initialState.statusCode)
|
|
137
137
|
const redirect = createMutableStore(initialState.redirect)
|
|
138
138
|
const matchesId = createMutableStore<Array<string>>([])
|
|
139
|
-
const
|
|
140
|
-
const
|
|
139
|
+
const pendingIds = createMutableStore<Array<string>>([])
|
|
140
|
+
const cachedIds = createMutableStore<Array<string>>([])
|
|
141
141
|
|
|
142
142
|
// 1st order derived stores
|
|
143
|
-
const
|
|
144
|
-
readPoolMatches(
|
|
143
|
+
const matches = createReadonlyStore(() =>
|
|
144
|
+
readPoolMatches(matchStores, matchesId.get()),
|
|
145
145
|
)
|
|
146
|
-
const
|
|
147
|
-
readPoolMatches(
|
|
146
|
+
const pendingMatches = createReadonlyStore(() =>
|
|
147
|
+
readPoolMatches(pendingMatchStores, pendingIds.get()),
|
|
148
148
|
)
|
|
149
|
-
const
|
|
150
|
-
readPoolMatches(
|
|
149
|
+
const cachedMatches = createReadonlyStore(() =>
|
|
150
|
+
readPoolMatches(cachedMatchStores, cachedIds.get()),
|
|
151
151
|
)
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
matchesId.
|
|
155
|
-
const store =
|
|
156
|
-
return store?.
|
|
152
|
+
const firstId = createReadonlyStore(() => matchesId.get()[0])
|
|
153
|
+
const hasPending = createReadonlyStore(() =>
|
|
154
|
+
matchesId.get().some((matchId) => {
|
|
155
|
+
const store = matchStores.get(matchId)
|
|
156
|
+
return store?.get().status === 'pending'
|
|
157
157
|
}),
|
|
158
158
|
)
|
|
159
|
-
const
|
|
160
|
-
locationHref: location.
|
|
161
|
-
resolvedLocationHref: resolvedLocation.
|
|
162
|
-
status: status.
|
|
159
|
+
const matchRouteDeps = createReadonlyStore(() => ({
|
|
160
|
+
locationHref: location.get().href,
|
|
161
|
+
resolvedLocationHref: resolvedLocation.get()?.href,
|
|
162
|
+
status: status.get(),
|
|
163
163
|
}))
|
|
164
164
|
|
|
165
165
|
// compatibility "big" state store
|
|
166
166
|
const __store = createReadonlyStore(() => ({
|
|
167
|
-
status: status.
|
|
168
|
-
loadedAt: loadedAt.
|
|
169
|
-
isLoading: isLoading.
|
|
170
|
-
isTransitioning: isTransitioning.
|
|
171
|
-
matches:
|
|
172
|
-
location: location.
|
|
173
|
-
resolvedLocation: resolvedLocation.
|
|
174
|
-
statusCode: statusCode.
|
|
175
|
-
redirect: redirect.
|
|
167
|
+
status: status.get(),
|
|
168
|
+
loadedAt: loadedAt.get(),
|
|
169
|
+
isLoading: isLoading.get(),
|
|
170
|
+
isTransitioning: isTransitioning.get(),
|
|
171
|
+
matches: matches.get(),
|
|
172
|
+
location: location.get(),
|
|
173
|
+
resolvedLocation: resolvedLocation.get(),
|
|
174
|
+
statusCode: statusCode.get(),
|
|
175
|
+
redirect: redirect.get(),
|
|
176
176
|
}))
|
|
177
177
|
|
|
178
178
|
// Per-routeId computed store cache.
|
|
@@ -188,21 +188,21 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
|
188
188
|
RouterReadableStore<AnyRouteMatch | undefined>
|
|
189
189
|
>(64)
|
|
190
190
|
|
|
191
|
-
function
|
|
191
|
+
function getRouteMatchStore(
|
|
192
192
|
routeId: string,
|
|
193
193
|
): RouterReadableStore<AnyRouteMatch | undefined> {
|
|
194
194
|
let cached = matchStoreByRouteIdCache.get(routeId)
|
|
195
195
|
if (!cached) {
|
|
196
196
|
cached = createReadonlyStore(() => {
|
|
197
|
-
// Reading matchesId.
|
|
197
|
+
// Reading matchesId.get() tracks it as a dependency.
|
|
198
198
|
// When matchesId changes (navigation), this computed re-evaluates.
|
|
199
|
-
const ids = matchesId.
|
|
199
|
+
const ids = matchesId.get()
|
|
200
200
|
for (const id of ids) {
|
|
201
|
-
const matchStore =
|
|
201
|
+
const matchStore = matchStores.get(id)
|
|
202
202
|
if (matchStore && matchStore.routeId === routeId) {
|
|
203
|
-
// Reading matchStore.
|
|
203
|
+
// Reading matchStore.get() tracks it as a dependency.
|
|
204
204
|
// When the match store's state changes, this re-evaluates.
|
|
205
|
-
return matchStore.
|
|
205
|
+
return matchStore.get()
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
return undefined
|
|
@@ -223,64 +223,64 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
|
223
223
|
statusCode,
|
|
224
224
|
redirect,
|
|
225
225
|
matchesId,
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
pendingIds,
|
|
227
|
+
cachedIds,
|
|
228
228
|
|
|
229
229
|
// derived
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
matches,
|
|
231
|
+
pendingMatches,
|
|
232
|
+
cachedMatches,
|
|
233
|
+
firstId,
|
|
234
|
+
hasPending,
|
|
235
|
+
matchRouteDeps,
|
|
236
236
|
|
|
237
237
|
// non-reactive state
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
matchStores,
|
|
239
|
+
pendingMatchStores,
|
|
240
|
+
cachedMatchStores,
|
|
241
241
|
|
|
242
242
|
// compatibility "big" state
|
|
243
243
|
__store,
|
|
244
244
|
|
|
245
245
|
// per-key computed stores
|
|
246
|
-
|
|
246
|
+
getRouteMatchStore,
|
|
247
247
|
|
|
248
248
|
// methods
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
setMatches,
|
|
250
|
+
setPending,
|
|
251
|
+
setCached,
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
// initialize the active matches
|
|
255
|
-
|
|
255
|
+
setMatches(initialState.matches as Array<AnyRouteMatch>)
|
|
256
256
|
init?.(store)
|
|
257
257
|
|
|
258
258
|
// setters to update non-reactive utilities in sync with the reactive stores
|
|
259
|
-
function
|
|
259
|
+
function setMatches(nextMatches: Array<AnyRouteMatch>) {
|
|
260
260
|
reconcileMatchPool(
|
|
261
261
|
nextMatches,
|
|
262
|
-
|
|
262
|
+
matchStores,
|
|
263
263
|
matchesId,
|
|
264
264
|
createMutableStore,
|
|
265
265
|
batch,
|
|
266
266
|
)
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
function
|
|
269
|
+
function setPending(nextMatches: Array<AnyRouteMatch>) {
|
|
270
270
|
reconcileMatchPool(
|
|
271
271
|
nextMatches,
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
pendingMatchStores,
|
|
273
|
+
pendingIds,
|
|
274
274
|
createMutableStore,
|
|
275
275
|
batch,
|
|
276
276
|
)
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
function
|
|
279
|
+
function setCached(nextMatches: Array<AnyRouteMatch>) {
|
|
280
280
|
reconcileMatchPool(
|
|
281
281
|
nextMatches,
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
cachedMatchStores,
|
|
283
|
+
cachedIds,
|
|
284
284
|
createMutableStore,
|
|
285
285
|
batch,
|
|
286
286
|
)
|
|
@@ -297,7 +297,7 @@ function readPoolMatches(
|
|
|
297
297
|
for (const id of ids) {
|
|
298
298
|
const matchStore = pool.get(id)
|
|
299
299
|
if (matchStore) {
|
|
300
|
-
matches.push(matchStore.
|
|
300
|
+
matches.push(matchStore.get())
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
return matches
|
|
@@ -330,13 +330,13 @@ function reconcileMatchPool(
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
existing.routeId = nextMatch.routeId
|
|
333
|
-
if (existing.
|
|
334
|
-
existing.
|
|
333
|
+
if (existing.get() !== nextMatch) {
|
|
334
|
+
existing.set(nextMatch)
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
if (!arraysEqual(idStore.
|
|
339
|
-
idStore.
|
|
338
|
+
if (!arraysEqual(idStore.get(), nextIds)) {
|
|
339
|
+
idStore.set(nextIds)
|
|
340
340
|
}
|
|
341
341
|
})
|
|
342
342
|
}
|
package/src/utils.ts
CHANGED
|
@@ -215,6 +215,13 @@ export function functionalUpdate<TPrevious, TResult = TPrevious>(
|
|
|
215
215
|
const hasOwn = Object.prototype.hasOwnProperty
|
|
216
216
|
const isEnumerable = Object.prototype.propertyIsEnumerable
|
|
217
217
|
|
|
218
|
+
export function hasKeys(obj: Record<string, unknown>) {
|
|
219
|
+
for (const key in obj) {
|
|
220
|
+
if (hasOwn.call(obj, key)) return true
|
|
221
|
+
}
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
|
|
218
225
|
const createNull = () => Object.create(null)
|
|
219
226
|
export const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>
|
|
220
227
|
replaceEqualDeep(prev, next, createNull)
|
package/bin/intent.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Auto-generated by @tanstack/intent setup
|
|
3
|
-
// Exposes the intent end-user CLI for consumers of this library.
|
|
4
|
-
// Commit this file, then add to your package.json:
|
|
5
|
-
// "bin": { "intent": "./bin/intent.js" }
|
|
6
|
-
try {
|
|
7
|
-
await import('@tanstack/intent/intent-library')
|
|
8
|
-
} catch (e) {
|
|
9
|
-
const isModuleNotFound =
|
|
10
|
-
e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND'
|
|
11
|
-
const missingIntentLibrary =
|
|
12
|
-
typeof e?.message === 'string' && e.message.includes('@tanstack/intent')
|
|
13
|
-
|
|
14
|
-
if (isModuleNotFound && missingIntentLibrary) {
|
|
15
|
-
console.error('@tanstack/intent is not installed.')
|
|
16
|
-
console.error('')
|
|
17
|
-
console.error('Install it as a dev dependency:')
|
|
18
|
-
console.error(' npm add -D @tanstack/intent')
|
|
19
|
-
console.error('')
|
|
20
|
-
console.error('Or run directly:')
|
|
21
|
-
console.error(' npx @tanstack/intent@latest list')
|
|
22
|
-
process.exit(1)
|
|
23
|
-
}
|
|
24
|
-
throw e
|
|
25
|
-
}
|