@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.3
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/index.cjs +8 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +6 -2
- package/dist/cjs/load-matches.cjs +636 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +16 -0
- package/dist/cjs/qss.cjs +19 -19
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/qss.d.cts +6 -4
- package/dist/cjs/redirect.cjs +3 -3
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/router.cjs +33 -702
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +14 -37
- package/dist/cjs/scroll-restoration.cjs +20 -25
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +7 -15
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/constants.cjs +5 -0
- package/dist/cjs/ssr/constants.cjs.map +1 -0
- package/dist/cjs/ssr/constants.d.cts +1 -0
- package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
- package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
- package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
- package/dist/cjs/ssr/ssr-client.cjs +15 -1
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-client.d.cts +5 -1
- package/dist/cjs/ssr/ssr-server.cjs +12 -10
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +0 -1
- package/dist/cjs/ssr/tsrScript.cjs +1 -1
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
- package/dist/cjs/utils.cjs +8 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/index.d.ts +6 -2
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/load-matches.d.ts +16 -0
- package/dist/esm/load-matches.js +636 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/qss.d.ts +6 -4
- package/dist/esm/qss.js +19 -19
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/redirect.js +3 -3
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/router.d.ts +14 -37
- package/dist/esm/router.js +33 -702
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +20 -25
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +7 -15
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/constants.d.ts +1 -0
- package/dist/esm/ssr/constants.js +5 -0
- package/dist/esm/ssr/constants.js.map +1 -0
- package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
- package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
- package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
- package/dist/esm/ssr/serializer/transformer.js +50 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/ssr-client.d.ts +5 -1
- package/dist/esm/ssr/ssr-client.js +15 -1
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +0 -1
- package/dist/esm/ssr/ssr-server.js +12 -10
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/tsrScript.js +1 -1
- package/dist/esm/ssr/tsrScript.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +8 -7
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +12 -2
- package/src/load-matches.ts +955 -0
- package/src/qss.ts +27 -24
- package/src/redirect.ts +3 -3
- package/src/router.ts +62 -1048
- package/src/scroll-restoration.ts +25 -32
- package/src/searchParams.ts +8 -19
- package/src/ssr/constants.ts +1 -0
- package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
- package/src/ssr/serializer/seroval-plugins.ts +9 -0
- package/src/ssr/serializer/transformer.ts +78 -0
- package/src/ssr/ssr-client.ts +30 -3
- package/src/ssr/ssr-server.ts +18 -10
- package/src/ssr/tsrScript.ts +5 -1
- package/src/utils.ts +11 -10
- package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
- 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
|
-
|
|
31
|
+
// silent
|
|
32
32
|
}
|
|
33
33
|
return undefined
|
|
34
34
|
}
|
|
@@ -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.
|
|
91
|
-
`${el.tagName}:nth-child(${
|
|
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,7 +120,7 @@ export function restoreScroll({
|
|
|
120
120
|
|
|
121
121
|
try {
|
|
122
122
|
byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
|
|
123
|
-
} catch (error
|
|
123
|
+
} catch (error) {
|
|
124
124
|
console.error(error)
|
|
125
125
|
return
|
|
126
126
|
}
|
|
@@ -132,7 +132,7 @@ export function restoreScroll({
|
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
window.history.state?.__hashScrollIntoViewOptions ?? true
|
|
172
172
|
|
|
173
173
|
if (hashScrollIntoViewOptions) {
|
|
174
174
|
const el = document.getElementById(hash)
|
|
@@ -177,30 +177,24 @@ export function restoreScroll({
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
|
@@ -294,11 +288,10 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
294
288
|
const restoreKey = getKey(router.state.location)
|
|
295
289
|
|
|
296
290
|
scrollRestorationCache.set((state) => {
|
|
297
|
-
const keyEntry = (state[restoreKey]
|
|
298
|
-
state[restoreKey] || ({} as ScrollRestorationByElement))
|
|
291
|
+
const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)
|
|
299
292
|
|
|
300
|
-
const elementEntry = (keyEntry[elementSelector]
|
|
301
|
-
|
|
293
|
+
const elementEntry = (keyEntry[elementSelector] ||=
|
|
294
|
+
{} as ScrollRestorationEntry)
|
|
302
295
|
|
|
303
296
|
if (elementSelector === 'window') {
|
|
304
297
|
elementEntry.scrollX = window.scrollX || 0
|
|
@@ -344,7 +337,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
344
337
|
if (router.isScrollRestoring) {
|
|
345
338
|
// Mark the location as having been seen
|
|
346
339
|
scrollRestorationCache.set((state) => {
|
|
347
|
-
state[cacheKey]
|
|
340
|
+
state[cacheKey] ||= {} as ScrollRestorationByElement
|
|
348
341
|
|
|
349
342
|
return state
|
|
350
343
|
})
|
package/src/searchParams.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
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 (
|
|
43
|
+
} catch (_err) {
|
|
43
44
|
// silent
|
|
44
45
|
}
|
|
45
|
-
} else if (
|
|
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 (
|
|
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
|
-
|
|
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: '
|
|
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
|
+
}
|
package/src/ssr/ssr-client.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -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 {
|
|
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}
|
|
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:
|
|
111
|
-
|
|
112
|
-
plugins: [ReadableStreamPlugin, ShallowErrorPlugin],
|
|
116
|
+
refs: new Map(),
|
|
117
|
+
plugins: [...plugins, ...defaultSerovalPlugins],
|
|
113
118
|
onSerialize: (data, initial) => {
|
|
114
|
-
|
|
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,
|
package/src/ssr/tsrScript.ts
CHANGED
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;"}
|