@vesk/adapter 0.2.59 → 0.2.61
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/default-pages.d.ts +66 -0
- package/dist/default-pages.d.ts.map +1 -0
- package/dist/default-pages.js +146 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +15 -2
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +24 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -0
- package/dist/platform-handler.d.ts.map +1 -1
- package/dist/platform-handler.js +23 -2
- package/dist/prod-server.d.ts.map +1 -1
- package/dist/prod-server.js +17 -3
- package/dist/runtime-bundle.d.ts.map +1 -1
- package/dist/runtime-bundle.js +35 -0
- package/dist/ssr-function.d.ts +5 -0
- package/dist/ssr-function.d.ts.map +1 -1
- package/dist/ssr-function.js +91 -6
- package/package.json +4 -4
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded built-in fallback pages.
|
|
3
|
+
*
|
|
4
|
+
* These are the pages a Vesk app gets when it has NOT defined its own
|
|
5
|
+
* `app/not-found.vsk` / `app/error.vsk` (or when rendering one throws). They
|
|
6
|
+
* deliberately mirror the shape of Next.js' built-in 404/500 page — a large
|
|
7
|
+
* status code, a hairline divider, the message — but carry the Vesk design
|
|
8
|
+
* language (light-default monochrome that follows `prefers-color-scheme`, a
|
|
9
|
+
* single rust accent, sharp corners, Space Grotesk / JetBrains Mono with system
|
|
10
|
+
* fallbacks) instead of the generic `<h1>404</h1>` that shipped before.
|
|
11
|
+
*
|
|
12
|
+
* The module is intentionally dependency-free and side-effect-free: the HTML
|
|
13
|
+
* is a plain string, so it can be served by the Node prod server, the dev
|
|
14
|
+
* server, the generated platform (edge) handler, and inlined into generated
|
|
15
|
+
* error bodies — all from this one source of truth.
|
|
16
|
+
*
|
|
17
|
+
* Fonts are NOT fetched here: a fallback page must render instantly and
|
|
18
|
+
* without network access, so the stacks degrade to system fonts gracefully.
|
|
19
|
+
*/
|
|
20
|
+
export interface DefaultPageOptions {
|
|
21
|
+
/** HTTP status shown and set by the caller (404 / 500). */
|
|
22
|
+
status?: number;
|
|
23
|
+
/** Document `<title>`. */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Short label rendered next to the big status code. */
|
|
26
|
+
message?: string;
|
|
27
|
+
/** The requested path, shown as a muted detail line when present. */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Primary action target. Pass `null` to omit the button. */
|
|
30
|
+
homeHref?: string | null;
|
|
31
|
+
/** Primary action label. */
|
|
32
|
+
homeLabel?: string;
|
|
33
|
+
/** Extra markup for `<head>` (baked onHead-plugin snippets). */
|
|
34
|
+
headExtra?: string;
|
|
35
|
+
/** Paint the status code in the rust accent (used for 5xx). */
|
|
36
|
+
accentStatus?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function escapeDefaultPageHtml(value: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* Render a complete, self-contained HTML document for a built-in fallback page.
|
|
41
|
+
*/
|
|
42
|
+
export declare function renderDefaultPage(opts?: DefaultPageOptions): string;
|
|
43
|
+
/** The built-in 404: matches Next.js' "This page could not be found." shape. */
|
|
44
|
+
export declare function renderDefaultNotFoundPage(opts?: {
|
|
45
|
+
url?: string;
|
|
46
|
+
headExtra?: string;
|
|
47
|
+
}): string;
|
|
48
|
+
/** The built-in 500: never leaks a stack trace; message is opt-in (dev). */
|
|
49
|
+
export declare function renderDefaultErrorPage(opts?: {
|
|
50
|
+
status?: number;
|
|
51
|
+
message?: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
headExtra?: string;
|
|
54
|
+
}): string;
|
|
55
|
+
/**
|
|
56
|
+
* Compact, self-contained error panel for contexts where a full document is
|
|
57
|
+
* wrong — chiefly the generated `__renderErrorBody` fallback, which is injected
|
|
58
|
+
* as the page body *inside* the app's layout chain when `error.vsk` exists but
|
|
59
|
+
* throws mid-render. Inline styles only (no external class dependency) so it
|
|
60
|
+
* survives inside any user layout.
|
|
61
|
+
*/
|
|
62
|
+
export declare function renderDefaultErrorFragment(opts?: {
|
|
63
|
+
status?: number;
|
|
64
|
+
message?: string;
|
|
65
|
+
}): string;
|
|
66
|
+
//# sourceMappingURL=default-pages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-pages.d.ts","sourceRoot":"","sources":["../src/default-pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,kBAAkB;IAClC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAO5D;AAgDD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,kBAAuB,GAAG,MAAM,CAgCvE;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CAAC,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAQjG;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAWjI;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CASnG"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded built-in fallback pages.
|
|
3
|
+
*
|
|
4
|
+
* These are the pages a Vesk app gets when it has NOT defined its own
|
|
5
|
+
* `app/not-found.vsk` / `app/error.vsk` (or when rendering one throws). They
|
|
6
|
+
* deliberately mirror the shape of Next.js' built-in 404/500 page — a large
|
|
7
|
+
* status code, a hairline divider, the message — but carry the Vesk design
|
|
8
|
+
* language (light-default monochrome that follows `prefers-color-scheme`, a
|
|
9
|
+
* single rust accent, sharp corners, Space Grotesk / JetBrains Mono with system
|
|
10
|
+
* fallbacks) instead of the generic `<h1>404</h1>` that shipped before.
|
|
11
|
+
*
|
|
12
|
+
* The module is intentionally dependency-free and side-effect-free: the HTML
|
|
13
|
+
* is a plain string, so it can be served by the Node prod server, the dev
|
|
14
|
+
* server, the generated platform (edge) handler, and inlined into generated
|
|
15
|
+
* error bodies — all from this one source of truth.
|
|
16
|
+
*
|
|
17
|
+
* Fonts are NOT fetched here: a fallback page must render instantly and
|
|
18
|
+
* without network access, so the stacks degrade to system fonts gracefully.
|
|
19
|
+
*/
|
|
20
|
+
export function escapeDefaultPageHtml(value) {
|
|
21
|
+
return String(value ?? '')
|
|
22
|
+
.replace(/&/g, '&')
|
|
23
|
+
.replace(/</g, '<')
|
|
24
|
+
.replace(/>/g, '>')
|
|
25
|
+
.replace(/"/g, '"')
|
|
26
|
+
.replace(/'/g, ''');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Theme tokens lifted from the vesk-doc `app/global.css` design system. Light is
|
|
30
|
+
* the default (matching the general web default); `prefers-color-scheme: dark`
|
|
31
|
+
* swaps to the inverted band so the page follows the visitor's theme. Kept
|
|
32
|
+
* inline so the page is fully self-contained.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_PAGE_CSS = [
|
|
35
|
+
':root{color-scheme:light;',
|
|
36
|
+
'--vsk-bg:oklch(0.941 0.014 88);',
|
|
37
|
+
'--vsk-fg:oklch(0.185 0.008 62);',
|
|
38
|
+
'--vsk-surface:oklch(0.965 0.012 88);',
|
|
39
|
+
'--vsk-muted:oklch(0.475 0.014 72);',
|
|
40
|
+
'--vsk-accent:oklch(0.560 0.140 42);',
|
|
41
|
+
'--vsk-border:oklch(0.86 0.012 82);',
|
|
42
|
+
'--vsk-border-strong:oklch(0.72 0.014 80);',
|
|
43
|
+
'--vsk-font:"Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;',
|
|
44
|
+
'--vsk-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}',
|
|
45
|
+
'@media (prefers-color-scheme:dark){:root{color-scheme:dark;',
|
|
46
|
+
'--vsk-bg:oklch(0.155 0.006 60);',
|
|
47
|
+
'--vsk-fg:oklch(0.931 0.014 88);',
|
|
48
|
+
'--vsk-surface:oklch(0.196 0.008 70);',
|
|
49
|
+
'--vsk-muted:oklch(0.618 0.016 80);',
|
|
50
|
+
'--vsk-accent:oklch(0.632 0.132 45);',
|
|
51
|
+
'--vsk-border:oklch(0.29 0.009 75);',
|
|
52
|
+
'--vsk-border-strong:oklch(0.40 0.011 75);}}',
|
|
53
|
+
'*{box-sizing:border-box}',
|
|
54
|
+
'html,body{margin:0;min-height:100%}',
|
|
55
|
+
'body{background:var(--vsk-bg);color:var(--vsk-fg);font-family:var(--vsk-font);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}',
|
|
56
|
+
'.vsk-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}',
|
|
57
|
+
'.vsk-page__card{width:100%;max-width:540px;border:1px solid var(--vsk-border);background:var(--vsk-surface);padding:clamp(28px,6vw,44px);text-align:center}',
|
|
58
|
+
'@supports (background:color-mix(in oklab,red,red)){.vsk-page__card{background:color-mix(in oklab,var(--vsk-surface) 60%,transparent)}}',
|
|
59
|
+
'.vsk-page__brand{margin:0 0 26px;font-family:var(--vsk-mono);font-size:10px;line-height:1;letter-spacing:.24em;text-transform:uppercase;color:var(--vsk-muted)}',
|
|
60
|
+
'.vsk-page__brand b{color:var(--vsk-fg);font-weight:600}',
|
|
61
|
+
'.vsk-page__row{display:flex;align-items:center;justify-content:center;gap:0;flex-wrap:wrap}',
|
|
62
|
+
'.vsk-page__status{font-size:clamp(34px,9vw,46px);font-weight:600;line-height:1;letter-spacing:-.03em;padding-right:22px;margin-right:22px;border-right:1px solid var(--vsk-border-strong)}',
|
|
63
|
+
'.vsk-page--error .vsk-page__status{color:var(--vsk-accent)}',
|
|
64
|
+
'.vsk-page__msg{font-family:var(--vsk-mono);font-size:13px;line-height:1.5;color:var(--vsk-muted);text-align:left;max-width:270px}',
|
|
65
|
+
'@media (max-width:440px){.vsk-page__row{flex-direction:column}.vsk-page__status{padding:0 0 16px;margin:0 0 16px;border-right:0;border-bottom:1px solid var(--vsk-border-strong)}.vsk-page__msg{text-align:center}}',
|
|
66
|
+
'.vsk-page__detail{margin:20px 0 0;font-family:var(--vsk-mono);font-size:11px;line-height:1.6;color:var(--vsk-muted);word-break:break-all}',
|
|
67
|
+
'.vsk-page__actions{margin-top:30px;display:flex;justify-content:center;gap:10px;flex-wrap:wrap}',
|
|
68
|
+
'.vsk-page__btn{display:inline-block;padding:11px 22px;font-size:13px;font-weight:500;line-height:1;text-decoration:none;border:1px solid var(--vsk-fg);background:var(--vsk-fg);color:var(--vsk-bg);transition:opacity .18s cubic-bezier(.2,0,0,1)}',
|
|
69
|
+
'.vsk-page__btn:hover{opacity:.86}',
|
|
70
|
+
'.vsk-page__btn--ghost{background:transparent;color:var(--vsk-fg);border-color:var(--vsk-border-strong)}',
|
|
71
|
+
'.vsk-page__btn--ghost:hover{background:var(--vsk-fg);color:var(--vsk-bg);border-color:var(--vsk-fg);opacity:1}',
|
|
72
|
+
].join('');
|
|
73
|
+
/**
|
|
74
|
+
* Render a complete, self-contained HTML document for a built-in fallback page.
|
|
75
|
+
*/
|
|
76
|
+
export function renderDefaultPage(opts = {}) {
|
|
77
|
+
const status = opts.status ?? 404;
|
|
78
|
+
const title = escapeDefaultPageHtml(opts.title ?? `${status} — Vesk`);
|
|
79
|
+
const message = escapeDefaultPageHtml(opts.message ?? 'This page could not be found.');
|
|
80
|
+
const url = opts.url ? escapeDefaultPageHtml(opts.url) : '';
|
|
81
|
+
const headExtra = opts.headExtra || '';
|
|
82
|
+
const homeHref = opts.homeHref === undefined ? '/' : opts.homeHref;
|
|
83
|
+
const homeLabel = escapeDefaultPageHtml(opts.homeLabel ?? 'Back to home');
|
|
84
|
+
const variant = `vsk-page${opts.accentStatus ? ' vsk-page--error' : ''}`;
|
|
85
|
+
const action = homeHref === null
|
|
86
|
+
? ''
|
|
87
|
+
: `<a class="vsk-page__btn" href="${escapeDefaultPageHtml(homeHref)}">${homeLabel}</a>`;
|
|
88
|
+
return '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">' +
|
|
89
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">' +
|
|
90
|
+
'<meta name="robots" content="noindex">' +
|
|
91
|
+
`<title>${title}</title>` +
|
|
92
|
+
`<style>${DEFAULT_PAGE_CSS}</style>` +
|
|
93
|
+
headExtra +
|
|
94
|
+
'</head><body>' +
|
|
95
|
+
`<main class="${variant}" data-vesk-default-page="${status}">` +
|
|
96
|
+
'<div class="vsk-page__card">' +
|
|
97
|
+
'<p class="vsk-page__brand"><b>Vesk</b> · built-in error page</p>' +
|
|
98
|
+
'<div class="vsk-page__row">' +
|
|
99
|
+
`<span class="vsk-page__status">${status}</span>` +
|
|
100
|
+
`<span class="vsk-page__msg">${message}</span>` +
|
|
101
|
+
'</div>' +
|
|
102
|
+
(url ? `<p class="vsk-page__detail">${url}</p>` : '') +
|
|
103
|
+
(action ? `<div class="vsk-page__actions">${action}</div>` : '') +
|
|
104
|
+
'</div></main>' +
|
|
105
|
+
'</body></html>';
|
|
106
|
+
}
|
|
107
|
+
/** The built-in 404: matches Next.js' "This page could not be found." shape. */
|
|
108
|
+
export function renderDefaultNotFoundPage(opts = {}) {
|
|
109
|
+
return renderDefaultPage({
|
|
110
|
+
status: 404,
|
|
111
|
+
title: '404 — Not Found',
|
|
112
|
+
message: 'This page could not be found.',
|
|
113
|
+
url: opts.url,
|
|
114
|
+
headExtra: opts.headExtra,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** The built-in 500: never leaks a stack trace; message is opt-in (dev). */
|
|
118
|
+
export function renderDefaultErrorPage(opts = {}) {
|
|
119
|
+
const status = opts.status ?? 500;
|
|
120
|
+
return renderDefaultPage({
|
|
121
|
+
status,
|
|
122
|
+
title: `${status} — Internal Server Error`,
|
|
123
|
+
message: opts.message || 'Internal Server Error',
|
|
124
|
+
url: opts.url,
|
|
125
|
+
headExtra: opts.headExtra,
|
|
126
|
+
accentStatus: true,
|
|
127
|
+
homeHref: null,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compact, self-contained error panel for contexts where a full document is
|
|
132
|
+
* wrong — chiefly the generated `__renderErrorBody` fallback, which is injected
|
|
133
|
+
* as the page body *inside* the app's layout chain when `error.vsk` exists but
|
|
134
|
+
* throws mid-render. Inline styles only (no external class dependency) so it
|
|
135
|
+
* survives inside any user layout.
|
|
136
|
+
*/
|
|
137
|
+
export function renderDefaultErrorFragment(opts = {}) {
|
|
138
|
+
const status = opts.status ?? 500;
|
|
139
|
+
const message = escapeDefaultPageHtml(opts.message || 'Internal Server Error');
|
|
140
|
+
return '<div data-vesk-default-error-fragment="' + status + '" style="display:flex;align-items:center;justify-content:center;min-height:60vh;padding:32px">' +
|
|
141
|
+
'<div style="display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">' +
|
|
142
|
+
'<div style="display:flex;align-items:center">' +
|
|
143
|
+
'<span style="font-size:24px;font-weight:500;line-height:49px;padding-right:23px;margin-right:20px;border-right:1px solid currentColor;opacity:.85">' + status + '</span>' +
|
|
144
|
+
'<span style="font-size:14px;line-height:49px;opacity:.7">' + message + '</span>' +
|
|
145
|
+
'</div></div></div>';
|
|
146
|
+
}
|
package/dist/dev-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AASjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AASjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAM7C,OAAO,KAAK,EAAa,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AAgBjG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAuCD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkBD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC;IAChC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAChH,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AA8CD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAorBvG"}
|
package/dist/dev-server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { buildErrorPayload, createHmrServer } from './hmr';
|
|
|
11
11
|
import * as hmrApi from './hmr';
|
|
12
12
|
import { createDevApiRouter } from './dev-api';
|
|
13
13
|
import { resolveRuntimeErrorPayload, renderDevErrorPage } from './ssr-error';
|
|
14
|
+
import { renderDefaultNotFoundPage, renderDefaultErrorPage } from '@vesk/adapter/src/default-pages';
|
|
14
15
|
import { buildTreeShakenRuntime, runtimeExportNames } from '@vesk/adapter/src/client-bundle';
|
|
15
16
|
import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
|
|
16
17
|
import { getPluginRecords } from './plugins';
|
|
@@ -381,7 +382,7 @@ export async function startDevServer(appDir, options) {
|
|
|
381
382
|
return injectDevScripts(renderDevErrorPage(payload, { status: 500, url: pathname }));
|
|
382
383
|
}
|
|
383
384
|
catch {
|
|
384
|
-
return
|
|
385
|
+
return injectDevScripts(renderDefaultErrorPage({ message: e instanceof Error ? e.message : undefined, url: pathname }));
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
388
|
let eventsMod = null;
|
|
@@ -567,6 +568,18 @@ export async function startDevServer(appDir, options) {
|
|
|
567
568
|
res.end(lines.join('\n') || '// no ssr data');
|
|
568
569
|
return;
|
|
569
570
|
}
|
|
571
|
+
// Persisted-store pre-paint payload — same token-stash/consume contract as
|
|
572
|
+
// /ssr-data.js above so the dev SPA fallback never serves HTML as JS.
|
|
573
|
+
if (url.pathname === '/store-init.js') {
|
|
574
|
+
const token = url.searchParams.get('t') || '';
|
|
575
|
+
const store = globalThis.__vsk_store_init_payload_store;
|
|
576
|
+
const payload = store?.[token];
|
|
577
|
+
if (payload)
|
|
578
|
+
delete store[token];
|
|
579
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-store' });
|
|
580
|
+
res.end(payload ?? '// no store init');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
570
583
|
if (url.pathname.startsWith('/_vesk/static/')) {
|
|
571
584
|
const relPath = url.pathname.slice('/_vesk/static/'.length);
|
|
572
585
|
const staticPath = resolveWithin(resolve(devDir, 'static'), relPath);
|
|
@@ -740,7 +753,7 @@ export async function startDevServer(appDir, options) {
|
|
|
740
753
|
}
|
|
741
754
|
}
|
|
742
755
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
743
|
-
res.end(
|
|
756
|
+
res.end(injectDevScripts(renderDefaultNotFoundPage({ url: url.pathname })));
|
|
744
757
|
});
|
|
745
758
|
const hmr = createHmrServer(server, appDir, devDir, componentMap);
|
|
746
759
|
hmrSession = hmr;
|
package/dist/hmr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAWxC,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEvG,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AAIzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAaD,wBAAgB,WAAW,IAAI;IAC7B,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAQA;AAED,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,wBAA6B,GAClC,eAAe,CAsGjB;AAqTD,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IACD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvH,WAAW,EAAE,MAAM,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CACnD,CA6KA"}
|
package/dist/hmr.js
CHANGED
|
@@ -5,8 +5,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { compileClient } from '@vesk/compiler/src/client-codegen';
|
|
6
6
|
import { buildHmrEvalSnippet } from './client-bundle';
|
|
7
7
|
import { resolveComponentName, randomToken } from '@vesk/compiler/src/server-codegen';
|
|
8
|
-
import { resolveErrorFile } from '@vesk/adapter/src/ssr-function';
|
|
8
|
+
import { resolveErrorFile, persistedStoreSsrCode } from '@vesk/adapter/src/ssr-function';
|
|
9
9
|
import { resolveCssUrls, hasBuiltGlobalCss } from '@vesk/adapter/src/css';
|
|
10
|
+
import { renderDefaultErrorFragment } from '@vesk/adapter/src/default-pages';
|
|
10
11
|
import { isAllowedWsUpgrade } from '@vesk/adapter/src/paths';
|
|
11
12
|
import { parseCompilerError, buildCodeframe } from '@vesk/adapter/src/error-codeframe';
|
|
12
13
|
import { suggestFor } from '@vesk/adapter/src/error-tips';
|
|
@@ -363,6 +364,7 @@ function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options)
|
|
|
363
364
|
// serialized by `renderFullPage` under another and the page would ship
|
|
364
365
|
// with no ssr-data script.
|
|
365
366
|
' return withSsrStore(async () => {',
|
|
367
|
+
' resetPersistedStoreKeys();',
|
|
366
368
|
' let page;',
|
|
367
369
|
' let caughtError = null;',
|
|
368
370
|
' try {',
|
|
@@ -382,7 +384,7 @@ function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options)
|
|
|
382
384
|
" if (_inner.head) _head = _inner.head + _head;",
|
|
383
385
|
' }',
|
|
384
386
|
" const html = await renderFullPage(_layoutSrcList[0], _layoutCompList[0], { params, children: _body }, __componentRegistry, { hydrate: true, cached: _layoutCompiledList[0]" + bakedOptions + clientScriptOption + dataScriptOption + ', pageHead: _head, sourcePath: _layoutPathList[0] });',
|
|
385
|
-
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
387
|
+
" return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
386
388
|
' });',
|
|
387
389
|
].join('\n');
|
|
388
390
|
}
|
|
@@ -390,6 +392,7 @@ function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options)
|
|
|
390
392
|
renderCode = [
|
|
391
393
|
' return withSsrStore(async () => {',
|
|
392
394
|
' let stream;',
|
|
395
|
+
" const __streamToken = globalThis.__vsk_ssr_token || '';",
|
|
393
396
|
' try {',
|
|
394
397
|
' stream = renderPageStream(_src, _comp, { params }, __componentRegistry, { hydrate: true, cached: _srcCompiled' + bakedOptions + clientScriptOption + dataScriptOption + ", sourcePath: _srcPath });",
|
|
395
398
|
' } catch (err) {',
|
|
@@ -398,13 +401,26 @@ function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options)
|
|
|
398
401
|
" const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err);",
|
|
399
402
|
" const stack = err && typeof err === 'object' && 'stack' in err ? String(err.stack) : '';",
|
|
400
403
|
" const html = await renderFullPage(_errorSrc, _errorComp, { params, statusCode: 500, error: message, stack, url: url.href }, __componentRegistry, { hydrate: true, cached: _errorCompiled" + bakedOptions + clientScriptOption + dataScriptOption + ', sourcePath: _errorPath });',
|
|
401
|
-
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 500 });",
|
|
404
|
+
" return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: 500 });",
|
|
402
405
|
' }',
|
|
403
406
|
' return new Response(new ReadableStream({',
|
|
404
407
|
' async start(controller) {',
|
|
405
408
|
' const enc = new TextEncoder();',
|
|
409
|
+
" let __tail = null;",
|
|
410
|
+
' resetPersistedStoreKeys(__streamToken);',
|
|
406
411
|
' for await (const chunk of stream) {',
|
|
407
|
-
' controller.enqueue(enc.encode(
|
|
412
|
+
' if (__tail !== null) controller.enqueue(enc.encode(__tail));',
|
|
413
|
+
' __tail = chunk;',
|
|
414
|
+
' }',
|
|
415
|
+
' if (__tail !== null) {',
|
|
416
|
+
' const __tag = __storeInitTag(__streamToken);',
|
|
417
|
+
" if (__tag !== '') {",
|
|
418
|
+
" const __bi = __tail.lastIndexOf('</body>');",
|
|
419
|
+
" __tail = __bi === -1 ? __tail + __tag : __tail.slice(0, __bi) + __tag + __tail.slice(__bi);",
|
|
420
|
+
' }',
|
|
421
|
+
' controller.enqueue(enc.encode(__tail));',
|
|
422
|
+
' } else {',
|
|
423
|
+
' resetPersistedStoreKeys(__streamToken);',
|
|
408
424
|
' }',
|
|
409
425
|
' controller.close();',
|
|
410
426
|
' },',
|
|
@@ -419,15 +435,17 @@ function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options)
|
|
|
419
435
|
' const result = await renderPage(_errorSrc, _errorComp, props, __componentRegistry, { hydrate: true, cached: _errorCompiled, sourcePath: _errorPath });',
|
|
420
436
|
' return result.body;',
|
|
421
437
|
' } catch {',
|
|
422
|
-
|
|
438
|
+
` return ${JSON.stringify(renderDefaultErrorFragment())};`,
|
|
423
439
|
' }',
|
|
424
440
|
'}',
|
|
425
441
|
].join('\n');
|
|
426
442
|
const funcCode = [
|
|
427
|
-
"import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, storeDataScriptGlobal, withSsrStore } from '../runtime.js';",
|
|
443
|
+
"import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, storeDataScriptGlobal, withSsrStore, installPersistedStoreServerHooks, persistedStoreCookieName, getPersistedStoreKeys, resetPersistedStoreKeys, buildStorePrePaintPayload, storeInitScriptGlobal, storeInitScriptTag } from '../runtime.js';",
|
|
428
444
|
'', registryCode, src, '',
|
|
429
445
|
errorBodyFnCode,
|
|
430
446
|
'',
|
|
447
|
+
persistedStoreSsrCode(),
|
|
448
|
+
'',
|
|
431
449
|
'export async function handle(request) {',
|
|
432
450
|
' const url = new URL(request.url);',
|
|
433
451
|
" const urlParts = url.pathname.split('/').filter(Boolean);",
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;AAoDjC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,EAAE,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACxC,UAAU,EAAE,CAGd;AAkDD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAiXpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,38 @@ async function resolveCompilerApi(name) {
|
|
|
28
28
|
}
|
|
29
29
|
return import(`@vesk/compiler/src/${name.replace(/\.js$/, '')}`);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Build gate against two page files claiming one URL.
|
|
33
|
+
*
|
|
34
|
+
* SSR routes are keyed by URL pattern (fullPath) — last write wins in the
|
|
35
|
+
* path->file map — while the client router walks the manifest tree first-match
|
|
36
|
+
* wins, so a group page and a root page both claiming `/` ship a document for
|
|
37
|
+
* one of them and hydrate the other. Both respond 200; the only symptom is a
|
|
38
|
+
* page whose claims land on the layout's elements. Refuse the build instead.
|
|
39
|
+
*/
|
|
40
|
+
function findRouteConflicts(routeTree, appDir) {
|
|
41
|
+
const projectRoot = resolve(appDir, '..');
|
|
42
|
+
const byPath = new Map();
|
|
43
|
+
const walk = (nodes) => {
|
|
44
|
+
for (const node of nodes) {
|
|
45
|
+
if (node.page) {
|
|
46
|
+
const pageFile = join(node.sourceDir, 'page.vsk');
|
|
47
|
+
const files = byPath.get(node.fullPath) ?? [];
|
|
48
|
+
files.push(relative(projectRoot, pageFile));
|
|
49
|
+
byPath.set(node.fullPath, files);
|
|
50
|
+
}
|
|
51
|
+
if (node.children && node.children.length > 0)
|
|
52
|
+
walk(node.children);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
walk(routeTree);
|
|
56
|
+
const conflicts = [];
|
|
57
|
+
for (const [fullPath, files] of byPath) {
|
|
58
|
+
if (files.length > 1)
|
|
59
|
+
conflicts.push({ fullPath, files });
|
|
60
|
+
}
|
|
61
|
+
return conflicts;
|
|
62
|
+
}
|
|
31
63
|
/**
|
|
32
64
|
* Defensive local mirror of the plugin-manager's `filterActivePlugins`
|
|
33
65
|
* (`@vesk/adapter/src/plugins`): keep when there is no matching record, or the
|
|
@@ -214,6 +246,14 @@ export async function build(appDir, options) {
|
|
|
214
246
|
console.error('vesk build: no routes found in', appDir);
|
|
215
247
|
return;
|
|
216
248
|
}
|
|
249
|
+
const routeConflicts = findRouteConflicts(routeTree, appDir);
|
|
250
|
+
if (routeConflicts.length > 0) {
|
|
251
|
+
for (const conflict of routeConflicts) {
|
|
252
|
+
console.error(`vesk build: conflicting routes for ${conflict.fullPath}: ${conflict.files.join(', ')}`);
|
|
253
|
+
}
|
|
254
|
+
const first = routeConflicts[0];
|
|
255
|
+
throw new Error(`conflicting routes: ${first.fullPath} is claimed by both ${first.files.join(' and ')}`);
|
|
256
|
+
}
|
|
217
257
|
const projectRoot = resolve(appDir, '..');
|
|
218
258
|
const componentsDir = resolve(projectRoot, 'components');
|
|
219
259
|
const componentMap = scanComponents(componentsDir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AASvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAgBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAyMjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
|
package/dist/platform-handler.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve, dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
// Relative (not `@vesk/adapter/src/...`) so the compiled module keeps working
|
|
5
|
+
// when the dist is copied into a registry-style node_modules layout — see
|
|
6
|
+
// platform-handler.test.ts. No other runtime import in this file, so the pack
|
|
7
|
+
// stays standalone.
|
|
8
|
+
import { renderDefaultNotFoundPage } from './default-pages';
|
|
4
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
10
|
export function routeName(segments) {
|
|
6
11
|
const parts = segments.filter(Boolean).map(s => {
|
|
@@ -65,6 +70,7 @@ export function generatePlatformHandlerSource(input) {
|
|
|
65
70
|
? `const __prerendered = new Set(${JSON.stringify(prerenderedPaths)});\n`
|
|
66
71
|
: 'const __prerendered = new Set();\n';
|
|
67
72
|
const isrCache = 'const __isrCache = new Map();';
|
|
73
|
+
const defaultNotFoundLine = `const __defaultNotFoundHtml = ${JSON.stringify(renderDefaultNotFoundPage())};`;
|
|
68
74
|
const serverStoreLine = hasEvents ? 'const __serverStore = globalThis.__vesk_server_ctx || {};' : '';
|
|
69
75
|
const eventsStartLine = hasEvents ? ' await __eventsStart({ server: null });' : '';
|
|
70
76
|
const eventsRequestLine = hasEvents ? ' await __eventsRequest(mwCtx);' : '';
|
|
@@ -80,6 +86,7 @@ ${mwImport}
|
|
|
80
86
|
${eventsImport}
|
|
81
87
|
${prerenderedList}
|
|
82
88
|
${isrCache}
|
|
89
|
+
${defaultNotFoundLine}
|
|
83
90
|
const __routes = [${routeEntries.join(',\n')}];
|
|
84
91
|
|
|
85
92
|
async function __invokeRoute(route, request) {
|
|
@@ -93,7 +100,7 @@ async function __invokeRoute(route, request) {
|
|
|
93
100
|
});
|
|
94
101
|
}
|
|
95
102
|
if (e && typeof e === 'object' && e.name === 'NotFoundError') {
|
|
96
|
-
return new Response(
|
|
103
|
+
return new Response(__defaultNotFoundHtml, {
|
|
97
104
|
status: 404,
|
|
98
105
|
headers: { 'Content-Type': 'text/html' },
|
|
99
106
|
});
|
|
@@ -154,6 +161,20 @@ ${serverStoreLine}
|
|
|
154
161
|
});
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
// Persisted-store pre-paint payload (buildStorePrePaintPayload) — same
|
|
165
|
+
// token-stash/consume contract as /ssr-data.js, own store so neither handoff
|
|
166
|
+
// can eat the other.
|
|
167
|
+
if (pathname === '/store-init.js') {
|
|
168
|
+
const initToken = url.searchParams.get('t') || '';
|
|
169
|
+
const initStore = (globalThis.__vsk_store_init_payload_store || {});
|
|
170
|
+
const initPayload = initStore[initToken];
|
|
171
|
+
if (initPayload !== undefined) delete initStore[initToken];
|
|
172
|
+
return new Response(initPayload ?? '// no store init', {
|
|
173
|
+
status: 200,
|
|
174
|
+
headers: { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-store' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
157
178
|
${eventsStartLine}
|
|
158
179
|
let mwCtx = { params: {}, url, locals: ${localsSeed}, cookies: {}, request, resolveUrl(u) { return new URL(u, request.url).href; }, set() {}, get() { return undefined; } };
|
|
159
180
|
if (${hasMwLiteral}) {
|
|
@@ -206,7 +227,7 @@ ${eventsRequestLine}
|
|
|
206
227
|
return response;
|
|
207
228
|
}
|
|
208
229
|
|
|
209
|
-
return new Response(
|
|
230
|
+
return new Response(__defaultNotFoundHtml, {
|
|
210
231
|
status: 404,
|
|
211
232
|
headers: { 'Content-Type': 'text/html' },
|
|
212
233
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAkDjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkGD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAqexI"}
|
package/dist/prod-server.js
CHANGED
|
@@ -10,6 +10,7 @@ import { executeError } from '@vesk/adapter/src/events';
|
|
|
10
10
|
import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
|
|
11
11
|
import { resolveCssUrls, hasBuiltGlobalCss } from '@vesk/adapter/src/css';
|
|
12
12
|
import { loadVeskConfig } from '@vesk/adapter/src/load-config';
|
|
13
|
+
import { renderDefaultNotFoundPage, renderDefaultErrorPage } from '@vesk/adapter/src/default-pages';
|
|
13
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
15
16
|
const chunks = [];
|
|
@@ -290,6 +291,19 @@ export async function startProdServer(outDir, options) {
|
|
|
290
291
|
res.end(lines.join('\n') || '// no ssr data');
|
|
291
292
|
return;
|
|
292
293
|
}
|
|
294
|
+
// Persisted-store pre-paint payload (buildStorePrePaintPayload), stashed by
|
|
295
|
+
// storeInitScriptGlobal under its own token store so consuming one never
|
|
296
|
+
// eats the page's /ssr-data.js handoff. Served once, consumed-and-deleted.
|
|
297
|
+
if (url.pathname === '/store-init.js') {
|
|
298
|
+
const token = url.searchParams.get('t') || '';
|
|
299
|
+
const store = globalThis.__vsk_store_init_payload_store;
|
|
300
|
+
const payload = store?.[token];
|
|
301
|
+
if (payload)
|
|
302
|
+
delete store[token];
|
|
303
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-store' });
|
|
304
|
+
res.end(payload ?? '// no store init');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
293
307
|
// Stable alias for the client runtime: the file on disk is content-hashed in
|
|
294
308
|
// production, so the name is resolved from the manifest rather than assumed.
|
|
295
309
|
if (url.pathname === '/_vesk/runtime.js') {
|
|
@@ -509,7 +523,7 @@ export async function startProdServer(outDir, options) {
|
|
|
509
523
|
}
|
|
510
524
|
if (err.name === 'NotFoundError') {
|
|
511
525
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
512
|
-
res.end(notFoundHtml ||
|
|
526
|
+
res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
|
|
513
527
|
return;
|
|
514
528
|
}
|
|
515
529
|
console.error('vesk ssr error:', err.message);
|
|
@@ -541,7 +555,7 @@ export async function startProdServer(outDir, options) {
|
|
|
541
555
|
catch { }
|
|
542
556
|
}
|
|
543
557
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
544
|
-
res.end(errorHtml || '
|
|
558
|
+
res.end(errorHtml || renderDefaultErrorPage({ message: process.env.NODE_ENV !== 'production' ? err.message : undefined, url: url.pathname, headExtra: bakedHeadExtra }));
|
|
545
559
|
return;
|
|
546
560
|
}
|
|
547
561
|
}
|
|
@@ -549,7 +563,7 @@ export async function startProdServer(outDir, options) {
|
|
|
549
563
|
}
|
|
550
564
|
}
|
|
551
565
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
552
|
-
res.end(notFoundHtml ||
|
|
566
|
+
res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
|
|
553
567
|
});
|
|
554
568
|
try {
|
|
555
569
|
await eventsMod?.executeStart?.({ server, port, host });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+LnF"}
|
package/dist/runtime-bundle.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function bundleRuntime(appDir, outDir) {
|
|
|
42
42
|
`import { renderPage, renderFullPage, renderPageStream, compileFile, setRuntimeModule, setVskHydrate, hydratePrecompile, assertSameOrigin, withSsrStore } from ${JSON.stringify(resolve(compilerRoot, 'server-codegen.js'))};`,
|
|
43
43
|
`import { parseCookies } from ${JSON.stringify(resolve(compilerRoot, 'server-cookies.js'))};`,
|
|
44
44
|
`import * as __veskRuntime from ${JSON.stringify(resolve(runtimeRoot, 'index-server.js'))};`,
|
|
45
|
+
`import * as __persistence from ${JSON.stringify(resolve(runtimeRoot, 'store-persistence.js'))};`,
|
|
45
46
|
'',
|
|
46
47
|
'// Inject runtime module so server-codegen can find components like NavLink, Link, etc.',
|
|
47
48
|
'setRuntimeModule(__veskRuntime);',
|
|
@@ -107,6 +108,40 @@ export async function bundleRuntime(appDir, outDir) {
|
|
|
107
108
|
" return '/ssr-data.js?t=' + token;",
|
|
108
109
|
'}',
|
|
109
110
|
'',
|
|
111
|
+
'// Persisted-store SSR continuity. `installPersistedStoreServerHooks` installs the',
|
|
112
|
+
'// adapter-side read hook; `get/getReset` handle the per-request key registry',
|
|
113
|
+
'// the server drains into the pre-paint payload. The init payload is delivered',
|
|
114
|
+
'// as an origin-served script (/store-init.js?t=...) exactly like /ssr-data.js',
|
|
115
|
+
'// so strict CSP (no unsafe-inline) cannot block it; it is never inlined.',
|
|
116
|
+
'export const installPersistedStoreServerHooks = __persistence.installPersistedStoreServerHooks;',
|
|
117
|
+
'export const persistedStoreCookieName = __persistence.persistedStoreCookieName;',
|
|
118
|
+
'export const getPersistedStoreKeys = __persistence.getPersistedStoreKeys;',
|
|
119
|
+
'export const resetPersistedStoreKeys = __persistence.resetPersistedStoreKeys;',
|
|
120
|
+
'export const buildStorePrePaintPayload = __persistence.buildStorePrePaintPayload;',
|
|
121
|
+
'export const storeInitScriptTag = __persistence.storeInitScriptTag;',
|
|
122
|
+
'',
|
|
123
|
+
'// Stash the pre-paint payload under a CSPRNG token and return the origin URL',
|
|
124
|
+
'// the blocking <head> script points at. Own token store (NOT __vsk_ssr_payload_store)',
|
|
125
|
+
'// so consuming the page hydration handoff never eats the store-init payload',
|
|
126
|
+
'// and vice versa. Same 100-entry bound as /ssr-data.js.',
|
|
127
|
+
'export function storeInitScriptGlobal(payload) {',
|
|
128
|
+
" if (!payload) return null;",
|
|
129
|
+
' const bytes = new Uint8Array(12);',
|
|
130
|
+
' crypto.getRandomValues(bytes);',
|
|
131
|
+
" let tk = '';",
|
|
132
|
+
" for (const b of bytes) tk += b.toString(16).padStart(2, '0');",
|
|
133
|
+
' const token = tk;',
|
|
134
|
+
" const store = (globalThis.__vsk_store_init_payload_store ||= {});",
|
|
135
|
+
" store[token] = payload;",
|
|
136
|
+
" const keys = Object.keys(store);",
|
|
137
|
+
' if (keys.length > 100) {',
|
|
138
|
+
' for (let i = 0; i < keys.length - 100; i++) {',
|
|
139
|
+
' delete store[keys[i]];',
|
|
140
|
+
' }',
|
|
141
|
+
' }',
|
|
142
|
+
" return '/store-init.js?t=' + token;",
|
|
143
|
+
'}',
|
|
144
|
+
'',
|
|
110
145
|
'// Re-export runtime classes used by API routes',
|
|
111
146
|
'export const VeskRequest = __veskRuntime.VeskRequest;',
|
|
112
147
|
'export const PayloadTooLargeError = __veskRuntime.PayloadTooLargeError;',
|
package/dist/ssr-function.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { RouteNode, SsrFunctionOptions } from '@vesk/adapter/src/types';
|
|
2
2
|
export declare function resolveErrorFile(sourceDir: string, appDir: string): string | null;
|
|
3
|
+
/** Shared persisted-store SSR helper block emitted into every generated SSR
|
|
4
|
+
* function (prod via ssr-function.ts, dev via hmr.ts — keep the two callers
|
|
5
|
+
* in sync by calling this). Emit it AFTER the function declarations so the
|
|
6
|
+
* module-level `installPersistedStoreServerHooks` call runs on import. */
|
|
7
|
+
export declare function persistedStoreSsrCode(): string;
|
|
3
8
|
export declare function generateSsrFunction(routeNode: RouteNode, appDir: string, outDir: string, componentMap?: Map<string, string>, options?: SsrFunctionOptions): {
|
|
4
9
|
funcPath: string;
|
|
5
10
|
funcCode: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAI7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAgED;;;2EAG2E;AAC3E,wBAAgB,qBAAqB,IAAI,MAAM,CA4C9C;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA4btD"}
|
package/dist/ssr-function.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
2
2
|
import { resolve, relative, join } from 'node:path';
|
|
3
3
|
import { resolveComponentName, precompileFile } from '@vesk/compiler/src/server-codegen';
|
|
4
4
|
import { resolveCssUrls, hasBuiltGlobalCss } from '@vesk/adapter/src/css';
|
|
5
|
+
import { renderDefaultErrorFragment } from '@vesk/adapter/src/default-pages';
|
|
5
6
|
// Finds the nearest error.vsk walking up from the route's own directory to the
|
|
6
7
|
// app root, mirroring the router's findErrorComponent chain semantics.
|
|
7
8
|
export function resolveErrorFile(sourceDir, appDir) {
|
|
@@ -77,6 +78,55 @@ function buildParamExtraction(node, urlParts) {
|
|
|
77
78
|
walk(node);
|
|
78
79
|
return parts;
|
|
79
80
|
}
|
|
81
|
+
/** Shared persisted-store SSR helper block emitted into every generated SSR
|
|
82
|
+
* function (prod via ssr-function.ts, dev via hmr.ts — keep the two callers
|
|
83
|
+
* in sync by calling this). Emit it AFTER the function declarations so the
|
|
84
|
+
* module-level `installPersistedStoreServerHooks` call runs on import. */
|
|
85
|
+
export function persistedStoreSsrCode() {
|
|
86
|
+
return [
|
|
87
|
+
'// ── Persisted-store SSR continuity (createStore / createPathStore persist) ──',
|
|
88
|
+
'// Reads a persisted store\'s cookie value through the adapter request context.',
|
|
89
|
+
'// Client writes mirror URL-encoded; decode on the way back (best effort).',
|
|
90
|
+
'function __persistRead(key) {',
|
|
91
|
+
' const cookieName = persistedStoreCookieName(key);',
|
|
92
|
+
' const req = globalThis.__vesk_request;',
|
|
93
|
+
' if (!req) return null;',
|
|
94
|
+
' const c = req.cookies || {};',
|
|
95
|
+
' const v = typeof c.get === \'function\' ? c.get(cookieName) : c[cookieName];',
|
|
96
|
+
' if (v == null) return null;',
|
|
97
|
+
' try { return decodeURIComponent(String(v)); } catch { return String(v); }',
|
|
98
|
+
'}',
|
|
99
|
+
'',
|
|
100
|
+
'// Drain this request\'s registered persisted keys into a served pre-paint',
|
|
101
|
+
'// script (/store-init.js?t=...) then forget them. `token` selects the',
|
|
102
|
+
'// render\'s bucket for callers outside the ALS scope (the no-layout stream',
|
|
103
|
+
'// path captures its token at the renderPageStream call site); the layout',
|
|
104
|
+
'// path runs this inside the scope and relies on the ambient token.',
|
|
105
|
+
'function __storeInitTag(token) {',
|
|
106
|
+
' const keys = getPersistedStoreKeys(token);',
|
|
107
|
+
' if (keys.length === 0) { resetPersistedStoreKeys(token); return \'\'; }',
|
|
108
|
+
' const values = {};',
|
|
109
|
+
' for (let __i = 0; __i < keys.length; __i++) values[keys[__i]] = __persistRead(keys[__i]);',
|
|
110
|
+
' resetPersistedStoreKeys(token);',
|
|
111
|
+
' const payload = buildStorePrePaintPayload(values);',
|
|
112
|
+
' const url = storeInitScriptGlobal(payload);',
|
|
113
|
+
' return url ? storeInitScriptTag(url) : \'\';',
|
|
114
|
+
'}',
|
|
115
|
+
'',
|
|
116
|
+
'// Inject the blocking store-init <script src> into a full HTML string',
|
|
117
|
+
'// (before </head>; append if the document has no head).',
|
|
118
|
+
'function __injectStoreInit(html) {',
|
|
119
|
+
' const tag = __storeInitTag();',
|
|
120
|
+
' if (tag === \'\') return html;',
|
|
121
|
+
' const idx = html.lastIndexOf(\'</head>\');',
|
|
122
|
+
' if (idx === -1) return html + tag;',
|
|
123
|
+
' return html.slice(0, idx) + tag + html.slice(idx);',
|
|
124
|
+
'}',
|
|
125
|
+
'',
|
|
126
|
+
'installPersistedStoreServerHooks({ read: __persistRead });',
|
|
127
|
+
'',
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
80
130
|
export function generateSsrFunction(routeNode, appDir, outDir, componentMap, options) {
|
|
81
131
|
const ancestorLayouts = options?.ancestorLayouts || [];
|
|
82
132
|
const middlewareCode = options?.middlewareCode || null;
|
|
@@ -161,12 +211,13 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
161
211
|
' const result = await renderPage(\'\', _errorComp, props, __componentRegistry, { hydrate: true, cached: _errorCompiled });',
|
|
162
212
|
' return result.body;',
|
|
163
213
|
' } catch {',
|
|
164
|
-
|
|
214
|
+
` return ${JSON.stringify(renderDefaultErrorFragment())};`,
|
|
165
215
|
' }',
|
|
166
216
|
'}',
|
|
167
217
|
'',
|
|
168
218
|
'async function __renderHtml(params, requestUrl) {',
|
|
169
219
|
' return withSsrStore(async () => {',
|
|
220
|
+
' resetPersistedStoreKeys();',
|
|
170
221
|
' let page;',
|
|
171
222
|
' let caughtError = null;',
|
|
172
223
|
" const __expose = process.env.NODE_ENV !== 'production';",
|
|
@@ -187,7 +238,7 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
187
238
|
' if (_inner.head) _head = _inner.head + _head;',
|
|
188
239
|
' }',
|
|
189
240
|
" const html = await renderFullPage('', _layoutCompList[0], { params, children: _body }, __componentRegistry, { hydrate: true, cached: _layoutCompiledList[0]" + bakedOptions + clientScriptOption + dataScriptOption + ", pageHead: _head });",
|
|
190
|
-
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
241
|
+
" return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
191
242
|
' });',
|
|
192
243
|
'}',
|
|
193
244
|
'',
|
|
@@ -207,25 +258,48 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
207
258
|
'async function __renderHtml(params, requestUrl) {',
|
|
208
259
|
' return withSsrStore(async () => {',
|
|
209
260
|
' let stream;',
|
|
261
|
+
// The streamed no-layout page emits its <head> before the component body
|
|
262
|
+
// renders, so its persisted stores don't exist yet when the head is
|
|
263
|
+
// flushed — the pre-paint tag therefore goes into the final chunk, right
|
|
264
|
+
// before </body> (still a blocking, pre-paint script). Capture the render
|
|
265
|
+
// token now (renderPageStream adopts this exact ambient store) so the
|
|
266
|
+
// registry drain in the consumer's start() can hit the right bucket even
|
|
267
|
+
// though it runs outside the ALS scope.
|
|
268
|
+
" const __streamToken = globalThis.__vsk_ssr_token || '';",
|
|
210
269
|
' try {',
|
|
211
270
|
" stream = renderPageStream('', _comp, { params }, __componentRegistry, { hydrate: true, cached: _compiled" + bakedOptions + clientScriptOption + dataScriptOption + ' });',
|
|
212
271
|
' } catch (err) {',
|
|
213
272
|
' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
|
|
214
273
|
' const html = await __renderErrorFullPage(params, requestUrl, err);',
|
|
215
|
-
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 500 });",
|
|
274
|
+
" return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: 500 });",
|
|
216
275
|
' }',
|
|
217
276
|
' return new Response(new ReadableStream({',
|
|
218
277
|
' async start(controller) {',
|
|
219
278
|
' const enc = new TextEncoder();',
|
|
279
|
+
" let __tail = null;",
|
|
280
|
+
' resetPersistedStoreKeys(__streamToken);',
|
|
220
281
|
' try {',
|
|
221
282
|
' for await (const chunk of stream) {',
|
|
222
|
-
' controller.enqueue(enc.encode(
|
|
283
|
+
' if (__tail !== null) controller.enqueue(enc.encode(__tail));',
|
|
284
|
+
' __tail = chunk;',
|
|
285
|
+
' }',
|
|
286
|
+
' if (__tail !== null) {',
|
|
287
|
+
' const __tag = __storeInitTag(__streamToken);',
|
|
288
|
+
' if (__tag !== \'\') {',
|
|
289
|
+
' const __bi = __tail.lastIndexOf(\'</body>\');',
|
|
290
|
+
' __tail = __bi === -1 ? __tail + __tag : __tail.slice(0, __bi) + __tag + __tail.slice(__bi);',
|
|
291
|
+
' }',
|
|
292
|
+
' controller.enqueue(enc.encode(__tail));',
|
|
293
|
+
' } else {',
|
|
294
|
+
' // Empty stream (no chunks) — nothing to attach the tag to; just',
|
|
295
|
+
' // clear the registry so a later render can\'t inherit these keys.',
|
|
296
|
+
' resetPersistedStoreKeys(__streamToken);',
|
|
223
297
|
' }',
|
|
224
298
|
' } catch (err) {',
|
|
225
299
|
' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
|
|
226
300
|
' try {',
|
|
227
301
|
' const html = await __renderErrorFullPage(params, requestUrl, err);',
|
|
228
|
-
' controller.enqueue(enc.encode(html));',
|
|
302
|
+
' controller.enqueue(enc.encode(__injectStoreInit(html)));',
|
|
229
303
|
' } catch {}',
|
|
230
304
|
' }',
|
|
231
305
|
' controller.close();',
|
|
@@ -236,6 +310,12 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
236
310
|
'',
|
|
237
311
|
].join('\n');
|
|
238
312
|
}
|
|
313
|
+
// Persisted-store SSR continuity (createStore / createPathStore persist).
|
|
314
|
+
// The adapter-installed read hook pulls each store's cookie value through the
|
|
315
|
+
// request context; the tag the generated pages emit points at an origin-served
|
|
316
|
+
// /store-init.js?t=<token> file (CSP-safe, never inlined — see
|
|
317
|
+
// buildStorePrePaintPayload) so the client can pre-paint the user's value.
|
|
318
|
+
const persistCode = persistedStoreSsrCode();
|
|
239
319
|
let dataCode;
|
|
240
320
|
const exposeErr = "process.env.NODE_ENV !== 'production'";
|
|
241
321
|
// SPA data-nav renders page + layouts through renderPage and never runs
|
|
@@ -254,6 +334,9 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
254
334
|
// process-global token — the cross-request data-slot race, for good. The
|
|
255
335
|
// token dies with the withSsrStore scope wrapped around this block instead.
|
|
256
336
|
' delete globalThis.__vsk_ssr_data;',
|
|
337
|
+
// SPA data-nav only runs renderPage, so the persisted-store registration
|
|
338
|
+
// bucket would otherwise keep this request's keys forever.
|
|
339
|
+
' resetPersistedStoreKeys();',
|
|
257
340
|
' }',
|
|
258
341
|
];
|
|
259
342
|
// The data-nav branch never runs renderFullPage/renderPageStream, so it needs
|
|
@@ -446,7 +529,7 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
446
529
|
'',
|
|
447
530
|
].join('\n');
|
|
448
531
|
const funcCode = [
|
|
449
|
-
"import { renderFullPage, renderPageStream, renderPage, hydratePrecompile, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin, VeskRequest } from '../runtime.js';",
|
|
532
|
+
"import { renderFullPage, renderPageStream, renderPage, hydratePrecompile, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin, VeskRequest, installPersistedStoreServerHooks, persistedStoreCookieName, getPersistedStoreKeys, resetPersistedStoreKeys, buildStorePrePaintPayload, storeInitScriptGlobal, storeInitScriptTag } from '../runtime.js';",
|
|
450
533
|
'',
|
|
451
534
|
middlewareCode || '',
|
|
452
535
|
registryCode,
|
|
@@ -455,6 +538,8 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
455
538
|
paramsCode,
|
|
456
539
|
htmlFnCode,
|
|
457
540
|
'',
|
|
541
|
+
persistCode,
|
|
542
|
+
'',
|
|
458
543
|
'export async function handle(request) {',
|
|
459
544
|
' const url = new URL(request.url);',
|
|
460
545
|
' const params = __paramsFor(url.pathname);',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"sharp": "^0.35.3"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@vesk/compiler": "^0.2.
|
|
27
|
-
"@vesk/runtime": "^0.2.
|
|
28
|
-
"@vesk/types": "^0.2.
|
|
26
|
+
"@vesk/compiler": "^0.2.61",
|
|
27
|
+
"@vesk/runtime": "^0.2.61",
|
|
28
|
+
"@vesk/types": "^0.2.61"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/ws": "^8.18.1",
|