@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/vite.js CHANGED
@@ -1,16 +1,21 @@
1
- import { t as buildRouteManifestFromFileIds } from "./route-manifest-DPgGIyrS.js";
2
- import { r as transformRequestFileSource } from "./request-files-C-0LLSsx.js";
3
- import { __marklessRouteHref } from "./vite/route-href.js";
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 | \"intent\" | \"viewport\";",
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 manifest = buildRouteManifestFromFileIds(await discoverPageFiles(fs, root));
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 ROUTER_IMPORT_SOURCES = new Set(["@markless/router", "@markless/core/router"]);
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 transformed = transformAnchorSource(code, parseTransformAst(code, id, (source, options) => this.parse(source, options)), routePatterns);
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$1(astInput);
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$1(current.name));
322
+ const name = jsxName$1(node$2(current.name));
238
323
  if (name !== "a" && (!name || !linkNames.has(name))) return;
239
- const attributes = nodes$1(current.attributes);
324
+ const attributes = nodes$2(current.attributes);
240
325
  const href = findJsxAttribute(attributes, "href");
241
- const hrefValue = href ? stringLiteralValue(node$1(href.value)) : void 0;
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$1(params.value));
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$1(expression.properties)) {
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$1(property.key);
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$1(property.value));
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$1(expression.elements)) {
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$1(current.source)) ?? "")) return;
307
- for (const specifier of nodes$1(current.specifiers)) if (specifier.type === "ImportSpecifier" && identifierName(node$1(specifier.imported)) === "Link") {
308
- const localName = identifierName(node$1(specifier.local));
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$1(expression.properties).flatMap((property) => {
412
+ return nodes$2(expression.properties).flatMap((property) => {
328
413
  if (property.type !== "Property") return [];
329
- const key = node$1(property.key);
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$1(attr.name)) === name);
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$1(value.expression);
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$1(stack.pop());
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$1(value) {
406
- return Array.isArray(value) ? value.filter((item) => !!node$1(item)) : [];
490
+ function nodes$2(value) {
491
+ return Array.isArray(value) ? value.filter((item) => !!node$2(item)) : [];
407
492
  }
408
- function node$1(value) {
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 = ".vite/markless-router-client-manifest.json";
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 hrefs = new Set([
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 assetPath = join(clientOutDir, clientAssetFileName(href, base));
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 helper = `export function ${HELPER}(${propsSource}) {\n return ${attrs.length ? `{\n${attrs.map(([name, value]) => ` ${JSON.stringify(name)}: ${value}`).join(",\n")}\n }` : "{}"};\n}\n`;
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 '@markless/router/vite/runtime/mdx-route';`,
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 { resumeFromPayloadDocument } from '@markless/core/web/resume';`,
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
- "export async function resumeContainerEvent(input) {",
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") || !defaultImport || statement.specifiers.length !== 1) throw new Error(`Markless Router MDX currently supports default imports from .tsrx files only: ${id}`);
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.id);
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.id);
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 children = renderStaticHtml(node.children, context.id);
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: `m${componentIndex}:`,
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, id) {
1109
+ function appendHtmlPart(parts, nodes, context) {
877
1110
  if (nodes.length === 0) return;
878
- const staticNodes = normalizeStaticHastNodes(nodes, id);
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 renderStaticHtml(nodes, id) {
894
- if (nodes.length === 0) return "";
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 normalizeStaticHastNodes(nodes, id) {
901
- return nodes.flatMap((node) => normalizeStaticHastNode(node, id));
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)) throw new Error(`Markless Router MDX only supports imported TSRX components as route-level MDX elements or static markdown children: ${id}`);
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, id);
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, id)
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 import(${JSON.stringify(`${component.specifier}?markless-symbols`)}).then((mod) => mod.loadSymbol(symbolId.slice(${component.prefix.length}))); } }`).join(", ")}]`;
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
- return `() => Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
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
- config(config) {
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") return;
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: [...new Set([".", ...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 === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === PRERENDER_WAKE_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID || baseId === ROUTER_OPTIONS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
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}`)) return "export const routerMode = \"path\"; // retained for compat; hash paths are always first-class";
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 { routeModulePreloads, routeSsrModulePreloads, routeStylesheets } from '${ROUTE_PRELOADS_ID}${query}';`,
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(parsePath(id).search),
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 ? [] : [`export const routeStylesheets = ${state.persisted ? "routePreloadData.styles ?? {}" : "undefined"};`];
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
- `const routePreloadData = routePreloadsJson === ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)} ? ${JSON.stringify(routes)} : JSON.parse(routePreloadsJson);`,
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 chunks = outputChunks(input.bundle);
1557
- const chunksByFileName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
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, chunksByFileName, input.navigationChunk.fileName);
1577
- for (const fileName of input.navigationChunk.dynamicImports) if (!routeChunkFileNames.has(fileName)) includeChunk(navigationFileNames, chunksByFileName, fileName);
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, chunksByFileName, routeChunk.fileName);
1580
- includeChunk(navigationFileNames, chunksByFileName, routeChunk.fileName, true);
1581
- if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames, chunksByFileName, fileName, true);
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, chunksByFileName, input.resumeChunk.fileName);
1585
- for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
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, chunksByFileName, input.prerenderWakeChunk.fileName);
1589
- for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
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, chunksByFileName, routeChunk.fileName, true);
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, chunksByFileName, fileName, true);
1597
- includeChunk(ssrFileNames, chunksByFileName, fileName, true);
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
- ssr[routeFile] = [...ssrFileNames].map((fileName) => joinURL(input.base, fileName));
1602
- styles[routeFile] = routeStylesheetsForChunks(routeFileChunks, chunksByFileName, input.base);
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) for (const moduleId of chunk.moduleIds ?? []) {
1628
- const sourceFile = symbolVirtualModuleSourceFile(moduleId);
1629
- if (!sourceFile) continue;
1630
- const key = sourceModulePathname(sourceFile);
1631
- const fileNames = bySourceFile.get(key) ?? [];
1632
- if (!fileNames.includes(chunk.fileName)) fileNames.push(chunk.fileName);
1633
- bySourceFile.set(key, fileNames);
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, chunksByFileName, fileName, includeDynamic = false, visitedDynamic = /* @__PURE__ */ new Set()) {
1650
- if (!fileNames.has(fileName)) fileNames.add(fileName);
1651
- const chunk = chunksByFileName.get(fileName);
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
- for (const imported of chunk.imports) includeChunk(fileNames, chunksByFileName, imported, includeDynamic, visitedDynamic);
1654
- for (const imported of codeStaticImports(chunk)) {
1655
- if (!chunksByFileName.has(imported)) continue;
1656
- includeChunk(fileNames, chunksByFileName, imported, includeDynamic, visitedDynamic);
1657
- }
1658
- if (!includeDynamic || visitedDynamic.has(fileName)) return;
1659
- visitedDynamic.add(fileName);
1660
- for (const imported of chunk.dynamicImports) includeChunk(fileNames, chunksByFileName, imported, true, visitedDynamic);
1661
- for (const imported of codeDynamicImports(chunk)) {
1662
- if (!chunksByFileName.has(imported)) continue;
1663
- includeChunk(fileNames, chunksByFileName, imported, true, visitedDynamic);
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 codeImportSpecifiers(chunk, /(?:import\s*(?:[^('"`]*?\bfrom\s*)?|export\s*[^('"`]*?\bfrom\s*)["'](\.\/[^"']+\.js)["']/g);
2245
+ return cachedCodeImports(chunk).static;
1679
2246
  }
1680
2247
  function codeDynamicImports(chunk) {
1681
- return codeDynamicImportSpecifiers(chunk.code ?? "").map((specifier) => normalize(join(dirname(chunk.fileName), specifier)));
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
- let selected = chunks[0];
1713
- let selectedRank = routeChunkRank(root, routeFile, selected);
1714
- for (const chunk of chunks.slice(1)) {
1715
- const rank = routeChunkRank(root, routeFile, chunk);
1716
- if (rank <= selectedRank) continue;
1717
- selected = chunk;
1718
- selectedRank = rank;
1719
- }
1720
- return selected;
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 search = parsePath(moduleId).search;
1727
- if (/(?:^|[?&])markless-symbols(?:[=&]|$)/.test(search)) rank = Math.max(rank, 1);
1728
- else if (/(?:^|[?&])markless-resume(?:[=&]|$)/.test(search)) rank = Math.max(rank, 2);
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 };