@markless/router 0.3.3 → 0.5.0
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/debug-channel-CewnC9fH.js +2 -0
- package/dist/{debug-channel-CSzo-Zj8.js → debug-channel-Di6SppZv.js} +36 -17
- package/dist/fragment-navigation-CIiNnFFc.js +323 -0
- package/dist/index.d.ts +54 -33
- package/dist/index.js +50 -31
- package/dist/link-attributes-nY1Qkmx2.js +7 -0
- package/dist/protocol-constants-B4WxrZAS.js +26 -0
- package/dist/{request-files-C-0LLSsx.js → request-files-Ca8E0FOo.js} +3 -2
- package/dist/request-locals-BFPF6Vdm.js +28 -0
- package/dist/route-href-CGooO_g6.js +18 -0
- package/dist/{route-manifest-DPgGIyrS.js → route-manifest-Bw_nFohL.js} +9 -6
- package/dist/typescript-plugin.cjs +8 -5
- package/dist/typescript-plugin.d.ts +3 -11194
- package/dist/typescript-plugin.js +8 -5
- package/dist/vite/route-href.d.ts +2 -3
- package/dist/vite/route-href.js +1 -17
- package/dist/vite/runtime/create-route-discovery.d.ts +3 -4
- package/dist/vite/runtime/create-route-discovery.js +1 -1
- package/dist/vite/runtime/create-server-entry.d.ts +11 -5
- package/dist/vite/runtime/create-server-entry.js +354 -96
- package/dist/vite/runtime/mdx-route.d.ts +1097 -17
- package/dist/vite/runtime/mdx-route.js +1094 -31
- package/dist/vite.d.ts +10 -6
- package/dist/vite.js +782 -188
- package/package.json +7 -6
- package/src/vite/entries/client-entry.ts +10 -1
- package/src/vite/entries/fragment-entry.ts +543 -0
- package/src/vite/entries/resume-entry.ts +41 -7
|
@@ -1,25 +1,125 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { i as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { i as SCROLL_ATTRIBUTE, n as PREFETCH_ATTRIBUTE, r as REPLACE_ATTRIBUTE, t as LINK_ATTRIBUTE } from "../../link-attributes-nY1Qkmx2.js";
|
|
2
|
+
import { i as ROUTE_SCRIPT_TYPE, n as requestLocals, r as HTML_ATTRIBUTES_SLOT } from "../../request-locals-BFPF6Vdm.js";
|
|
3
|
+
import { a as normalizeRouteFileId, r as matchRouteManifest, t as buildRouteManifestFromFileIds } from "../../route-manifest-Bw_nFohL.js";
|
|
4
|
+
import { a as startFragmentNavigation, i as FRAGMENT_REQUEST_HEADER, n as FRAGMENT_PREFETCH_TTL_MS, o as appendModulePreloads, r as FRAGMENT_REGION_END, s as listenForLinkIntent, t as FRAGMENT_APPEND_END } from "../../fragment-navigation-CIiNnFFc.js";
|
|
5
|
+
import { n as __marklessDebugBootstrapSource } from "../../debug-channel-Di6SppZv.js";
|
|
6
|
+
import { CONTAINER_RETIRE_PROPERTY, renderToString } from "@markless/web/render-to-string";
|
|
7
|
+
import { STREAMED_ARM_SELECTOR, STREAM_ARM_EXECUTOR_GLOBAL, renderToStream } from "@markless/web/render-to-stream";
|
|
5
8
|
import { normalizeMarklessDevError, renderMarklessDevErrorDocument } from "@markless/bundler/dev-error";
|
|
9
|
+
//#region src/vite/runtime/link-bridge-module.ts
|
|
10
|
+
/** Marks the page's link bridge script: inline in dev, a module stub importing the shared bridge in a build. */
|
|
11
|
+
const LINK_BRIDGE_SCRIPT_ATTRIBUTE = "data-markless-router-link-resumer";
|
|
12
|
+
function linkBridgeModulePath(base, hash) {
|
|
13
|
+
return `${base.replace(/\/?$/, "/")}_markless/links.${hash}.js`;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/vite/runtime/page-http.ts
|
|
17
|
+
const RESPONSE_COMMITTED_CODE = "MARKLESS_ROUTER_RESPONSE_COMMITTED";
|
|
18
|
+
function createPageHttp(request, url) {
|
|
19
|
+
let committed;
|
|
20
|
+
let status;
|
|
21
|
+
let statusText;
|
|
22
|
+
const refuse = (write) => {
|
|
23
|
+
throw new Error(`${RESPONSE_COMMITTED_CODE}: ${write} ran after the page's response started streaming to the browser, so its status and headers were already sent. Set http.response before the first byte goes out: in the page body, before any await that outlives the first flush.`);
|
|
24
|
+
};
|
|
25
|
+
const headers = new PageHeaders((write, unchanged) => {
|
|
26
|
+
if (committed && !unchanged(committed.headers)) refuse(write);
|
|
27
|
+
});
|
|
28
|
+
const response = {
|
|
29
|
+
headers,
|
|
30
|
+
get status() {
|
|
31
|
+
return status;
|
|
32
|
+
},
|
|
33
|
+
set status(value) {
|
|
34
|
+
if (committed && value !== committed.status) refuse(`http.response.status = ${value}`);
|
|
35
|
+
status = value;
|
|
36
|
+
},
|
|
37
|
+
get statusText() {
|
|
38
|
+
return statusText;
|
|
39
|
+
},
|
|
40
|
+
set statusText(value) {
|
|
41
|
+
if (committed && value !== committed.statusText) refuse(`http.response.statusText = ${JSON.stringify(value)}`);
|
|
42
|
+
statusText = value;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
context: {
|
|
47
|
+
locals: requestLocals(request) ?? {},
|
|
48
|
+
request,
|
|
49
|
+
response,
|
|
50
|
+
url
|
|
51
|
+
},
|
|
52
|
+
commit(fallbackStatus, routerHeaders) {
|
|
53
|
+
const sent = new Headers(routerHeaders);
|
|
54
|
+
for (const [name, value] of headers) if (name !== "set-cookie") sent.set(name, value);
|
|
55
|
+
for (const cookie of headers.getSetCookie()) sent.append("set-cookie", cookie);
|
|
56
|
+
committed = {
|
|
57
|
+
status: status ?? fallbackStatus,
|
|
58
|
+
statusText,
|
|
59
|
+
headers: sent
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
status: committed.status,
|
|
63
|
+
...statusText === void 0 ? {} : { statusText },
|
|
64
|
+
headers: sent
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var PageHeaders = class extends Headers {
|
|
70
|
+
#guard;
|
|
71
|
+
constructor(guard) {
|
|
72
|
+
super();
|
|
73
|
+
this.#guard = guard;
|
|
74
|
+
}
|
|
75
|
+
set(name, value) {
|
|
76
|
+
this.#guard(`http.response.headers.set(${JSON.stringify(name)})`, (sent) => name.toLowerCase() === "set-cookie" ? sent.getSetCookie().includes(value) : sent.get(name) === value);
|
|
77
|
+
super.set(name, value);
|
|
78
|
+
}
|
|
79
|
+
append(name, value) {
|
|
80
|
+
this.#guard(`http.response.headers.append(${JSON.stringify(name)})`, (sent) => name.toLowerCase() === "set-cookie" ? sent.getSetCookie().includes(value) : (sent.get(name)?.split(", ") ?? []).includes(value));
|
|
81
|
+
if (!headerValues(this, name).includes(value)) super.append(name, value);
|
|
82
|
+
}
|
|
83
|
+
delete(name) {
|
|
84
|
+
this.#guard(`http.response.headers.delete(${JSON.stringify(name)})`, (sent) => !sent.has(name));
|
|
85
|
+
super.delete(name);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function headerValues(headers, name) {
|
|
89
|
+
return name.toLowerCase() === "set-cookie" ? headers.getSetCookie() : headers.get(name)?.split(", ") ?? [];
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
6
92
|
//#region src/vite/runtime/create-server-entry.ts
|
|
7
93
|
const DOCUMENT_CHILDREN_PLACEHOLDER = "__markless_router_document_children__";
|
|
8
94
|
function createServerEntry(options) {
|
|
9
95
|
const manifest = buildRouteManifestFromFileIds(options.routeFileIds);
|
|
10
96
|
assertCurrentRouteAssets(options);
|
|
97
|
+
const fragmentBridge = options.fragmentEntryPath !== void 0 && options.navigation !== "render" ? {
|
|
98
|
+
entry: options.fragmentEntryPath,
|
|
99
|
+
source: options.fragmentNavigationSource ?? startFragmentNavigation.toString(),
|
|
100
|
+
prefetch: options.prefetch !== false,
|
|
101
|
+
pages: manifest.routes.map((route) => route.pathname.replace(/:[^/]+/g, ":"))
|
|
102
|
+
} : void 0;
|
|
103
|
+
const fragmentNavigation = fragmentBridge !== void 0;
|
|
104
|
+
const linkBridges = !options.dev && options.navigationEntryPath ? linkBridgeModules(options.base ?? "/", options.navigationEntryPath, fragmentNavigation || options.prefetch !== false, options.documentNavigation === "document", fragmentBridge) : void 0;
|
|
11
105
|
async function fetch(request) {
|
|
12
106
|
const url = new URL(request.url);
|
|
107
|
+
const linkBridge = linkBridges?.find((module) => module.path === url.pathname);
|
|
108
|
+
if (linkBridge) return new Response(linkBridge.source, { headers: {
|
|
109
|
+
"content-type": "text/javascript;charset=utf-8",
|
|
110
|
+
"cache-control": "public, max-age=31536000, s-maxage=31536000, immutable"
|
|
111
|
+
} });
|
|
112
|
+
const fragment = fragmentNavigation && request.headers.get("x-markless-fragment") === "1";
|
|
13
113
|
if (isNitroApiPathname(url.pathname)) return new Response("Not found", { status: 404 });
|
|
14
114
|
const match = matchRouteManifest(url.pathname, manifest);
|
|
15
|
-
if (!match) return renderStatusPage(url, manifest.statusPages.notFound, 404, "Not found");
|
|
115
|
+
if (!match) return renderStatusPage(url, manifest.statusPages.notFound, 404, "Not found", fragment, request);
|
|
16
116
|
try {
|
|
17
|
-
return await renderPage(url, match.route.file, match.params, 200);
|
|
117
|
+
return await renderPage(url, match.route.file, match.params, 200, fragment, request);
|
|
18
118
|
} catch (error) {
|
|
19
119
|
console.error("[markless-router] page render failed:", error);
|
|
20
120
|
if (options.dev) return renderDevelopmentError(url, error);
|
|
21
121
|
try {
|
|
22
|
-
return await renderStatusPage(url, manifest.statusPages.error, 500, "Internal Server Error");
|
|
122
|
+
return await renderStatusPage(url, manifest.statusPages.error, 500, "Internal Server Error", fragment, request);
|
|
23
123
|
} catch (errorPageError) {
|
|
24
124
|
console.error("[markless-router] error page render failed:", errorPageError);
|
|
25
125
|
return new Response("Internal Server Error", {
|
|
@@ -50,15 +150,18 @@ function createServerEntry(options) {
|
|
|
50
150
|
});
|
|
51
151
|
}
|
|
52
152
|
}
|
|
53
|
-
async function renderStatusPage(url, file, status, fallbackText) {
|
|
153
|
+
async function renderStatusPage(url, file, status, fallbackText, fragment, request) {
|
|
54
154
|
if (!file) return new Response(fallbackText, { status });
|
|
55
|
-
return renderPage(url, file, {}, status);
|
|
155
|
+
return renderPage(url, file, {}, status, fragment, request);
|
|
56
156
|
}
|
|
57
|
-
async function renderPage(url, file, params, status) {
|
|
157
|
+
async function renderPage(url, file, params, status, fragment, request) {
|
|
58
158
|
const loadPageModule = options.pageModuleLoaders[file];
|
|
59
159
|
if (!loadPageModule) return new Response(`Page module not found: ${file}`, { status: 500 });
|
|
160
|
+
const cookie = request.headers.get("cookie");
|
|
60
161
|
const pageModule = await loadPageModule();
|
|
162
|
+
const http = createPageHttp(request, url);
|
|
61
163
|
const pageProps = {
|
|
164
|
+
http: http.context,
|
|
62
165
|
params,
|
|
63
166
|
url: {
|
|
64
167
|
href: url.href,
|
|
@@ -69,45 +172,60 @@ function createServerEntry(options) {
|
|
|
69
172
|
};
|
|
70
173
|
const documentModule = options.documentModuleLoader ? await options.documentModuleLoader() : void 0;
|
|
71
174
|
const headers = { "content-type": "text/html;charset=utf-8" };
|
|
175
|
+
if (fragmentNavigation) headers.vary = FRAGMENT_REQUEST_HEADER;
|
|
72
176
|
const baseArtifact = pageModule.default;
|
|
73
177
|
const renderSsr = baseArtifact?.renderSsr ?? pageModule.marklessRenderSsr;
|
|
74
178
|
if (!renderSsr) {
|
|
75
179
|
const shell = await renderDocumentShell(documentModule, pageProps, "");
|
|
76
180
|
const message = `Page module must export an Markless compiled artifact: ${escapeHtml(file)}`;
|
|
77
|
-
return new Response(fillDocumentChildren(shell, message),
|
|
78
|
-
status,
|
|
79
|
-
headers
|
|
80
|
-
});
|
|
181
|
+
return new Response(fillDocumentChildren(shell, message), http.commit(status, headers));
|
|
81
182
|
}
|
|
82
|
-
const
|
|
83
|
-
const
|
|
183
|
+
const documentLoads = options.documentNavigation === "document";
|
|
184
|
+
const linkIntent = fragmentNavigation || options.prefetch !== false;
|
|
185
|
+
const destinationPreloads = documentLoads || fragmentNavigation ? options.routeSsrModulePreloads : options.routeModulePreloads;
|
|
186
|
+
const pageArtifact = routedPageArtifact(renderSsr, baseArtifact, pageProps, file, options.navigationEntryPath, pageStylesheets(options, file), documentModule?.default?.headInjections ?? [], linkIntent ? (html) => linkIntentPreloads(html, pageProps.url.href, manifest, destinationPreloads, fragmentNavigation) : void 0, documentLoads, fragmentBridge, linkBridges);
|
|
187
|
+
let rendersStorage = false;
|
|
188
|
+
const routedArtifact = {
|
|
84
189
|
...pageArtifact,
|
|
85
|
-
prerenderWakeModuleUrl: options.prerenderWakeEntryPath
|
|
86
|
-
|
|
190
|
+
...options.prerenderWakeEntryPath ? { prerenderWakeModuleUrl: options.prerenderWakeEntryPath } : {},
|
|
191
|
+
async renderSsr(renderProps, renderContext) {
|
|
192
|
+
const output = await pageArtifact.renderSsr(renderProps, renderContext);
|
|
193
|
+
if (documentStorageRecords(output.state).length > 0) rendersStorage = true;
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const privateToVisitor = () => {
|
|
198
|
+
if (!rendersStorage) return;
|
|
199
|
+
headers["cache-control"] = "private";
|
|
200
|
+
headers.vary = headers.vary ? `${headers.vary}, cookie` : "cookie";
|
|
201
|
+
};
|
|
87
202
|
const renderOptions = {
|
|
203
|
+
cookie,
|
|
88
204
|
props: pageProps,
|
|
89
205
|
resumeModuleUrl: options.resumeEntryPath ?? baseArtifact?.resumeModuleUrl,
|
|
90
|
-
modulePreloads: (html) =>
|
|
206
|
+
modulePreloads: (html) => landingModulePreloads(options.routeSsrModulePreloads?.[file], linkBridges?.[html.includes("href=\"#/") ? 1 : 0].path)
|
|
91
207
|
};
|
|
92
208
|
if (options.render === "blocking") {
|
|
93
209
|
const pageHtml = splitLeadingHeadHtml(await renderToString(routedArtifact, renderOptions));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
210
|
+
privateToVisitor();
|
|
211
|
+
const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, options.importMap);
|
|
212
|
+
const html = fragment ? fragmentDocument(shell, pageHtml.bodyHtml, documentLoads) + FRAGMENT_REGION_END : fillDocumentChildren(shell, pageHtml.bodyHtml);
|
|
213
|
+
return new Response(html, http.commit(status, headers));
|
|
99
214
|
}
|
|
100
215
|
const stream = await renderToStream(routedArtifact, renderOptions);
|
|
216
|
+
privateToVisitor();
|
|
101
217
|
const pageHtml = splitLeadingHeadHtml(stream.shell);
|
|
102
|
-
const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml);
|
|
103
|
-
if (stream.pendingArmCount === 0)
|
|
104
|
-
|
|
105
|
-
headers
|
|
106
|
-
}
|
|
218
|
+
const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, options.importMap);
|
|
219
|
+
if (stream.pendingArmCount === 0) {
|
|
220
|
+
const html = fragment ? fragmentDocument(shell, pageHtml.bodyHtml, documentLoads) + FRAGMENT_REGION_END : fillDocumentChildren(shell, pageHtml.bodyHtml);
|
|
221
|
+
return new Response(html, http.commit(status, headers));
|
|
222
|
+
}
|
|
107
223
|
const placeholderAt = shell.indexOf(DOCUMENT_CHILDREN_PLACEHOLDER);
|
|
108
|
-
const prefix = placeholderAt === -1 ? shell : shell.slice(0, placeholderAt) + pageHtml.bodyHtml;
|
|
109
|
-
const suffix = placeholderAt === -1 ? "" : shell.slice(placeholderAt + 37);
|
|
224
|
+
const prefix = fragment ? fragmentDocument(shell, pageHtml.bodyHtml, documentLoads) + FRAGMENT_REGION_END : placeholderAt === -1 ? shell : shell.slice(0, placeholderAt) + pageHtml.bodyHtml;
|
|
225
|
+
const suffix = fragment || placeholderAt === -1 ? "" : shell.slice(placeholderAt + 37);
|
|
226
|
+
const appendEnd = fragment ? FRAGMENT_APPEND_END : "";
|
|
110
227
|
const encoder = new TextEncoder();
|
|
228
|
+
const sent = http.commit(status, headers);
|
|
111
229
|
let cancelled = false;
|
|
112
230
|
let completed = false;
|
|
113
231
|
const body = new ReadableStream({
|
|
@@ -116,7 +234,7 @@ function createServerEntry(options) {
|
|
|
116
234
|
try {
|
|
117
235
|
for await (const chunk of stream.appends()) {
|
|
118
236
|
if (cancelled) return;
|
|
119
|
-
controller.enqueue(encoder.encode(chunk));
|
|
237
|
+
controller.enqueue(encoder.encode(chunk + appendEnd));
|
|
120
238
|
}
|
|
121
239
|
} catch (error) {
|
|
122
240
|
if (cancelled) return;
|
|
@@ -126,7 +244,7 @@ function createServerEntry(options) {
|
|
|
126
244
|
return;
|
|
127
245
|
}
|
|
128
246
|
if (cancelled) return;
|
|
129
|
-
controller.enqueue(encoder.encode(suffix));
|
|
247
|
+
if (suffix) controller.enqueue(encoder.encode(suffix));
|
|
130
248
|
controller.close();
|
|
131
249
|
completed = true;
|
|
132
250
|
},
|
|
@@ -136,10 +254,7 @@ function createServerEntry(options) {
|
|
|
136
254
|
stream.cancel(reason);
|
|
137
255
|
}
|
|
138
256
|
});
|
|
139
|
-
return new Response(body,
|
|
140
|
-
status,
|
|
141
|
-
headers
|
|
142
|
-
});
|
|
257
|
+
return new Response(body, sent);
|
|
143
258
|
}
|
|
144
259
|
return { fetch };
|
|
145
260
|
}
|
|
@@ -161,15 +276,35 @@ function assertCurrentRouteAssets(options) {
|
|
|
161
276
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error(`Markless Router ${label} client-asset routes are stale; expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}. Rebuild the client environment before the server environment.`);
|
|
162
277
|
}
|
|
163
278
|
}
|
|
164
|
-
function
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
279
|
+
function dedupeHeadLinks(injections) {
|
|
280
|
+
const seen = /* @__PURE__ */ new Set();
|
|
281
|
+
return injections.filter((injection) => {
|
|
282
|
+
const href = injection.tag === "link" ? injection.attributes?.href : void 0;
|
|
283
|
+
if (typeof href !== "string") return true;
|
|
284
|
+
if (seen.has(href)) return false;
|
|
285
|
+
seen.add(href);
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function pageStylesheets(options, file) {
|
|
290
|
+
const documentHrefs = options.documentStylesheets ?? [];
|
|
291
|
+
const routeHrefs = options.routeStylesheets?.[file];
|
|
292
|
+
if (documentHrefs.length === 0) return routeHrefs;
|
|
293
|
+
return [.../* @__PURE__ */ new Set([...documentHrefs, ...routeHrefs ?? []])];
|
|
294
|
+
}
|
|
295
|
+
function routedPageArtifact(renderSsr, baseArtifact, pageProps, file, navigationEntryPath, stylesheetHrefs, documentHeadInjections = [], intentPreloads, documentLoads = false, fragment, linkBridgeModules) {
|
|
296
|
+
const headInjections = dedupeHeadLinks([
|
|
297
|
+
...documentHeadInjections,
|
|
298
|
+
...baseArtifact?.headInjections ?? [],
|
|
299
|
+
...(stylesheetHrefs ?? []).map((href) => ({
|
|
300
|
+
tag: "link",
|
|
301
|
+
location: "head",
|
|
302
|
+
attributes: {
|
|
303
|
+
rel: "stylesheet",
|
|
304
|
+
href
|
|
305
|
+
}
|
|
306
|
+
}))
|
|
307
|
+
]);
|
|
173
308
|
return {
|
|
174
309
|
resumeModuleUrl: baseArtifact?.resumeModuleUrl,
|
|
175
310
|
inlineResumerSources: baseArtifact?.inlineResumerSources,
|
|
@@ -179,7 +314,7 @@ function routedPageArtifact(renderSsr, baseArtifact, pageProps, file, navigation
|
|
|
179
314
|
const output = await renderSsr(renderProps ?? pageProps, renderContext);
|
|
180
315
|
if (!output) return { html: "" };
|
|
181
316
|
const routeScript = output.state || output.view ? renderRouteScript(file) : "";
|
|
182
|
-
const linkBridge = navigationEntryPath ? renderLinkBridgeScript(navigationEntryPath) : "";
|
|
317
|
+
const linkBridge = navigationEntryPath ? renderLinkBridgeScript(navigationEntryPath, intentPreloads?.(output.html), documentLoads, fragment, output.html.includes("href=\"#/"), linkBridgeModules) : "";
|
|
183
318
|
const stateWithProps = withPagePropsCell(output.state, pageProps);
|
|
184
319
|
return routeScript || linkBridge || stateWithProps !== output.state ? {
|
|
185
320
|
...output,
|
|
@@ -191,6 +326,7 @@ function routedPageArtifact(renderSsr, baseArtifact, pageProps, file, navigation
|
|
|
191
326
|
}
|
|
192
327
|
function withPagePropsCell(state, props) {
|
|
193
328
|
if (!state || typeof state !== "object") return state;
|
|
329
|
+
const { http: _serverOnly, ...browserProps } = props;
|
|
194
330
|
const cells = state.cells;
|
|
195
331
|
if (!Array.isArray(cells) || cells.some((cell) => cell?.graphNodeId === "prop:props")) return state;
|
|
196
332
|
return {
|
|
@@ -199,25 +335,50 @@ function withPagePropsCell(state, props) {
|
|
|
199
335
|
graphNodeId: "prop:props",
|
|
200
336
|
name: "props",
|
|
201
337
|
valueKind: "object",
|
|
202
|
-
directValue:
|
|
338
|
+
directValue: browserProps
|
|
203
339
|
}]
|
|
204
340
|
};
|
|
205
341
|
}
|
|
206
|
-
function
|
|
342
|
+
function landingModulePreloads(routePreloads, linkBridgePath) {
|
|
207
343
|
const preloads = [];
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!routeModulePreloads || !html.includes("data-markless-router-link")) return preloads.length > 0 ? preloads : void 0;
|
|
211
|
-
for (const href of routerLinkHrefs(html)) {
|
|
212
|
-
const url = parseSameOriginUrl(href, baseHref);
|
|
213
|
-
const match = url && matchRouteManifest(url.pathname, manifest);
|
|
214
|
-
addModulePreloads(preloads, seen, match && routeModulePreloads[match.route.file]);
|
|
215
|
-
}
|
|
344
|
+
addModulePreloads(preloads, /* @__PURE__ */ new Set(), routePreloads);
|
|
345
|
+
if (linkBridgePath) preloads.push(linkBridgePath);
|
|
216
346
|
return preloads.length > 0 ? preloads : void 0;
|
|
217
347
|
}
|
|
218
348
|
function routerLinkHrefs(html) {
|
|
219
349
|
return [...html.matchAll(/<a\b(?=[^>]*\bdata-markless-router-link(?:[\s=>]|$))(?=[^>]*\bhref="([^"]*)")[^>]*>/g)].map((match) => unescapeHtmlAttribute(match[1] ?? ""));
|
|
220
350
|
}
|
|
351
|
+
function anchorHrefs(html) {
|
|
352
|
+
return [...html.matchAll(/<a\b(?=[^>]*\bhref="([^"]*)")[^>]*>/g)].map((match) => unescapeHtmlAttribute(match[1] ?? ""));
|
|
353
|
+
}
|
|
354
|
+
function linkIntentPreloads(html, baseHref, manifest, routes, plainAnchors = false) {
|
|
355
|
+
const destinations = Object.create(null);
|
|
356
|
+
for (const href of plainAnchors ? anchorHrefs(html) : routerLinkHrefs(html)) {
|
|
357
|
+
const url = parseSameOriginUrl(href, baseHref);
|
|
358
|
+
const match = url && matchRouteManifest(url.pathname, manifest);
|
|
359
|
+
const preloads = match && routes?.[match.route.file];
|
|
360
|
+
if (url && preloads?.length) destinations[url.pathname] = preloads;
|
|
361
|
+
}
|
|
362
|
+
return destinations;
|
|
363
|
+
}
|
|
364
|
+
function indexedPreloads(intentPreloads) {
|
|
365
|
+
const preloads = [];
|
|
366
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
367
|
+
const destinations = Object.create(null);
|
|
368
|
+
for (const [pathname, list] of Object.entries(intentPreloads)) destinations[pathname] = list.map((preload) => {
|
|
369
|
+
const key = JSON.stringify(preload);
|
|
370
|
+
let index = indexes.get(key);
|
|
371
|
+
if (index === void 0) {
|
|
372
|
+
index = preloads.push(preload) - 1;
|
|
373
|
+
indexes.set(key, index);
|
|
374
|
+
}
|
|
375
|
+
return index;
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
preloads,
|
|
379
|
+
destinations
|
|
380
|
+
};
|
|
381
|
+
}
|
|
221
382
|
function parseSameOriginUrl(href, base) {
|
|
222
383
|
try {
|
|
223
384
|
const current = new URL(base);
|
|
@@ -249,16 +410,17 @@ function splitLeadingHeadHtml(html) {
|
|
|
249
410
|
headHtml: html.slice(0, containerStart)
|
|
250
411
|
};
|
|
251
412
|
}
|
|
252
|
-
async function renderDocumentShell(documentModule, pageProps, headHtml) {
|
|
253
|
-
const
|
|
413
|
+
async function renderDocumentShell(documentModule, pageProps, headHtml, importMap = "") {
|
|
414
|
+
const written = {};
|
|
254
415
|
const documentHtml = await renderDocumentModule(documentModule, {
|
|
255
416
|
...pageProps,
|
|
256
417
|
children: DOCUMENT_CHILDREN_PLACEHOLDER
|
|
257
|
-
});
|
|
418
|
+
}, written);
|
|
419
|
+
const attributes = htmlAttributes(documentModule, pageProps, written);
|
|
258
420
|
if (documentHtml !== void 0) return [
|
|
259
421
|
"<!doctype html>",
|
|
260
422
|
`<html${renderAttributes(attributes)}>`,
|
|
261
|
-
insertHeadHtml(documentHtml, headHtml),
|
|
423
|
+
insertImportMap(insertHeadHtml(documentHtml, headHtml), importMap),
|
|
262
424
|
"</html>"
|
|
263
425
|
].join("");
|
|
264
426
|
return [
|
|
@@ -266,6 +428,7 @@ async function renderDocumentShell(documentModule, pageProps, headHtml) {
|
|
|
266
428
|
`<html${renderAttributes(attributes)}>`,
|
|
267
429
|
"<head>",
|
|
268
430
|
"<meta charset=\"utf-8\">",
|
|
431
|
+
importMap,
|
|
269
432
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
|
|
270
433
|
headHtml,
|
|
271
434
|
"</head>",
|
|
@@ -280,8 +443,14 @@ function fillDocumentChildren(documentShell, children) {
|
|
|
280
443
|
if (at === -1) return documentShell;
|
|
281
444
|
return documentShell.slice(0, at) + children + documentShell.slice(at + 37);
|
|
282
445
|
}
|
|
283
|
-
|
|
284
|
-
const
|
|
446
|
+
function fragmentDocument(shell, bodyHtml, documentLoads) {
|
|
447
|
+
const bodyAt = documentLoads ? -1 : shell.search(/<body[\s>]/i);
|
|
448
|
+
const bodyOpenEnd = bodyAt === -1 ? -1 : shell.indexOf(">", bodyAt);
|
|
449
|
+
if (bodyOpenEnd === -1) return fillDocumentChildren(shell, bodyHtml);
|
|
450
|
+
return `${shell.slice(0, bodyOpenEnd + 1)}${bodyHtml}</body></html>`;
|
|
451
|
+
}
|
|
452
|
+
async function renderDocumentModule(documentModule, props, htmlAttributes) {
|
|
453
|
+
const output = await documentModule?.default?.renderSsr?.(props, { [HTML_ATTRIBUTES_SLOT]: htmlAttributes });
|
|
285
454
|
assertDocumentStorageFree(output);
|
|
286
455
|
return output?.html;
|
|
287
456
|
}
|
|
@@ -303,31 +472,28 @@ function insertHeadHtml(documentHtml, headHtml) {
|
|
|
303
472
|
return `${documentHtml.slice(0, headEnd)}${headHtml}${documentHtml.slice(headEnd)}`;
|
|
304
473
|
}
|
|
305
474
|
function renderRouteScript(file) {
|
|
306
|
-
return `<script type="
|
|
475
|
+
return `<script type="${ROUTE_SCRIPT_TYPE}">${escapeScriptJson({ file })}<\/script>`;
|
|
307
476
|
}
|
|
308
|
-
function
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
const linkAttr = 'data-markless-router-link';
|
|
329
|
-
const replaceAttr = 'data-markless-router-replace';
|
|
330
|
-
const scrollAttr = 'data-markless-router-scroll';
|
|
477
|
+
function linkBridgeBody(resumeEntryPath, linkIntent, documentLoads, fragment, page) {
|
|
478
|
+
const inline = !("module" in page);
|
|
479
|
+
const fragmentNavigation = fragment !== void 0;
|
|
480
|
+
const fragmentSource = fragment ? `
|
|
481
|
+
(${fragment.source})(d,()=>(${escapeScriptJson(fragmentConfig(fragment, documentLoads))}),${inline ? escapeScriptJson(page.intentPreloads ?? {}) : "r"},(href)=>import(href));` : "";
|
|
482
|
+
const indexed = !fragmentNavigation && linkIntent ? inline ? page.intentPreloads && indexedPreloads(page.intentPreloads) : {
|
|
483
|
+
preloads: "preloads",
|
|
484
|
+
destinations: "destinations"
|
|
485
|
+
} : void 0;
|
|
486
|
+
const intentSource = indexed ? `
|
|
487
|
+
const preloads = ${inline ? escapeScriptJson(indexed.preloads) : "p"};
|
|
488
|
+
const destinations = ${inline ? escapeScriptJson(indexed.destinations) : "q"};
|
|
489
|
+
const destination = (url) => destinations[url.pathname]?.map((index) => preloads[index]);
|
|
490
|
+
const append = (${appendModulePreloads.toString()});
|
|
491
|
+
(${listenForLinkIntent.toString()})(d, linkAttr, (url) => {
|
|
492
|
+
append(d, destination(url));
|
|
493
|
+
}, prefetchAttr);` : "";
|
|
494
|
+
const hashClickSource = fragment && !page.hashLinks ? "" : `
|
|
495
|
+
if (d.__marklessRouterHashClickStarted) return;
|
|
496
|
+
d.__marklessRouterHashClickStarted = true;
|
|
331
497
|
const anchorFrom = (event) => {
|
|
332
498
|
const target = event.composedPath && event.composedPath()[0] || event.target;
|
|
333
499
|
return target && target.closest ? target.closest('a[href]') : target && target.parentElement && target.parentElement.closest ? target.parentElement.closest('a[href]') : null;
|
|
@@ -340,10 +506,7 @@ function renderLinkBridgeScript(resumeEntryPath) {
|
|
|
340
506
|
return null;
|
|
341
507
|
}
|
|
342
508
|
};
|
|
343
|
-
|
|
344
|
-
import(${JSON.stringify(resumeEntryPath)}).catch(() => {});
|
|
345
|
-
}
|
|
346
|
-
r.addEventListener('click', async (event) => {
|
|
509
|
+
d.addEventListener('click', async (event) => {
|
|
347
510
|
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return;
|
|
348
511
|
const anchor = anchorFrom(event);
|
|
349
512
|
if (!anchor || !anchor.hasAttribute(linkAttr) || anchor.hasAttribute('download')) return;
|
|
@@ -351,7 +514,7 @@ function renderLinkBridgeScript(resumeEntryPath) {
|
|
|
351
514
|
if (target && target !== '_self') return;
|
|
352
515
|
if (anchor.relList && anchor.relList.contains('external')) return;
|
|
353
516
|
const url = sameOrigin(anchor.href);
|
|
354
|
-
if (!url) return;
|
|
517
|
+
if (!url${documentLoads || fragmentNavigation ? " || !url.hash.startsWith('#/')" : ""}) return;
|
|
355
518
|
event.preventDefault();
|
|
356
519
|
try {
|
|
357
520
|
const mod = await import(${JSON.stringify(resumeEntryPath)});
|
|
@@ -369,11 +532,95 @@ function renderLinkBridgeScript(resumeEntryPath) {
|
|
|
369
532
|
setTimeout(() => { throw error; });
|
|
370
533
|
location.assign(url.href);
|
|
371
534
|
}
|
|
372
|
-
}, true)
|
|
535
|
+
}, true);`;
|
|
536
|
+
const debugRegistration = typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__ ? `
|
|
537
|
+
try {
|
|
538
|
+
const key = Symbol.for('markless.debug.channel.v1.bootstrap');
|
|
539
|
+
const md = globalThis[key];
|
|
540
|
+
if (md) { md.router('ssr-link-bridge'); md.activate(); }
|
|
541
|
+
} catch {}` : "";
|
|
542
|
+
return `${fragmentSource}${fragment && !hashClickSource ? "" : `
|
|
543
|
+
if(d.__marklessRouterLinkResumerStarted${fragment ? "&&d.__marklessRouterHashClickStarted" : ""})return;d.__marklessRouterLinkResumerStarted=true;`}${intentSource || hashClickSource ? `
|
|
544
|
+
const linkAttr = ${JSON.stringify(LINK_ATTRIBUTE)};` : ""}${intentSource ? `
|
|
545
|
+
const prefetchAttr = ${JSON.stringify(PREFETCH_ATTRIBUTE)};` : ""}${hashClickSource ? `
|
|
546
|
+
const replaceAttr = ${JSON.stringify(REPLACE_ATTRIBUTE)};
|
|
547
|
+
const scrollAttr = ${JSON.stringify(SCROLL_ATTRIBUTE)};` : ""}${intentSource}
|
|
548
|
+
if(location.hash&&location.hash.startsWith('#/'))import(${JSON.stringify(resumeEntryPath)}).catch(()=>{});${hashClickSource}${debugRegistration}`;
|
|
549
|
+
}
|
|
550
|
+
function linkBridgeModules(base, resumeEntryPath, linkIntent, documentLoads, fragment) {
|
|
551
|
+
const variant = (hashLinks) => {
|
|
552
|
+
const source = `export default(r,p,q)=>{const d=document;${linkBridgeBody(resumeEntryPath, linkIntent, documentLoads, fragment, {
|
|
553
|
+
module: true,
|
|
554
|
+
hashLinks
|
|
555
|
+
})}\n};`;
|
|
556
|
+
return {
|
|
557
|
+
path: linkBridgeModulePath(base, contentHash(source)),
|
|
558
|
+
source
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
const withoutHashLinks = variant(false);
|
|
562
|
+
return [withoutHashLinks, fragment ? variant(true) : withoutHashLinks];
|
|
563
|
+
}
|
|
564
|
+
function contentHash(text) {
|
|
565
|
+
let hash = 2166136261;
|
|
566
|
+
for (let at = 0; at < text.length; at++) hash = Math.imul(hash ^ text.charCodeAt(at), 16777619);
|
|
567
|
+
return (hash >>> 0).toString(36);
|
|
568
|
+
}
|
|
569
|
+
function renderLinkBridgeScript(resumeEntryPath, intentPreloads, documentLoads = false, fragment, hashLinks = false, modules) {
|
|
570
|
+
const debugBootstrap = typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__ ? `<script data-markless-router-debug-bootstrap>${escapeInlineScript(`(() => {
|
|
571
|
+
const d = document;
|
|
572
|
+
const s = d.currentScript;
|
|
573
|
+
const r = s && s.closest('[data-async-container]');
|
|
574
|
+
if (!r) return;
|
|
575
|
+
try { globalThis[Symbol.for('markless.debug.channel.v1.bootstrap')] = ${__marklessDebugBootstrapSource()}(r, 'ssr-inline', false); } catch {}
|
|
576
|
+
})();`)}<\/script>` : "";
|
|
577
|
+
const module = modules?.[hashLinks ? 1 : 0];
|
|
578
|
+
if (module) {
|
|
579
|
+
const indexed = !fragment && intentPreloads ? indexedPreloads(intentPreloads) : void 0;
|
|
580
|
+
const args = fragment ? [escapeScriptJson(intentPreloads ?? {})] : indexed ? [
|
|
581
|
+
"0",
|
|
582
|
+
escapeScriptJson(indexed.preloads),
|
|
583
|
+
escapeScriptJson(indexed.destinations)
|
|
584
|
+
] : [];
|
|
585
|
+
return `${debugBootstrap}<script type="module" ${LINK_BRIDGE_SCRIPT_ATTRIBUTE}>${escapeInlineScript(`import b from${JSON.stringify(module.path)};b(${args.join(",")})`)}<\/script>`;
|
|
586
|
+
}
|
|
587
|
+
const body = linkBridgeBody(resumeEntryPath, intentPreloads !== void 0, documentLoads, fragment, {
|
|
588
|
+
intentPreloads,
|
|
589
|
+
hashLinks
|
|
590
|
+
});
|
|
591
|
+
return `${debugBootstrap}<script ${LINK_BRIDGE_SCRIPT_ATTRIBUTE}>${escapeInlineScript(`(()=>{const d=document,s=d.currentScript,r=s&&s.closest('[data-async-container]');if(!r)return;${body}
|
|
373
592
|
})();`)}<\/script>`;
|
|
374
593
|
}
|
|
375
|
-
function
|
|
376
|
-
|
|
594
|
+
function fragmentConfig(fragment, documentLoads) {
|
|
595
|
+
return {
|
|
596
|
+
entry: fragment.entry,
|
|
597
|
+
linkAttribute: LINK_ATTRIBUTE,
|
|
598
|
+
prefetchAttribute: PREFETCH_ATTRIBUTE,
|
|
599
|
+
replaceAttribute: REPLACE_ATTRIBUTE,
|
|
600
|
+
scrollAttribute: SCROLL_ATTRIBUTE,
|
|
601
|
+
routeScriptType: ROUTE_SCRIPT_TYPE,
|
|
602
|
+
retireProperty: CONTAINER_RETIRE_PROPERTY,
|
|
603
|
+
streamExecutorGlobal: STREAM_ARM_EXECUTOR_GLOBAL,
|
|
604
|
+
streamedArmSelector: STREAMED_ARM_SELECTOR,
|
|
605
|
+
requestHeader: FRAGMENT_REQUEST_HEADER,
|
|
606
|
+
regionEndMarker: FRAGMENT_REGION_END,
|
|
607
|
+
appendMarker: FRAGMENT_APPEND_END,
|
|
608
|
+
documentLoads,
|
|
609
|
+
hoverDwellMs: 20,
|
|
610
|
+
prefetchTtlMs: FRAGMENT_PREFETCH_TTL_MS,
|
|
611
|
+
prefetchConnections: 2,
|
|
612
|
+
endpointPrefixes: [API_PATHNAME_PREFIX],
|
|
613
|
+
idleFragments: 2,
|
|
614
|
+
idleWithoutConnectionInfo: true,
|
|
615
|
+
prefetch: fragment.prefetch,
|
|
616
|
+
pages: fragment.pages
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function htmlAttributes(documentModule, pageProps, written) {
|
|
620
|
+
const attributes = documentModule?.__marklessRouterHtmlAttributes?.(pageProps) ?? {
|
|
621
|
+
lang: "en",
|
|
622
|
+
...written
|
|
623
|
+
};
|
|
377
624
|
const normalized = {};
|
|
378
625
|
for (const [name, value] of Object.entries(attributes)) {
|
|
379
626
|
if (value === false || value === null || value === void 0) continue;
|
|
@@ -393,8 +640,19 @@ function escapeScriptJson(value) {
|
|
|
393
640
|
function escapeInlineScript(value) {
|
|
394
641
|
return value.replace(/<\/script/gi, "<\\/script");
|
|
395
642
|
}
|
|
643
|
+
const API_PATHNAME_PREFIX = "/api";
|
|
396
644
|
function isNitroApiPathname(pathname) {
|
|
397
|
-
return pathname ===
|
|
645
|
+
return pathname === API_PATHNAME_PREFIX || pathname.startsWith(`${API_PATHNAME_PREFIX}/`);
|
|
646
|
+
}
|
|
647
|
+
function insertImportMap(html, importMap) {
|
|
648
|
+
if (!importMap) return html;
|
|
649
|
+
const head = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
650
|
+
if (!head) return `${importMap}${html}`;
|
|
651
|
+
const afterHead = head.index + head[0].length;
|
|
652
|
+
const headEnd = html.indexOf("</head>", afterHead);
|
|
653
|
+
const charset = /<meta\s[^>]*charset[^>]*>(?:<\/meta>)?/i.exec(html.slice(afterHead));
|
|
654
|
+
const at = charset && (headEnd === -1 || afterHead + charset.index < headEnd) ? afterHead + charset.index + charset[0].length : afterHead;
|
|
655
|
+
return `${html.slice(0, at)}${importMap}${html.slice(at)}`;
|
|
398
656
|
}
|
|
399
657
|
//#endregion
|
|
400
658
|
export { createServerEntry };
|