@marko/run 0.10.0 → 0.11.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,9 @@ import { resolveToEsbuildTarget } from "esbuild-plugin-browserslist";
17
17
  import fs3 from "fs";
18
18
  import { glob } from "glob";
19
19
  import path6 from "path";
20
+ import {
21
+ RolldownMagicString
22
+ } from "rolldown";
20
23
  import { fileURLToPath } from "url";
21
24
  import {
22
25
  buildErrorMessage,
@@ -65,10 +68,10 @@ var httpVerbs = [
65
68
  "options"
66
69
  ];
67
70
  var RoutableFileTypes = {
68
- Page: "page",
69
- Layout: "layout",
70
- Handler: "handler",
71
71
  Middleware: "middleware",
72
+ Handler: "handler",
73
+ Layout: "layout",
74
+ Page: "page",
72
75
  Meta: "meta",
73
76
  NotFound: "404",
74
77
  Error: "500"
@@ -257,19 +260,21 @@ function normalizedRelativePath(from, to) {
257
260
  const relativePath = normalizePath(path2.relative(from, to));
258
261
  return relativePath.startsWith(".") ? relativePath : "./" + relativePath;
259
262
  }
260
- function renderRouteTemplate(route, markoApi) {
263
+ function renderRouteTemplate(route, markoApi, dev = false) {
261
264
  if (!route.page) {
262
265
  throw new Error(`Route ${route.key} has no page to render`);
263
266
  }
264
- if (!route.templateFilePath) {
265
- throw new Error(`Route ${route.key} has no template file path`);
266
- }
267
267
  const writer = createStringWriter();
268
268
  if (markoApi) {
269
269
  writer.writeLines(`<!-- use ${markoApi} -->
270
270
  `);
271
271
  }
272
- writer.branch("imports");
272
+ const importWriter = writer.branch("imports");
273
+ if (dev) {
274
+ importWriter.writeLines(
275
+ `client import "virtual:marko-run/runtime/client";`
276
+ );
277
+ }
273
278
  writer.writeLines("");
274
279
  writeEntryTemplateTag(
275
280
  writer,
@@ -317,7 +322,10 @@ function renderRouteEntry(route, rootDir) {
317
322
  runtimeImports.push("normalizeMeta");
318
323
  }
319
324
  if (handler || middleware.length) {
320
- runtimeImports.push("call");
325
+ runtimeImports.push("call", "mergeOptions");
326
+ }
327
+ if (page) {
328
+ runtimeImports.push("render");
321
329
  }
322
330
  if (!page || verbs.some((verb) => verb !== "get" && verb !== "head")) {
323
331
  runtimeImports.push("noContent");
@@ -356,7 +364,7 @@ function renderRouteEntry(route, rootDir) {
356
364
  }
357
365
  if (page) {
358
366
  imports.writeLines(
359
- `import page from "${normalizedRelativePath(rootDir, route.templateFilePath || page.filePath)}";`
367
+ `import page from "${normalizedRelativePath(rootDir, route.templateFilePath)}";`
360
368
  );
361
369
  }
362
370
  if (meta) {
@@ -374,11 +382,34 @@ function renderRouteEntry(route, rootDir) {
374
382
  `export const { ${metaVerbsExports} } = normalizeMeta(${metaName});`
375
383
  );
376
384
  }
385
+ const optionsWriter = writer.branch("options").writeLines("");
377
386
  for (const verb of verbs) {
387
+ writeRouteOptions(optionsWriter, route, verb);
378
388
  writeRouteEntryHandler(writer, route, verb);
379
389
  }
390
+ optionsWriter.join();
380
391
  return writer.end();
381
392
  }
393
+ function writeRouteOptions(writer, route, verb) {
394
+ var _a, _b;
395
+ const hasHandler = (_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes(verb);
396
+ writer.write(`export const ${verb}${route.index}_options = `);
397
+ if (route.middleware.length || hasHandler) {
398
+ writer.write(`mergeOptions(`);
399
+ let sep = "";
400
+ for (const { id } of route.middleware) {
401
+ writer.write(`${sep}mware${id}`);
402
+ sep = ", ";
403
+ }
404
+ if (hasHandler) {
405
+ writer.write(`${sep}${verb}Handler`);
406
+ }
407
+ writer.write(");");
408
+ } else {
409
+ writer.write("{};");
410
+ }
411
+ writer.write("\n");
412
+ }
382
413
  function writeRouteEntryHandler(writer, route, verb) {
383
414
  var _a, _b, _c, _d;
384
415
  const { key, index, page, handler, middleware } = route;
@@ -398,13 +429,13 @@ function writeRouteEntryHandler(writer, route, verb) {
398
429
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
399
430
  const name = `${verb}Handler`;
400
431
  continuations.writeLines(
401
- `const ${currentName} = () => context.render(page, {});`
432
+ `const ${currentName} = (data) => render(context, page, {}, data);`
402
433
  );
403
434
  if (len) {
404
435
  nextName = currentName;
405
436
  currentName = `__${name}`;
406
437
  continuations.writeLines(
407
- `const ${currentName} = () => call(${name}, ${nextName}, context);`
438
+ `const ${currentName} = (data) => call(${name}, ${nextName}, context, data);`
408
439
  );
409
440
  } else {
410
441
  if (verb === "head") {
@@ -421,10 +452,10 @@ function writeRouteEntryHandler(writer, route, verb) {
421
452
  hasBody = true;
422
453
  } else if (len) {
423
454
  continuations.writeLines(
424
- `const ${currentName} = () => context.render(page, {});`
455
+ `const ${currentName} = (data) => render(context, page, {}, data);`
425
456
  );
426
457
  } else {
427
- writer.writeLines(`return context.render(page, {});`);
458
+ writer.writeLines(`return render(context, page, {});`);
428
459
  hasBody = true;
429
460
  }
430
461
  } else if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes(verb)) {
@@ -433,7 +464,7 @@ function writeRouteEntryHandler(writer, route, verb) {
433
464
  nextName = "noContent";
434
465
  if (len) {
435
466
  continuations.writeLines(
436
- `const ${currentName} = () => call(${name}, ${nextName}, context);`
467
+ `const ${currentName} = (data) => call(${name}, ${nextName}, context, data);`
437
468
  );
438
469
  } else {
439
470
  if (verb === "head") {
@@ -460,7 +491,7 @@ function writeRouteEntryHandler(writer, route, verb) {
460
491
  currentName = i ? `__${name}` : "";
461
492
  if (currentName) {
462
493
  continuations.writeLines(
463
- `const ${currentName} = () => call(${name}, ${nextName}, context);`
494
+ `const ${currentName} = (data) => call(${name}, ${nextName}, context, data);`
464
495
  );
465
496
  } else if (verb === "head") {
466
497
  continuations.writeLines(
@@ -493,6 +524,7 @@ function renderRouter(routes, rootDir, runtimeInclude, options = {
493
524
  for (const verb of verbs) {
494
525
  const verbName = `${verb}${route.index}`;
495
526
  routeImports.push(verbName);
527
+ routeImports.push(`${verbName}_options`);
496
528
  if (route.meta) {
497
529
  routeImports.push(`${verbName}_meta`);
498
530
  }
@@ -503,7 +535,7 @@ function renderRouter(routes, rootDir, runtimeInclude, options = {
503
535
  }
504
536
  for (const route of Object.values(routes.special)) {
505
537
  imports.writeLines(
506
- `import page${route.key} from "${normalizedRelativePath(rootDir, route.templateFilePath || route.page.filePath)}";`
538
+ `import page${route.key} from "${normalizedRelativePath(rootDir, route.templateFilePath)}";`
507
539
  );
508
540
  }
509
541
  writer.writeLines(
@@ -788,10 +820,10 @@ function renderParams(params, pathIndex) {
788
820
  return result ? result + " }" : "{}";
789
821
  }
790
822
  function renderMatch(verb, route, path7, pathIndex) {
791
- const handler = `${verb}${route.index}`;
823
+ const name = `${verb}${route.index}`;
792
824
  const params = path7.params ? renderParams(path7.params, pathIndex) : "{}";
793
- const meta = route.meta ? `${verb}${route.index}_meta` : "{}";
794
- return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path7.path}' }`;
825
+ const meta = route.meta ? `${name}_meta` : "{}";
826
+ return `{ handler: ${name}, path: '${path7.path}', params: ${params}, options: ${name}_options, meta: ${meta} }`;
795
827
  }
796
828
  function renderMiddleware(middleware, rootDir) {
797
829
  const writer = createStringWriter();
@@ -822,8 +854,14 @@ function stripTsExtension(path7) {
822
854
  }
823
855
  return path7;
824
856
  }
857
+ function* routeFileIter(route) {
858
+ yield* route.middleware;
859
+ if (route.handler) yield route.handler;
860
+ yield* route.layouts;
861
+ if (route.page) yield route.page;
862
+ if (route.meta) yield route.meta;
863
+ }
825
864
  async function renderRouteTypeInfo(routes, outDir, adapter) {
826
- var _a, _b, _c, _d;
827
865
  const writer = createStringWriter();
828
866
  writer.writeLines(
829
867
  `/*
@@ -832,10 +870,11 @@ async function renderRouteTypeInfo(routes, outDir, adapter) {
832
870
  */
833
871
  `,
834
872
  `import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";`,
835
- `import type * as Run from "@marko/run";`
873
+ `import type * as $ from "@marko/run";`,
874
+ ""
836
875
  );
837
876
  const headWriter = writer.branch("head");
838
- writer.writeLines("\n").writeBlockStart(`declare module "@marko/run" {`);
877
+ writer.writeLines("").writeBlockStart(`declare module "@marko/run" {`);
839
878
  if (adapter && adapter.typeInfo) {
840
879
  const platformType = await adapter.typeInfo(
841
880
  (data) => headWriter.write(data)
@@ -845,139 +884,120 @@ async function renderRouteTypeInfo(routes, outDir, adapter) {
845
884
  `);
846
885
  }
847
886
  }
848
- headWriter.join();
849
- writer.writeBlockStart(`interface AppData extends Run.DefineApp<{`).writeBlockStart("routes: {");
850
- const routesWriter = writer.branch("routes");
851
- writer.writeBlockEnd("}").writeBlockEnd(`}> {}`).writeBlockEnd(`}`);
852
- const routeTypes = /* @__PURE__ */ new Map();
853
- for (const route of routes.list) {
854
- let routeType = "";
855
- let routeDefinition = "";
856
- if (route.page || route.handler) {
857
- const verbs = [];
858
- if (route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes("get"))) {
859
- verbs.push(`"get"`);
860
- }
861
- if ((_d = (_c = route.handler) == null ? void 0 : _c.verbs) == null ? void 0 : _d.includes("post")) {
862
- verbs.push(`"post"`);
863
- }
864
- routeDefinition = `{ verb: ${verbs.join(" | ")};`;
865
- if (route.meta) {
866
- const metaPath = stripTsExtension(
867
- normalizedRelativePath(outDir, route.meta.filePath)
868
- );
869
- let metaType = `typeof import("${metaPath}")`;
870
- if (/\.(ts|js|mjs)$/.test(route.meta.name)) {
871
- metaType += `["default"]`;
872
- }
873
- routeDefinition += ` meta: ${metaType};`;
874
- }
875
- routeDefinition += " }";
876
- }
877
- const pathType = `"${route.path.path}"`;
878
- routeType += routeType ? " | " + pathType : pathType;
879
- routesWriter.writeLines(`${pathType}: ${routeDefinition};`);
880
- for (const file of [route.handler, route.page]) {
881
- if (file) {
882
- const existing = routeTypes.get(file);
883
- if (!existing) {
884
- routeTypes.set(file, [routeType]);
885
- } else {
886
- existing.push(routeType);
887
- }
887
+ const fileInfoByType = /* @__PURE__ */ new Map();
888
+ let fileIndex = 1;
889
+ function addFile(file) {
890
+ let group = fileInfoByType.get(file.type);
891
+ if (!group) {
892
+ fileInfoByType.set(file.type, group = /* @__PURE__ */ new Map());
893
+ }
894
+ let info = group.get(file);
895
+ if (!info) {
896
+ info = {
897
+ id: "",
898
+ typeName: null,
899
+ modulePath: stripTsExtension(
900
+ normalizedRelativePath(outDir, file.filePath)
901
+ ),
902
+ routes: /* @__PURE__ */ new Set()
903
+ };
904
+ switch (file.type) {
905
+ case RoutableFileTypes.Middleware:
906
+ info.id = `M${group.size + 1}`;
907
+ info.typeName = "Middleware";
908
+ break;
909
+ case RoutableFileTypes.Handler:
910
+ info.id = `H${group.size + 1}`;
911
+ info.typeName = "Handler";
912
+ break;
913
+ case RoutableFileTypes.Meta:
914
+ info.id = `D${group.size + 1}`;
915
+ info.typeName = "Meta";
916
+ break;
917
+ case RoutableFileTypes.Layout:
918
+ info.id = `L${group.size + 1}`;
919
+ info.typeName = "Template";
920
+ break;
921
+ case RoutableFileTypes.Page:
922
+ info.id = `P${group.size + 1}`;
923
+ info.typeName = "Template";
924
+ break;
925
+ default:
926
+ info.id = `F${fileIndex++}`;
927
+ break;
888
928
  }
929
+ group.set(file, info);
889
930
  }
890
- for (const files of [route.middleware, route.layouts]) {
891
- if (files) {
892
- for (const file of files) {
893
- const existing = routeTypes.get(file);
894
- if (!existing) {
895
- routeTypes.set(file, [routeType]);
896
- } else {
897
- existing.push(routeType);
898
- }
899
- }
931
+ return info;
932
+ }
933
+ writer.writeBlockStart(`interface App extends $.DefineRoutes<{`);
934
+ for (const route of routes.list) {
935
+ let routeDefFiles = "";
936
+ for (const file of routeFileIter(route)) {
937
+ const fileInfo = addFile(file);
938
+ fileInfo.routes.add(route);
939
+ if (routeDefFiles) {
940
+ routeDefFiles += ", ";
900
941
  }
942
+ routeDefFiles += fileInfo.id;
901
943
  }
944
+ writer.writeLines(`"${route.path.path}": [${routeDefFiles}];`);
902
945
  }
903
946
  for (const special of Object.values(routes.special)) {
904
- routeTypes.set(special.page, []);
905
- }
906
- routesWriter.join();
907
- const handlerWriter = writer.branch("handler");
908
- const middlewareWriter = writer.branch("middleware");
909
- const pageWriter = writer.branch("page");
910
- const layoutWriter = writer.branch("layout");
911
- for (const [file, types] of routeTypes) {
912
- const modulePath = stripTsExtension(
913
- normalizedRelativePath(outDir, file.filePath)
914
- );
915
- const routeType = `Run.Routes[${types.join(" | ")}]`;
916
- switch (file.type) {
917
- case RoutableFileTypes.Handler:
918
- writeModuleDeclaration(handlerWriter, modulePath, routeType);
919
- break;
920
- case RoutableFileTypes.Middleware:
921
- writeModuleDeclaration(middlewareWriter, modulePath, routeType);
922
- break;
923
- case RoutableFileTypes.Page:
924
- writeModuleDeclaration(pageWriter, modulePath, routeType);
925
- break;
926
- case RoutableFileTypes.Layout:
927
- writeModuleDeclaration(
928
- layoutWriter,
929
- modulePath,
930
- routeType,
931
- `
932
- export interface Input extends Run.LayoutInput<typeof import("${modulePath}")> {}`
947
+ addFile(special.page);
948
+ }
949
+ writer.writeBlockEnd(`}> {}`).writeBlockEnd(`}`);
950
+ for (const fileType of Object.values(RoutableFileTypes)) {
951
+ const fileGroup = fileInfoByType.get(fileType);
952
+ if (!fileGroup) continue;
953
+ for (const info of fileGroup.values()) {
954
+ writer.writeLines("");
955
+ if (info.typeName) {
956
+ writer.writeLines(
957
+ `type ${info.id} = $.${info.typeName}<"${info.id}", typeof import("${info.modulePath}")>;`
933
958
  );
934
- break;
935
- case RoutableFileTypes.Error:
936
- writeModuleDeclaration(
937
- writer,
938
- modulePath,
939
- "globalThis.MarkoRun.Route",
940
- `
959
+ }
960
+ if (fileType === RoutableFileTypes.Meta) continue;
961
+ writer.write(`declare module "${info.modulePath}" {`);
962
+ switch (fileType) {
963
+ case RoutableFileTypes.Layout:
964
+ writer.write(`
965
+ interface Input extends $.LayoutInput<${info.id}> {}`);
966
+ break;
967
+ case RoutableFileTypes.Error:
968
+ writer.write(`
941
969
  export interface Input {
942
970
  error: unknown;
943
- }`
944
- );
945
- break;
946
- case RoutableFileTypes.NotFound:
947
- writeModuleDeclaration(writer, modulePath, "Run.Route");
948
- break;
949
- }
950
- }
951
- handlerWriter.join();
952
- middlewareWriter.join();
953
- pageWriter.join();
954
- layoutWriter.join();
955
- return writer.end();
956
- }
957
- function writeModuleDeclaration(writer, name, routeType, moduleTypes) {
958
- writer.writeLines("").write(`declare module "${name}" {`);
959
- if (moduleTypes) {
960
- writer.write(moduleTypes);
971
+ }`);
972
+ break;
973
+ }
974
+ if (info.typeName) {
975
+ writer.write(`
976
+ const Run: $.Namespace<${info.id}>;
977
+ namespace Run {
978
+ type Context = $.ContextForFile<${info.id}>${info.typeName === "Template" ? " & Marko.Global" : ""};
961
979
  }
962
- if (routeType) {
963
- const isMarko = name.endsWith(".marko");
964
- writer.write(`
980
+ `);
981
+ }
982
+ writer.write(`
983
+ /** @deprecated use \`Run\` namespace instead */
965
984
  namespace MarkoRun {
966
985
  export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
967
- export type Route = ${routeType};
968
- export type Context = Run.MultiRouteContext<Route>${isMarko ? " & Marko.Global" : ""};
969
- export type Handler = Run.HandlerLike<Route>;`);
970
- for (const verb of httpVerbs) {
986
+ export type Route = ${info.routes.size ? `$.Routes["${[...info.routes].map((route) => route.path.path).join('" | "')}"]` : "globalThis.MarkoRun.Route"};
987
+ export type Context = ${info.modulePath.endsWith(".marko") ? "Run.Context" : "$.MultiRouteContext<Route>"};
988
+ export type Handler = $.HandlerLike<Route>;`);
989
+ for (const verb of httpVerbs) {
990
+ writer.write(`
991
+ export type ${verb.toUpperCase()} = $.HandlerLike<Route, "${verb.toUpperCase()}">;`);
992
+ }
971
993
  writer.write(`
972
- export type ${verb.toUpperCase()} = Run.HandlerLike<Route, "${verb.toUpperCase()}">;`);
973
- }
974
- writer.write(`
975
- /** @deprecated use \`((context, next) => { ... }) satisfies MarkoRun.Handler\` instead */
976
- export const route: Run.HandlerTypeFn<Route>;
977
994
  }`);
978
- }
979
- writer.writeLines(`
995
+ writer.writeLines(`
980
996
  }`);
997
+ }
998
+ }
999
+ headWriter.join();
1000
+ return writer.end();
981
1001
  }
982
1002
  function createRouteTrie(routes) {
983
1003
  const root = {
@@ -1462,7 +1482,7 @@ async function buildRoutes(sources, outDir) {
1462
1482
  middleware: [],
1463
1483
  layouts: [...currentLayouts],
1464
1484
  page: file,
1465
- templateFilePath: currentLayouts.size ? path3.join(outDir, `${type}.marko`) : void 0
1485
+ templateFilePath: path3.join(outDir, `${type}.marko`)
1466
1486
  };
1467
1487
  layoutsUsed = true;
1468
1488
  }
@@ -1495,7 +1515,7 @@ async function buildRoutes(sources, outDir) {
1495
1515
  meta: dir.files.get(RoutableFileTypes.Meta),
1496
1516
  page,
1497
1517
  handler,
1498
- templateFilePath: page && currentLayouts.size ? path3.join(outDir, key + ".marko") : void 0
1518
+ templateFilePath: page && path3.join(outDir, key + ".marko")
1499
1519
  });
1500
1520
  layoutsUsed = !!page;
1501
1521
  for (const middleware2 of currentMiddleware) {
@@ -1534,7 +1554,7 @@ function createFSWalker(dir) {
1534
1554
  onDir,
1535
1555
  maxDepth = 50
1536
1556
  }) {
1537
- async function walk(dir2, depth) {
1557
+ async function walk2(dir2, depth) {
1538
1558
  const onExit = onEnter == null ? void 0 : onEnter(dir2);
1539
1559
  if (onExit !== false) {
1540
1560
  const dirs = [];
@@ -1555,13 +1575,13 @@ function createFSWalker(dir) {
1555
1575
  }
1556
1576
  if ((onDir == null ? void 0 : onDir()) !== false && --depth > 0) {
1557
1577
  for (const entry of dirs) {
1558
- await walk(entry, depth);
1578
+ await walk2(entry, depth);
1559
1579
  }
1560
1580
  }
1561
1581
  onExit == null ? void 0 : onExit();
1562
1582
  }
1563
1583
  }
1564
- await walk(
1584
+ await walk2(
1565
1585
  {
1566
1586
  path: dir,
1567
1587
  name: path4.basename(dir)
@@ -1618,6 +1638,304 @@ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfi
1618
1638
  var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1619
1639
  var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1620
1640
 
1641
+ // src/runtime/url-builder.ts
1642
+ var encode = encodeURIComponent;
1643
+ var pathParts = /* @__PURE__ */ new Map();
1644
+ function parsePathParts(path7) {
1645
+ let parts = pathParts.get(path7);
1646
+ if (!parts) {
1647
+ let lastEnd = 0;
1648
+ let paramStart;
1649
+ pathParts.set(path7, parts = [[]]);
1650
+ while (lastEnd >= 0 && (paramStart = path7.indexOf("/$", lastEnd) + 1)) {
1651
+ parts.push(path7.slice(lastEnd, paramStart++));
1652
+ if (path7.charAt(paramStart) === "$") {
1653
+ paramStart++;
1654
+ lastEnd = -1;
1655
+ } else {
1656
+ lastEnd = path7.indexOf("/", paramStart);
1657
+ }
1658
+ parts[0].push(path7.slice(paramStart, lastEnd < 0 ? void 0 : lastEnd));
1659
+ }
1660
+ parts.push(lastEnd >= 0 ? path7.slice(lastEnd) : "");
1661
+ }
1662
+ return parts;
1663
+ }
1664
+ function joinHref(path7, options) {
1665
+ let result = path7;
1666
+ if (options.search) {
1667
+ const query = "" + new URLSearchParams(options.search);
1668
+ if (query) result += "?" + query;
1669
+ }
1670
+ if (options.hash) result += "#" + encode(options.hash);
1671
+ return result;
1672
+ }
1673
+ function href(path7, ...[options]) {
1674
+ return options ? "params" in options ? ((parts) => href_keys(parts, options, ...parts[0]))(
1675
+ parsePathParts(path7)
1676
+ ) : joinHref(path7, options) : path7;
1677
+ }
1678
+ function href_path(strings, ...params) {
1679
+ let i = 0;
1680
+ let j = 0;
1681
+ let result = strings[i++];
1682
+ if (!result || Array.isArray(result)) result = strings[i++];
1683
+ while (i < strings.length) {
1684
+ const param = params[j++];
1685
+ result += (Array.isArray(param) ? param.map(encode).join("/") : encode(param)) + strings[i++];
1686
+ }
1687
+ return result;
1688
+ }
1689
+ function href_values(strings, options, ...params) {
1690
+ return joinHref(href_path(strings, ...params), options);
1691
+ }
1692
+ function href_keys(strings, options, ...keys) {
1693
+ return href_values(strings, options, ...keys.map((k) => options.params[k]));
1694
+ }
1695
+
1696
+ // src/vite/utils/href-replace.ts
1697
+ function findHrefReplacements(code, ast) {
1698
+ const replacements = [];
1699
+ walk(ast, (node) => {
1700
+ var _a, _b;
1701
+ if (node.type !== "CallExpression") return;
1702
+ const callee = node.callee;
1703
+ if (callee.type !== "MemberExpression" || callee.computed || callee.object.type !== "Identifier" || callee.object.name !== "Run" || callee.property.type !== "Identifier" || callee.property.name !== "href") {
1704
+ return;
1705
+ }
1706
+ const args = node.arguments;
1707
+ if (args.length === 0 || args.length > 2 || args.some((a) => a.type === "SpreadElement")) {
1708
+ return;
1709
+ }
1710
+ const pathString = (_a = tryStaticEval(args[0])) == null ? void 0 : _a.value;
1711
+ if (typeof pathString !== "string") {
1712
+ replacements.push({
1713
+ helper: "href",
1714
+ edits: [{ start: callee.start, end: callee.end, code: "href" }]
1715
+ });
1716
+ return;
1717
+ }
1718
+ if (args.length === 1) {
1719
+ replacements.push({
1720
+ helper: false,
1721
+ edits: [
1722
+ {
1723
+ start: node.start,
1724
+ end: node.end,
1725
+ code: JSON.stringify(pathString)
1726
+ }
1727
+ ]
1728
+ });
1729
+ return;
1730
+ }
1731
+ const optionsNode = args[1];
1732
+ const optionsObject = (_b = tryStaticEval(optionsNode)) == null ? void 0 : _b.value;
1733
+ if (optionsObject && typeof optionsObject === "object" && !Array.isArray(optionsObject)) {
1734
+ replacements.push({
1735
+ helper: false,
1736
+ edits: [
1737
+ {
1738
+ start: node.start,
1739
+ end: node.end,
1740
+ code: JSON.stringify(href(pathString, optionsObject))
1741
+ }
1742
+ ]
1743
+ });
1744
+ return;
1745
+ }
1746
+ const parsed = parsePathPattern(pathString);
1747
+ if (optionsNode.type === "ObjectExpression") {
1748
+ const params = tryExtractObjectProperty(optionsNode, "params");
1749
+ if (params && parsed.params.every((p) => params.map.has(p))) {
1750
+ const pathPart = buildPathTemplate(code, parsed, params.map);
1751
+ if (params.only) {
1752
+ replacements.push({
1753
+ helper: "href_path",
1754
+ edits: [
1755
+ {
1756
+ start: node.start,
1757
+ end: node.end,
1758
+ code: "href_path`" + pathPart + "`"
1759
+ }
1760
+ ]
1761
+ });
1762
+ } else {
1763
+ const props = optionsNode.properties;
1764
+ const remaining = props.filter((_, i) => i !== params.index);
1765
+ if (remaining.length === 1 && remaining[0].type === "SpreadElement") {
1766
+ replacements.push({
1767
+ helper: "href_values",
1768
+ edits: [
1769
+ {
1770
+ start: node.start,
1771
+ end: remaining[0].argument.start,
1772
+ code: "href_values`${"
1773
+ },
1774
+ {
1775
+ start: remaining[0].argument.end,
1776
+ end: node.end,
1777
+ code: "}" + pathPart + "`"
1778
+ }
1779
+ ]
1780
+ });
1781
+ } else {
1782
+ replacements.push({
1783
+ helper: "href_values",
1784
+ edits: [
1785
+ {
1786
+ start: node.start,
1787
+ end: optionsNode.start,
1788
+ code: "href_values`${"
1789
+ },
1790
+ {
1791
+ start: optionsNode.end,
1792
+ end: node.end,
1793
+ code: "}" + pathPart + "`"
1794
+ },
1795
+ params.index < props.length - 1 ? {
1796
+ start: props[params.index].start,
1797
+ end: props[params.index + 1].start,
1798
+ code: ""
1799
+ } : {
1800
+ start: props[params.index - 1].end,
1801
+ end: props[params.index].end,
1802
+ code: ""
1803
+ }
1804
+ ]
1805
+ });
1806
+ }
1807
+ }
1808
+ return;
1809
+ }
1810
+ }
1811
+ replacements.push({
1812
+ helper: "href_keys",
1813
+ edits: [
1814
+ { start: node.start, end: optionsNode.start, code: "href_keys`${" },
1815
+ {
1816
+ start: optionsNode.end,
1817
+ end: node.end,
1818
+ code: `}${buildPathTemplate(code, parsed)}\``
1819
+ }
1820
+ ]
1821
+ });
1822
+ });
1823
+ return replacements;
1824
+ }
1825
+ function walk(node, visitor) {
1826
+ if (!node || typeof node !== "object") return;
1827
+ if (node.type) {
1828
+ visitor(node);
1829
+ }
1830
+ for (const key of Object.keys(node)) {
1831
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "range") {
1832
+ continue;
1833
+ }
1834
+ const child = node[key];
1835
+ if (Array.isArray(child)) {
1836
+ for (const item of child) {
1837
+ if (item && typeof item === "object" && item.type) {
1838
+ walk(item, visitor);
1839
+ }
1840
+ }
1841
+ } else if (child && typeof child === "object" && child.type) {
1842
+ walk(child, visitor);
1843
+ }
1844
+ }
1845
+ }
1846
+ function parsePathPattern(path7) {
1847
+ const parts = parsePathParts(path7);
1848
+ return { segments: parts.slice(1), params: parts[0] };
1849
+ }
1850
+ function buildPathTemplate(code, parsed, paramsMap) {
1851
+ let template = "";
1852
+ for (let i = 0; i < parsed.params.length; i++) {
1853
+ template += parsed.segments[i];
1854
+ if (paramsMap) {
1855
+ const paramProp = paramsMap.get(parsed.params[i]);
1856
+ const valueNode = paramProp.shorthand ? paramProp.key : paramProp.value;
1857
+ template += "${" + code.slice(valueNode.start, valueNode.end) + "}";
1858
+ } else {
1859
+ template += '${"' + parsed.params[i] + '"}';
1860
+ }
1861
+ }
1862
+ if (parsed.segments.length > parsed.params.length) {
1863
+ template += parsed.segments[parsed.segments.length - 1];
1864
+ }
1865
+ return template;
1866
+ }
1867
+ function getStaticKey(prop) {
1868
+ if (prop.computed) return null;
1869
+ if (prop.key.type === "Identifier") return prop.key.name;
1870
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string")
1871
+ return prop.key.value;
1872
+ return null;
1873
+ }
1874
+ function tryStaticEval(node) {
1875
+ switch (node.type) {
1876
+ case "Literal":
1877
+ return { value: node.value };
1878
+ case "TemplateLiteral":
1879
+ return node.expressions.length ? null : { value: node.quasis[0].value.cooked };
1880
+ case "ObjectExpression": {
1881
+ const value = {};
1882
+ for (const prop of node.properties) {
1883
+ if (prop.type === "SpreadElement" || prop.shorthand) return null;
1884
+ const key = getStaticKey(prop);
1885
+ if (!key) return null;
1886
+ const val = tryStaticEval(prop.value);
1887
+ if (!val) return null;
1888
+ value[key] = val.value;
1889
+ }
1890
+ return { value };
1891
+ }
1892
+ case "ArrayExpression": {
1893
+ const value = [];
1894
+ for (const elem of node.elements) {
1895
+ if (!elem || elem.type === "SpreadElement") return null;
1896
+ const val = tryStaticEval(elem);
1897
+ if (!val) return null;
1898
+ value.push(val.value);
1899
+ }
1900
+ return { value };
1901
+ }
1902
+ case "UnaryExpression":
1903
+ if (node.prefix && node.operator === "-") {
1904
+ const arg = tryStaticEval(node.argument);
1905
+ if (arg && typeof arg.value === "number") {
1906
+ return { value: -arg.value };
1907
+ }
1908
+ }
1909
+ return null;
1910
+ default:
1911
+ return null;
1912
+ }
1913
+ }
1914
+ function tryExtractObjectProperty(obj, propertyName) {
1915
+ const props = obj.properties;
1916
+ for (let i = props.length - 1; i >= 0; i--) {
1917
+ const prop = props[i];
1918
+ if (prop.type === "SpreadElement") return null;
1919
+ if (getStaticKey(prop) !== propertyName) continue;
1920
+ if (prop.value.type !== "ObjectExpression") return null;
1921
+ const map = /* @__PURE__ */ new Map();
1922
+ for (const paramProp of prop.value.properties) {
1923
+ if (paramProp.type === "SpreadElement") return null;
1924
+ const paramKey = getStaticKey(paramProp);
1925
+ if (!paramKey) return null;
1926
+ map.set(paramKey, paramProp);
1927
+ }
1928
+ let only = i === props.length - 1;
1929
+ let j = i;
1930
+ while (only && j--) {
1931
+ const prop2 = props[j];
1932
+ only = prop2.type === "Property" && getStaticKey(prop2) === propertyName;
1933
+ }
1934
+ return { map, index: i, only };
1935
+ }
1936
+ return null;
1937
+ }
1938
+
1621
1939
  // src/vite/utils/log.ts
1622
1940
  import zlib from "node:zlib";
1623
1941
  import Table from "cli-table3";
@@ -1902,7 +2220,7 @@ function markoRun(opts = {}) {
1902
2220
  var _a, _b;
1903
2221
  routeMarkoApiCache ?? (routeMarkoApiCache = /* @__PURE__ */ new Map());
1904
2222
  if (!routeMarkoApiCache.has(route)) {
1905
- const markoAPI = route.templateFilePath && ((_b = (_a = await loadModule(context, normalizePath(route.layouts[0].filePath))) == null ? void 0 : _a.meta) == null ? void 0 : _b.markoAPI);
2223
+ const markoAPI = route.layouts.length ? (_b = (_a = await loadModule(context, normalizePath(route.layouts[0].filePath))) == null ? void 0 : _a.meta) == null ? void 0 : _b.markoAPI : void 0;
1906
2224
  routeMarkoApiCache.set(route, markoAPI);
1907
2225
  return markoAPI;
1908
2226
  }
@@ -1924,7 +2242,6 @@ function markoRun(opts = {}) {
1924
2242
  let buildVirtualFilesResult;
1925
2243
  function buildVirtualFiles() {
1926
2244
  return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1927
- var _a, _b;
1928
2245
  virtualFiles.clear();
1929
2246
  if (fs3.existsSync(resolvedRoutesDir)) {
1930
2247
  routes = await buildRoutes(
@@ -1949,9 +2266,8 @@ function markoRun(opts = {}) {
1949
2266
  entryTemplates = /* @__PURE__ */ new Set();
1950
2267
  entryTemplateImporters = /* @__PURE__ */ new Set();
1951
2268
  for (const route of routes.list) {
1952
- const routeEntryPath = route.templateFilePath || ((_a = route.page) == null ? void 0 : _a.filePath);
1953
- if (routeEntryPath) {
1954
- entryTemplates.add(normalizePath(routeEntryPath));
2269
+ if (route.templateFilePath) {
2270
+ entryTemplates.add(normalizePath(route.templateFilePath));
1955
2271
  }
1956
2272
  for (const middleware of route.middleware) {
1957
2273
  entryTemplateImporters.add(normalizePath(middleware.filePath));
@@ -1965,9 +2281,8 @@ function markoRun(opts = {}) {
1965
2281
  );
1966
2282
  }
1967
2283
  for (const route of Object.values(routes.special)) {
1968
- const routeEntryPath = route.templateFilePath || ((_b = route.page) == null ? void 0 : _b.filePath);
1969
- if (routeEntryPath) {
1970
- entryTemplates.add(normalizePath(routeEntryPath));
2284
+ if (route.templateFilePath) {
2285
+ entryTemplates.add(normalizePath(route.templateFilePath));
1971
2286
  }
1972
2287
  }
1973
2288
  if (routes.middleware.length) {
@@ -2022,7 +2337,8 @@ function markoRun(opts = {}) {
2022
2337
  route.templateFilePath,
2023
2338
  renderRouteTemplate(
2024
2339
  route,
2025
- await getMarkoApiForRoute(context, route)
2340
+ await getMarkoApiForRoute(context, route),
2341
+ !isBuild
2026
2342
  )
2027
2343
  );
2028
2344
  }
@@ -2032,18 +2348,17 @@ function markoRun(opts = {}) {
2032
2348
  );
2033
2349
  }
2034
2350
  for (const route of Object.values(routes2.special)) {
2035
- if (route.templateFilePath) {
2036
- fs3.mkdirSync(path6.dirname(route.templateFilePath), {
2037
- recursive: true
2038
- });
2039
- fs3.writeFileSync(
2040
- route.templateFilePath,
2041
- renderRouteTemplate(
2042
- route,
2043
- await getMarkoApiForRoute(context, route)
2044
- )
2045
- );
2046
- }
2351
+ fs3.mkdirSync(path6.dirname(route.templateFilePath), {
2352
+ recursive: true
2353
+ });
2354
+ fs3.writeFileSync(
2355
+ route.templateFilePath,
2356
+ renderRouteTemplate(
2357
+ route,
2358
+ await getMarkoApiForRoute(context, route),
2359
+ !isBuild
2360
+ )
2361
+ );
2047
2362
  }
2048
2363
  if (routes2.middleware.length) {
2049
2364
  for (const middleware of routes2.middleware) {
@@ -2348,6 +2663,48 @@ function markoRun(opts = {}) {
2348
2663
  {
2349
2664
  name: `${PLUGIN_NAME_PREFIX}:post`,
2350
2665
  enforce: "post",
2666
+ async transform(code) {
2667
+ if (!isBuild || isSSRBuild || !code.includes("Run.href")) {
2668
+ return;
2669
+ }
2670
+ try {
2671
+ const replacements = findHrefReplacements(
2672
+ code,
2673
+ this.parse(code, { lang: "js" })
2674
+ );
2675
+ if (replacements.length) {
2676
+ const helpers = /* @__PURE__ */ new Set();
2677
+ const s = new RolldownMagicString(code);
2678
+ for (const { helper, edits } of replacements) {
2679
+ for (const { start, end, code: code2 } of edits) {
2680
+ if (code2) {
2681
+ s.overwrite(start, end, code2);
2682
+ } else {
2683
+ s.remove(start, end);
2684
+ }
2685
+ }
2686
+ if (helper) {
2687
+ helpers.add(helper);
2688
+ }
2689
+ }
2690
+ if (helpers.size) {
2691
+ s.prepend(
2692
+ `import { ${[...helpers].join(", ")} } from "virtual:marko-run/runtime/url-builder";`
2693
+ );
2694
+ }
2695
+ return {
2696
+ code: s
2697
+ };
2698
+ }
2699
+ return null;
2700
+ } catch {
2701
+ return {
2702
+ code: new RolldownMagicString(code).prepend(
2703
+ 'import "virtual:marko-run/runtime/client";'
2704
+ )
2705
+ };
2706
+ }
2707
+ },
2351
2708
  generateBundle(options, bundle) {
2352
2709
  if (options.sourcemap && options.sourcemap !== "inline") {
2353
2710
  for (const key of Object.keys(bundle)) {