@markless/router 0.4.0 → 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 +51 -30
- 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
package/dist/vite.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
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 { a as normalizeRouteFileId, n as isPageModuleFile, t as buildRouteManifestFromFileIds } from "./route-manifest-Bw_nFohL.js";
|
|
3
|
+
import { r as transformRequestFileSource } from "./request-files-Ca8E0FOo.js";
|
|
4
|
+
import { t as __marklessRouteHref } from "./route-href-CGooO_g6.js";
|
|
5
|
+
import { r as protocolIslandSegment } from "./protocol-constants-B4WxrZAS.js";
|
|
6
|
+
import { a as startFragmentNavigation, s as listenForLinkIntent } from "./fragment-navigation-CIiNnFFc.js";
|
|
4
7
|
import { dirname, extname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
5
8
|
import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutLeadingSlash } from "ufo";
|
|
6
9
|
import { existsSync } from "node:fs";
|
|
7
|
-
import { nitro } from "nitro/vite";
|
|
8
|
-
import { sortUserPlugins } from "vite";
|
|
9
|
-
import { symbolVirtualModuleSourceFile } from "@markless/bundler/preload";
|
|
10
|
-
import { parseModule } from "yuku-tsrx";
|
|
11
10
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
11
|
+
import { nitro } from "nitro/vite";
|
|
12
|
+
import { minifySync, sortUserPlugins } from "vite";
|
|
13
|
+
import { isClientPrimarySourceRequest, isExecutionLogChunk, isMarklessNavigationPack, isRenderDataSourceRequest, isResumeSourceRequest, isRouteNavigationSourceRequest, isSymbolOnlySourceRequest, lazyStartupPackName, marklessDeferredChunkFileNames, optimizedDepsToInclude, plannedLandingFiles, startupPackOfLazy, symbolVirtualModuleSourceFile } from "@markless/bundler/preload";
|
|
14
|
+
import { MARKLESS_BUILD_METADATA_FILES, MARKLESS_BUILD_PREFIX, MARKLESS_CHUNK_SPECIFIER_PREFIX, MARKLESS_IMPORT_MAP_ASSET, importMapScript, marklessBuildManifestDir } from "@markless/bundler/rolldown";
|
|
15
|
+
import { parseModule } from "@tsrx/yuku";
|
|
12
16
|
import { toHtml } from "hast-util-to-html";
|
|
13
17
|
import { HastReader, createMdxHastHandle, defineHastPlugin, dropHandle, materializeHastTree, resolveHastSubscriptions, serializeHandle, visitHastHandle } from "satteri";
|
|
18
|
+
import "@markless/web/render-to-string";
|
|
14
19
|
//#region src/route-types.ts
|
|
15
20
|
const routeTypesEnvPath = "markless-router-env.d.ts";
|
|
16
21
|
const routeTypesOutputPath = ".output/markless/router/types/routes.d.ts";
|
|
@@ -96,7 +101,7 @@ function createLinkTypes() {
|
|
|
96
101
|
"};",
|
|
97
102
|
"",
|
|
98
103
|
"export type MarklessRouterNavigationProps = {",
|
|
99
|
-
" readonly prefetch?: boolean
|
|
104
|
+
" readonly prefetch?: boolean;",
|
|
100
105
|
" readonly replace?: boolean;",
|
|
101
106
|
" readonly scroll?: boolean;",
|
|
102
107
|
"};",
|
|
@@ -113,7 +118,7 @@ function createLinkTypes() {
|
|
|
113
118
|
}
|
|
114
119
|
//#endregion
|
|
115
120
|
//#region src/vite/route-typegen.ts
|
|
116
|
-
const PAGE_EXTENSIONS = new Set([".tsrx", ".mdx"]);
|
|
121
|
+
const PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsrx", ".mdx"]);
|
|
117
122
|
function routeTypegenPlugin() {
|
|
118
123
|
let root = "";
|
|
119
124
|
return {
|
|
@@ -130,7 +135,8 @@ function routeTypegenPlugin() {
|
|
|
130
135
|
};
|
|
131
136
|
}
|
|
132
137
|
async function writeRouteTypes(fs, root) {
|
|
133
|
-
const
|
|
138
|
+
const pageFiles = await discoverPageFiles(fs, root);
|
|
139
|
+
const manifest = buildRouteManifestFromFileIds(pageFiles);
|
|
134
140
|
await writeIfChanged(fs, join(root, routeTypesEnvPath), routeTypesEnvDeclaration);
|
|
135
141
|
await writeIfChanged(fs, join(root, routeTypesOutputPath), createRouteTypesDeclaration(manifest));
|
|
136
142
|
}
|
|
@@ -180,14 +186,20 @@ function isNotFoundError(error) {
|
|
|
180
186
|
//#region src/vite/anchor-transform.ts
|
|
181
187
|
const ROUTE_HREF_HELPER_ID = "virtual:markless-router/route-href";
|
|
182
188
|
const JSX_FILE_FILTER = /\.(?:[jt]sx|tsrx)(?:$|\?)/;
|
|
183
|
-
const
|
|
189
|
+
const REWRITE_CACHE_LIMIT = 2048;
|
|
190
|
+
const ROUTER_IMPORT_SOURCES = /* @__PURE__ */ new Set(["@markless/router", "@markless/core/router"]);
|
|
184
191
|
function anchorTransformPlugin() {
|
|
185
192
|
let root = "";
|
|
186
193
|
let routePatterns = /* @__PURE__ */ new Map();
|
|
187
194
|
let routesLoaded = false;
|
|
195
|
+
let routeFingerprint = "";
|
|
196
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
188
197
|
const refreshRoutes = async (fs) => {
|
|
189
198
|
routePatterns = routePatternMap(buildRouteManifestFromFileIds(await discoverPageFiles(fs, root)));
|
|
190
199
|
routesLoaded = true;
|
|
200
|
+
const fingerprint = JSON.stringify([...routePatterns]);
|
|
201
|
+
if (fingerprint !== routeFingerprint) rewrites.clear();
|
|
202
|
+
routeFingerprint = fingerprint;
|
|
191
203
|
};
|
|
192
204
|
return {
|
|
193
205
|
name: "markless-router:anchors",
|
|
@@ -197,7 +209,18 @@ function anchorTransformPlugin() {
|
|
|
197
209
|
async handler(code, id) {
|
|
198
210
|
if (!isTransformCandidateCode(code)) return;
|
|
199
211
|
if (!routesLoaded) await refreshRoutes(this.fs);
|
|
200
|
-
const
|
|
212
|
+
const key = `${id.includes(".tsrx") ? "tsrx" : id.includes(".jsx") ? "jsx" : "tsx"}\0${code}`;
|
|
213
|
+
let transformed = rewrites.get(key);
|
|
214
|
+
if (transformed === void 0) {
|
|
215
|
+
const ast = parseTransformAst(code, id, (source, options) => this.parse(source, options));
|
|
216
|
+
transformed = transformAnchorSource(code, ast, routePatterns);
|
|
217
|
+
if (id.includes(".tsrx")) {
|
|
218
|
+
const linkedAst = transformed === code ? ast : parseTransformAst(transformed, id, (source, options) => this.parse(source, options));
|
|
219
|
+
transformed = lowerRouterLinks(transformed, linkedAst);
|
|
220
|
+
}
|
|
221
|
+
if (rewrites.size >= REWRITE_CACHE_LIMIT) rewrites.delete(rewrites.keys().next().value);
|
|
222
|
+
rewrites.set(key, transformed);
|
|
223
|
+
}
|
|
201
224
|
return transformed === code ? void 0 : {
|
|
202
225
|
code: transformed,
|
|
203
226
|
map: null
|
|
@@ -215,6 +238,68 @@ function anchorTransformPlugin() {
|
|
|
215
238
|
}
|
|
216
239
|
};
|
|
217
240
|
}
|
|
241
|
+
function lowerRouterLinks(code, ast) {
|
|
242
|
+
const linkNames = marklessRouterLinkImportNames(ast);
|
|
243
|
+
const edits = [];
|
|
244
|
+
walk(ast, (current) => {
|
|
245
|
+
if (current.type !== "JSXElement") return;
|
|
246
|
+
const opening = node$2(current.openingElement);
|
|
247
|
+
const name = opening && node$2(opening.name);
|
|
248
|
+
if (!name || !linkNames.has(jsxName$1(name) ?? "")) return;
|
|
249
|
+
const attributes = nodes$2(opening?.attributes);
|
|
250
|
+
if (attributes.some((attribute) => attribute.type === "JSXSpreadAttribute")) return;
|
|
251
|
+
if (findJsxAttribute(attributes, "children")) return;
|
|
252
|
+
const href = findJsxAttribute(attributes, "href") ? "" : " href=\"#\"";
|
|
253
|
+
edits.push({
|
|
254
|
+
...rangeOf(name),
|
|
255
|
+
text: `a ${LINK_ATTRIBUTE}${href}`
|
|
256
|
+
});
|
|
257
|
+
const closingName = node$2(node$2(current.closingElement)?.name);
|
|
258
|
+
if (closingName) edits.push({
|
|
259
|
+
...rangeOf(closingName),
|
|
260
|
+
text: "a"
|
|
261
|
+
});
|
|
262
|
+
for (const attribute of attributes) {
|
|
263
|
+
const attributeName = jsxName$1(node$2(attribute.name));
|
|
264
|
+
if (attributeName === "params" || attributeName === "prefetch" && !attribute.value) edits.push({
|
|
265
|
+
...attributeRemovalRange(code, attribute),
|
|
266
|
+
text: ""
|
|
267
|
+
});
|
|
268
|
+
else if (attributeName === "prefetch") {
|
|
269
|
+
const value = node$2(attribute.value);
|
|
270
|
+
const literal = jsxExpression(value) ?? value;
|
|
271
|
+
if ([
|
|
272
|
+
"Literal",
|
|
273
|
+
"StringLiteral",
|
|
274
|
+
"BooleanLiteral"
|
|
275
|
+
].includes(literal.type ?? "")) {
|
|
276
|
+
const choice = literal.value;
|
|
277
|
+
edits.push(choice === false ? {
|
|
278
|
+
...rangeOf(attribute),
|
|
279
|
+
text: `${PREFETCH_ATTRIBUTE}="none"`
|
|
280
|
+
} : {
|
|
281
|
+
...attributeRemovalRange(code, attribute),
|
|
282
|
+
text: ""
|
|
283
|
+
});
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const expression = `(${slice$1(code, jsxExpression(value) ?? value)})`;
|
|
287
|
+
edits.push({
|
|
288
|
+
...rangeOf(attribute),
|
|
289
|
+
text: `${PREFETCH_ATTRIBUTE}={${expression} === false ? 'none' : undefined}`
|
|
290
|
+
});
|
|
291
|
+
} else if (attributeName === "replace" || attributeName === "scroll") {
|
|
292
|
+
const value = node$2(attribute.value);
|
|
293
|
+
const expression = value ? slice$1(code, jsxExpression(value) ?? value) : "true";
|
|
294
|
+
edits.push({
|
|
295
|
+
...rangeOf(attribute),
|
|
296
|
+
text: attributeName === "replace" ? `${REPLACE_ATTRIBUTE}={(${expression}) ? '' : undefined}` : `${SCROLL_ATTRIBUTE}={(${expression}) === false ? 'manual' : undefined}`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
return applyEdits(code, edits);
|
|
302
|
+
}
|
|
218
303
|
function parseTransformAst(code, id, parse) {
|
|
219
304
|
if (id.includes(".tsrx")) return parseModule(code, id);
|
|
220
305
|
return parse(code, {
|
|
@@ -227,24 +312,24 @@ function isTransformCandidateCode(code) {
|
|
|
227
312
|
return code.includes("<a") || [...ROUTER_IMPORT_SOURCES].some((importSource) => code.includes(importSource));
|
|
228
313
|
}
|
|
229
314
|
function transformAnchorSource(code, astInput, routePatterns) {
|
|
230
|
-
const ast = node$
|
|
315
|
+
const ast = node$2(astInput);
|
|
231
316
|
if (!ast) return code;
|
|
232
317
|
const edits = [];
|
|
233
318
|
let needsHrefHelper = false;
|
|
234
319
|
const linkNames = marklessRouterLinkImportNames(ast);
|
|
235
320
|
walk(ast, (current) => {
|
|
236
321
|
if (current.type !== "JSXOpeningElement") return;
|
|
237
|
-
const name = jsxName$1(node$
|
|
322
|
+
const name = jsxName$1(node$2(current.name));
|
|
238
323
|
if (name !== "a" && (!name || !linkNames.has(name))) return;
|
|
239
|
-
const attributes = nodes$
|
|
324
|
+
const attributes = nodes$2(current.attributes);
|
|
240
325
|
const href = findJsxAttribute(attributes, "href");
|
|
241
|
-
const hrefValue = href ? stringLiteralValue(node$
|
|
326
|
+
const hrefValue = href ? stringLiteralValue(node$2(href.value)) : void 0;
|
|
242
327
|
if (!href || !hrefValue || !isRoutePatternHref(hrefValue)) return;
|
|
243
328
|
const routeParams = routePatterns.get(hrefValue);
|
|
244
329
|
if (!routeParams) fail$1(`Typed route error: ${hrefValue} does not match any route in pages/.`);
|
|
245
330
|
const params = findJsxAttribute(attributes, "params");
|
|
246
331
|
if (!params) fail$1(missingParamsMessage(hrefValue, routeParams));
|
|
247
|
-
const paramsExpression = jsxExpression(node$
|
|
332
|
+
const paramsExpression = jsxExpression(node$2(params.value));
|
|
248
333
|
if (!paramsExpression) fail$1(missingParamsMessage(hrefValue, routeParams));
|
|
249
334
|
validateObjectLiteralParams(hrefValue, routeParams, paramsExpression);
|
|
250
335
|
const staticParams = staticObjectValue(paramsExpression);
|
|
@@ -270,11 +355,11 @@ function transformAnchorSource(code, astInput, routePatterns) {
|
|
|
270
355
|
function staticObjectValue(expression) {
|
|
271
356
|
if (expression.type !== "ObjectExpression") return void 0;
|
|
272
357
|
const value = {};
|
|
273
|
-
for (const property of nodes$
|
|
358
|
+
for (const property of nodes$2(expression.properties)) {
|
|
274
359
|
if (property.type !== "Property" || property.computed === true) return void 0;
|
|
275
|
-
const key = node$
|
|
360
|
+
const key = node$2(property.key);
|
|
276
361
|
const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
|
|
277
|
-
const propertyValue = staticExpressionValue(node$
|
|
362
|
+
const propertyValue = staticExpressionValue(node$2(property.value));
|
|
278
363
|
if (!name || !propertyValue.known) return void 0;
|
|
279
364
|
value[name] = propertyValue.value;
|
|
280
365
|
}
|
|
@@ -288,7 +373,7 @@ function staticExpressionValue(expression) {
|
|
|
288
373
|
};
|
|
289
374
|
if (expression.type === "ArrayExpression") {
|
|
290
375
|
const values = [];
|
|
291
|
-
for (const element of nodes$
|
|
376
|
+
for (const element of nodes$2(expression.elements)) {
|
|
292
377
|
const value = staticExpressionValue(element);
|
|
293
378
|
if (!value.known) return { known: false };
|
|
294
379
|
values.push(value.value);
|
|
@@ -303,9 +388,9 @@ function staticExpressionValue(expression) {
|
|
|
303
388
|
function marklessRouterLinkImportNames(ast) {
|
|
304
389
|
const names = /* @__PURE__ */ new Set();
|
|
305
390
|
walk(ast, (current) => {
|
|
306
|
-
if (current.type !== "ImportDeclaration" || !ROUTER_IMPORT_SOURCES.has(literalString(node$
|
|
307
|
-
for (const specifier of nodes$
|
|
308
|
-
const localName = identifierName(node$
|
|
391
|
+
if (current.type !== "ImportDeclaration" || !ROUTER_IMPORT_SOURCES.has(literalString(node$2(current.source)) ?? "")) return;
|
|
392
|
+
for (const specifier of nodes$2(current.specifiers)) if (specifier.type === "ImportSpecifier" && identifierName(node$2(specifier.imported)) === "Link") {
|
|
393
|
+
const localName = identifierName(node$2(specifier.local));
|
|
309
394
|
if (localName) names.add(localName);
|
|
310
395
|
}
|
|
311
396
|
});
|
|
@@ -324,9 +409,9 @@ function validateObjectLiteralParams(pattern, routeParams, paramsExpression) {
|
|
|
324
409
|
if (missing.length > 0) fail$1(`Typed route error: ${pattern} requires params:\n${list(missing)}`);
|
|
325
410
|
}
|
|
326
411
|
function objectLiteralKeys(expression) {
|
|
327
|
-
return nodes$
|
|
412
|
+
return nodes$2(expression.properties).flatMap((property) => {
|
|
328
413
|
if (property.type !== "Property") return [];
|
|
329
|
-
const key = node$
|
|
414
|
+
const key = node$2(property.key);
|
|
330
415
|
const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
|
|
331
416
|
return name ? [name] : [];
|
|
332
417
|
});
|
|
@@ -341,7 +426,7 @@ function list(items) {
|
|
|
341
426
|
return items.map((item) => `- ${item}`).join("\n");
|
|
342
427
|
}
|
|
343
428
|
function findJsxAttribute(attributes, name) {
|
|
344
|
-
return attributes.find((attr) => attr.type === "JSXAttribute" && jsxName$1(node$
|
|
429
|
+
return attributes.find((attr) => attr.type === "JSXAttribute" && jsxName$1(node$2(attr.name)) === name);
|
|
345
430
|
}
|
|
346
431
|
function stringLiteralValue(value) {
|
|
347
432
|
return value?.type === "Literal" ? string$1(value.value) : void 0;
|
|
@@ -354,7 +439,7 @@ function identifierName(value) {
|
|
|
354
439
|
}
|
|
355
440
|
function jsxExpression(value) {
|
|
356
441
|
if (value?.type !== "JSXExpressionContainer") return;
|
|
357
|
-
const expression = node$
|
|
442
|
+
const expression = node$2(value.expression);
|
|
358
443
|
return expression?.type === "JSXEmptyExpression" ? void 0 : expression;
|
|
359
444
|
}
|
|
360
445
|
function isRoutePatternHref(href) {
|
|
@@ -377,7 +462,7 @@ function applyEdits(code, edits) {
|
|
|
377
462
|
function walk(root, visit) {
|
|
378
463
|
const stack = [root];
|
|
379
464
|
while (stack.length > 0) {
|
|
380
|
-
const current = node$
|
|
465
|
+
const current = node$2(stack.pop());
|
|
381
466
|
if (!current) continue;
|
|
382
467
|
visit(current);
|
|
383
468
|
for (const child of Object.values(current)) if (Array.isArray(child)) stack.push(...child);
|
|
@@ -402,10 +487,10 @@ function rangeOf(value) {
|
|
|
402
487
|
end: value.end
|
|
403
488
|
};
|
|
404
489
|
}
|
|
405
|
-
function nodes$
|
|
406
|
-
return Array.isArray(value) ? value.filter((item) => !!node$
|
|
490
|
+
function nodes$2(value) {
|
|
491
|
+
return Array.isArray(value) ? value.filter((item) => !!node$2(item)) : [];
|
|
407
492
|
}
|
|
408
|
-
function node$
|
|
493
|
+
function node$2(value) {
|
|
409
494
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
410
495
|
}
|
|
411
496
|
function string$1(value) {
|
|
@@ -416,7 +501,7 @@ function fail$1(message) {
|
|
|
416
501
|
}
|
|
417
502
|
//#endregion
|
|
418
503
|
//#region src/vite/client-assets-manifest.ts
|
|
419
|
-
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST = "
|
|
504
|
+
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST = "router-client-manifest.json";
|
|
420
505
|
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST_VERSION = 1;
|
|
421
506
|
const VALID_ROUTE_FILE = /^pages\/.+\.(?:tsrx|mdx)$/;
|
|
422
507
|
const ABSOLUTE_OR_PROTOCOL_RELATIVE_URL = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
|
|
@@ -427,7 +512,8 @@ function createClientAssetsManifest(input) {
|
|
|
427
512
|
entries: {
|
|
428
513
|
resume: input.resumeEntry,
|
|
429
514
|
...input.prerenderWakeEntry ? { prerenderWake: input.prerenderWakeEntry } : {},
|
|
430
|
-
navigation: input.navigationEntry
|
|
515
|
+
navigation: input.navigationEntry,
|
|
516
|
+
...input.fragmentEntry ? { fragment: input.fragmentEntry } : {}
|
|
431
517
|
},
|
|
432
518
|
routes: {
|
|
433
519
|
navigation: sortRouteMap(input.routes.navigation),
|
|
@@ -436,6 +522,16 @@ function createClientAssetsManifest(input) {
|
|
|
436
522
|
}
|
|
437
523
|
};
|
|
438
524
|
}
|
|
525
|
+
function renameClientAssetChunks(manifest, renames) {
|
|
526
|
+
const urls = new Map([...renames].map(([previous, next]) => [joinURL(manifest.base, previous), joinURL(manifest.base, next)]));
|
|
527
|
+
return renameUrls(manifest, (url) => urls.get(url) ?? url);
|
|
528
|
+
}
|
|
529
|
+
function renameUrls(value, rename) {
|
|
530
|
+
if (typeof value === "string") return rename(value);
|
|
531
|
+
if (Array.isArray(value)) return value.map((item) => renameUrls(item, rename));
|
|
532
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, key === "base" ? item : renameUrls(item, rename)]));
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
439
535
|
async function removeClientAssetsManifest(clientOutDir) {
|
|
440
536
|
const path = clientAssetsManifestPath(clientOutDir);
|
|
441
537
|
await Promise.all([rm(path, { force: true }), rm(clientAssetsManifestTemporaryPath(clientOutDir), { force: true })]);
|
|
@@ -489,8 +585,9 @@ function clientAssetFileName(href, base) {
|
|
|
489
585
|
if (href !== joinURL(base, fileName)) throw clientAssetsManifestError(`contains a non-canonical asset URL ${JSON.stringify(href)} for base ${JSON.stringify(base)}`);
|
|
490
586
|
return fileName;
|
|
491
587
|
}
|
|
588
|
+
/** Where the client build leaves this manifest for the server build: outside the served client output. */
|
|
492
589
|
function clientAssetsManifestPath(clientOutDir) {
|
|
493
|
-
return join(clientOutDir, MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST);
|
|
590
|
+
return join(marklessBuildManifestDir(clientOutDir), MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST);
|
|
494
591
|
}
|
|
495
592
|
function clientAssetsManifestTemporaryPath(clientOutDir) {
|
|
496
593
|
return `${clientAssetsManifestPath(clientOutDir)}.tmp`;
|
|
@@ -504,7 +601,8 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
504
601
|
const entries = {
|
|
505
602
|
resume: requiredString(input.entries.resume, "entries.resume"),
|
|
506
603
|
...input.entries.prerenderWake !== void 0 ? { prerenderWake: requiredString(input.entries.prerenderWake, "entries.prerenderWake") } : {},
|
|
507
|
-
navigation: requiredString(input.entries.navigation, "entries.navigation")
|
|
604
|
+
navigation: requiredString(input.entries.navigation, "entries.navigation"),
|
|
605
|
+
...input.entries.fragment !== void 0 ? { fragment: requiredString(input.entries.fragment, "entries.fragment") } : {}
|
|
508
606
|
};
|
|
509
607
|
const routes = {
|
|
510
608
|
navigation: routeMap(input.routes.navigation, "routes.navigation"),
|
|
@@ -512,16 +610,20 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
512
610
|
styles: routeMap(input.routes.styles, "routes.styles")
|
|
513
611
|
};
|
|
514
612
|
assertMatchingRouteKeys(routes);
|
|
515
|
-
const
|
|
613
|
+
const importMap = input.importMap === void 0 ? void 0 : importMapImports(input.importMap);
|
|
614
|
+
const hrefs = /* @__PURE__ */ new Set([
|
|
516
615
|
entries.resume,
|
|
517
616
|
...entries.prerenderWake !== void 0 ? [entries.prerenderWake] : [],
|
|
518
617
|
entries.navigation,
|
|
618
|
+
...entries.fragment !== void 0 ? [entries.fragment] : [],
|
|
519
619
|
...Object.values(routes.navigation).flat(),
|
|
520
620
|
...Object.values(routes.ssr).flat(),
|
|
521
|
-
...Object.values(routes.styles).flat()
|
|
621
|
+
...Object.values(routes.styles).flat(),
|
|
622
|
+
...Object.values(importMap?.imports ?? {})
|
|
522
623
|
]);
|
|
523
624
|
for (const href of hrefs) {
|
|
524
|
-
const
|
|
625
|
+
const fileName = clientAssetFileName(href, base);
|
|
626
|
+
const assetPath = join(clientOutDir, fileName);
|
|
525
627
|
const relativeAssetPath = relative(clientOutDir, assetPath);
|
|
526
628
|
if (relativeAssetPath === ".." || relativeAssetPath.startsWith("../") || isAbsolute(relativeAssetPath)) throw clientAssetsManifestError(`contains an unsafe asset path ${JSON.stringify(href)}`);
|
|
527
629
|
try {
|
|
@@ -534,7 +636,8 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
534
636
|
version: MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST_VERSION,
|
|
535
637
|
base,
|
|
536
638
|
entries,
|
|
537
|
-
routes
|
|
639
|
+
routes,
|
|
640
|
+
...importMap ? { importMap } : {}
|
|
538
641
|
};
|
|
539
642
|
}
|
|
540
643
|
function routeMap(input, label) {
|
|
@@ -580,10 +683,15 @@ function isRecord$1(input) {
|
|
|
580
683
|
function clientAssetsManifestError(message, cause) {
|
|
581
684
|
return new Error(`Markless Router client-assets manifest ${message}.`, { cause });
|
|
582
685
|
}
|
|
686
|
+
function importMapImports(input) {
|
|
687
|
+
const imports = isRecord$1(input) ? input.imports : void 0;
|
|
688
|
+
if (!isRecord$1(imports) || !Object.values(imports).every((href) => typeof href === "string")) throw clientAssetsManifestError("has an invalid import map");
|
|
689
|
+
return { imports };
|
|
690
|
+
}
|
|
583
691
|
//#endregion
|
|
584
692
|
//#region src/vite/html-transform.ts
|
|
585
693
|
const HELPER = "__marklessRouterHtmlAttributes";
|
|
586
|
-
const SAFE_GLOBALS = new Set([
|
|
694
|
+
const SAFE_GLOBALS = /* @__PURE__ */ new Set([
|
|
587
695
|
"String",
|
|
588
696
|
"Number",
|
|
589
697
|
"Boolean",
|
|
@@ -609,41 +717,42 @@ function htmlTransformPlugin() {
|
|
|
609
717
|
};
|
|
610
718
|
}
|
|
611
719
|
function transformHtmlSource(code, astInput) {
|
|
612
|
-
const ast = node(astInput);
|
|
720
|
+
const ast = node$1(astInput);
|
|
613
721
|
if (!ast) fail(`MarklessRouter could not read ${DOCUMENT_FILE_LABEL}. Check it for syntax errors.`);
|
|
614
722
|
const fn = findDefaultDocumentFunction(ast);
|
|
615
723
|
if (fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression") fail(`MarklessRouter needs to see your document body in ${DOCUMENT_FILE_LABEL}. Write \`export default (props) => <Html>...</Html>\` instead of passing a named function.`);
|
|
616
|
-
const props = first(nodes(fn.params));
|
|
724
|
+
const props = first(nodes$1(fn.params));
|
|
617
725
|
const propsName = props?.type === "Identifier" ? string(props.name) : void 0;
|
|
618
726
|
const propsSource = props ? slice(code, props) : "";
|
|
619
|
-
const attrs = nodes(node(findReturnedHtml(fn).openingElement)?.attributes).map((attr) => htmlAttr(code, attr, propsName));
|
|
620
|
-
const
|
|
727
|
+
const attrs = nodes$1(node$1(findReturnedHtml(fn).openingElement)?.attributes).map((attr) => htmlAttr(code, attr, propsName));
|
|
728
|
+
const body = attrs.length ? `{\n${attrs.map(([name, value]) => ` ${JSON.stringify(name)}: ${value}`).join(",\n")}\n }` : "{}";
|
|
729
|
+
const helper = `export function ${HELPER}(${propsSource}) {\n return ${body};\n}\n`;
|
|
621
730
|
return code.endsWith("\n") ? `${code}\n${helper}` : `${code}\n\n${helper}`;
|
|
622
731
|
}
|
|
623
732
|
function findDefaultDocumentFunction(ast) {
|
|
624
|
-
for (const statement of nodes(ast.body)) {
|
|
625
|
-
const declaration = node(statement.declaration);
|
|
733
|
+
for (const statement of nodes$1(ast.body)) {
|
|
734
|
+
const declaration = node$1(statement.declaration);
|
|
626
735
|
if (statement.type !== "ExportDefaultDeclaration") continue;
|
|
627
736
|
if (declaration?.type === "ArrowFunctionExpression" || declaration?.type === "FunctionExpression") return declaration;
|
|
628
737
|
}
|
|
629
738
|
fail(`MarklessRouter could not find the document function. ${DOCUMENT_FILE_LABEL} must default export \`() => <Html>...</Html>\`.`);
|
|
630
739
|
}
|
|
631
740
|
function findReturnedHtml(fn) {
|
|
632
|
-
const body = unwrap(node(fn.body));
|
|
633
|
-
const returned = body?.type === "BlockStatement" ? unwrap(node(nodes(body.body).find((item) => item.type === "ReturnStatement")?.argument)) : body;
|
|
741
|
+
const body = unwrap(node$1(fn.body));
|
|
742
|
+
const returned = body?.type === "BlockStatement" ? unwrap(node$1(nodes$1(body.body).find((item) => item.type === "ReturnStatement")?.argument)) : body;
|
|
634
743
|
if (returned?.type !== "JSXElement") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Wrap your document shell in \`<Html>...</Html>\`.`);
|
|
635
|
-
if (jsxName(node(node(returned.openingElement)?.name)) !== "Html") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Put <head> and <body> inside \`<Html>...</Html>\`.`);
|
|
744
|
+
if (jsxName(node$1(node$1(returned.openingElement)?.name)) !== "Html") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Put <head> and <body> inside \`<Html>...</Html>\`.`);
|
|
636
745
|
return returned;
|
|
637
746
|
}
|
|
638
747
|
function htmlAttr(code, attr, propsName) {
|
|
639
748
|
if (attr.type === "JSXSpreadAttribute") fail("MarklessRouter does not support spreading props onto <Html> yet. Write each html attribute directly, like `<Html lang=\"en\">`.");
|
|
640
|
-
const name = jsxName(node(attr.name));
|
|
749
|
+
const name = jsxName(node$1(attr.name));
|
|
641
750
|
if (!name) fail("MarklessRouter found an unsupported <Html> attribute name. Use plain html attributes like `lang`, `class`, or `data-theme`.");
|
|
642
|
-
const value = node(attr.value);
|
|
751
|
+
const value = node$1(attr.value);
|
|
643
752
|
if (!value) return [name, "true"];
|
|
644
753
|
if (value.type === "Literal") return [name, JSON.stringify(value.value)];
|
|
645
754
|
if (value.type !== "JSXExpressionContainer") fail(`MarklessRouter could not understand the <Html> "${name}" attribute. Use a string or JSX expression, like \`${name}="value"\` or \`${name}={props.url.pathname}\`.`);
|
|
646
|
-
const expression = unwrap(node(value.expression));
|
|
755
|
+
const expression = unwrap(node$1(value.expression));
|
|
647
756
|
if (!expression || expression.type === "JSXEmptyExpression") fail(`MarklessRouter found an empty <Html> "${name}" attribute expression. Add a value or remove the attribute.`);
|
|
648
757
|
const ref = unsupportedIdentifier(expression, propsName);
|
|
649
758
|
if (ref) fail(`MarklessRouter cannot use "${ref}" in <Html ${name}={...}> because html attributes are read before Markless renders ${DOCUMENT_FILE_LABEL}. Use props directly, like \`${name}={props.url.pathname}\`, or a literal value.`);
|
|
@@ -652,7 +761,7 @@ function htmlAttr(code, attr, propsName) {
|
|
|
652
761
|
function unsupportedIdentifier(root, propsName) {
|
|
653
762
|
const stack = [root];
|
|
654
763
|
while (stack.length > 0) {
|
|
655
|
-
const current = node(stack.pop());
|
|
764
|
+
const current = node$1(stack.pop());
|
|
656
765
|
if (!current) continue;
|
|
657
766
|
if (current.type === "Identifier") {
|
|
658
767
|
const name = string(current.name);
|
|
@@ -678,7 +787,7 @@ function jsxName(name) {
|
|
|
678
787
|
}
|
|
679
788
|
function unwrap(value) {
|
|
680
789
|
let current = value;
|
|
681
|
-
while (current?.type === "ParenthesizedExpression" || current?.type === "TSAsExpression" || current?.type === "TSNonNullExpression") current = node(current.expression);
|
|
790
|
+
while (current?.type === "ParenthesizedExpression" || current?.type === "TSAsExpression" || current?.type === "TSNonNullExpression") current = node$1(current.expression);
|
|
682
791
|
return current;
|
|
683
792
|
}
|
|
684
793
|
function slice(code, value) {
|
|
@@ -686,10 +795,10 @@ function slice(code, value) {
|
|
|
686
795
|
if (range) return code.slice(range[0], range[1]);
|
|
687
796
|
return code.slice(value.start, value.end);
|
|
688
797
|
}
|
|
689
|
-
function nodes(value) {
|
|
690
|
-
return Array.isArray(value) ? value.filter((item) => !!node(item)) : [];
|
|
798
|
+
function nodes$1(value) {
|
|
799
|
+
return Array.isArray(value) ? value.filter((item) => !!node$1(item)) : [];
|
|
691
800
|
}
|
|
692
|
-
function node(value) {
|
|
801
|
+
function node$1(value) {
|
|
693
802
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
694
803
|
}
|
|
695
804
|
function first(items) {
|
|
@@ -702,11 +811,71 @@ function fail(message) {
|
|
|
702
811
|
throw new Error(message);
|
|
703
812
|
}
|
|
704
813
|
//#endregion
|
|
814
|
+
//#region ../bundler/src/scalar-plan-source.ts
|
|
815
|
+
const MARKLESS_SCALAR_PLAN_SOURCE_QUERY = "markless-scalar-plans";
|
|
816
|
+
function scalarPlanSourceReference(source) {
|
|
817
|
+
return withQuery(source, {
|
|
818
|
+
"markless-symbols": null,
|
|
819
|
+
[MARKLESS_SCALAR_PLAN_SOURCE_QUERY]: null
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* The same one line for the live roster, gated on the same fact the reconcile
|
|
824
|
+
* plane is gated on: a payload with no computed nodes can hold no roster
|
|
825
|
+
* derivation, so it never names the module and never emits its chunk.
|
|
826
|
+
*
|
|
827
|
+
* Both call sites (the start-up wiring and a sync computed's re-derive) sit in
|
|
828
|
+
* modules every resumed page runs, which is why neither may write the specifier.
|
|
829
|
+
* A composed MDX route writes it too: it cannot see its islands' computeds.
|
|
830
|
+
*/
|
|
831
|
+
function emitRosterResumeLoaderInstall() {
|
|
832
|
+
return "globalThis.__marklessRosterResume ??= () => import('@markless/web/fns/roster-resume');";
|
|
833
|
+
}
|
|
834
|
+
function emitQueuedResumeContainerEvent(source, { censusRepair = true } = {}) {
|
|
835
|
+
const implementation = source.replace("export async function resumeContainerEvent", "async function marklessResumeContainerEvent");
|
|
836
|
+
if (implementation === source) throw new Error("MARKLESS_RESUME_DISPATCH_EXPORT_MISSING");
|
|
837
|
+
return [
|
|
838
|
+
implementation,
|
|
839
|
+
"export function resumeContainerEvent(input) {",
|
|
840
|
+
" const root = input.root;",
|
|
841
|
+
censusRepair ? " if (root.__marklessShifted) return import('@markless/web/fns/served-census-repair').then((census) => (census.marklessRepairCensus(root), resumeContainerEvent(input)));" : "",
|
|
842
|
+
" if (!root.__marklessDispatch) {",
|
|
843
|
+
" let handler = marklessResumeContainerEvent, tail = Promise.resolve(), done = tail, lastEvent = null, lastEventTime = undefined;",
|
|
844
|
+
" root.__marklessRegisterDispatch = next => {",
|
|
845
|
+
" const fallback = handler;",
|
|
846
|
+
" handler = input => next(input, fallback);",
|
|
847
|
+
" };",
|
|
848
|
+
" root.__marklessDispatch = next => {",
|
|
849
|
+
" const event = next.event, eventTime = event?.timeStamp;",
|
|
850
|
+
" if (event && next.eventRecord && next.element) event.__marklessPressHost ??= next.element;",
|
|
851
|
+
" if (event === lastEvent && eventTime === lastEventTime) return done;",
|
|
852
|
+
" lastEvent = event;",
|
|
853
|
+
" lastEventTime = eventTime;",
|
|
854
|
+
" let release;",
|
|
855
|
+
" const started = new Promise(resolve => release = resolve);",
|
|
856
|
+
" const run = () => (root.__marklessReleaseDispatch = release, handler(next));",
|
|
857
|
+
" done = tail.then(run, run);",
|
|
858
|
+
" done.then(release, release);",
|
|
859
|
+
" tail = started;",
|
|
860
|
+
" return done;",
|
|
861
|
+
" };",
|
|
862
|
+
" }",
|
|
863
|
+
" return root.__marklessDispatch(input);",
|
|
864
|
+
"}"
|
|
865
|
+
].join("\n");
|
|
866
|
+
}
|
|
867
|
+
//#endregion
|
|
705
868
|
//#region src/vite/mdx.ts
|
|
869
|
+
const MDX_ROUTE_RUNTIME_SPECIFIER = "@markless/router/vite/runtime/mdx-route";
|
|
706
870
|
function mdxTransformPlugin() {
|
|
707
871
|
return {
|
|
708
872
|
name: "markless-router:mdx",
|
|
709
873
|
enforce: "pre",
|
|
874
|
+
config(config, env) {
|
|
875
|
+
if (env.command !== "serve") return void 0;
|
|
876
|
+
const include = optimizedDepsToInclude([MDX_ROUTE_RUNTIME_SPECIFIER], config);
|
|
877
|
+
return include.length > 0 ? { optimizeDeps: { include } } : void 0;
|
|
878
|
+
},
|
|
710
879
|
transform: {
|
|
711
880
|
order: "pre",
|
|
712
881
|
async handler(code, id) {
|
|
@@ -723,7 +892,7 @@ async function transformMdxRoute(source, id) {
|
|
|
723
892
|
const route = parseMdxRoute(source, id);
|
|
724
893
|
const html = route.parts.map((part) => part.kind === "html" ? part.html : "").join("");
|
|
725
894
|
if (route.components.length === 0) return [
|
|
726
|
-
`import { createMdxRenderDataSurface } from '
|
|
895
|
+
`import { createMdxRenderDataSurface } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
727
896
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
728
897
|
"const marklessMdxRenderData = createMdxRenderDataSurface(marklessMdxParts, []);",
|
|
729
898
|
"const marklessMdxPage = {",
|
|
@@ -736,24 +905,52 @@ async function transformMdxRoute(source, id) {
|
|
|
736
905
|
"export default marklessMdxPage;",
|
|
737
906
|
""
|
|
738
907
|
].join("\n");
|
|
908
|
+
const query = new URLSearchParams(parsePath(id).search);
|
|
909
|
+
if (query.has("markless-route") || query.has("markless-resume")) return emitMdxClientRoute(route, id, query.has("markless-resume"));
|
|
739
910
|
return emitComposedMdxRoute(route, id);
|
|
740
911
|
}
|
|
912
|
+
function emitMdxClientRoute(route, id, resume) {
|
|
913
|
+
return [
|
|
914
|
+
`import { createMdxRenderDataSurface, loadMdxSymbol${resume ? ", tryResumeMdxScalar" : ""} } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
915
|
+
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
916
|
+
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id, route.parts)};`,
|
|
917
|
+
renderOverlayLoader(),
|
|
918
|
+
emitRosterResumeLoaderInstall(),
|
|
919
|
+
"const marklessMdxPage = {",
|
|
920
|
+
" renderData: marklessMdxRenderData,",
|
|
921
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
922
|
+
"};",
|
|
923
|
+
"export default marklessMdxPage;",
|
|
924
|
+
...resume ? renderMdxResumeHandler() : [],
|
|
925
|
+
"function marklessMdxLoadSymbol(symbolId, children = []) {",
|
|
926
|
+
" return loadMdxSymbol(symbolId, children, marklessMdxSymbolLoaders);",
|
|
927
|
+
"}",
|
|
928
|
+
""
|
|
929
|
+
].join("\n");
|
|
930
|
+
}
|
|
931
|
+
function renderOverlayLoader() {
|
|
932
|
+
return "globalThis.__marklessOverlay ??= (root) => root.querySelector('[overlay]') ? import('@markless/web/fns/overlay').then((m) => m.installOverlayBehavior(root)) : undefined;";
|
|
933
|
+
}
|
|
741
934
|
function emitComposedMdxRoute(route, id) {
|
|
742
935
|
return [
|
|
743
|
-
`import {
|
|
744
|
-
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild } from '@markless/router/vite/runtime/mdx-route';`,
|
|
936
|
+
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild, tryResumeMdxScalar } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
745
937
|
...route.imports,
|
|
746
938
|
"",
|
|
747
939
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
748
940
|
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
749
941
|
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id)};`,
|
|
750
942
|
`const marklessMdxStorageSeeds = ${renderStorageSeeds(route.components)};`,
|
|
943
|
+
`const marklessMdxHeadInjections = ${renderHeadInjections(route.components)};`,
|
|
944
|
+
"",
|
|
945
|
+
renderOverlayLoader(),
|
|
946
|
+
emitRosterResumeLoaderInstall(),
|
|
751
947
|
"",
|
|
752
948
|
"const marklessMdxPage = {",
|
|
753
949
|
" renderData: marklessMdxRenderData,",
|
|
754
950
|
" storageSeeds: marklessMdxStorageSeeds,",
|
|
951
|
+
" headInjections: marklessMdxHeadInjections,",
|
|
755
952
|
" loadSymbol: marklessMdxLoadSymbol,",
|
|
756
|
-
" async renderSsr(props = {}) {",
|
|
953
|
+
" async renderSsr(props = {}, marklessMdxRenderContext) {",
|
|
757
954
|
" const marklessMdxChildren = [];",
|
|
758
955
|
` const html = ${renderHtmlExpression(route)};`,
|
|
759
956
|
" const state = composeMdxState(marklessMdxChildren);",
|
|
@@ -764,15 +961,7 @@ function emitComposedMdxRoute(route, id) {
|
|
|
764
961
|
"};",
|
|
765
962
|
"export default marklessMdxPage;",
|
|
766
963
|
"",
|
|
767
|
-
|
|
768
|
-
" input.root.__asyncResumeRuntimeStarted = true;",
|
|
769
|
-
" const { runtime } = await resumeFromPayloadDocument({",
|
|
770
|
-
" document: input.root,",
|
|
771
|
-
" root: input.root,",
|
|
772
|
-
" loadSymbol: marklessMdxLoadSymbol,",
|
|
773
|
-
" });",
|
|
774
|
-
" await runtime.dispatch(input.event, { syncPolicyAlreadyApplied: true });",
|
|
775
|
-
"}",
|
|
964
|
+
...renderMdxResumeHandler(),
|
|
776
965
|
"",
|
|
777
966
|
"function marklessMdxLoadSymbol(symbolId, children = []) {",
|
|
778
967
|
" return loadMdxSymbol(symbolId, children, marklessMdxSymbolLoaders);",
|
|
@@ -780,6 +969,32 @@ function emitComposedMdxRoute(route, id) {
|
|
|
780
969
|
""
|
|
781
970
|
].join("\n");
|
|
782
971
|
}
|
|
972
|
+
function renderMdxResumeHandler() {
|
|
973
|
+
return [
|
|
974
|
+
"let marklessResumeModule;",
|
|
975
|
+
emitQueuedResumeContainerEvent([
|
|
976
|
+
"export async function resumeContainerEvent(input) {",
|
|
977
|
+
" if (await tryResumeMdxScalar(input, marklessMdxLoadScalarPlan, marklessMdxLoadSymbol)) return;",
|
|
978
|
+
" input.root.__asyncResumeRuntimeStarted = true;",
|
|
979
|
+
" const { resumeFromPayloadDocument } = await (marklessResumeModule ??= import('@markless/core/web/resume').catch(error => { marklessResumeModule = undefined; throw error; }));",
|
|
980
|
+
" const { runtime } = await resumeFromPayloadDocument({",
|
|
981
|
+
" document: input.root,",
|
|
982
|
+
" root: input.root,",
|
|
983
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
984
|
+
" handoffDispatchOptions: marklessMdxDispatchOptions,",
|
|
985
|
+
" renderData: marklessMdxRenderData,",
|
|
986
|
+
" });",
|
|
987
|
+
" await runtime.dispatch(input.event, marklessMdxDispatchOptions(input));",
|
|
988
|
+
"}",
|
|
989
|
+
"function marklessMdxDispatchOptions(input) {",
|
|
990
|
+
" return { syncPolicyAlreadyApplied: true, propagationStopped: input.propagationStopped, ignoreUnmatched: input.eventRecord == null };",
|
|
991
|
+
"}"
|
|
992
|
+
].join("\n")),
|
|
993
|
+
"function marklessMdxLoadScalarPlan(symbolId) {",
|
|
994
|
+
" return marklessMdxSymbolLoaders.find(loader => symbolId.startsWith(loader.prefix))?.loadScalarActionPlan(symbolId);",
|
|
995
|
+
"}"
|
|
996
|
+
];
|
|
997
|
+
}
|
|
783
998
|
function renderMdxPreload(components) {
|
|
784
999
|
return [
|
|
785
1000
|
" preload() {",
|
|
@@ -828,7 +1043,8 @@ function tsrxImportsFromProgram(program, id) {
|
|
|
828
1043
|
if (statement.type !== "ImportDeclaration") throw new Error(`Markless Router MDX only supports importing TSRX components before content: ${id}`);
|
|
829
1044
|
const specifier = statement.source.value;
|
|
830
1045
|
const defaultImport = statement.specifiers.find((importSpecifier) => importSpecifier.type === "ImportDefaultSpecifier");
|
|
831
|
-
if (typeof specifier !== "string" || !specifier.endsWith(".tsrx")
|
|
1046
|
+
if (typeof specifier !== "string" || !specifier.endsWith(".tsrx")) throw new Error(`Markless Router MDX currently supports default imports from .tsrx files only: ${id}`);
|
|
1047
|
+
if (!defaultImport || statement.specifiers.length !== 1) throw new Error(tsrxImportShapeMessage(statement.specifiers, specifier, id));
|
|
832
1048
|
imports.push({
|
|
833
1049
|
localName: defaultImport.local.name,
|
|
834
1050
|
specifier
|
|
@@ -836,20 +1052,32 @@ function tsrxImportsFromProgram(program, id) {
|
|
|
836
1052
|
}
|
|
837
1053
|
return imports;
|
|
838
1054
|
}
|
|
1055
|
+
function tsrxImportShapeMessage(specifiers, specifier, id) {
|
|
1056
|
+
const intro = `Markless Router MDX imports a .tsrx component as that file's default export, one component per import: ${id}.`;
|
|
1057
|
+
const source = JSON.stringify(specifier).slice(1, -1);
|
|
1058
|
+
const defaultImport = specifiers.find((entry) => entry.type === "ImportDefaultSpecifier");
|
|
1059
|
+
if (defaultImport) return `${intro} Write \`import ${defaultImport.local.name} from '${source}';\` alone.`;
|
|
1060
|
+
const named = specifiers.find((entry) => entry.type === "ImportSpecifier");
|
|
1061
|
+
if (named) {
|
|
1062
|
+
const exported = named.imported.type === "Identifier" ? named.imported.name : String(named.imported.value);
|
|
1063
|
+
return `${intro} Write \`import ${named.local.name} from '${source}';\` and make ${exported} the file's \`export default\`.`;
|
|
1064
|
+
}
|
|
1065
|
+
return `${intro} Write \`import Component from '${source}';\` and make the component the file's \`export default\`.`;
|
|
1066
|
+
}
|
|
839
1067
|
function routePartsFromHast(nodes, context) {
|
|
840
1068
|
const parts = [];
|
|
841
1069
|
let staticNodes = [];
|
|
842
1070
|
for (const node of nodes) {
|
|
843
1071
|
if (isMdxEsm(node)) continue;
|
|
844
|
-
if (isMdxJsxFlowElement(node)) {
|
|
845
|
-
appendHtmlPart(parts, staticNodes, context
|
|
1072
|
+
if (isMdxJsxFlowElement(node) && !isIntrinsicElement(node, context)) {
|
|
1073
|
+
appendHtmlPart(parts, staticNodes, context);
|
|
846
1074
|
staticNodes = [];
|
|
847
1075
|
parts.push(componentPart(node, context));
|
|
848
1076
|
continue;
|
|
849
1077
|
}
|
|
850
1078
|
staticNodes.push(node);
|
|
851
1079
|
}
|
|
852
|
-
appendHtmlPart(parts, staticNodes, context
|
|
1080
|
+
appendHtmlPart(parts, staticNodes, context);
|
|
853
1081
|
return parts;
|
|
854
1082
|
}
|
|
855
1083
|
function componentPart(node, context) {
|
|
@@ -858,10 +1086,15 @@ function componentPart(node, context) {
|
|
|
858
1086
|
if (!imported) throw new Error(`Markless Router MDX component is not imported from .tsrx: ${name}`);
|
|
859
1087
|
const componentIndex = context.components.length;
|
|
860
1088
|
const props = componentProps(node, context.id);
|
|
861
|
-
const
|
|
1089
|
+
const childNodes = normalizeStaticHastNodes(node.children, context);
|
|
1090
|
+
const children = childNodes.length ? toHtml({
|
|
1091
|
+
type: "root",
|
|
1092
|
+
children: childNodes
|
|
1093
|
+
}, { allowDangerousHtml: true }) : "";
|
|
862
1094
|
context.components.push({
|
|
863
1095
|
...imported,
|
|
864
|
-
prefix:
|
|
1096
|
+
prefix: protocolIslandSegment(componentIndex),
|
|
1097
|
+
childrenElementTags: children ? hastElementTags(childNodes) : [],
|
|
865
1098
|
props: children ? [...props, {
|
|
866
1099
|
name: "children",
|
|
867
1100
|
value: children,
|
|
@@ -873,50 +1106,72 @@ function componentPart(node, context) {
|
|
|
873
1106
|
componentIndex
|
|
874
1107
|
};
|
|
875
1108
|
}
|
|
876
|
-
function appendHtmlPart(parts, nodes,
|
|
1109
|
+
function appendHtmlPart(parts, nodes, context) {
|
|
877
1110
|
if (nodes.length === 0) return;
|
|
878
|
-
const staticNodes = normalizeStaticHastNodes(nodes,
|
|
1111
|
+
const staticNodes = normalizeStaticHastNodes(nodes, context);
|
|
879
1112
|
const html = toHtml({
|
|
880
1113
|
type: "root",
|
|
881
1114
|
children: staticNodes
|
|
882
1115
|
}, { allowDangerousHtml: true });
|
|
883
1116
|
if (html) {
|
|
884
1117
|
const elementTags = hastElementTags(staticNodes);
|
|
1118
|
+
const commentCount = hastCommentCount(staticNodes);
|
|
885
1119
|
parts.push({
|
|
886
1120
|
kind: "html",
|
|
887
1121
|
html,
|
|
888
1122
|
elementCount: elementTags.length,
|
|
1123
|
+
...commentCount > 0 ? { commentCount } : {},
|
|
889
1124
|
elementTags
|
|
890
1125
|
});
|
|
891
1126
|
}
|
|
892
1127
|
}
|
|
893
|
-
function
|
|
894
|
-
|
|
895
|
-
return toHtml({
|
|
896
|
-
type: "root",
|
|
897
|
-
children: normalizeStaticHastNodes(nodes, id)
|
|
898
|
-
}, { allowDangerousHtml: true });
|
|
1128
|
+
function normalizeStaticHastNodes(nodes, context) {
|
|
1129
|
+
return nodes.flatMap((node) => normalizeStaticHastNode(node, context));
|
|
899
1130
|
}
|
|
900
|
-
function
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
function normalizeStaticHastNode(node, id) {
|
|
1131
|
+
function normalizeStaticHastNode(node, context) {
|
|
1132
|
+
const { id } = context;
|
|
904
1133
|
if (isMdxEsm(node)) return [];
|
|
905
|
-
if (isMdxJsxFlowElement(node) || isMdxJsxTextElement(node))
|
|
1134
|
+
if (isMdxJsxFlowElement(node) || isMdxJsxTextElement(node)) {
|
|
1135
|
+
if (!isIntrinsicElement(node, context)) throw new Error(`Markless Router MDX only supports imported TSRX components as route-level MDX elements or static markdown children: ${id}`);
|
|
1136
|
+
return [intrinsicElement(node, context)];
|
|
1137
|
+
}
|
|
906
1138
|
if (isMdxExpression(node)) {
|
|
1139
|
+
if (isEmptyMdxExpression(node.value)) return [];
|
|
907
1140
|
const value = literalSafeExpressionValue(node.value, id);
|
|
908
1141
|
return [{
|
|
909
1142
|
type: "text",
|
|
910
1143
|
value: value == null ? "" : String(value)
|
|
911
1144
|
}];
|
|
912
1145
|
}
|
|
913
|
-
if (node.type === "root") return normalizeStaticHastNodes(node.children,
|
|
1146
|
+
if (node.type === "root") return normalizeStaticHastNodes(node.children, context);
|
|
914
1147
|
if ("children" in node && Array.isArray(node.children)) return [{
|
|
915
1148
|
...node,
|
|
916
|
-
children: normalizeStaticHastNodes(node.children,
|
|
1149
|
+
children: normalizeStaticHastNodes(node.children, context)
|
|
917
1150
|
}];
|
|
918
1151
|
return [node];
|
|
919
1152
|
}
|
|
1153
|
+
function isIntrinsicElement(node, context) {
|
|
1154
|
+
const name = node.name;
|
|
1155
|
+
return typeof name === "string" && /^[a-z][a-z0-9-]*$/.test(name) && !context.importedComponents.has(name);
|
|
1156
|
+
}
|
|
1157
|
+
const JSX_ATTRIBUTE_NAMES = {
|
|
1158
|
+
className: "class",
|
|
1159
|
+
htmlFor: "for"
|
|
1160
|
+
};
|
|
1161
|
+
function intrinsicElement(node, context) {
|
|
1162
|
+
const properties = {};
|
|
1163
|
+
for (const { name, value } of componentProps(node, context.id)) {
|
|
1164
|
+
if (value === false || value == null) continue;
|
|
1165
|
+
if (typeof value !== "string" && typeof value !== "number" && value !== true) throw literalExpressionError(context.id);
|
|
1166
|
+
properties[JSX_ATTRIBUTE_NAMES[name] ?? name.toLowerCase()] = value === true ? true : String(value);
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
type: "element",
|
|
1170
|
+
tagName: node.name,
|
|
1171
|
+
properties,
|
|
1172
|
+
children: normalizeStaticHastNodes(node.children, context)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
920
1175
|
function componentProps(node, id) {
|
|
921
1176
|
return node.attributes.map((attribute) => componentProp(attribute, id));
|
|
922
1177
|
}
|
|
@@ -947,6 +1202,9 @@ function literalSafeExpressionValue(source, id) {
|
|
|
947
1202
|
if (program.body.length !== 1 || statement?.type !== "ExpressionStatement") throw new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
|
|
948
1203
|
return evaluateLiteralExpression(statement.expression, id);
|
|
949
1204
|
}
|
|
1205
|
+
function isEmptyMdxExpression(source) {
|
|
1206
|
+
return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "").trim() === "";
|
|
1207
|
+
}
|
|
950
1208
|
function parseMdxExpressionValue(source, id) {
|
|
951
1209
|
const handle = createMdxHastHandle(`{${source}}`);
|
|
952
1210
|
let program = null;
|
|
@@ -1015,9 +1273,12 @@ function renderHtmlExpression(route) {
|
|
|
1015
1273
|
return joinExpressions(route.parts.map((part) => {
|
|
1016
1274
|
if (part.kind === "html") return JSON.stringify(part.html);
|
|
1017
1275
|
const component = route.components[part.componentIndex];
|
|
1018
|
-
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)} }))`;
|
|
1276
|
+
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}${childrenElementTagsField(component)} }, marklessMdxRenderContext))`;
|
|
1019
1277
|
}));
|
|
1020
1278
|
}
|
|
1279
|
+
function childrenElementTagsField(component) {
|
|
1280
|
+
return component.childrenElementTags.length > 0 ? `, childrenElementTags: ${JSON.stringify(component.childrenElementTags)}` : "";
|
|
1281
|
+
}
|
|
1021
1282
|
function componentPropsExpression(component) {
|
|
1022
1283
|
if (component.props.length === 0) return "{}";
|
|
1023
1284
|
return `{ ${component.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.valueExpression}`).join(", ")} }`;
|
|
@@ -1034,14 +1295,26 @@ function hastElementTags(nodes) {
|
|
|
1034
1295
|
}
|
|
1035
1296
|
return tags;
|
|
1036
1297
|
}
|
|
1298
|
+
function hastCommentCount(nodes) {
|
|
1299
|
+
let comments = 0;
|
|
1300
|
+
for (const node of nodes) {
|
|
1301
|
+
if (node.type === "comment") comments++;
|
|
1302
|
+
if ("children" in node && Array.isArray(node.children)) comments += hastCommentCount(node.children);
|
|
1303
|
+
}
|
|
1304
|
+
return comments;
|
|
1305
|
+
}
|
|
1037
1306
|
function renderStorageSeeds(components) {
|
|
1038
1307
|
const localNames = [...new Set(components.map((component) => component.localName))];
|
|
1039
1308
|
return localNames.length === 0 ? "[]" : `[${localNames.map((localName) => `...(${localName}.storageSeeds ?? [])`).join(", ")}]`;
|
|
1040
1309
|
}
|
|
1310
|
+
function renderHeadInjections(components) {
|
|
1311
|
+
const localNames = [...new Set(components.map((component) => component.localName))];
|
|
1312
|
+
return localNames.length === 0 ? "[]" : `[...new Map([${localNames.map((localName) => `...(${localName}.headInjections ?? [])`).join(", ")}].map((injection) => [JSON.stringify(injection), injection])).values()]`;
|
|
1313
|
+
}
|
|
1041
1314
|
function renderSymbolLoaders(components) {
|
|
1042
|
-
return `[${components.map((component) => `{ prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return
|
|
1315
|
+
return `[${components.map((component) => `(() => { let module; const loadModule = () => module ??= import(${JSON.stringify(scalarPlanSourceReference(component.specifier))}).catch(error => { module = undefined; throw error; }); return { prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return loadModule().then((mod) => mod.loadSymbol(symbolId.slice(${component.prefix.length}))); }, loadScalarActionPlan(symbolId) { return loadModule().then(mod => mod.loadScalarActionPlan?.(symbolId.slice(${component.prefix.length}))).then(action => action && ({ ...action, scope: ${JSON.stringify(component.prefix)} + action.scope })); } }; })()`).join(", ")}]`;
|
|
1043
1316
|
}
|
|
1044
|
-
function renderMdxRenderDataLoader(components, id) {
|
|
1317
|
+
function renderMdxRenderDataLoader(components, id, parts) {
|
|
1045
1318
|
const reachedFrom = mdxMaterializedReachRoot(id);
|
|
1046
1319
|
const imports = components.map((component) => {
|
|
1047
1320
|
const source = withQuery(component.specifier, {
|
|
@@ -1052,7 +1325,8 @@ function renderMdxRenderDataLoader(components, id) {
|
|
|
1052
1325
|
});
|
|
1053
1326
|
const exportName = reachedFrom ? "marklessPrerenderData" : "marklessRenderData";
|
|
1054
1327
|
const children = components.map((component, index) => `{ componentIndex: ${index}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}, props: ${componentPropsExpression(component)}, surface: modules[${index}].${exportName} }`);
|
|
1055
|
-
|
|
1328
|
+
const load = `Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
|
|
1329
|
+
return parts ? `() => {\nconst marklessMdxParts = ${JSON.stringify(parts)};\nreturn ${load};\n}` : `() => ${load}`;
|
|
1056
1330
|
}
|
|
1057
1331
|
function mdxMaterializedReachRoot(id) {
|
|
1058
1332
|
const query = new URLSearchParams(parsePath(id).search);
|
|
@@ -1073,7 +1347,109 @@ function isMdxExpression(node) {
|
|
|
1073
1347
|
return node.type === "mdxFlowExpression" || node.type === "mdxTextExpression";
|
|
1074
1348
|
}
|
|
1075
1349
|
//#endregion
|
|
1350
|
+
//#region src/vite/route-preload-data.ts
|
|
1351
|
+
function compactRoutePreloadData(data) {
|
|
1352
|
+
const urls = [];
|
|
1353
|
+
const indices = /* @__PURE__ */ new Map();
|
|
1354
|
+
const encode = (routes) => Object.fromEntries(Object.keys(routes).sort().map((route) => [route, routes[route].map((href) => {
|
|
1355
|
+
let index = indices.get(href);
|
|
1356
|
+
if (index === void 0) {
|
|
1357
|
+
index = urls.length;
|
|
1358
|
+
urls.push(href);
|
|
1359
|
+
indices.set(href, index);
|
|
1360
|
+
}
|
|
1361
|
+
return index;
|
|
1362
|
+
})]));
|
|
1363
|
+
return [
|
|
1364
|
+
urls,
|
|
1365
|
+
encode(data.navigation),
|
|
1366
|
+
encode(data.ssr)
|
|
1367
|
+
];
|
|
1368
|
+
}
|
|
1369
|
+
function routePreloadDecoderSource(chunkSpecifiers) {
|
|
1370
|
+
return [
|
|
1371
|
+
"if (Array.isArray(routePreloadData)) {",
|
|
1372
|
+
" const [urls, navigation, ssr] = routePreloadData;",
|
|
1373
|
+
...chunkSpecifiers ? [` const imports = Object.assign({}, ...[...(globalThis.document?.querySelectorAll('script[type="importmap"]') ?? [])].map((script) => { try { return JSON.parse(script.textContent).imports; } catch { return {}; } }));`, ` const href = (url) => (url.startsWith(${JSON.stringify(MARKLESS_CHUNK_SPECIFIER_PREFIX)}) && imports[url]) || url;`] : [],
|
|
1374
|
+
` const decode = (routes) => Object.fromEntries(Object.entries(routes).map(([file, indices]) => [file, indices.map((index) => ${chunkSpecifiers ? "href(urls[index])" : "urls[index]"})]));`,
|
|
1375
|
+
" routePreloadData = { navigation: decode(navigation), ssr: decode(ssr) };",
|
|
1376
|
+
"}"
|
|
1377
|
+
].join("\n");
|
|
1378
|
+
}
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/navigation-polyfill.ts
|
|
1381
|
+
const NAVIGATION_POLYFILL_MODULE = "@virtualstate/navigation";
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/vite/document-navigation.ts
|
|
1384
|
+
const ROUTE_INVARIANT_DOCUMENT_PROPS = /* @__PURE__ */ new Set(["children"]);
|
|
1385
|
+
function documentNavigationFor(source, id) {
|
|
1386
|
+
if (source === void 0) return "client";
|
|
1387
|
+
let program;
|
|
1388
|
+
try {
|
|
1389
|
+
program = parseModule(source, id);
|
|
1390
|
+
} catch {
|
|
1391
|
+
return "document";
|
|
1392
|
+
}
|
|
1393
|
+
const body = nodes(program.body);
|
|
1394
|
+
if (body.some(exportsHtmlAttributesHook)) return "document";
|
|
1395
|
+
const component = defaultExportedFunction(body);
|
|
1396
|
+
if (!component) return "document";
|
|
1397
|
+
return readsOnlyRouteInvariantProps(nodes(component.params)) ? "client" : "document";
|
|
1398
|
+
}
|
|
1399
|
+
function readsOnlyRouteInvariantProps(params) {
|
|
1400
|
+
if (params.length === 0) return true;
|
|
1401
|
+
if (params.length > 1) return false;
|
|
1402
|
+
const [param] = params;
|
|
1403
|
+
const pattern = param?.type === "AssignmentPattern" ? node(param.left) : param;
|
|
1404
|
+
if (pattern?.type !== "ObjectPattern") return false;
|
|
1405
|
+
return nodes(pattern.properties).every((property) => {
|
|
1406
|
+
if (property.type !== "Property" || property.computed === true) return false;
|
|
1407
|
+
const key = node(property.key);
|
|
1408
|
+
const name = key?.type === "Identifier" ? key.name : key?.value;
|
|
1409
|
+
return typeof name === "string" && ROUTE_INVARIANT_DOCUMENT_PROPS.has(name);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function defaultExportedFunction(body) {
|
|
1413
|
+
const declaration = node(node(body.find((statement) => statement.type === "ExportDefaultDeclaration"))?.declaration);
|
|
1414
|
+
if (isFunction(declaration)) return declaration;
|
|
1415
|
+
if (declaration?.type !== "Identifier") return void 0;
|
|
1416
|
+
const name = declaration.name;
|
|
1417
|
+
for (const statement of body) {
|
|
1418
|
+
const local = statement.type === "ExportNamedDeclaration" ? node(statement.declaration) : statement;
|
|
1419
|
+
if (local?.type === "FunctionDeclaration" && node(local.id)?.name === name) return local;
|
|
1420
|
+
if (local?.type !== "VariableDeclaration") continue;
|
|
1421
|
+
for (const declarator of nodes(local.declarations)) {
|
|
1422
|
+
if (node(declarator.id)?.name !== name) continue;
|
|
1423
|
+
const init = node(declarator.init);
|
|
1424
|
+
return isFunction(init) ? init : void 0;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function exportsHtmlAttributesHook(statement) {
|
|
1429
|
+
if (statement.type !== "ExportNamedDeclaration") return false;
|
|
1430
|
+
const declaration = node(statement.declaration);
|
|
1431
|
+
return [
|
|
1432
|
+
node(declaration?.id)?.name,
|
|
1433
|
+
...nodes(declaration?.declarations).map((declarator) => node(declarator.id)?.name),
|
|
1434
|
+
...nodes(statement.specifiers).map((specifier) => node(specifier.exported)?.name)
|
|
1435
|
+
].includes("__marklessRouterHtmlAttributes");
|
|
1436
|
+
}
|
|
1437
|
+
function isFunction(value) {
|
|
1438
|
+
return value?.type === "FunctionDeclaration" || value?.type === "FunctionExpression" || value?.type === "ArrowFunctionExpression";
|
|
1439
|
+
}
|
|
1440
|
+
function node(value) {
|
|
1441
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
1442
|
+
}
|
|
1443
|
+
function nodes(value) {
|
|
1444
|
+
return Array.isArray(value) ? value.filter((item) => node(item)) : [];
|
|
1445
|
+
}
|
|
1446
|
+
//#endregion
|
|
1076
1447
|
//#region src/vite/index.ts
|
|
1448
|
+
const PAGE_SERVER_ONLY_PROPS = [{
|
|
1449
|
+
exportName: "default",
|
|
1450
|
+
propName: "http",
|
|
1451
|
+
description: "the request with its cookies and headers, the locals middleware set, and the response being written"
|
|
1452
|
+
}];
|
|
1077
1453
|
const ROUTE_DISCOVERY_ID = "virtual:markless-router/routes";
|
|
1078
1454
|
const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
1079
1455
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
@@ -1085,12 +1461,16 @@ const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-ent
|
|
|
1085
1461
|
const NAVIGATION_ENTRY_ID = "virtual:markless-router/navigation-entry";
|
|
1086
1462
|
const NAVIGATION_ENTRY_ORIGIN = "/entries/client-entry.ts";
|
|
1087
1463
|
const NAVIGATION_ENTRY_PATH_ID = "virtual:markless-router/navigation-entry-path";
|
|
1464
|
+
const FRAGMENT_ENTRY_ID = "virtual:markless-router/fragment-entry";
|
|
1465
|
+
const FRAGMENT_ENTRY_ORIGIN = "/entries/fragment-entry.ts";
|
|
1466
|
+
const FRAGMENT_ENTRY_PATH_ID = "virtual:markless-router/fragment-entry-path";
|
|
1088
1467
|
const ROUTE_PRELOADS_ID = "virtual:markless-router/route-preloads";
|
|
1089
1468
|
const ROUTE_PRELOADS_PLACEHOLDER = "__MARKLESS_ROUTER_ROUTE_PRELOADS__";
|
|
1469
|
+
const DOCUMENT_STYLESHEETS_PLACEHOLDER = "__MARKLESS_ROUTER_DOCUMENT_STYLESHEETS__";
|
|
1090
1470
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1091
1471
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1092
1472
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
1093
|
-
const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|prerender-wake-entry|prerender-wake-entry-path|navigation-entry|navigation-entry-path|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
|
|
1473
|
+
const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|prerender-wake-entry|prerender-wake-entry-path|navigation-entry|navigation-entry-path|fragment-entry|fragment-entry-path|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
|
|
1094
1474
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1095
1475
|
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "src/vite/entries");
|
|
1096
1476
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1105,36 +1485,48 @@ const virtualEntryFiles = {
|
|
|
1105
1485
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1106
1486
|
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1107
1487
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1488
|
+
[FRAGMENT_ENTRY_ID]: "fragment-entry.ts",
|
|
1108
1489
|
[SERVER_ENTRY_ID]: "server-entry.ts",
|
|
1109
1490
|
[ROUTE_HREF_ID]: "route-href.ts"
|
|
1110
1491
|
};
|
|
1111
1492
|
function router(options = {}) {
|
|
1112
1493
|
prerenderWakeChannelCaptured = process.env.MARKLESS_PRERENDER_WAKE === "1";
|
|
1113
1494
|
if (options.nitro === false) return [
|
|
1495
|
+
pageServerOnlyPropsPlugin(),
|
|
1114
1496
|
mdxTransformPlugin(),
|
|
1115
1497
|
requestFileTransformPlugin(),
|
|
1116
1498
|
routeTypegenPlugin(),
|
|
1117
1499
|
anchorTransformPlugin(),
|
|
1118
1500
|
htmlTransformPlugin(),
|
|
1119
|
-
virtualModulesPlugin()
|
|
1501
|
+
virtualModulesPlugin(void 0, void 0, void 0, void 0, options)
|
|
1120
1502
|
];
|
|
1121
1503
|
const nitroPlugins = nitro();
|
|
1122
1504
|
const resumeEntry = resumeEntryState();
|
|
1123
1505
|
const prerenderWakeEntry = resumeEntryState();
|
|
1124
1506
|
const navigationEntry = resumeEntryState();
|
|
1507
|
+
const fragmentEntry = resumeEntryState();
|
|
1125
1508
|
const routePreloads = routePreloadState();
|
|
1126
1509
|
return [
|
|
1127
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1510
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry),
|
|
1128
1511
|
devSourceModuleRequestPlugin(),
|
|
1512
|
+
pageServerOnlyPropsPlugin(),
|
|
1129
1513
|
mdxTransformPlugin(),
|
|
1130
1514
|
requestFileTransformPlugin(),
|
|
1131
1515
|
routeTypegenPlugin(),
|
|
1132
1516
|
anchorTransformPlugin(),
|
|
1133
1517
|
htmlTransformPlugin(),
|
|
1134
|
-
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1518
|
+
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, options, fragmentEntry),
|
|
1135
1519
|
nitroPlugins
|
|
1136
1520
|
];
|
|
1137
1521
|
}
|
|
1522
|
+
function pageServerOnlyPropsPlugin() {
|
|
1523
|
+
return {
|
|
1524
|
+
name: "markless-router:page-server-only-props",
|
|
1525
|
+
configResolved(config) {
|
|
1526
|
+
config.plugins.find((plugin) => typeof plugin.api?.declareServerOnlyProps === "function")?.api.declareServerOnlyProps((moduleId) => isPageModuleFile(normalizeRouteFileId(moduleId)) ? PAGE_SERVER_ONLY_PROPS : void 0);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1138
1530
|
function devSourceModuleRequestPlugin() {
|
|
1139
1531
|
let root = "";
|
|
1140
1532
|
return {
|
|
@@ -1155,18 +1547,32 @@ function devSourceModuleRequestPlugin() {
|
|
|
1155
1547
|
}
|
|
1156
1548
|
};
|
|
1157
1549
|
}
|
|
1158
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads) {
|
|
1550
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState()) {
|
|
1159
1551
|
let clientOutDir = "";
|
|
1160
1552
|
let clientEnvironmentName = "";
|
|
1161
1553
|
let clientEnvironmentConfig;
|
|
1162
1554
|
let serverEnvironmentConfig;
|
|
1163
1555
|
let productionBuild = false;
|
|
1164
1556
|
let pendingClientAssets;
|
|
1557
|
+
let capturedChunkNames = /* @__PURE__ */ new Map();
|
|
1558
|
+
const navigationPolyfillIds = /* @__PURE__ */ new Set();
|
|
1165
1559
|
return {
|
|
1166
1560
|
name: "markless-router:vite",
|
|
1167
1561
|
enforce: "pre",
|
|
1168
1562
|
sharedDuringBuild: true,
|
|
1169
|
-
|
|
1563
|
+
resolveId: {
|
|
1564
|
+
filter: { id: new RegExp(`^${escapeRegExp(NAVIGATION_POLYFILL_MODULE)}$`) },
|
|
1565
|
+
async handler(source, importer, options) {
|
|
1566
|
+
if (source !== "@virtualstate/navigation") return null;
|
|
1567
|
+
const resolved = await this.resolve(source, importer, {
|
|
1568
|
+
...options,
|
|
1569
|
+
skipSelf: true
|
|
1570
|
+
});
|
|
1571
|
+
if (resolved) navigationPolyfillIds.add(resolved.id);
|
|
1572
|
+
return resolved;
|
|
1573
|
+
}
|
|
1574
|
+
},
|
|
1575
|
+
config(config, env) {
|
|
1170
1576
|
throwIfUserAddedNitro(config.plugins, nitroPluginsFromRouter);
|
|
1171
1577
|
config.environments ??= {};
|
|
1172
1578
|
const ssrEnvironment = config.environments.ssr ??= {};
|
|
@@ -1175,7 +1581,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1175
1581
|
ssrEnvironment.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, config.root);
|
|
1176
1582
|
const serverWatch = withWatchIgnores(config.server?.watch);
|
|
1177
1583
|
return {
|
|
1178
|
-
nitro: createNitroConfig(config.nitro, config.root, serverWatch.ignored),
|
|
1584
|
+
nitro: createNitroConfig(config.nitro, config.root, serverWatch.ignored, env?.command === "build"),
|
|
1179
1585
|
server: {
|
|
1180
1586
|
...config.server,
|
|
1181
1587
|
watch: serverWatch
|
|
@@ -1197,9 +1603,13 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1197
1603
|
resumeEntry,
|
|
1198
1604
|
prerenderWakeEntry,
|
|
1199
1605
|
navigationEntry,
|
|
1606
|
+
fragmentEntry,
|
|
1200
1607
|
routePreloads
|
|
1201
1608
|
]) entry.base = config.base;
|
|
1202
1609
|
routePreloads.root = config.root;
|
|
1610
|
+
const markless = (config.plugins ?? []).find((plugin) => typeof plugin.api?.chunkImportMap === "function");
|
|
1611
|
+
markless?.api.enableChunkImportMap?.();
|
|
1612
|
+
routePreloads.chunkSpecifiers = markless?.api.chunkImportMap() === true;
|
|
1203
1613
|
},
|
|
1204
1614
|
configEnvironment(_name, config) {
|
|
1205
1615
|
configureRouteInputs(config);
|
|
@@ -1215,12 +1625,14 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1215
1625
|
resumeEntry.fileName = void 0;
|
|
1216
1626
|
prerenderWakeEntry.fileName = void 0;
|
|
1217
1627
|
navigationEntry.fileName = void 0;
|
|
1628
|
+
fragmentEntry.fileName = void 0;
|
|
1218
1629
|
routePreloads.routes = {
|
|
1219
1630
|
navigation: {},
|
|
1220
1631
|
ssr: {},
|
|
1221
1632
|
styles: {}
|
|
1222
1633
|
};
|
|
1223
1634
|
routePreloads.persisted = false;
|
|
1635
|
+
routePreloads.importMap = void 0;
|
|
1224
1636
|
await removeClientAssetsManifest(clientOutDir);
|
|
1225
1637
|
return;
|
|
1226
1638
|
}
|
|
@@ -1229,28 +1641,37 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1229
1641
|
resumeEntry.fileName = clientAssetFileName(manifest.entries.resume, manifest.base);
|
|
1230
1642
|
prerenderWakeEntry.fileName = manifest.entries.prerenderWake ? clientAssetFileName(manifest.entries.prerenderWake, manifest.base) : void 0;
|
|
1231
1643
|
navigationEntry.fileName = clientAssetFileName(manifest.entries.navigation, manifest.base);
|
|
1644
|
+
fragmentEntry.fileName = manifest.entries.fragment ? clientAssetFileName(manifest.entries.fragment, manifest.base) : void 0;
|
|
1232
1645
|
routePreloads.routes = manifest.routes;
|
|
1233
1646
|
routePreloads.persisted = true;
|
|
1647
|
+
routePreloads.importMap = manifest.importMap;
|
|
1234
1648
|
},
|
|
1235
1649
|
generateBundle: {
|
|
1236
1650
|
order: "post",
|
|
1237
1651
|
handler(_options, bundle) {
|
|
1238
|
-
if (this.environment?.config.consumer !== "client")
|
|
1652
|
+
if (this.environment?.config.consumer !== "client") {
|
|
1653
|
+
if (this.environment?.config.consumer === "server") patchDocumentStylesheetsInBundle(bundle, documentStylesheetsFromBundle(bundle, routePreloads.root, routePreloads.base));
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1239
1656
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1240
1657
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
1241
1658
|
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1242
1659
|
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1243
1660
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
1244
1661
|
if (navigationChunk) navigationEntry.fileName = navigationChunk.fileName;
|
|
1662
|
+
const fragmentChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, FRAGMENT_ENTRY_ORIGIN));
|
|
1663
|
+
if (fragmentChunk) fragmentEntry.fileName = fragmentChunk.fileName;
|
|
1245
1664
|
routePreloads.routes = routeModulePreloadsFromBundle({
|
|
1246
1665
|
base: routePreloads.base,
|
|
1247
1666
|
bundle,
|
|
1248
1667
|
navigationChunk,
|
|
1668
|
+
navigationPolyfillIds,
|
|
1249
1669
|
prerenderWakeChunk,
|
|
1250
1670
|
resumeChunk,
|
|
1251
1671
|
root: routePreloads.root
|
|
1252
1672
|
});
|
|
1253
1673
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
1674
|
+
capturedChunkNames = new Map(outputChunks(bundle).map((chunk) => [chunk.fileName, chunk.preliminaryFileName]));
|
|
1254
1675
|
if (productionBuild) {
|
|
1255
1676
|
const wakeRequired = prerenderWakeChannelEnabled();
|
|
1256
1677
|
if (!resumeEntry.fileName || wakeRequired && !prerenderWakeEntry.fileName || !navigationEntry.fileName) throw new Error(wakeRequired ? "Markless Router client build did not emit its resume, prerender-wake, and navigation entries." : "Markless Router client build did not emit its resume and navigation entries.");
|
|
@@ -1259,16 +1680,33 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1259
1680
|
resumeEntry: joinURL(routePreloads.base, resumeEntry.fileName),
|
|
1260
1681
|
...prerenderWakeEntry.fileName ? { prerenderWakeEntry: joinURL(routePreloads.base, prerenderWakeEntry.fileName) } : {},
|
|
1261
1682
|
navigationEntry: joinURL(routePreloads.base, navigationEntry.fileName),
|
|
1683
|
+
...fragmentEntry.fileName ? { fragmentEntry: joinURL(routePreloads.base, fragmentEntry.fileName) } : {},
|
|
1262
1684
|
routes: routePreloads.routes
|
|
1263
1685
|
});
|
|
1264
1686
|
}
|
|
1265
1687
|
}
|
|
1266
1688
|
},
|
|
1267
|
-
async writeBundle() {
|
|
1689
|
+
async writeBundle(_options, bundle) {
|
|
1268
1690
|
const environment = this.environment;
|
|
1269
1691
|
const isClientEnvironment = environment?.name ? environment.name === clientEnvironmentName : environment?.config === clientEnvironmentConfig;
|
|
1270
1692
|
if (!productionBuild || !isClientEnvironment) return;
|
|
1271
1693
|
if (!pendingClientAssets) throw new Error("Markless Router client build finished without a client-assets manifest.");
|
|
1694
|
+
const renames = bundle ? chunkRenamesSince(capturedChunkNames, bundle) : /* @__PURE__ */ new Map();
|
|
1695
|
+
if (renames.size > 0) {
|
|
1696
|
+
pendingClientAssets = renameClientAssetChunks(pendingClientAssets, renames);
|
|
1697
|
+
routePreloads.routes = pendingClientAssets.routes;
|
|
1698
|
+
for (const entry of [
|
|
1699
|
+
resumeEntry,
|
|
1700
|
+
prerenderWakeEntry,
|
|
1701
|
+
navigationEntry,
|
|
1702
|
+
fragmentEntry
|
|
1703
|
+
]) if (entry.fileName) entry.fileName = renames.get(entry.fileName) ?? entry.fileName;
|
|
1704
|
+
}
|
|
1705
|
+
const importMap = bundle?.[MARKLESS_IMPORT_MAP_ASSET];
|
|
1706
|
+
if (importMap?.type === "asset") pendingClientAssets = {
|
|
1707
|
+
...pendingClientAssets,
|
|
1708
|
+
importMap: JSON.parse(String(importMap.source))
|
|
1709
|
+
};
|
|
1272
1710
|
await writeClientAssetsManifest(clientOutDir, pendingClientAssets);
|
|
1273
1711
|
}
|
|
1274
1712
|
};
|
|
@@ -1279,16 +1717,7 @@ function configureRouteInputs(config) {
|
|
|
1279
1717
|
if (config.consumer === "client") {
|
|
1280
1718
|
config.build.rolldownOptions.input = routerClientInput(config.build.rolldownOptions.input, configRoot(config));
|
|
1281
1719
|
config.build.rolldownOptions.preserveEntrySignatures ??= "exports-only";
|
|
1282
|
-
} else
|
|
1283
|
-
config.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, configRoot(config));
|
|
1284
|
-
config.build.rolldownOptions.external = withExternal(config.build.rolldownOptions.external, "nitro");
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function withExternal(external, id) {
|
|
1288
|
-
if (Array.isArray(external)) return external.includes(id) ? external : [...external, id];
|
|
1289
|
-
if (typeof external === "string") return external === id ? external : [external, id];
|
|
1290
|
-
if (external === void 0) return [id];
|
|
1291
|
-
return external;
|
|
1720
|
+
} else config.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, configRoot(config));
|
|
1292
1721
|
}
|
|
1293
1722
|
function requestFileTransformPlugin() {
|
|
1294
1723
|
let root = "";
|
|
@@ -1331,7 +1760,7 @@ function requestFileBuildTransformPlugin(root) {
|
|
|
1331
1760
|
}
|
|
1332
1761
|
};
|
|
1333
1762
|
}
|
|
1334
|
-
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = []) {
|
|
1763
|
+
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false) {
|
|
1335
1764
|
const scanDirs = Array.isArray(nitroConfig?.scanDirs) ? nitroConfig.scanDirs.filter((dir) => typeof dir === "string") : [];
|
|
1336
1765
|
const watchOptions = withWatchIgnores(nitroConfig?.watchOptions, serverWatchIgnored);
|
|
1337
1766
|
const publicAssets = Array.isArray(nitroConfig?.publicAssets) ? nitroConfig.publicAssets : [];
|
|
@@ -1351,9 +1780,14 @@ function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = []) {
|
|
|
1351
1780
|
...publicAssets
|
|
1352
1781
|
],
|
|
1353
1782
|
routesDir: nitroConfig?.routesDir ?? ".output/markless/router/nitro-routes",
|
|
1783
|
+
...isBuild && { routeRules: {
|
|
1784
|
+
[`/${MARKLESS_BUILD_PREFIX}**`]: { headers: { "cache-control": "public, max-age=31536000, immutable" } },
|
|
1785
|
+
...Object.fromEntries(MARKLESS_BUILD_METADATA_FILES.map((file) => [`/${file}`, { headers: { "cache-control": "public, max-age=0, must-revalidate" } }])),
|
|
1786
|
+
...nitroConfig?.routeRules
|
|
1787
|
+
} },
|
|
1354
1788
|
rolldownConfig: withRequestFileBuildPlugin(nitroConfig?.rolldownConfig, root),
|
|
1355
1789
|
rollupConfig: withRequestFileBuildPlugin(nitroConfig?.rollupConfig, root),
|
|
1356
|
-
scanDirs: [
|
|
1790
|
+
scanDirs: [.../* @__PURE__ */ new Set([".", ...scanDirs])],
|
|
1357
1791
|
watchOptions
|
|
1358
1792
|
};
|
|
1359
1793
|
}
|
|
@@ -1400,6 +1834,7 @@ function resumeEntryState() {
|
|
|
1400
1834
|
function routePreloadState() {
|
|
1401
1835
|
return {
|
|
1402
1836
|
base: "/",
|
|
1837
|
+
chunkSpecifiers: false,
|
|
1403
1838
|
persisted: false,
|
|
1404
1839
|
root: ".",
|
|
1405
1840
|
routes: {
|
|
@@ -1417,33 +1852,40 @@ function routerClientInput(input, root) {
|
|
|
1417
1852
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
1418
1853
|
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
1419
1854
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
1855
|
+
const fragmentEntryId = scopedVirtualEntryId(FRAGMENT_ENTRY_ID, root);
|
|
1420
1856
|
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
1421
1857
|
if (input === void 0) return [
|
|
1422
1858
|
resumeEntryId,
|
|
1423
1859
|
...wakeEntries,
|
|
1424
|
-
navigationEntryId
|
|
1860
|
+
navigationEntryId,
|
|
1861
|
+
fragmentEntryId
|
|
1425
1862
|
];
|
|
1426
1863
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
1427
1864
|
resumeEntryId,
|
|
1428
1865
|
...wakeEntries,
|
|
1429
1866
|
navigationEntryId,
|
|
1867
|
+
fragmentEntryId,
|
|
1430
1868
|
...Array.isArray(input) ? input : [input]
|
|
1431
1869
|
];
|
|
1432
1870
|
if (isRecord(input)) return {
|
|
1433
1871
|
...input,
|
|
1434
1872
|
"markless-router-resume": resumeEntryId,
|
|
1435
1873
|
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
1436
|
-
"markless-router-navigation": navigationEntryId
|
|
1874
|
+
"markless-router-navigation": navigationEntryId,
|
|
1875
|
+
"markless-router-fragment": fragmentEntryId
|
|
1437
1876
|
};
|
|
1438
1877
|
return input;
|
|
1439
1878
|
}
|
|
1440
1879
|
function isVirtualEntryChunk(chunk, origin) {
|
|
1441
1880
|
return [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id ? decodePath(parseURL(id).pathname).endsWith(origin) : false);
|
|
1442
1881
|
}
|
|
1443
|
-
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1882
|
+
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState(), options = {}, fragmentEntry = resumeEntryState()) {
|
|
1444
1883
|
let root = "";
|
|
1445
1884
|
return {
|
|
1446
1885
|
name: "markless-router:routes",
|
|
1886
|
+
config() {
|
|
1887
|
+
return { define: { __MARKLESS_ROUTER_LINK_INTENT__: JSON.stringify(linkIntentEnabled(options)) } };
|
|
1888
|
+
},
|
|
1447
1889
|
sharedDuringBuild: true,
|
|
1448
1890
|
configResolved(config) {
|
|
1449
1891
|
root = config.root;
|
|
@@ -1452,48 +1894,78 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
1452
1894
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
1453
1895
|
handler(id) {
|
|
1454
1896
|
const baseId = virtualModuleBaseId(id);
|
|
1455
|
-
if (baseId ===
|
|
1897
|
+
if (baseId === ROUTER_OPTIONS_ID) return {
|
|
1898
|
+
id: `\0${baseId}${rootScopeQuery(root, id)}`,
|
|
1899
|
+
moduleSideEffects: false
|
|
1900
|
+
};
|
|
1901
|
+
if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === PRERENDER_WAKE_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === FRAGMENT_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
|
|
1456
1902
|
const entryFile = virtualEntryFiles[baseId];
|
|
1457
1903
|
if (!entryFile) return;
|
|
1904
|
+
if (baseId === ROUTE_HREF_ID) return join(VIRTUAL_ENTRY_DIR, entryFile);
|
|
1458
1905
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
1459
1906
|
}
|
|
1460
1907
|
},
|
|
1461
1908
|
load(id) {
|
|
1462
|
-
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root);
|
|
1909
|
+
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root, options);
|
|
1463
1910
|
if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
|
|
1464
1911
|
if (id.startsWith(`\0${PRERENDER_WAKE_ENTRY_PATH_ID}`)) {
|
|
1465
1912
|
if (!prerenderWakeChannelEnabled() || !prerenderWakeEntry.fileName) return "export const prerenderWakeEntryPath = undefined;";
|
|
1466
1913
|
return entryPathSource("prerenderWakeEntryPath", prerenderWakeEntry, PRERENDER_WAKE_ENTRY_ID, root);
|
|
1467
1914
|
}
|
|
1468
1915
|
if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
|
|
1916
|
+
if (id.startsWith(`\0${FRAGMENT_ENTRY_PATH_ID}`)) return entryPathSource("fragmentEntryPath", fragmentEntry, FRAGMENT_ENTRY_ID, root);
|
|
1469
1917
|
if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads, this?.environment?.config.consumer === "client");
|
|
1470
|
-
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`))
|
|
1918
|
+
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) {
|
|
1919
|
+
const documentFile = join(root, "document.tsrx");
|
|
1920
|
+
this?.addWatchFile?.(documentFile);
|
|
1921
|
+
return routerOptionsSource(options, documentFile);
|
|
1922
|
+
}
|
|
1471
1923
|
}
|
|
1472
1924
|
};
|
|
1473
1925
|
}
|
|
1926
|
+
async function routerOptionsSource(options, documentFile) {
|
|
1927
|
+
const documentSource = await readFile(documentFile, "utf8").catch(() => void 0);
|
|
1928
|
+
return [
|
|
1929
|
+
"export const routerMode = \"path\";",
|
|
1930
|
+
`export const documentNavigation = ${JSON.stringify(documentNavigationFor(documentSource, documentFile))};`,
|
|
1931
|
+
linkIntentEnabled(options) ? `export const startLinkIntentPreloading = (root, preload) => (${listenForLinkIntent.toString()})(root, ${JSON.stringify(LINK_ATTRIBUTE)}, preload, ${JSON.stringify(PREFETCH_ATTRIBUTE)});` : "export const startLinkIntentPreloading = () => () => {};"
|
|
1932
|
+
].join("\n");
|
|
1933
|
+
}
|
|
1934
|
+
function linkIntentEnabled(options) {
|
|
1935
|
+
return options.prefetch !== false;
|
|
1936
|
+
}
|
|
1474
1937
|
function entryPathSource(exportName, entry, entryId, root) {
|
|
1475
1938
|
const path = entry.fileName ? joinURL(entry.base, entry.fileName) : joinURL(entry.base, "@id", scopedVirtualEntryId(entryId, root));
|
|
1476
1939
|
return `export const ${exportName} = ${JSON.stringify(path)};`;
|
|
1477
1940
|
}
|
|
1478
|
-
function serverEntrySource(root) {
|
|
1941
|
+
function serverEntrySource(root, options) {
|
|
1479
1942
|
const query = rootScopeQuery(root);
|
|
1480
1943
|
return [
|
|
1481
1944
|
`import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';`,
|
|
1482
1945
|
`import { resumeEntryPath } from '${RESUME_ENTRY_PATH_ID}${query}';`,
|
|
1483
1946
|
`import { prerenderWakeEntryPath } from '${PRERENDER_WAKE_ENTRY_PATH_ID}${query}';`,
|
|
1484
1947
|
`import { navigationEntryPath } from '${NAVIGATION_ENTRY_PATH_ID}${query}';`,
|
|
1485
|
-
`import {
|
|
1948
|
+
`import { fragmentEntryPath } from '${FRAGMENT_ENTRY_PATH_ID}${query}';`,
|
|
1949
|
+
`import { documentStylesheets, routeModulePreloads, routeSsrModulePreloads, routeStylesheets, documentImportMap } from '${ROUTE_PRELOADS_ID}${query}';`,
|
|
1486
1950
|
`import { pageModuleLoaders, routeFileIds } from '${ROUTE_DISCOVERY_ID}${query}';`,
|
|
1951
|
+
`import { documentNavigation } from '${ROUTER_OPTIONS_ID}${query}';`,
|
|
1487
1952
|
`const documentModuleLoaders = import.meta.glob(['/document.tsrx']);`,
|
|
1488
1953
|
`const entry = createServerEntry({`,
|
|
1489
1954
|
` dev: import.meta.env.DEV,`,
|
|
1955
|
+
` base: import.meta.env.BASE_URL,`,
|
|
1490
1956
|
` resumeEntryPath,`,
|
|
1491
1957
|
` prerenderWakeEntryPath,`,
|
|
1492
1958
|
` navigationEntryPath,`,
|
|
1959
|
+
` fragmentEntryPath,`,
|
|
1960
|
+
` fragmentNavigationSource: ${JSON.stringify(minifiedFragmentNavigationSource())},`,
|
|
1961
|
+
...options.prefetch === false ? [" prefetch: false,"] : [],
|
|
1493
1962
|
` routeModulePreloads,`,
|
|
1494
1963
|
` routeSsrModulePreloads,`,
|
|
1495
1964
|
` routeStylesheets,`,
|
|
1965
|
+
` documentStylesheets,`,
|
|
1966
|
+
` importMap: documentImportMap,`,
|
|
1496
1967
|
` documentModuleLoader: documentModuleLoaders['/document.tsrx'],`,
|
|
1968
|
+
` documentNavigation,`,
|
|
1497
1969
|
` pageModuleLoaders,`,
|
|
1498
1970
|
` routeFileIds,`,
|
|
1499
1971
|
`});`,
|
|
@@ -1501,9 +1973,21 @@ function serverEntrySource(root) {
|
|
|
1501
1973
|
`export default entry;`
|
|
1502
1974
|
].join("\n");
|
|
1503
1975
|
}
|
|
1976
|
+
function minifiedFragmentNavigationSource() {
|
|
1977
|
+
const result = minifySync("markless-fragment-navigation.js", `export default (${startFragmentNavigation.toString()});`, {
|
|
1978
|
+
compress: true,
|
|
1979
|
+
mangle: true,
|
|
1980
|
+
module: true
|
|
1981
|
+
});
|
|
1982
|
+
const prefix = "export default";
|
|
1983
|
+
const code = result.code.trim();
|
|
1984
|
+
if (result.errors.length > 0 || !code.startsWith(prefix)) throw new Error(`MARKLESS_ROUTER_FRAGMENT_MINIFY_FAILED: ${result.errors.map((error) => error.message).join("\n")}`);
|
|
1985
|
+
return code.slice(14).replace(/;$/, "").trim();
|
|
1986
|
+
}
|
|
1504
1987
|
function rootScopeQuery(root, id = "") {
|
|
1988
|
+
const parsed = parsePath(id);
|
|
1505
1989
|
const query = stringifyQuery({
|
|
1506
|
-
...parseQuery(
|
|
1990
|
+
...parseQuery(parsed.search),
|
|
1507
1991
|
"markless-router-root": root || "."
|
|
1508
1992
|
});
|
|
1509
1993
|
return query ? `?${query}&lang.ts` : "";
|
|
@@ -1513,10 +1997,16 @@ function routePreloadsSource(state, client) {
|
|
|
1513
1997
|
navigation: state.routes.navigation,
|
|
1514
1998
|
ssr: state.routes.ssr
|
|
1515
1999
|
} : state.routes;
|
|
1516
|
-
const routeStylesheetExport = client ? [] : [
|
|
2000
|
+
const routeStylesheetExport = client ? [] : [
|
|
2001
|
+
`export const routeStylesheets = ${state.persisted ? "routePreloadData.styles ?? {}" : "undefined"};`,
|
|
2002
|
+
`const documentStylesheetsJson = globalThis.__marklessRouterDocumentStylesheetsJson ?? ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)};`,
|
|
2003
|
+
`export const documentStylesheets = documentStylesheetsJson === ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)} ? [] : JSON.parse(documentStylesheetsJson);`,
|
|
2004
|
+
`export const documentImportMap = ${JSON.stringify(state.persisted && state.importMap ? importMapScript(state.importMap) : "")};`
|
|
2005
|
+
];
|
|
1517
2006
|
return [
|
|
1518
2007
|
`const routePreloadsJson = globalThis.__marklessRouterRoutePreloadsJson ?? ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)};`,
|
|
1519
|
-
`
|
|
2008
|
+
`let routePreloadData = routePreloadsJson === ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)} ? ${JSON.stringify(routes)} : JSON.parse(routePreloadsJson);`,
|
|
2009
|
+
routePreloadDecoderSource(client && state.chunkSpecifiers),
|
|
1520
2010
|
`export const routeModulePreloads = routePreloadData.navigation ?? {};`,
|
|
1521
2011
|
`export const routeSsrModulePreloads = routePreloadData.ssr ?? {};`,
|
|
1522
2012
|
...routeStylesheetExport,
|
|
@@ -1540,21 +2030,51 @@ function routePreloadsSource(state, client) {
|
|
|
1540
2030
|
].join("\n");
|
|
1541
2031
|
}
|
|
1542
2032
|
function patchRoutePreloadsInBundle(bundle, routes) {
|
|
1543
|
-
const replacement = jsStringLiteralContent(JSON.stringify(
|
|
1544
|
-
navigation: routes.navigation,
|
|
1545
|
-
ssr: routes.ssr
|
|
1546
|
-
}));
|
|
2033
|
+
const replacement = jsStringLiteralContent(JSON.stringify(compactRoutePreloadData(routes)));
|
|
1547
2034
|
for (const chunk of outputChunks(bundle)) {
|
|
1548
2035
|
if (!chunk.code?.includes(ROUTE_PRELOADS_PLACEHOLDER)) continue;
|
|
1549
2036
|
chunk.code = chunk.code.replace(ROUTE_PRELOADS_PLACEHOLDER, replacement);
|
|
1550
2037
|
}
|
|
1551
2038
|
}
|
|
2039
|
+
function documentStylesheetsFromBundle(bundle, root, base) {
|
|
2040
|
+
const chunks = outputChunks(bundle);
|
|
2041
|
+
const chunksByFileName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
|
|
2042
|
+
const documentFile = join(root, "document.tsrx");
|
|
2043
|
+
const documentChunks = chunks.filter((chunk) => [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id !== null && id !== void 0 && decodePath(parseURL(id).pathname) === documentFile));
|
|
2044
|
+
if (documentChunks.length === 0) return [];
|
|
2045
|
+
return routeStylesheetsForChunks(documentChunks, new Set(documentChunks.map((chunk) => chunk.fileName)), chunksByFileName, base);
|
|
2046
|
+
}
|
|
2047
|
+
function patchDocumentStylesheetsInBundle(bundle, stylesheets) {
|
|
2048
|
+
const replacement = jsStringLiteralContent(JSON.stringify(stylesheets));
|
|
2049
|
+
for (const chunk of outputChunks(bundle)) {
|
|
2050
|
+
if (!chunk.code?.includes(DOCUMENT_STYLESHEETS_PLACEHOLDER)) continue;
|
|
2051
|
+
chunk.code = chunk.code.replace(DOCUMENT_STYLESHEETS_PLACEHOLDER, replacement);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
1552
2054
|
function jsStringLiteralContent(value) {
|
|
1553
2055
|
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("'", "\\'").replaceAll("`", "\\`").replaceAll("${", "\\${");
|
|
1554
2056
|
}
|
|
1555
2057
|
function routeModulePreloadsFromBundle(input) {
|
|
1556
|
-
const
|
|
1557
|
-
const
|
|
2058
|
+
const allChunks = outputChunks(input.bundle).filter((chunk) => !isExecutionLogChunk(chunk));
|
|
2059
|
+
const deferred = marklessDeferredChunkFileNames(allChunks);
|
|
2060
|
+
const chunks = allChunks.filter((chunk) => !deferred.has(chunk.fileName));
|
|
2061
|
+
const deferredChunks = allChunks.filter((chunk) => deferred.has(chunk.fileName));
|
|
2062
|
+
const everyChunk = new Map(allChunks.map((chunk) => [chunk.fileName, chunk]));
|
|
2063
|
+
const plannedLanding = plannedLandingFiles(input.bundle);
|
|
2064
|
+
const deferredFileNames = new Set(deferredChunks.map((chunk) => chunk.fileName));
|
|
2065
|
+
const resumeRuntimeFileNames = /* @__PURE__ */ new Set();
|
|
2066
|
+
for (const entry of [input.resumeChunk, input.prerenderWakeChunk]) if (entry) includeChunk(resumeRuntimeFileNames, {
|
|
2067
|
+
chunks: everyChunk,
|
|
2068
|
+
everyChunk
|
|
2069
|
+
}, entry.fileName);
|
|
2070
|
+
const lazyRuntimePacks = new Set([...resumeRuntimeFileNames].flatMap((fileName) => {
|
|
2071
|
+
const name = everyChunk.get(fileName)?.name;
|
|
2072
|
+
return name && lazyStartupPackName(name) !== name ? [lazyStartupPackName(name)] : [];
|
|
2073
|
+
}));
|
|
2074
|
+
for (const chunk of allChunks) if (chunk.name && lazyRuntimePacks.has(chunk.name)) includeChunk(resumeRuntimeFileNames, {
|
|
2075
|
+
chunks: everyChunk,
|
|
2076
|
+
everyChunk
|
|
2077
|
+
}, chunk.fileName);
|
|
1558
2078
|
const routeChunks = /* @__PURE__ */ new Map();
|
|
1559
2079
|
const routeChunkFileNames = /* @__PURE__ */ new Set();
|
|
1560
2080
|
for (const chunk of chunks) {
|
|
@@ -1566,40 +2086,79 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1566
2086
|
routeChunkFileNames.add(chunk.fileName);
|
|
1567
2087
|
}
|
|
1568
2088
|
const symbolChunks = symbolChunksBySourceFile(chunks);
|
|
2089
|
+
const polyfillFileNames = new Set(chunks.filter((chunk) => chunk.facadeModuleId && input.navigationPolyfillIds?.has(chunk.facadeModuleId)).map((chunk) => chunk.fileName));
|
|
1569
2090
|
const navigation = {};
|
|
1570
2091
|
const ssr = {};
|
|
1571
2092
|
const styles = {};
|
|
1572
2093
|
for (const [routeFile, routeFileChunks] of routeChunks) {
|
|
2094
|
+
const currentRouteFiles = new Set(routeFileChunks.map((chunk) => chunk.fileName));
|
|
2095
|
+
const chunksByFileName = new Map(chunks.filter((chunk) => !routeChunkFileNames.has(chunk.fileName) || currentRouteFiles.has(chunk.fileName)).map((chunk) => [chunk.fileName, chunk]));
|
|
2096
|
+
const scope = {
|
|
2097
|
+
chunks: chunksByFileName,
|
|
2098
|
+
everyChunk
|
|
2099
|
+
};
|
|
2100
|
+
const landingScope = {
|
|
2101
|
+
chunks: new Map([...chunksByFileName].filter(([, chunk]) => !isMarklessNavigationPack(chunk.name))),
|
|
2102
|
+
everyChunk
|
|
2103
|
+
};
|
|
1573
2104
|
const routeChunk = primaryRouteChunk(input.root, routeFile, routeFileChunks);
|
|
2105
|
+
const landingChunks = routeFileChunks.filter((chunk) => !isMarklessNavigationPack(chunk.name) && !isNavigationOnlyRouteChunk(input.root, routeFile, chunk));
|
|
2106
|
+
const landingChunk = landingChunks.length ? primaryRouteChunk(input.root, routeFile, landingChunks) : routeChunk;
|
|
1574
2107
|
const navigationFileNames = /* @__PURE__ */ new Set();
|
|
1575
2108
|
if (input.navigationChunk) {
|
|
1576
|
-
includeChunk(navigationFileNames,
|
|
1577
|
-
|
|
2109
|
+
includeChunk(navigationFileNames, scope, input.navigationChunk.fileName);
|
|
2110
|
+
const renderPathChunks = new Map(chunksByFileName);
|
|
2111
|
+
for (const chunk of deferredChunks) renderPathChunks.set(chunk.fileName, chunk);
|
|
2112
|
+
const renderPath = /* @__PURE__ */ new Set();
|
|
2113
|
+
for (const fileName of input.navigationChunk.dynamicImports) if (!routeChunkFileNames.has(fileName) && !polyfillFileNames.has(fileName)) includeChunk(renderPath, scope, fileName, true);
|
|
2114
|
+
for (const fileName of renderPath) {
|
|
2115
|
+
navigationFileNames.add(fileName);
|
|
2116
|
+
if (resumeRuntimeFileNames.has(fileName)) continue;
|
|
2117
|
+
const chunk = everyChunk.get(fileName);
|
|
2118
|
+
for (const target of [...chunk.dynamicImports, ...codeDynamicImports(chunk)]) if (deferredFileNames.has(target)) includeChunk(navigationFileNames, {
|
|
2119
|
+
chunks: renderPathChunks,
|
|
2120
|
+
everyChunk
|
|
2121
|
+
}, target, true);
|
|
2122
|
+
}
|
|
1578
2123
|
}
|
|
1579
|
-
includeChunk(navigationFileNames,
|
|
1580
|
-
includeChunk(navigationFileNames,
|
|
1581
|
-
if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames,
|
|
2124
|
+
includeChunk(navigationFileNames, scope, routeChunk.fileName);
|
|
2125
|
+
includeChunk(navigationFileNames, scope, routeChunk.fileName, true);
|
|
2126
|
+
if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames, scope, fileName, true);
|
|
1582
2127
|
const ssrFileNames = /* @__PURE__ */ new Set();
|
|
1583
2128
|
if (input.resumeChunk) {
|
|
1584
|
-
includeChunk(ssrFileNames,
|
|
1585
|
-
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames,
|
|
2129
|
+
includeChunk(ssrFileNames, landingScope, input.resumeChunk.fileName);
|
|
2130
|
+
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1586
2131
|
}
|
|
1587
2132
|
if (input.prerenderWakeChunk) {
|
|
1588
|
-
includeChunk(ssrFileNames,
|
|
1589
|
-
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames,
|
|
2133
|
+
includeChunk(ssrFileNames, landingScope, input.prerenderWakeChunk.fileName);
|
|
2134
|
+
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1590
2135
|
}
|
|
1591
|
-
includeChunk(ssrFileNames,
|
|
1592
|
-
const routeSourceFiles = sourceFilesForChunks(new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
2136
|
+
includeChunk(ssrFileNames, landingScope, landingChunk.fileName, true);
|
|
2137
|
+
const routeSourceFiles = sourceFilesForChunks(/* @__PURE__ */ new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
1593
2138
|
for (const [sourceFile, symbolFileNames] of symbolChunks) {
|
|
1594
2139
|
if (!routeSourceFiles.has(sourceFile)) continue;
|
|
1595
2140
|
for (const fileName of symbolFileNames) {
|
|
1596
|
-
includeChunk(navigationFileNames,
|
|
1597
|
-
includeChunk(ssrFileNames,
|
|
2141
|
+
includeChunk(navigationFileNames, scope, fileName, true);
|
|
2142
|
+
includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1598
2143
|
}
|
|
1599
2144
|
}
|
|
2145
|
+
const styleFileNames = /* @__PURE__ */ new Set([...navigationFileNames, ...ssrFileNames]);
|
|
2146
|
+
const planned = plannedLanding?.get(routeFile);
|
|
2147
|
+
if (planned) narrowToPlanned(ssrFileNames, planned, landingScope, [input.resumeChunk, input.prerenderWakeChunk]);
|
|
1600
2148
|
navigation[routeFile] = [...navigationFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1601
|
-
|
|
1602
|
-
|
|
2149
|
+
const firstEventPath = /* @__PURE__ */ new Set();
|
|
2150
|
+
const lazySiblings = [...ssrFileNames].filter((fileName) => {
|
|
2151
|
+
const name = everyChunk.get(fileName)?.name;
|
|
2152
|
+
return !!name && startupPackOfLazy(name) !== name;
|
|
2153
|
+
});
|
|
2154
|
+
for (const entry of lazySiblings.length ? [input.resumeChunk, input.prerenderWakeChunk] : []) {
|
|
2155
|
+
if (!entry) continue;
|
|
2156
|
+
includeChunk(firstEventPath, landingScope, entry.fileName);
|
|
2157
|
+
for (const fileName of routeScopedDynamicImports(entry, routeFile)) includeChunk(firstEventPath, landingScope, fileName);
|
|
2158
|
+
}
|
|
2159
|
+
for (const fileName of lazySiblings) includeChunk(firstEventPath, landingScope, fileName);
|
|
2160
|
+
ssr[routeFile] = [...[...firstEventPath].filter((fileName) => ssrFileNames.has(fileName)), ...[...ssrFileNames].filter((fileName) => !firstEventPath.has(fileName))].map((fileName) => joinURL(input.base, fileName));
|
|
2161
|
+
styles[routeFile] = routeStylesheetsForChunks(routeFileChunks, styleFileNames, chunksByFileName, input.base);
|
|
1603
2162
|
}
|
|
1604
2163
|
return {
|
|
1605
2164
|
navigation,
|
|
@@ -1607,7 +2166,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1607
2166
|
styles
|
|
1608
2167
|
};
|
|
1609
2168
|
}
|
|
1610
|
-
function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
2169
|
+
function routeStylesheetsForChunks(routeFileChunks, closureFileNames, chunksByFileName, base) {
|
|
1611
2170
|
const styles = /* @__PURE__ */ new Set();
|
|
1612
2171
|
const visited = /* @__PURE__ */ new Set();
|
|
1613
2172
|
const visit = (chunk) => {
|
|
@@ -1619,18 +2178,25 @@ function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
|
1619
2178
|
}
|
|
1620
2179
|
for (const stylesheet of chunk.viteMetadata?.importedCss ?? []) styles.add(joinURL(base, stylesheet));
|
|
1621
2180
|
};
|
|
2181
|
+
for (const fileName of closureFileNames) {
|
|
2182
|
+
const chunk = chunksByFileName.get(fileName);
|
|
2183
|
+
if (chunk) visit(chunk);
|
|
2184
|
+
}
|
|
1622
2185
|
for (const chunk of routeFileChunks) visit(chunk);
|
|
1623
2186
|
return [...styles];
|
|
1624
2187
|
}
|
|
1625
2188
|
function symbolChunksBySourceFile(chunks) {
|
|
1626
2189
|
const bySourceFile = /* @__PURE__ */ new Map();
|
|
1627
|
-
for (const chunk of chunks)
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
2190
|
+
for (const chunk of chunks) {
|
|
2191
|
+
const moduleIds = chunk.facadeModuleId ? [...chunk.moduleIds ?? [], chunk.facadeModuleId] : chunk.moduleIds ?? [];
|
|
2192
|
+
for (const moduleId of moduleIds) {
|
|
2193
|
+
const sourceFile = symbolVirtualModuleSourceFile(moduleId);
|
|
2194
|
+
if (!sourceFile) continue;
|
|
2195
|
+
const key = sourceModulePathname(sourceFile);
|
|
2196
|
+
const fileNames = bySourceFile.get(key) ?? [];
|
|
2197
|
+
if (!fileNames.includes(chunk.fileName)) fileNames.push(chunk.fileName);
|
|
2198
|
+
bySourceFile.set(key, fileNames);
|
|
2199
|
+
}
|
|
1634
2200
|
}
|
|
1635
2201
|
return bySourceFile;
|
|
1636
2202
|
}
|
|
@@ -1646,22 +2212,23 @@ function sourceFilesForChunks(fileNames, chunksByFileName) {
|
|
|
1646
2212
|
function sourceModulePathname(moduleId) {
|
|
1647
2213
|
return decodePath(parseURL(moduleId).pathname);
|
|
1648
2214
|
}
|
|
1649
|
-
function includeChunk(fileNames,
|
|
1650
|
-
if (
|
|
1651
|
-
const chunk =
|
|
2215
|
+
function includeChunk(fileNames, scope, fileName, includeDynamic = false, visited = /* @__PURE__ */ new Set(), requiredByStaticImport = false) {
|
|
2216
|
+
if (visited.has(fileName)) return;
|
|
2217
|
+
const chunk = (requiredByStaticImport ? scope.everyChunk : scope.chunks).get(fileName);
|
|
1652
2218
|
if (!chunk) return;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
for (const
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2219
|
+
visited.add(fileName);
|
|
2220
|
+
fileNames.add(fileName);
|
|
2221
|
+
for (const imported of /* @__PURE__ */ new Set([...chunk.imports, ...codeStaticImports(chunk)])) includeChunk(fileNames, scope, imported, includeDynamic, visited, true);
|
|
2222
|
+
if (!includeDynamic) return;
|
|
2223
|
+
for (const imported of /* @__PURE__ */ new Set([...chunk.dynamicImports, ...codeDynamicImports(chunk)])) includeChunk(fileNames, scope, imported, true, visited);
|
|
2224
|
+
}
|
|
2225
|
+
function narrowToPlanned(fileNames, planned, scope, entries) {
|
|
2226
|
+
const kept = /* @__PURE__ */ new Set();
|
|
2227
|
+
for (const entry of entries) if (entry) includeChunk(kept, scope, entry.fileName);
|
|
2228
|
+
for (const fileName of fileNames) if (planned.has(fileName)) includeChunk(kept, scope, fileName, false, /* @__PURE__ */ new Set(), true);
|
|
2229
|
+
const ordered = [...fileNames].filter((fileName) => kept.has(fileName));
|
|
2230
|
+
fileNames.clear();
|
|
2231
|
+
for (const fileName of [...ordered, ...kept]) fileNames.add(fileName);
|
|
1665
2232
|
}
|
|
1666
2233
|
function routeScopedDynamicImports(chunk, routeFile) {
|
|
1667
2234
|
if (!chunk.code) return [];
|
|
@@ -1675,10 +2242,23 @@ function routeScopedDynamicImports(chunk, routeFile) {
|
|
|
1675
2242
|
return [...imports];
|
|
1676
2243
|
}
|
|
1677
2244
|
function codeStaticImports(chunk) {
|
|
1678
|
-
return
|
|
2245
|
+
return cachedCodeImports(chunk).static;
|
|
1679
2246
|
}
|
|
1680
2247
|
function codeDynamicImports(chunk) {
|
|
1681
|
-
return
|
|
2248
|
+
return cachedCodeImports(chunk).dynamic;
|
|
2249
|
+
}
|
|
2250
|
+
const chunkCodeImports = /* @__PURE__ */ new WeakMap();
|
|
2251
|
+
function cachedCodeImports(chunk) {
|
|
2252
|
+
const cached = chunkCodeImports.get(chunk);
|
|
2253
|
+
if (cached?.code === chunk.code && cached?.fileName === chunk.fileName) return cached;
|
|
2254
|
+
const imports = {
|
|
2255
|
+
code: chunk.code,
|
|
2256
|
+
fileName: chunk.fileName,
|
|
2257
|
+
static: codeImportSpecifiers(chunk, /(?:import\s*(?:[^('"`]*?\bfrom\s*)?|export\s*[^('"`]*?\bfrom\s*)["'](\.\/[^"']+\.js)["']/g),
|
|
2258
|
+
dynamic: codeDynamicImportSpecifiers(chunk.code ?? "").map((specifier) => normalize(join(dirname(chunk.fileName), specifier)))
|
|
2259
|
+
};
|
|
2260
|
+
chunkCodeImports.set(chunk, imports);
|
|
2261
|
+
return imports;
|
|
1682
2262
|
}
|
|
1683
2263
|
function codeDynamicImportSpecifiers(code) {
|
|
1684
2264
|
const imports = /* @__PURE__ */ new Set();
|
|
@@ -1697,6 +2277,15 @@ function codeImportSpecifiers(chunk, pattern) {
|
|
|
1697
2277
|
}
|
|
1698
2278
|
return [...imports];
|
|
1699
2279
|
}
|
|
2280
|
+
function chunkRenamesSince(captured, bundle) {
|
|
2281
|
+
const finalNames = new Map(outputChunks(bundle).map((chunk) => [chunk.preliminaryFileName, chunk.fileName]));
|
|
2282
|
+
const renames = /* @__PURE__ */ new Map();
|
|
2283
|
+
for (const [fileName, preliminary] of captured) {
|
|
2284
|
+
const next = preliminary === void 0 ? void 0 : finalNames.get(preliminary);
|
|
2285
|
+
if (next !== void 0 && next !== fileName) renames.set(fileName, next);
|
|
2286
|
+
}
|
|
2287
|
+
return renames;
|
|
2288
|
+
}
|
|
1700
2289
|
function outputChunks(bundle) {
|
|
1701
2290
|
return Object.values(bundle).filter((item) => {
|
|
1702
2291
|
return isRecord(item) && item.type === "chunk" && typeof item.fileName === "string" && Array.isArray(item.imports) && Array.isArray(item.dynamicImports);
|
|
@@ -1709,24 +2298,26 @@ function routeFileForChunk(root, chunk) {
|
|
|
1709
2298
|
}
|
|
1710
2299
|
}
|
|
1711
2300
|
function primaryRouteChunk(root, routeFile, chunks) {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
2301
|
+
const ranked = chunks.map((chunk) => ({
|
|
2302
|
+
chunk,
|
|
2303
|
+
rank: routeChunkRank(root, routeFile, chunk)
|
|
2304
|
+
}));
|
|
2305
|
+
const highestRank = Math.max(...ranked.map(({ rank }) => rank));
|
|
2306
|
+
const candidates = ranked.filter(({ rank }) => rank === highestRank);
|
|
2307
|
+
if (candidates.length !== 1) throw new Error(`Markless Router found ambiguous primary chunks for ${routeFile}: ${candidates.map(({ chunk }) => chunk.fileName).sort().join(", ")}`);
|
|
2308
|
+
return candidates[0].chunk;
|
|
2309
|
+
}
|
|
2310
|
+
function isNavigationOnlyRouteChunk(root, routeFile, chunk) {
|
|
2311
|
+
const owned = [chunk.facadeModuleId, ...chunk.moduleIds ?? []].filter((moduleId) => !!moduleId && routeFileForModuleId(root, moduleId) === routeFile);
|
|
2312
|
+
return owned.length > 0 && owned.every(isRouteNavigationSourceRequest);
|
|
1721
2313
|
}
|
|
1722
2314
|
function routeChunkRank(root, routeFile, chunk) {
|
|
1723
2315
|
let rank = 0;
|
|
1724
2316
|
for (const moduleId of [chunk.facadeModuleId, ...chunk.moduleIds ?? []]) {
|
|
1725
|
-
if (routeFileForModuleId(root, moduleId) !== routeFile) continue;
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
else return 3;
|
|
2317
|
+
if (!moduleId || routeFileForModuleId(root, moduleId) !== routeFile) continue;
|
|
2318
|
+
const role = isClientPrimarySourceRequest(moduleId) ? 5 : isRenderDataSourceRequest(moduleId) ? 4 : isSymbolOnlySourceRequest(moduleId) ? 1 : isResumeSourceRequest(moduleId) ? 2 : 3;
|
|
2319
|
+
const ownsModule = chunk.moduleIds?.includes(moduleId) ? 1 : 0;
|
|
2320
|
+
rank = Math.max(rank, role * 2 + ownsModule);
|
|
1730
2321
|
}
|
|
1731
2322
|
return rank;
|
|
1732
2323
|
}
|
|
@@ -1747,5 +2338,8 @@ function configRoot(config) {
|
|
|
1747
2338
|
const root = config.root;
|
|
1748
2339
|
return typeof root === "string" ? root : ".";
|
|
1749
2340
|
}
|
|
2341
|
+
function escapeRegExp(value) {
|
|
2342
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2343
|
+
}
|
|
1750
2344
|
//#endregion
|
|
1751
2345
|
export { router };
|