@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.4

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.
Files changed (102) hide show
  1. package/dist/cjs/index.cjs +8 -2
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +6 -2
  4. package/dist/cjs/load-matches.cjs +636 -0
  5. package/dist/cjs/load-matches.cjs.map +1 -0
  6. package/dist/cjs/load-matches.d.cts +16 -0
  7. package/dist/cjs/qss.cjs +19 -19
  8. package/dist/cjs/qss.cjs.map +1 -1
  9. package/dist/cjs/qss.d.cts +6 -4
  10. package/dist/cjs/redirect.cjs +3 -3
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/router.cjs +33 -702
  13. package/dist/cjs/router.cjs.map +1 -1
  14. package/dist/cjs/router.d.cts +18 -39
  15. package/dist/cjs/scroll-restoration.cjs +32 -29
  16. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  17. package/dist/cjs/scroll-restoration.d.cts +1 -1
  18. package/dist/cjs/searchParams.cjs +7 -15
  19. package/dist/cjs/searchParams.cjs.map +1 -1
  20. package/dist/cjs/ssr/constants.cjs +5 -0
  21. package/dist/cjs/ssr/constants.cjs.map +1 -0
  22. package/dist/cjs/ssr/constants.d.cts +1 -0
  23. package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
  24. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  25. package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
  26. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
  27. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  28. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  29. package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
  30. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  31. package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
  32. package/dist/cjs/ssr/ssr-client.cjs +15 -1
  33. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  34. package/dist/cjs/ssr/ssr-client.d.cts +5 -1
  35. package/dist/cjs/ssr/ssr-server.cjs +12 -10
  36. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  37. package/dist/cjs/ssr/ssr-server.d.cts +0 -1
  38. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  39. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  40. package/dist/cjs/utils.cjs +8 -7
  41. package/dist/cjs/utils.cjs.map +1 -1
  42. package/dist/cjs/utils.d.cts +1 -1
  43. package/dist/esm/index.d.ts +6 -2
  44. package/dist/esm/index.js +9 -3
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/load-matches.d.ts +16 -0
  47. package/dist/esm/load-matches.js +636 -0
  48. package/dist/esm/load-matches.js.map +1 -0
  49. package/dist/esm/qss.d.ts +6 -4
  50. package/dist/esm/qss.js +19 -19
  51. package/dist/esm/qss.js.map +1 -1
  52. package/dist/esm/redirect.js +3 -3
  53. package/dist/esm/redirect.js.map +1 -1
  54. package/dist/esm/router.d.ts +18 -39
  55. package/dist/esm/router.js +33 -702
  56. package/dist/esm/router.js.map +1 -1
  57. package/dist/esm/scroll-restoration.d.ts +1 -1
  58. package/dist/esm/scroll-restoration.js +32 -29
  59. package/dist/esm/scroll-restoration.js.map +1 -1
  60. package/dist/esm/searchParams.js +7 -15
  61. package/dist/esm/searchParams.js.map +1 -1
  62. package/dist/esm/ssr/constants.d.ts +1 -0
  63. package/dist/esm/ssr/constants.js +5 -0
  64. package/dist/esm/ssr/constants.js.map +1 -0
  65. package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
  66. package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
  67. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  68. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  69. package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
  70. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  71. package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
  72. package/dist/esm/ssr/serializer/transformer.js +50 -0
  73. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  74. package/dist/esm/ssr/ssr-client.d.ts +5 -1
  75. package/dist/esm/ssr/ssr-client.js +15 -1
  76. package/dist/esm/ssr/ssr-client.js.map +1 -1
  77. package/dist/esm/ssr/ssr-server.d.ts +0 -1
  78. package/dist/esm/ssr/ssr-server.js +12 -10
  79. package/dist/esm/ssr/ssr-server.js.map +1 -1
  80. package/dist/esm/ssr/tsrScript.js +1 -1
  81. package/dist/esm/ssr/tsrScript.js.map +1 -1
  82. package/dist/esm/utils.d.ts +1 -1
  83. package/dist/esm/utils.js +8 -7
  84. package/dist/esm/utils.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/index.ts +12 -2
  87. package/src/load-matches.ts +955 -0
  88. package/src/qss.ts +27 -24
  89. package/src/redirect.ts +3 -3
  90. package/src/router.ts +66 -1050
  91. package/src/scroll-restoration.ts +42 -37
  92. package/src/searchParams.ts +8 -19
  93. package/src/ssr/constants.ts +1 -0
  94. package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
  95. package/src/ssr/serializer/seroval-plugins.ts +9 -0
  96. package/src/ssr/serializer/transformer.ts +78 -0
  97. package/src/ssr/ssr-client.ts +30 -3
  98. package/src/ssr/ssr-server.ts +18 -10
  99. package/src/ssr/tsrScript.ts +5 -1
  100. package/src/utils.ts +11 -10
  101. package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
  102. package/dist/esm/ssr/seroval-plugins.js.map +0 -1
@@ -28,7 +28,7 @@ function getSafeSessionStorage() {
28
28
  return window.sessionStorage
29
29
  }
30
30
  } catch {
31
- return undefined
31
+ // silent
32
32
  }
33
33
  return undefined
34
34
  }
@@ -47,10 +47,10 @@ const throttle = (fn: (...args: Array<any>) => void, wait: number) => {
47
47
  }
48
48
  }
49
49
 
50
- function createScrollRestorationCache(): ScrollRestorationCache | undefined {
50
+ function createScrollRestorationCache(): ScrollRestorationCache | null {
51
51
  const safeSessionStorage = getSafeSessionStorage()
52
52
  if (!safeSessionStorage) {
53
- return undefined
53
+ return null
54
54
  }
55
55
 
56
56
  const persistedState = safeSessionStorage.getItem(storageKey)
@@ -85,14 +85,14 @@ export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
85
85
 
86
86
  export function getCssSelector(el: any): string {
87
87
  const path = []
88
- let parent
88
+ let parent: HTMLElement
89
89
  while ((parent = el.parentNode)) {
90
- path.unshift(
91
- `${el.tagName}:nth-child(${([].indexOf as any).call(parent.children, el) + 1})`,
90
+ path.push(
91
+ `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,
92
92
  )
93
93
  el = parent
94
94
  }
95
- return `${path.join(' > ')}`.toLowerCase()
95
+ return `${path.reverse().join(' > ')}`.toLowerCase()
96
96
  }
97
97
 
98
98
  let ignoreScroll = false
@@ -120,19 +120,19 @@ export function restoreScroll({
120
120
 
121
121
  try {
122
122
  byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
123
- } catch (error: any) {
123
+ } catch (error) {
124
124
  console.error(error)
125
125
  return
126
126
  }
127
127
 
128
- const resolvedKey = key || window.history.state?.key
128
+ const resolvedKey = key || window.history.state?.__TSR_key
129
129
  const elementEntries = byKey[resolvedKey]
130
130
 
131
131
  //
132
132
  ignoreScroll = true
133
133
 
134
134
  //
135
- ;(() => {
135
+ scroll: {
136
136
  // If we have a cached entry for this location state,
137
137
  // we always need to prefer that over the hash scroll.
138
138
  if (
@@ -157,18 +157,18 @@ export function restoreScroll({
157
157
  }
158
158
  }
159
159
 
160
- return
160
+ break scroll
161
161
  }
162
162
 
163
163
  // If we don't have a cached entry for the hash,
164
164
  // Which means we've never seen this location before,
165
165
  // we need to check if there is a hash in the URL.
166
166
  // If there is, we need to scroll it's ID into view.
167
- const hash = (location ?? window.location).hash.split('#')[1]
167
+ const hash = (location ?? window.location).hash.split('#', 2)[1]
168
168
 
169
169
  if (hash) {
170
170
  const hashScrollIntoViewOptions =
171
- (window.history.state || {}).__hashScrollIntoViewOptions ?? true
171
+ window.history.state?.__hashScrollIntoViewOptions ?? true
172
172
 
173
173
  if (hashScrollIntoViewOptions) {
174
174
  const el = document.getElementById(hash)
@@ -177,37 +177,31 @@ export function restoreScroll({
177
177
  }
178
178
  }
179
179
 
180
- return
180
+ break scroll
181
181
  }
182
182
 
183
183
  // If there is no cached entry for the hash and there is no hash in the URL,
184
184
  // we need to scroll to the top of the page for every scrollToTop element
185
- ;[
186
- 'window',
187
- ...(scrollToTopSelectors?.filter((d) => d !== 'window') ?? []),
188
- ].forEach((selector) => {
189
- const element =
190
- selector === 'window'
191
- ? window
192
- : typeof selector === 'function'
185
+ const scrollOptions = { top: 0, left: 0, behavior }
186
+ window.scrollTo(scrollOptions)
187
+ if (scrollToTopSelectors) {
188
+ for (const selector of scrollToTopSelectors) {
189
+ if (selector === 'window') continue
190
+ const element =
191
+ typeof selector === 'function'
193
192
  ? selector()
194
193
  : document.querySelector(selector)
195
- if (element) {
196
- element.scrollTo({
197
- top: 0,
198
- left: 0,
199
- behavior,
200
- })
194
+ if (element) element.scrollTo(scrollOptions)
201
195
  }
202
- })
203
- })()
196
+ }
197
+ }
204
198
 
205
199
  //
206
200
  ignoreScroll = false
207
201
  }
208
202
 
209
203
  export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
210
- if (scrollRestorationCache === undefined) {
204
+ if (!scrollRestorationCache && !router.isServer) {
211
205
  return
212
206
  }
213
207
  const shouldScrollRestoration =
@@ -217,7 +211,11 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
217
211
  router.isScrollRestoring = true
218
212
  }
219
213
 
220
- if (typeof document === 'undefined' || router.isScrollRestorationSetup) {
214
+ if (
215
+ router.isServer ||
216
+ router.isScrollRestorationSetup ||
217
+ !scrollRestorationCache
218
+ ) {
221
219
  return
222
220
  }
223
221
 
@@ -294,11 +292,10 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
294
292
  const restoreKey = getKey(router.state.location)
295
293
 
296
294
  scrollRestorationCache.set((state) => {
297
- const keyEntry = (state[restoreKey] =
298
- state[restoreKey] || ({} as ScrollRestorationByElement))
295
+ const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)
299
296
 
300
- const elementEntry = (keyEntry[elementSelector] =
301
- keyEntry[elementSelector] || ({} as ScrollRestorationEntry))
297
+ const elementEntry = (keyEntry[elementSelector] ||=
298
+ {} as ScrollRestorationEntry)
302
299
 
303
300
  if (elementSelector === 'window') {
304
301
  elementEntry.scrollX = window.scrollX || 0
@@ -331,6 +328,14 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
331
328
  router.resetNextScroll = true
332
329
  return
333
330
  }
331
+ if (typeof router.options.scrollRestoration === 'function') {
332
+ const shouldRestore = router.options.scrollRestoration({
333
+ location: router.latestLocation,
334
+ })
335
+ if (!shouldRestore) {
336
+ return
337
+ }
338
+ }
334
339
 
335
340
  restoreScroll({
336
341
  storageKey,
@@ -344,7 +349,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
344
349
  if (router.isScrollRestoring) {
345
350
  // Mark the location as having been seen
346
351
  scrollRestorationCache.set((state) => {
347
- state[cacheKey] = state[cacheKey] || ({} as ScrollRestorationByElement)
352
+ state[cacheKey] ||= {} as ScrollRestorationByElement
348
353
 
349
354
  return state
350
355
  })
@@ -9,7 +9,7 @@ export const defaultStringifySearch = stringifySearchWith(
9
9
 
10
10
  export function parseSearchWith(parser: (str: string) => any) {
11
11
  return (searchStr: string): AnySchema => {
12
- if (searchStr.substring(0, 1) === '?') {
12
+ if (searchStr[0] === '?') {
13
13
  searchStr = searchStr.substring(1)
14
14
  }
15
15
 
@@ -21,8 +21,8 @@ export function parseSearchWith(parser: (str: string) => any) {
21
21
  if (typeof value === 'string') {
22
22
  try {
23
23
  query[key] = parser(value)
24
- } catch (err) {
25
- //
24
+ } catch (_err) {
25
+ // silent
26
26
  }
27
27
  }
28
28
  }
@@ -35,20 +35,21 @@ export function stringifySearchWith(
35
35
  stringify: (search: any) => string,
36
36
  parser?: (str: string) => any,
37
37
  ) {
38
+ const hasParser = typeof parser === 'function'
38
39
  function stringifyValue(val: any) {
39
40
  if (typeof val === 'object' && val !== null) {
40
41
  try {
41
42
  return stringify(val)
42
- } catch (err) {
43
+ } catch (_err) {
43
44
  // silent
44
45
  }
45
- } else if (typeof val === 'string' && typeof parser === 'function') {
46
+ } else if (hasParser && typeof val === 'string') {
46
47
  try {
47
48
  // Check if it's a valid parseable string.
48
49
  // If it is, then stringify it again.
49
50
  parser(val)
50
51
  return stringify(val)
51
- } catch (err) {
52
+ } catch (_err) {
52
53
  // silent
53
54
  }
54
55
  }
@@ -56,19 +57,7 @@ export function stringifySearchWith(
56
57
  }
57
58
 
58
59
  return (search: Record<string, any>) => {
59
- search = { ...search }
60
-
61
- Object.keys(search).forEach((key) => {
62
- const val = search[key]
63
- if (typeof val === 'undefined' || val === undefined) {
64
- delete search[key]
65
- } else {
66
- search[key] = stringifyValue(val)
67
- }
68
- })
69
-
70
- const searchStr = encode(search as Record<string, string>).toString()
71
-
60
+ const searchStr = encode(search, stringifyValue)
72
61
  return searchStr ? `?${searchStr}` : ''
73
62
  }
74
63
  }
@@ -0,0 +1 @@
1
+ export const GLOBAL_TSR = '$_TSR'
@@ -1,7 +1,7 @@
1
1
  import { createPlugin } from 'seroval'
2
2
  import type { SerovalNode } from 'seroval'
3
3
 
4
- interface ErrorNode {
4
+ export interface ErrorNode {
5
5
  message: SerovalNode
6
6
  }
7
7
 
@@ -13,7 +13,7 @@ export const ShallowErrorPlugin = /* @__PURE__ */ createPlugin<
13
13
  Error,
14
14
  ErrorNode
15
15
  >({
16
- tag: 'tanstack-start:seroval-plugins/Error',
16
+ tag: '$TSR/Error',
17
17
  test(value) {
18
18
  return value instanceof Error
19
19
  },
@@ -0,0 +1,9 @@
1
+ import { ReadableStreamPlugin } from 'seroval-plugins/web'
2
+ import { ShallowErrorPlugin } from './ShallowErrorPlugin'
3
+ import type { Plugin } from 'seroval'
4
+
5
+ export const defaultSerovalPlugins = [
6
+ ShallowErrorPlugin as Plugin<Error, any>,
7
+ // ReadableStreamNode is not exported by seroval
8
+ ReadableStreamPlugin as Plugin<ReadableStream, any>,
9
+ ]
@@ -0,0 +1,78 @@
1
+ import { createPlugin } from 'seroval'
2
+ import { GLOBAL_TSR } from '../constants'
3
+ import type { SerovalNode } from 'seroval'
4
+
5
+ export type Transformer<TInput, TTransformed> = {
6
+ key: string
7
+ test: (value: any) => value is TInput
8
+ toSerializable: (value: TInput) => TTransformed
9
+ fromSerializable: (value: TTransformed) => TInput
10
+ }
11
+
12
+ export type AnyTransformer = Transformer<any, any>
13
+
14
+ export function createSerializationAdapter<
15
+ TKey extends string,
16
+ TInput,
17
+ TTransformed /* we need to check that this type is actually serializable taking into account all seroval native types and any custom plugin WE=router/start add!!! */,
18
+ >(opts: {
19
+ key: TKey
20
+ test: (value: any) => value is TInput
21
+ toSerializable: (value: TInput) => TTransformed
22
+ fromSerializable: (value: TTransformed) => TInput
23
+ }): Transformer<TInput, TTransformed> {
24
+ return opts
25
+ }
26
+
27
+ export function makeSsrSerovalPlugin<TInput, TTransformed>(
28
+ transformer: Transformer<TInput, TTransformed>,
29
+ options: { didRun: boolean },
30
+ ) {
31
+ return createPlugin<TInput, SerovalNode>({
32
+ tag: '$TSR/t/' + transformer.key,
33
+ test: transformer.test,
34
+ parse: {
35
+ stream(value, ctx) {
36
+ return ctx.parse(transformer.toSerializable(value))
37
+ },
38
+ },
39
+ serialize(node, ctx) {
40
+ options.didRun = true
41
+ return (
42
+ GLOBAL_TSR +
43
+ '.t.get("' +
44
+ transformer.key +
45
+ '")(' +
46
+ ctx.serialize(node) +
47
+ ')'
48
+ )
49
+ },
50
+ // we never deserialize on the server during SSR
51
+ deserialize: undefined as never,
52
+ })
53
+ }
54
+
55
+ export function makeSerovalPlugin<TInput, TTransformed>(
56
+ transformer: Transformer<TInput, TTransformed>,
57
+ ) {
58
+ return createPlugin<TInput, SerovalNode>({
59
+ tag: '$TSR/t/' + transformer.key,
60
+ test: transformer.test,
61
+ parse: {
62
+ sync(value, ctx) {
63
+ return ctx.parse(transformer.toSerializable(value))
64
+ },
65
+ async async(value, ctx) {
66
+ return await ctx.parse(transformer.toSerializable(value))
67
+ },
68
+ stream(value, ctx) {
69
+ return ctx.parse(transformer.toSerializable(value))
70
+ },
71
+ },
72
+ // we don't generate JS code outside of SSR (for now)
73
+ serialize: undefined as never,
74
+ deserialize(node, ctx) {
75
+ return transformer.fromSerializable(ctx.deserialize(node) as TTransformed)
76
+ },
77
+ })
78
+ }
@@ -5,7 +5,8 @@ import type { AnyRouteMatch, MakeRouteMatch } from '../Matches'
5
5
  import type { AnyRouter } from '../router'
6
6
  import type { Manifest } from '../manifest'
7
7
  import type { RouteContextOptions } from '../route'
8
- import type { GLOBAL_TSR } from './ssr-server'
8
+ import type { AnyTransformer } from './serializer/transformer'
9
+ import type { GLOBAL_TSR } from './constants'
9
10
 
10
11
  declare global {
11
12
  interface Window {
@@ -15,8 +16,15 @@ declare global {
15
16
 
16
17
  export interface TsrSsrGlobal {
17
18
  router?: DehydratedRouter
18
- // clean scripts, shortened since this is sent for each streamed script
19
+ // clean scripts; shortened since this is sent for each streamed script
19
20
  c: () => void
21
+ // push script into buffer; shortened since this is sent for each streamed script as soon as the first custom transformer was invoked
22
+ p: (script: () => void) => void
23
+ buffer: Array<() => void>
24
+ // custom transformers, shortened since this is sent for each streamed value that needs a custom transformer
25
+ t?: Map<string, (value: any) => any>
26
+ // this flag indicates whether the transformers were initialized
27
+ initialized?: boolean
20
28
  }
21
29
 
22
30
  function hydrateMatch(
@@ -50,7 +58,26 @@ export interface DehydratedRouter {
50
58
 
51
59
  export async function hydrate(router: AnyRouter): Promise<any> {
52
60
  invariant(
53
- window.$_TSR?.router,
61
+ window.$_TSR,
62
+ 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',
63
+ )
64
+
65
+ const serializationAdapters = router.options.serializationAdapters as
66
+ | Array<AnyTransformer>
67
+ | undefined
68
+
69
+ if (serializationAdapters?.length) {
70
+ const fromSerializableMap = new Map()
71
+ serializationAdapters.forEach((adapter) => {
72
+ fromSerializableMap.set(adapter.key, adapter.fromSerializable)
73
+ })
74
+ window.$_TSR.t = fromSerializableMap
75
+ window.$_TSR.buffer.forEach((script) => script())
76
+ }
77
+ window.$_TSR.initialized = true
78
+
79
+ invariant(
80
+ window.$_TSR.router,
54
81
  'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',
55
82
  )
56
83
 
@@ -1,14 +1,16 @@
1
1
  import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
2
- import { ReadableStreamPlugin } from 'seroval-plugins/web'
3
2
  import invariant from 'tiny-invariant'
4
3
  import { createControlledPromise } from '../utils'
5
4
  import minifiedTsrBootStrapScript from './tsrScript?script-string'
6
- import { ShallowErrorPlugin } from './seroval-plugins'
5
+ import { GLOBAL_TSR } from './constants'
6
+ import { defaultSerovalPlugins } from './serializer/seroval-plugins'
7
+ import { makeSsrSerovalPlugin } from './serializer/transformer'
7
8
  import type { AnyRouter } from '../router'
8
9
  import type { DehydratedMatch } from './ssr-client'
9
10
  import type { DehydratedRouter } from './client'
10
11
  import type { AnyRouteMatch } from '../Matches'
11
12
  import type { Manifest } from '../manifest'
13
+ import type { AnyTransformer } from './serializer/transformer'
12
14
 
13
15
  declare module '../router' {
14
16
  interface ServerSsr {
@@ -22,7 +24,6 @@ declare module '../router' {
22
24
  }
23
25
  }
24
26
 
25
- export const GLOBAL_TSR = '$_TSR'
26
27
  const SCOPE_ID = 'tsr'
27
28
 
28
29
  export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {
@@ -54,8 +55,6 @@ export function attachRouterServerSsrUtils(
54
55
  router.ssr = {
55
56
  manifest,
56
57
  }
57
- const serializationRefs = new Map<unknown, number>()
58
-
59
58
  let initialScriptSent = false
60
59
  const getInitialScript = () => {
61
60
  if (initialScriptSent) {
@@ -82,7 +81,7 @@ export function attachRouterServerSsrUtils(
82
81
  injectScript: (getScript) => {
83
82
  return router.serverSsr!.injectHtml(async () => {
84
83
  const script = await getScript()
85
- return `<script class='$tsr'>${getInitialScript()}${script};if (typeof $_TSR !== 'undefined') $_TSR.c()</script>`
84
+ return `<script class='$tsr'>${getInitialScript()}${script};$_TSR.c()</script>`
86
85
  })
87
86
  },
88
87
  dehydrate: async () => {
@@ -106,12 +105,21 @@ export function attachRouterServerSsrUtils(
106
105
  _dehydrated = true
107
106
 
108
107
  const p = createControlledPromise<string>()
108
+ const trackPlugins = { didRun: false }
109
+ const plugins =
110
+ (
111
+ router.options.serializationAdapters as
112
+ | Array<AnyTransformer>
113
+ | undefined
114
+ )?.map((t) => makeSsrSerovalPlugin(t, trackPlugins)) ?? []
109
115
  crossSerializeStream(dehydratedRouter, {
110
- refs: serializationRefs,
111
- // TODO make plugins configurable
112
- plugins: [ReadableStreamPlugin, ShallowErrorPlugin],
116
+ refs: new Map(),
117
+ plugins: [...plugins, ...defaultSerovalPlugins],
113
118
  onSerialize: (data, initial) => {
114
- const serialized = initial ? `${GLOBAL_TSR}["router"]=` + data : data
119
+ let serialized = initial ? GLOBAL_TSR + '.router=' + data : data
120
+ if (trackPlugins.didRun) {
121
+ serialized = GLOBAL_TSR + '.p(()=>' + serialized + ')'
122
+ }
115
123
  router.serverSsr!.injectScript(() => serialized)
116
124
  },
117
125
  scopeId: SCOPE_ID,
@@ -1,7 +1,11 @@
1
1
  self.$_TSR = {
2
- c: () => {
2
+ c() {
3
3
  document.querySelectorAll('.\\$tsr').forEach((o) => {
4
4
  o.remove()
5
5
  })
6
6
  },
7
+ p(script) {
8
+ !this.initialized ? this.buffer.push(script) : script()
9
+ },
10
+ buffer: [],
7
11
  }
package/src/utils.ts CHANGED
@@ -203,16 +203,6 @@ export function functionalUpdate<TPrevious, TResult = TPrevious>(
203
203
  return updater
204
204
  }
205
205
 
206
- export function pick<TValue, TKey extends keyof TValue>(
207
- parent: TValue,
208
- keys: Array<TKey>,
209
- ): Pick<TValue, TKey> {
210
- return keys.reduce((obj: any, key: TKey) => {
211
- obj[key] = parent[key]
212
- return obj
213
- }, {} as any)
214
- }
215
-
216
206
  /**
217
207
  * This function returns `prev` if `_next` is deeply equal.
218
208
  * If not, it will replace any deeply equal children of `b` with those of `a`.
@@ -442,3 +432,14 @@ export function isPromise<T>(
442
432
  typeof (value as Promise<T>).then === 'function',
443
433
  )
444
434
  }
435
+
436
+ export function findLast<T>(
437
+ array: ReadonlyArray<T>,
438
+ predicate: (item: T) => boolean,
439
+ ): T | undefined {
440
+ for (let i = array.length - 1; i >= 0; i--) {
441
+ const item = array[i]!
442
+ if (predicate(item)) return item
443
+ }
444
+ return undefined
445
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"seroval-plugins.cjs","sources":["../../../src/ssr/seroval-plugins.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport type { SerovalNode } from 'seroval'\n\ninterface ErrorNode {\n message: SerovalNode\n}\n\n/**\n * this plugin serializes only the `message` part of an Error\n * this helps with serializing e.g. a ZodError which has functions attached that cannot be serialized\n */\nexport const ShallowErrorPlugin = /* @__PURE__ */ createPlugin<\n Error,\n ErrorNode\n>({\n tag: 'tanstack-start:seroval-plugins/Error',\n test(value) {\n return value instanceof Error\n },\n parse: {\n sync(value, ctx) {\n return {\n message: ctx.parse(value.message),\n }\n },\n async async(value, ctx) {\n return {\n message: await ctx.parse(value.message),\n }\n },\n stream(value, ctx) {\n return {\n message: ctx.parse(value.message),\n }\n },\n },\n serialize(node, ctx) {\n return 'new Error(' + ctx.serialize(node.message) + ')'\n },\n deserialize(node, ctx) {\n return new Error(ctx.deserialize(node.message) as string)\n },\n})\n"],"names":["createPlugin"],"mappings":";;;AAWO,MAAM,qBAAqCA,wBAAAA,aAGhD;AAAA,EACA,KAAK;AAAA,EACL,KAAK,OAAO;AACV,WAAO,iBAAiB;AAAA,EAC1B;AAAA,EACA,OAAO;AAAA,IACL,KAAK,OAAO,KAAK;AACf,aAAO;AAAA,QACL,SAAS,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAEpC;AAAA,IACA,MAAM,MAAM,OAAO,KAAK;AACtB,aAAO;AAAA,QACL,SAAS,MAAM,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAE1C;AAAA,IACA,OAAO,OAAO,KAAK;AACjB,aAAO;AAAA,QACL,SAAS,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAEpC;AAAA,EAAA;AAAA,EAEF,UAAU,MAAM,KAAK;AACnB,WAAO,eAAe,IAAI,UAAU,KAAK,OAAO,IAAI;AAAA,EACtD;AAAA,EACA,YAAY,MAAM,KAAK;AACrB,WAAO,IAAI,MAAM,IAAI,YAAY,KAAK,OAAO,CAAW;AAAA,EAC1D;AACF,CAAC;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"seroval-plugins.js","sources":["../../../src/ssr/seroval-plugins.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport type { SerovalNode } from 'seroval'\n\ninterface ErrorNode {\n message: SerovalNode\n}\n\n/**\n * this plugin serializes only the `message` part of an Error\n * this helps with serializing e.g. a ZodError which has functions attached that cannot be serialized\n */\nexport const ShallowErrorPlugin = /* @__PURE__ */ createPlugin<\n Error,\n ErrorNode\n>({\n tag: 'tanstack-start:seroval-plugins/Error',\n test(value) {\n return value instanceof Error\n },\n parse: {\n sync(value, ctx) {\n return {\n message: ctx.parse(value.message),\n }\n },\n async async(value, ctx) {\n return {\n message: await ctx.parse(value.message),\n }\n },\n stream(value, ctx) {\n return {\n message: ctx.parse(value.message),\n }\n },\n },\n serialize(node, ctx) {\n return 'new Error(' + ctx.serialize(node.message) + ')'\n },\n deserialize(node, ctx) {\n return new Error(ctx.deserialize(node.message) as string)\n },\n})\n"],"names":[],"mappings":";AAWO,MAAM,qBAAqC,6BAGhD;AAAA,EACA,KAAK;AAAA,EACL,KAAK,OAAO;AACV,WAAO,iBAAiB;AAAA,EAC1B;AAAA,EACA,OAAO;AAAA,IACL,KAAK,OAAO,KAAK;AACf,aAAO;AAAA,QACL,SAAS,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAEpC;AAAA,IACA,MAAM,MAAM,OAAO,KAAK;AACtB,aAAO;AAAA,QACL,SAAS,MAAM,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAE1C;AAAA,IACA,OAAO,OAAO,KAAK;AACjB,aAAO;AAAA,QACL,SAAS,IAAI,MAAM,MAAM,OAAO;AAAA,MAAA;AAAA,IAEpC;AAAA,EAAA;AAAA,EAEF,UAAU,MAAM,KAAK;AACnB,WAAO,eAAe,IAAI,UAAU,KAAK,OAAO,IAAI;AAAA,EACtD;AAAA,EACA,YAAY,MAAM,KAAK;AACrB,WAAO,IAAI,MAAM,IAAI,YAAY,KAAK,OAAO,CAAW;AAAA,EAC1D;AACF,CAAC;"}