@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.
- package/dist/esm/Asset.js +122 -8
- package/dist/esm/Asset.js.map +1 -1
- package/dist/esm/Body.d.ts +4 -0
- package/dist/esm/Body.js +26 -0
- package/dist/esm/Body.js.map +1 -0
- package/dist/esm/CatchBoundary.d.ts +1 -1
- package/dist/esm/CatchBoundary.js +8 -8
- package/dist/esm/CatchBoundary.js.map +1 -1
- package/dist/esm/Html.d.ts +4 -0
- package/dist/esm/Html.js +63 -0
- package/dist/esm/Html.js.map +1 -0
- package/dist/esm/Match.js +87 -49
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +3 -2
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +3 -0
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/ScriptOnce.d.ts +12 -5
- package/dist/esm/ScriptOnce.js +35 -15
- package/dist/esm/ScriptOnce.js.map +1 -1
- package/dist/esm/Scripts.d.ts +2 -1
- package/dist/esm/Scripts.js +101 -35
- package/dist/esm/Scripts.js.map +1 -1
- package/dist/esm/Transitioner.d.ts +16 -0
- package/dist/esm/Transitioner.js +136 -133
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/awaited.d.ts +20 -5
- package/dist/esm/awaited.js +17 -20
- package/dist/esm/awaited.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lazyRouteComponent.js +2 -2
- package/dist/esm/lazyRouteComponent.js.map +1 -1
- package/dist/esm/link.js +27 -35
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +8 -1
- package/dist/esm/scroll-restoration.js +44 -12
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/RouterClient.d.ts +15 -0
- package/dist/esm/ssr/RouterClient.js +46 -0
- package/dist/esm/ssr/RouterClient.js.map +1 -0
- package/dist/esm/ssr/RouterServer.d.ts +15 -0
- package/dist/esm/ssr/RouterServer.js +37 -0
- package/dist/esm/ssr/RouterServer.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +1 -0
- package/dist/esm/ssr/client.js +5 -0
- package/dist/esm/ssr/client.js.map +1 -0
- package/dist/esm/ssr/defaultRenderHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultRenderHandler.js +15 -0
- package/dist/esm/ssr/defaultRenderHandler.js.map +1 -0
- package/dist/esm/ssr/defaultStreamHandler.d.ts +1 -0
- package/dist/esm/ssr/defaultStreamHandler.js +17 -0
- package/dist/esm/ssr/defaultStreamHandler.js.map +1 -0
- package/dist/esm/ssr/renderRouterToStream.d.ts +8 -0
- package/dist/esm/ssr/renderRouterToStream.js +70 -0
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -0
- package/dist/esm/ssr/renderRouterToString.d.ts +7 -0
- package/dist/esm/ssr/renderRouterToString.js +33 -0
- package/dist/esm/ssr/renderRouterToString.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +14 -0
- package/dist/esm/ssr/server.js.map +1 -0
- package/dist/source/Asset.jsx +119 -7
- package/dist/source/Asset.jsx.map +1 -1
- package/dist/source/Body.d.ts +4 -0
- package/dist/source/Body.jsx +15 -0
- package/dist/source/Body.jsx.map +1 -0
- package/dist/source/CatchBoundary.d.ts +1 -1
- package/dist/source/CatchBoundary.jsx +10 -23
- package/dist/source/CatchBoundary.jsx.map +1 -1
- package/dist/source/Html.d.ts +4 -0
- package/dist/source/Html.jsx +56 -0
- package/dist/source/Html.jsx.map +1 -0
- package/dist/source/Match.jsx +119 -54
- package/dist/source/Match.jsx.map +1 -1
- package/dist/source/Matches.jsx +15 -3
- package/dist/source/Matches.jsx.map +1 -1
- package/dist/source/RouterProvider.jsx +5 -0
- package/dist/source/RouterProvider.jsx.map +1 -1
- package/dist/source/ScriptOnce.d.ts +12 -5
- package/dist/source/ScriptOnce.jsx +27 -16
- package/dist/source/ScriptOnce.jsx.map +1 -1
- package/dist/source/Scripts.d.ts +2 -1
- package/dist/source/Scripts.jsx +100 -42
- package/dist/source/Scripts.jsx.map +1 -1
- package/dist/source/Transitioner.d.ts +16 -0
- package/dist/source/Transitioner.jsx +180 -160
- package/dist/source/Transitioner.jsx.map +1 -1
- package/dist/source/awaited.d.ts +20 -5
- package/dist/source/awaited.jsx +18 -25
- package/dist/source/awaited.jsx.map +1 -1
- package/dist/source/index.d.ts +2 -0
- package/dist/source/index.jsx +2 -0
- package/dist/source/index.jsx.map +1 -1
- package/dist/source/lazyRouteComponent.jsx +4 -2
- package/dist/source/lazyRouteComponent.jsx.map +1 -1
- package/dist/source/link.jsx +37 -51
- package/dist/source/link.jsx.map +1 -1
- package/dist/source/scroll-restoration.d.ts +8 -1
- package/dist/source/scroll-restoration.jsx +55 -12
- package/dist/source/scroll-restoration.jsx.map +1 -1
- package/dist/source/ssr/RouterClient.d.ts +15 -0
- package/dist/source/ssr/RouterClient.jsx +48 -0
- package/dist/source/ssr/RouterClient.jsx.map +1 -0
- package/dist/source/ssr/RouterServer.d.ts +15 -0
- package/dist/source/ssr/RouterServer.jsx +40 -0
- package/dist/source/ssr/RouterServer.jsx.map +1 -0
- package/dist/source/ssr/client.d.ts +1 -0
- package/dist/source/ssr/client.js +2 -0
- package/dist/source/ssr/client.js.map +1 -0
- package/dist/source/ssr/defaultRenderHandler.d.ts +1 -0
- package/dist/source/ssr/defaultRenderHandler.jsx +9 -0
- package/dist/source/ssr/defaultRenderHandler.jsx.map +1 -0
- package/dist/source/ssr/defaultStreamHandler.d.ts +1 -0
- package/dist/source/ssr/defaultStreamHandler.jsx +10 -0
- package/dist/source/ssr/defaultStreamHandler.jsx.map +1 -0
- package/dist/source/ssr/renderRouterToStream.d.ts +8 -0
- package/dist/source/ssr/renderRouterToStream.jsx +55 -0
- package/dist/source/ssr/renderRouterToStream.jsx.map +1 -0
- package/dist/source/ssr/renderRouterToString.d.ts +7 -0
- package/dist/source/ssr/renderRouterToString.jsx +26 -0
- package/dist/source/ssr/renderRouterToString.jsx.map +1 -0
- package/dist/source/ssr/server.d.ts +6 -0
- package/dist/source/ssr/server.js +7 -0
- package/dist/source/ssr/server.js.map +1 -0
- package/package.json +16 -3
- package/src/Asset.tsx +157 -7
- package/src/Body.tsx +26 -0
- package/src/CatchBoundary.tsx +11 -25
- package/src/Html.tsx +65 -0
- package/src/Match.tsx +135 -58
- package/src/Matches.tsx +16 -4
- package/src/RouterProvider.tsx +6 -0
- package/src/ScriptOnce.tsx +43 -28
- package/src/Scripts.tsx +121 -56
- package/src/Transitioner.tsx +197 -176
- package/src/awaited.tsx +17 -28
- package/src/index.tsx +2 -0
- package/src/lazyRouteComponent.tsx +4 -2
- package/src/link.tsx +42 -47
- package/src/scroll-restoration.tsx +69 -21
- package/src/ssr/RouterClient.tsx +58 -0
- package/src/ssr/RouterServer.tsx +51 -0
- package/src/ssr/client.ts +1 -0
- package/src/ssr/defaultRenderHandler.tsx +12 -0
- package/src/ssr/defaultStreamHandler.tsx +13 -0
- package/src/ssr/renderRouterToStream.tsx +85 -0
- package/src/ssr/renderRouterToString.tsx +37 -0
- 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.
|
|
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.
|
|
52
|
-
"@tanstack/router-core": "1.
|
|
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
|
|
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
|
-
|
|
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
|
+
})
|
package/src/CatchBoundary.tsx
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
return
|
|
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
|
+
})
|