@tanstack/vue-router 1.140.5 → 1.141.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/esm/Asset.js +122 -8
  2. package/dist/esm/Asset.js.map +1 -1
  3. package/dist/esm/Body.d.ts +4 -0
  4. package/dist/esm/Body.js +26 -0
  5. package/dist/esm/Body.js.map +1 -0
  6. package/dist/esm/CatchBoundary.d.ts +1 -1
  7. package/dist/esm/CatchBoundary.js +8 -8
  8. package/dist/esm/CatchBoundary.js.map +1 -1
  9. package/dist/esm/Html.d.ts +4 -0
  10. package/dist/esm/Html.js +63 -0
  11. package/dist/esm/Html.js.map +1 -0
  12. package/dist/esm/Match.js +87 -49
  13. package/dist/esm/Match.js.map +1 -1
  14. package/dist/esm/Matches.js +3 -2
  15. package/dist/esm/Matches.js.map +1 -1
  16. package/dist/esm/RouterProvider.js +3 -0
  17. package/dist/esm/RouterProvider.js.map +1 -1
  18. package/dist/esm/ScriptOnce.d.ts +12 -5
  19. package/dist/esm/ScriptOnce.js +35 -15
  20. package/dist/esm/ScriptOnce.js.map +1 -1
  21. package/dist/esm/Scripts.d.ts +2 -1
  22. package/dist/esm/Scripts.js +101 -35
  23. package/dist/esm/Scripts.js.map +1 -1
  24. package/dist/esm/Transitioner.d.ts +16 -0
  25. package/dist/esm/Transitioner.js +136 -133
  26. package/dist/esm/Transitioner.js.map +1 -1
  27. package/dist/esm/awaited.d.ts +20 -5
  28. package/dist/esm/awaited.js +17 -20
  29. package/dist/esm/awaited.js.map +1 -1
  30. package/dist/esm/index.d.ts +2 -0
  31. package/dist/esm/index.js +4 -0
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/lazyRouteComponent.js +2 -2
  34. package/dist/esm/lazyRouteComponent.js.map +1 -1
  35. package/dist/esm/link.js +27 -35
  36. package/dist/esm/link.js.map +1 -1
  37. package/dist/esm/scroll-restoration.d.ts +8 -1
  38. package/dist/esm/scroll-restoration.js +44 -12
  39. package/dist/esm/scroll-restoration.js.map +1 -1
  40. package/dist/esm/ssr/RouterClient.d.ts +15 -0
  41. package/dist/esm/ssr/RouterClient.js +46 -0
  42. package/dist/esm/ssr/RouterClient.js.map +1 -0
  43. package/dist/esm/ssr/RouterServer.d.ts +15 -0
  44. package/dist/esm/ssr/RouterServer.js +37 -0
  45. package/dist/esm/ssr/RouterServer.js.map +1 -0
  46. package/dist/esm/ssr/client.d.ts +1 -0
  47. package/dist/esm/ssr/client.js +5 -0
  48. package/dist/esm/ssr/client.js.map +1 -0
  49. package/dist/esm/ssr/defaultRenderHandler.d.ts +1 -0
  50. package/dist/esm/ssr/defaultRenderHandler.js +15 -0
  51. package/dist/esm/ssr/defaultRenderHandler.js.map +1 -0
  52. package/dist/esm/ssr/defaultStreamHandler.d.ts +1 -0
  53. package/dist/esm/ssr/defaultStreamHandler.js +17 -0
  54. package/dist/esm/ssr/defaultStreamHandler.js.map +1 -0
  55. package/dist/esm/ssr/renderRouterToStream.d.ts +8 -0
  56. package/dist/esm/ssr/renderRouterToStream.js +70 -0
  57. package/dist/esm/ssr/renderRouterToStream.js.map +1 -0
  58. package/dist/esm/ssr/renderRouterToString.d.ts +7 -0
  59. package/dist/esm/ssr/renderRouterToString.js +33 -0
  60. package/dist/esm/ssr/renderRouterToString.js.map +1 -0
  61. package/dist/esm/ssr/server.d.ts +6 -0
  62. package/dist/esm/ssr/server.js +14 -0
  63. package/dist/esm/ssr/server.js.map +1 -0
  64. package/dist/source/Asset.jsx +119 -7
  65. package/dist/source/Asset.jsx.map +1 -1
  66. package/dist/source/Body.d.ts +4 -0
  67. package/dist/source/Body.jsx +15 -0
  68. package/dist/source/Body.jsx.map +1 -0
  69. package/dist/source/CatchBoundary.d.ts +1 -1
  70. package/dist/source/CatchBoundary.jsx +10 -23
  71. package/dist/source/CatchBoundary.jsx.map +1 -1
  72. package/dist/source/Html.d.ts +4 -0
  73. package/dist/source/Html.jsx +56 -0
  74. package/dist/source/Html.jsx.map +1 -0
  75. package/dist/source/Match.jsx +119 -54
  76. package/dist/source/Match.jsx.map +1 -1
  77. package/dist/source/Matches.jsx +15 -3
  78. package/dist/source/Matches.jsx.map +1 -1
  79. package/dist/source/RouterProvider.jsx +5 -0
  80. package/dist/source/RouterProvider.jsx.map +1 -1
  81. package/dist/source/ScriptOnce.d.ts +12 -5
  82. package/dist/source/ScriptOnce.jsx +27 -16
  83. package/dist/source/ScriptOnce.jsx.map +1 -1
  84. package/dist/source/Scripts.d.ts +2 -1
  85. package/dist/source/Scripts.jsx +100 -42
  86. package/dist/source/Scripts.jsx.map +1 -1
  87. package/dist/source/Transitioner.d.ts +16 -0
  88. package/dist/source/Transitioner.jsx +180 -160
  89. package/dist/source/Transitioner.jsx.map +1 -1
  90. package/dist/source/awaited.d.ts +20 -5
  91. package/dist/source/awaited.jsx +18 -25
  92. package/dist/source/awaited.jsx.map +1 -1
  93. package/dist/source/index.d.ts +2 -0
  94. package/dist/source/index.jsx +2 -0
  95. package/dist/source/index.jsx.map +1 -1
  96. package/dist/source/lazyRouteComponent.jsx +4 -2
  97. package/dist/source/lazyRouteComponent.jsx.map +1 -1
  98. package/dist/source/link.jsx +37 -51
  99. package/dist/source/link.jsx.map +1 -1
  100. package/dist/source/scroll-restoration.d.ts +8 -1
  101. package/dist/source/scroll-restoration.jsx +55 -12
  102. package/dist/source/scroll-restoration.jsx.map +1 -1
  103. package/dist/source/ssr/RouterClient.d.ts +15 -0
  104. package/dist/source/ssr/RouterClient.jsx +48 -0
  105. package/dist/source/ssr/RouterClient.jsx.map +1 -0
  106. package/dist/source/ssr/RouterServer.d.ts +15 -0
  107. package/dist/source/ssr/RouterServer.jsx +40 -0
  108. package/dist/source/ssr/RouterServer.jsx.map +1 -0
  109. package/dist/source/ssr/client.d.ts +1 -0
  110. package/dist/source/ssr/client.js +2 -0
  111. package/dist/source/ssr/client.js.map +1 -0
  112. package/dist/source/ssr/defaultRenderHandler.d.ts +1 -0
  113. package/dist/source/ssr/defaultRenderHandler.jsx +9 -0
  114. package/dist/source/ssr/defaultRenderHandler.jsx.map +1 -0
  115. package/dist/source/ssr/defaultStreamHandler.d.ts +1 -0
  116. package/dist/source/ssr/defaultStreamHandler.jsx +10 -0
  117. package/dist/source/ssr/defaultStreamHandler.jsx.map +1 -0
  118. package/dist/source/ssr/renderRouterToStream.d.ts +8 -0
  119. package/dist/source/ssr/renderRouterToStream.jsx +55 -0
  120. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -0
  121. package/dist/source/ssr/renderRouterToString.d.ts +7 -0
  122. package/dist/source/ssr/renderRouterToString.jsx +26 -0
  123. package/dist/source/ssr/renderRouterToString.jsx.map +1 -0
  124. package/dist/source/ssr/server.d.ts +6 -0
  125. package/dist/source/ssr/server.js +7 -0
  126. package/dist/source/ssr/server.js.map +1 -0
  127. package/package.json +16 -3
  128. package/src/Asset.tsx +157 -7
  129. package/src/Body.tsx +26 -0
  130. package/src/CatchBoundary.tsx +11 -25
  131. package/src/Html.tsx +65 -0
  132. package/src/Match.tsx +135 -58
  133. package/src/Matches.tsx +16 -4
  134. package/src/RouterProvider.tsx +6 -0
  135. package/src/ScriptOnce.tsx +43 -28
  136. package/src/Scripts.tsx +121 -56
  137. package/src/Transitioner.tsx +197 -176
  138. package/src/awaited.tsx +17 -28
  139. package/src/index.tsx +2 -0
  140. package/src/lazyRouteComponent.tsx +4 -2
  141. package/src/link.tsx +42 -47
  142. package/src/scroll-restoration.tsx +69 -21
  143. package/src/ssr/RouterClient.tsx +58 -0
  144. package/src/ssr/RouterServer.tsx +51 -0
  145. package/src/ssr/client.ts +1 -0
  146. package/src/ssr/defaultRenderHandler.tsx +12 -0
  147. package/src/ssr/defaultStreamHandler.tsx +13 -0
  148. package/src/ssr/renderRouterToStream.tsx +85 -0
  149. package/src/ssr/renderRouterToString.tsx +37 -0
  150. package/src/ssr/server.ts +6 -0
@@ -0,0 +1,55 @@
1
+ import { ReadableStream as NodeReadableStream } from 'node:stream/web';
2
+ import * as Vue from 'vue';
3
+ import { pipeToWebWritable, renderToString } from 'vue/server-renderer';
4
+ import { isbot } from 'isbot';
5
+ import { transformReadableStreamWithRouter } from '@tanstack/router-core/ssr/server';
6
+ function prependDoctype(readable) {
7
+ const encoder = new TextEncoder();
8
+ let sentDoctype = false;
9
+ return new NodeReadableStream({
10
+ async start(controller) {
11
+ const reader = readable.getReader();
12
+ async function pump() {
13
+ const { done, value } = await reader.read();
14
+ if (done) {
15
+ controller.close();
16
+ return;
17
+ }
18
+ if (!sentDoctype) {
19
+ sentDoctype = true;
20
+ controller.enqueue(encoder.encode('<!DOCTYPE html>'));
21
+ }
22
+ controller.enqueue(value);
23
+ return pump();
24
+ }
25
+ pump().catch((err) => controller.error(err));
26
+ },
27
+ });
28
+ }
29
+ export const renderRouterToStream = async ({ request, router, responseHeaders, App, }) => {
30
+ const app = Vue.createSSRApp(App, { router });
31
+ if (isbot(request.headers.get('User-Agent'))) {
32
+ let fullHtml = await renderToString(app);
33
+ const htmlOpenIndex = fullHtml.indexOf('<html');
34
+ const htmlCloseIndex = fullHtml.indexOf('</html>');
35
+ if (htmlOpenIndex !== -1 && htmlCloseIndex !== -1) {
36
+ fullHtml = fullHtml.slice(htmlOpenIndex, htmlCloseIndex + 7);
37
+ }
38
+ else if (htmlOpenIndex !== -1) {
39
+ fullHtml = fullHtml.slice(htmlOpenIndex);
40
+ }
41
+ return new Response(`<!DOCTYPE html>${fullHtml}`, {
42
+ status: router.state.statusCode,
43
+ headers: responseHeaders,
44
+ });
45
+ }
46
+ const { writable, readable } = new TransformStream();
47
+ pipeToWebWritable(app, {}, writable);
48
+ const doctypedStream = prependDoctype(readable);
49
+ const responseStream = transformReadableStreamWithRouter(router, doctypedStream);
50
+ return new Response(responseStream, {
51
+ status: router.state.statusCode,
52
+ headers: responseHeaders,
53
+ });
54
+ };
55
+ //# sourceMappingURL=renderRouterToStream.jsx.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderRouterToStream.jsx","sourceRoot":"","sources":["../../../src/ssr/renderRouterToStream.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AnyRouter } from '@tanstack/router-core';
2
+ import type { Component } from 'vue';
3
+ export declare const renderRouterToString: ({ router, responseHeaders, App, }: {
4
+ router: AnyRouter;
5
+ responseHeaders: Headers;
6
+ App: Component;
7
+ }) => Promise<Response>;
@@ -0,0 +1,26 @@
1
+ import * as Vue from 'vue';
2
+ import { renderToString as vueRenderToString } from 'vue/server-renderer';
3
+ export const renderRouterToString = async ({ router, responseHeaders, App, }) => {
4
+ try {
5
+ const app = Vue.createSSRApp(App, { router });
6
+ let html = await vueRenderToString(app);
7
+ router.serverSsr.setRenderFinished();
8
+ const injectedHtml = await Promise.all(router.serverSsr.injectedHtml).then((htmls) => htmls.join(''));
9
+ html = html.replace(`</body>`, () => `${injectedHtml}</body>`);
10
+ return new Response(`<!DOCTYPE html>${html}`, {
11
+ status: router.state.statusCode,
12
+ headers: responseHeaders,
13
+ });
14
+ }
15
+ catch (error) {
16
+ console.error('Render to string error:', error);
17
+ return new Response('Internal Server Error', {
18
+ status: 500,
19
+ headers: responseHeaders,
20
+ });
21
+ }
22
+ finally {
23
+ router.serverSsr?.cleanup();
24
+ }
25
+ };
26
+ //# sourceMappingURL=renderRouterToString.jsx.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderRouterToString.jsx","sourceRoot":"","sources":["../../../src/ssr/renderRouterToString.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,cAAc,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAIzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,EACzC,MAAM,EACN,eAAe,EACf,GAAG,GAKJ,EAAE,EAAE;IACH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QAE7C,IAAI,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAA;QACvC,MAAM,CAAC,SAAU,CAAC,iBAAiB,EAAE,CAAA;QACrC,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAU,CAAC,YAAY,CAAC,CAAC,IAAI,CACzE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAC1B,CAAA;QACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,YAAY,SAAS,CAAC,CAAA;QAC9D,OAAO,IAAI,QAAQ,CAAC,kBAAkB,IAAI,EAAE,EAAE;YAC5C,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;YAC/B,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;QAC/C,OAAO,IAAI,QAAQ,CAAC,uBAAuB,EAAE;YAC3C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,eAAe;SACzB,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,CAAA;IAC7B,CAAC;AACH,CAAC,CAAA"}
@@ -0,0 +1,6 @@
1
+ export { RouterServer } from './RouterServer';
2
+ export { defaultRenderHandler } from './defaultRenderHandler';
3
+ export { defaultStreamHandler } from './defaultStreamHandler';
4
+ export { renderRouterToStream } from './renderRouterToStream';
5
+ export { renderRouterToString } from './renderRouterToString';
6
+ export * from '@tanstack/router-core/ssr/server';
@@ -0,0 +1,7 @@
1
+ export { RouterServer } from './RouterServer';
2
+ export { defaultRenderHandler } from './defaultRenderHandler';
3
+ export { defaultStreamHandler } from './defaultStreamHandler';
4
+ export { renderRouterToStream } from './renderRouterToStream';
5
+ export { renderRouterToString } from './renderRouterToString';
6
+ export * from '@tanstack/router-core/ssr/server';
7
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/ssr/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,cAAc,kCAAkC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/vue-router",
3
- "version": "1.140.5",
3
+ "version": "1.141.1",
4
4
  "description": "Modern and scalable routing for Vue applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -33,6 +33,18 @@
33
33
  "default": "./dist/esm/index.js"
34
34
  }
35
35
  },
36
+ "./ssr/server": {
37
+ "import": {
38
+ "types": "./dist/esm/ssr/server.d.ts",
39
+ "default": "./dist/esm/ssr/server.js"
40
+ }
41
+ },
42
+ "./ssr/client": {
43
+ "import": {
44
+ "types": "./dist/esm/ssr/client.d.ts",
45
+ "default": "./dist/esm/ssr/client.js"
46
+ }
47
+ },
36
48
  "./package.json": "./package.json"
37
49
  },
38
50
  "sideEffects": false,
@@ -45,11 +57,12 @@
45
57
  },
46
58
  "dependencies": {
47
59
  "@tanstack/vue-store": "^0.8.0",
60
+ "isbot": "^5.1.22",
48
61
  "jsesc": "^3.0.2",
49
62
  "tiny-invariant": "^1.3.3",
50
63
  "tiny-warning": "^1.0.3",
51
- "@tanstack/history": "1.140.0",
52
- "@tanstack/router-core": "1.140.5"
64
+ "@tanstack/history": "1.141.0",
65
+ "@tanstack/router-core": "1.141.1"
53
66
  },
54
67
  "devDependencies": {
55
68
  "@testing-library/jest-dom": "^6.6.3",
package/src/Asset.tsx CHANGED
@@ -1,9 +1,164 @@
1
+ import * as Vue from 'vue'
2
+ import { useRouter } from './useRouter'
1
3
  import type { RouterManagedTag } from '@tanstack/router-core'
2
4
 
5
+ interface ScriptAttrs {
6
+ [key: string]: string | boolean | undefined
7
+ src?: string
8
+ }
9
+
10
+ const Title = Vue.defineComponent({
11
+ name: 'Title',
12
+ props: {
13
+ children: {
14
+ type: String,
15
+ default: '',
16
+ },
17
+ },
18
+ setup(props) {
19
+ const router = useRouter()
20
+
21
+ if (!router.isServer) {
22
+ Vue.onMounted(() => {
23
+ if (props.children) {
24
+ document.title = props.children
25
+ }
26
+ })
27
+
28
+ Vue.watch(
29
+ () => props.children,
30
+ (newTitle) => {
31
+ if (newTitle) {
32
+ document.title = newTitle
33
+ }
34
+ },
35
+ )
36
+ }
37
+
38
+ return () => Vue.h('title', {}, props.children)
39
+ },
40
+ })
41
+
42
+ const Script = Vue.defineComponent({
43
+ name: 'Script',
44
+ props: {
45
+ attrs: {
46
+ type: Object as Vue.PropType<ScriptAttrs>,
47
+ default: () => ({}),
48
+ },
49
+ children: {
50
+ type: String,
51
+ default: undefined,
52
+ },
53
+ },
54
+ setup(props) {
55
+ const router = useRouter()
56
+
57
+ if (!router.isServer) {
58
+ Vue.onMounted(() => {
59
+ const attrs = props.attrs
60
+ const children = props.children
61
+
62
+ if (attrs?.src) {
63
+ const normSrc = (() => {
64
+ try {
65
+ const base = document.baseURI || window.location.href
66
+ return new URL(attrs.src, base).href
67
+ } catch {
68
+ return attrs.src
69
+ }
70
+ })()
71
+ const existingScript = Array.from(
72
+ document.querySelectorAll('script[src]'),
73
+ ).find((el) => (el as HTMLScriptElement).src === normSrc)
74
+
75
+ if (existingScript) {
76
+ return
77
+ }
78
+
79
+ const script = document.createElement('script')
80
+
81
+ for (const [key, value] of Object.entries(attrs)) {
82
+ if (value !== undefined && value !== false) {
83
+ script.setAttribute(
84
+ key,
85
+ typeof value === 'boolean' ? '' : String(value),
86
+ )
87
+ }
88
+ }
89
+
90
+ document.head.appendChild(script)
91
+ } else if (typeof children === 'string') {
92
+ const typeAttr =
93
+ typeof attrs?.type === 'string' ? attrs.type : 'text/javascript'
94
+ const nonceAttr =
95
+ typeof attrs?.nonce === 'string' ? attrs.nonce : undefined
96
+ const existingScript = Array.from(
97
+ document.querySelectorAll('script:not([src])'),
98
+ ).find((el) => {
99
+ if (!(el instanceof HTMLScriptElement)) return false
100
+ const sType = el.getAttribute('type') ?? 'text/javascript'
101
+ const sNonce = el.getAttribute('nonce') ?? undefined
102
+ return (
103
+ el.textContent === children &&
104
+ sType === typeAttr &&
105
+ sNonce === nonceAttr
106
+ )
107
+ })
108
+
109
+ if (existingScript) {
110
+ return
111
+ }
112
+
113
+ const script = document.createElement('script')
114
+ script.textContent = children
115
+
116
+ if (attrs) {
117
+ for (const [key, value] of Object.entries(attrs)) {
118
+ if (value !== undefined && value !== false) {
119
+ script.setAttribute(
120
+ key,
121
+ typeof value === 'boolean' ? '' : String(value),
122
+ )
123
+ }
124
+ }
125
+ }
126
+
127
+ document.head.appendChild(script)
128
+ }
129
+ })
130
+ }
131
+
132
+ return () => {
133
+ if (!router.isServer) {
134
+ const { src: _src, ...rest } = props.attrs || {}
135
+ return Vue.h('script', {
136
+ ...rest,
137
+ 'data-allow-mismatch': true,
138
+ innerHTML: '',
139
+ })
140
+ }
141
+
142
+ if (props.attrs?.src && typeof props.attrs.src === 'string') {
143
+ return Vue.h('script', props.attrs)
144
+ }
145
+
146
+ if (typeof props.children === 'string') {
147
+ return Vue.h('script', {
148
+ ...props.attrs,
149
+ innerHTML: props.children,
150
+ })
151
+ }
152
+
153
+ return null
154
+ }
155
+ },
156
+ })
157
+
3
158
  export function Asset({ tag, attrs, children }: RouterManagedTag): any {
4
159
  switch (tag) {
5
160
  case 'title':
6
- return <title {...attrs}>{children}</title>
161
+ return Vue.h(Title, { children: children })
7
162
  case 'meta':
8
163
  return <meta {...attrs} />
9
164
  case 'link':
@@ -11,12 +166,7 @@ export function Asset({ tag, attrs, children }: RouterManagedTag): any {
11
166
  case 'style':
12
167
  return <style {...attrs} innerHTML={children} />
13
168
  case 'script':
14
- if ((attrs as any) && (attrs as any).src) {
15
- return <script {...attrs} />
16
- }
17
- if (typeof children === 'string')
18
- return <script {...attrs} innerHTML={children} />
19
- return null
169
+ return Vue.h(Script, { attrs, children: children })
20
170
  default:
21
171
  return null
22
172
  }
package/src/Body.tsx ADDED
@@ -0,0 +1,26 @@
1
+ import * as Vue from 'vue'
2
+
3
+ export const Body = Vue.defineComponent({
4
+ name: 'Body',
5
+ setup(_, { slots }) {
6
+ const isServer = typeof window === 'undefined'
7
+
8
+ return () => {
9
+ const children = slots.default?.()
10
+
11
+ if (isServer) {
12
+ return Vue.h(
13
+ 'body',
14
+ {},
15
+ Vue.h(
16
+ 'div',
17
+ { id: '__app' },
18
+ Vue.h('div', { 'data-allow-mismatch': '' }, children),
19
+ ),
20
+ )
21
+ }
22
+
23
+ return Vue.h('div', { 'data-allow-mismatch': '' }, children)
24
+ }
25
+ },
26
+ })
@@ -1,13 +1,11 @@
1
1
  import * as Vue from 'vue'
2
2
  import type { ErrorRouteComponent } from './route'
3
3
 
4
- // Define the error component props interface
5
4
  interface ErrorComponentProps {
6
5
  error: Error
7
6
  reset: () => void
8
7
  }
9
8
 
10
- // Create a Vue error boundary component
11
9
  const VueErrorBoundary = Vue.defineComponent({
12
10
  name: 'VueErrorBoundary',
13
11
  props: {
@@ -23,7 +21,6 @@ const VueErrorBoundary = Vue.defineComponent({
23
21
  error.value = null
24
22
  }
25
23
 
26
- // Watch for changes in the reset key
27
24
  Vue.watch(
28
25
  () => props.resetKey,
29
26
  (newKey, oldKey) => {
@@ -33,52 +30,49 @@ const VueErrorBoundary = Vue.defineComponent({
33
30
  },
34
31
  )
35
32
 
36
- // Capture errors from child components
37
33
  Vue.onErrorCaptured((err: Error) => {
38
- // If the error is a Promise (thrown for Suspense), don't treat it as an error
39
- // Just ignore it - Suspense will handle it
40
34
  if (
41
35
  err instanceof Promise ||
42
36
  (err && typeof (err as any).then === 'function')
43
37
  ) {
44
- return false // Prevent from propagating as an error, but don't set error state
38
+ return false
45
39
  }
46
40
 
47
41
  error.value = err
48
42
  resetFn.value = reset
49
43
 
50
- // Call the onError callback if provided
51
44
  if (props.onError) {
52
45
  props.onError(err)
53
46
  }
54
47
 
55
- // Prevent the error from propagating further
56
48
  return false
57
49
  })
58
50
 
59
51
  return () => {
60
- // If there's an error, render the fallback
61
52
  if (error.value && slots.fallback) {
62
- return slots.fallback({
53
+ const fallbackContent = slots.fallback({
63
54
  error: error.value,
64
55
  reset,
65
56
  })
57
+ return Array.isArray(fallbackContent) && fallbackContent.length === 1
58
+ ? fallbackContent[0]
59
+ : fallbackContent
66
60
  }
67
61
 
68
- // Otherwise render the default slot
69
- return slots.default && slots.default()
62
+ const defaultContent = slots.default && slots.default()
63
+ return Array.isArray(defaultContent) && defaultContent.length === 1
64
+ ? defaultContent[0]
65
+ : defaultContent
70
66
  }
71
67
  },
72
68
  })
73
69
 
74
- // Main CatchBoundary component
75
70
  export function CatchBoundary(props: {
76
71
  getResetKey: () => number | string
77
72
  children: Vue.VNode
78
- errorComponent?: ErrorRouteComponent
73
+ errorComponent?: ErrorRouteComponent | Vue.Component
79
74
  onCatch?: (error: Error) => void
80
75
  }) {
81
- // Create a component to use in the template
82
76
  const CatchBoundaryWrapper = Vue.defineComponent({
83
77
  name: 'CatchBoundaryWrapper',
84
78
  inheritAttrs: false,
@@ -86,9 +80,6 @@ export function CatchBoundary(props: {
86
80
  const resetKey = Vue.computed(() => props.getResetKey())
87
81
 
88
82
  return () => {
89
- // Always use our default component as a safe fallback
90
- const defaultErrorComponent = ErrorComponent
91
-
92
83
  return Vue.h(
93
84
  VueErrorBoundary,
94
85
  {
@@ -98,14 +89,10 @@ export function CatchBoundary(props: {
98
89
  {
99
90
  default: () => props.children,
100
91
  fallback: ({ error, reset }: ErrorComponentProps) => {
101
- // Safely render the error component - either the provided one or the default
102
92
  if (props.errorComponent) {
103
- // Use the provided error component
104
93
  return Vue.h(props.errorComponent, { error, reset })
105
- } else {
106
- // Use the default error component
107
- return Vue.h(defaultErrorComponent, { error, reset })
108
94
  }
95
+ return Vue.h(ErrorComponent, { error, reset })
109
96
  },
110
97
  },
111
98
  )
@@ -116,7 +103,6 @@ export function CatchBoundary(props: {
116
103
  return Vue.h(CatchBoundaryWrapper)
117
104
  }
118
105
 
119
- // Error component
120
106
  export const ErrorComponent = Vue.defineComponent({
121
107
  name: 'ErrorComponent',
122
108
  props: {
package/src/Html.tsx ADDED
@@ -0,0 +1,65 @@
1
+ import * as Vue from 'vue'
2
+ import { Body } from './Body'
3
+
4
+ export const Html = Vue.defineComponent({
5
+ name: 'Html',
6
+ setup(_, { slots }) {
7
+ const isServer = typeof window === 'undefined'
8
+
9
+ const hydrated = Vue.ref(false)
10
+
11
+ if (!isServer) {
12
+ Vue.onMounted(() => {
13
+ hydrated.value = true
14
+ })
15
+ }
16
+
17
+ return () => {
18
+ if (isServer) {
19
+ return Vue.h('html', {}, slots.default?.())
20
+ }
21
+
22
+ const children = slots.default?.() || []
23
+ const flatChildren = Array.isArray(children) ? children : [children]
24
+ let bodyVnode: Vue.VNode | null = null
25
+ const headChildren: Array<Vue.VNode> = []
26
+
27
+ for (const child of flatChildren) {
28
+ if (typeof child === 'object' && child !== null) {
29
+ const vnode = child
30
+ if (vnode.type === 'head') {
31
+ if (vnode.children) {
32
+ if (Array.isArray(vnode.children)) {
33
+ for (const c of vnode.children) {
34
+ if (typeof c === 'object' && c !== null && 'type' in c) {
35
+ headChildren.push(c as Vue.VNode)
36
+ }
37
+ }
38
+ }
39
+ }
40
+ continue
41
+ }
42
+ if (vnode.type === Body) {
43
+ bodyVnode = vnode
44
+ continue
45
+ }
46
+ if (!bodyVnode) {
47
+ bodyVnode = vnode
48
+ }
49
+ }
50
+ }
51
+
52
+ const result: Array<Vue.VNode> = []
53
+
54
+ if (bodyVnode) {
55
+ result.push(bodyVnode)
56
+ }
57
+
58
+ if (hydrated.value && headChildren.length > 0) {
59
+ result.push(Vue.h(Vue.Teleport, { to: 'head' }, headChildren))
60
+ }
61
+
62
+ return result.length === 1 ? result[0] : Vue.h(Vue.Fragment, result)
63
+ }
64
+ },
65
+ })