@marko/run 0.0.1-beta2 → 0.0.1-beta4

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.
@@ -29,6 +29,8 @@ __export(vite_exports, {
29
29
  default: () => markoServe,
30
30
  getAvailablePort: () => getAvailablePort,
31
31
  isPortInUse: () => isPortInUse,
32
+ loadEnv: () => loadEnv,
33
+ parseEnv: () => parseEnv,
32
34
  spawnServer: () => spawnServer
33
35
  });
34
36
  module.exports = __toCommonJS(vite_exports);
@@ -36,13 +38,16 @@ module.exports = __toCommonJS(vite_exports);
36
38
  // src/vite/plugin.ts
37
39
  var import_path2 = __toESM(require("path"), 1);
38
40
  var import_crypto = __toESM(require("crypto"), 1);
41
+ var import_fs2 = __toESM(require("fs"), 1);
42
+ var import_glob = __toESM(require("glob"), 1);
39
43
  var import_vite = require("vite");
40
44
  var import_vite2 = __toESM(require("@marko/vite"), 1);
41
45
 
42
46
  // src/vite/constants.ts
43
- var markoServeFilePrefix = "__marko-serve__";
44
- var virtualFilePrefix = "virtual:marko-serve";
47
+ var markoRunFilePrefix = "__marko-run__";
48
+ var virtualFilePrefix = "virtual:marko-run";
45
49
  var virtualRoutesPrefix = `${virtualFilePrefix}/routes`;
50
+ var virtualRuntimePrefix = `${virtualFilePrefix}/internal`;
46
51
  var httpVerbs = ["get", "post", "put", "delete"];
47
52
  var serverEntryQuery = "?marko-server-entry";
48
53
  var browserEntryQuery = "?marko-browser-entry";
@@ -82,15 +87,20 @@ function isSpecialType(type) {
82
87
  return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
83
88
  }
84
89
  async function buildRoutes(walk, basePath) {
85
- const dirStack = basePath ? basePath.split("/") : [];
90
+ if (basePath) {
91
+ basePath = basePath.replace(/^\/+|\/+$/g, "");
92
+ }
93
+ const dirStack = [];
86
94
  const pathStack = [];
87
95
  const paramStack = [];
88
96
  const layoutsStack = [];
89
97
  const middlewareStack = [];
90
98
  const routes = /* @__PURE__ */ new Map();
91
99
  const special = {};
100
+ const middleware = /* @__PURE__ */ new Set();
92
101
  let isRoot = true;
93
- let nextId = 1;
102
+ let nextFileId = 1;
103
+ let nextRouteIndex = 1;
94
104
  let current;
95
105
  let children = [];
96
106
  await walk({
@@ -118,10 +128,13 @@ async function buildRoutes(walk, basePath) {
118
128
  if (!entries) {
119
129
  current.files.set(type, entries = []);
120
130
  }
131
+ const relativePath = current.originalPath ? `${current.originalPath}/${entry.name}` : entry.name;
121
132
  entries.push({
133
+ id: String(nextFileId++),
122
134
  type,
123
135
  filePath: entry.path,
124
- importPath: `${current.originalPath}/${entry.name}`,
136
+ relativePath,
137
+ importPath: `${basePath}/${relativePath}`,
125
138
  name: entry.name,
126
139
  verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
127
140
  });
@@ -132,20 +145,24 @@ async function buildRoutes(walk, basePath) {
132
145
  return;
133
146
  }
134
147
  const { path: path3, files } = current;
135
- const middleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
136
- const layout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
148
+ const localMiddleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
149
+ const localLayout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
137
150
  const handler = (_c = files.get(RoutableFileTypes.Handler)) == null ? void 0 : _c[0];
138
151
  const page = (_d = files.get(RoutableFileTypes.Page)) == null ? void 0 : _d[0];
139
152
  const middlewareStackLength = middlewareStack.length;
140
153
  const layoutsStackLength = layoutsStack.length;
141
- middleware && middlewareStack.push(middleware);
142
- layout && layoutsStack.push(layout);
154
+ if (localMiddleware) {
155
+ middlewareStack.push(localMiddleware);
156
+ }
157
+ if (localLayout) {
158
+ layoutsStack.push(localLayout);
159
+ }
143
160
  if (handler || page) {
144
161
  const key = path3.replace(/(\$\$?)[^\/]*/g, "$1").replace(/^\/+/, "").replace(/[^a-z0-9_$\/]+/gi, "").replace(/\//g, "__") || "index";
145
162
  if (routes.has(key)) {
146
163
  console.warn(`Duplicate route for path ${path3} -- ignoring`, current);
147
164
  } else {
148
- const index = nextId++;
165
+ const index = nextRouteIndex++;
149
166
  routes.set(key, {
150
167
  index,
151
168
  key,
@@ -158,6 +175,9 @@ async function buildRoutes(walk, basePath) {
158
175
  handler,
159
176
  score: scorePath(path3, index)
160
177
  });
178
+ for (const mw of middlewareStack) {
179
+ middleware.add(mw);
180
+ }
161
181
  }
162
182
  }
163
183
  if (isRoot) {
@@ -193,7 +213,7 @@ async function buildRoutes(walk, basePath) {
193
213
  if (name.charCodeAt(0) === 36) {
194
214
  if (name.charCodeAt(1) === 36) {
195
215
  paramStack.push({
196
- name: name.slice(2) || "*",
216
+ name: name.slice(2) || "rest",
197
217
  index: -1
198
218
  });
199
219
  } else if (name.length > 1) {
@@ -223,7 +243,8 @@ async function buildRoutes(walk, basePath) {
223
243
  });
224
244
  return {
225
245
  list: [...routes.values()],
226
- special
246
+ special,
247
+ middleware: [...middleware]
227
248
  };
228
249
  }
229
250
 
@@ -273,6 +294,17 @@ function createWriter(sink, options) {
273
294
  let firstOpenIndex = 0;
274
295
  const branches = [];
275
296
  const openWriters = /* @__PURE__ */ new Map();
297
+ function write(data) {
298
+ if (!writer.__isActive) {
299
+ throw new Error("Cannot write to branch that has been joined");
300
+ }
301
+ if (openWriters.size) {
302
+ buffer += data;
303
+ } else {
304
+ sink(data);
305
+ }
306
+ return writer;
307
+ }
276
308
  const writer = {
277
309
  __isActive: true,
278
310
  get indent() {
@@ -289,24 +321,16 @@ function createWriter(sink, options) {
289
321
  }
290
322
  }
291
323
  },
292
- write(data) {
293
- if (!writer.__isActive) {
294
- throw new Error("Cannot write to branch that has been joined");
295
- }
296
- if (openWriters.size) {
297
- buffer += data;
298
- } else {
299
- sink(data);
324
+ write(data, indent = false) {
325
+ if (indent && indentString) {
326
+ write(indentString);
300
327
  }
301
- return writer;
328
+ return write(data);
302
329
  },
303
330
  writeLines(...lines) {
304
331
  for (const line of lines) {
305
332
  if (line) {
306
- if (indentString) {
307
- writer.write(indentString);
308
- }
309
- writer.write(line);
333
+ writer.write(line, true);
310
334
  }
311
335
  writer.write("\n");
312
336
  }
@@ -453,16 +477,13 @@ function hasVerb(route, verb) {
453
477
  }
454
478
 
455
479
  // src/vite/codegen/index.ts
456
- var DefaultCodegenOptions = {
457
- trailingSlashes: "RedirectWithout"
458
- };
459
480
  function renderRouteTemplate(route) {
460
481
  if (!route.page) {
461
482
  throw new Error(`Route ${route.key} has no page to render`);
462
483
  }
463
484
  const writer = createStringWriter();
464
485
  writer.writeLines(
465
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.marko`
486
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.marko`
466
487
  );
467
488
  writer.branch("imports");
468
489
  writer.writeLines("");
@@ -494,25 +515,47 @@ function renderRouteEntry(route) {
494
515
  }
495
516
  const writer = createStringWriter();
496
517
  writer.writeLines(
497
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.js`
518
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.js`
498
519
  );
499
520
  const imports = writer.branch("imports");
500
- let i = 1;
501
- for (const { importPath } of middleware) {
502
- imports.writeLines(`import middleware$${i} from './${importPath}';`);
503
- i++;
521
+ const runtimeImports = [];
522
+ if (handler) {
523
+ runtimeImports.push("normalize");
504
524
  }
505
- if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
506
- const names = handler.verbs.map(
507
- (verb) => `${verb === "delete" ? "del" : verb} as handler$${verb}`
525
+ if (handler || middleware.length) {
526
+ runtimeImports.push("call");
527
+ }
528
+ if (!page || verbs.length > 1) {
529
+ runtimeImports.push("noContent");
530
+ }
531
+ if (runtimeImports.length) {
532
+ imports.writeLines(
533
+ `import { ${runtimeImports.join(", ")} } from '${virtualRuntimePrefix}';`
508
534
  );
535
+ }
536
+ if (middleware.length) {
537
+ const names = middleware.map((m) => `mware$${m.id}`);
538
+ imports.writeLines(
539
+ `import { ${names.join(
540
+ ", "
541
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
542
+ );
543
+ }
544
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
545
+ writer.writeLines("");
546
+ const names = [];
547
+ for (const verb of handler.verbs) {
548
+ const name = verb === "delete" ? "del" : verb;
549
+ names.push(name);
550
+ writer.writeLines(`const handler$${name} = normalize(${name});`);
551
+ }
509
552
  imports.writeLines(
510
553
  `import { ${names.join(", ")} } from './${handler.importPath}';`
511
554
  );
512
555
  }
513
556
  if (page) {
514
557
  imports.writeLines(
515
- `import page from '${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.marko${serverEntryQuery}';`
558
+ `import page from '${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.marko${serverEntryQuery}';`
516
559
  );
517
560
  }
518
561
  if (meta) {
@@ -520,21 +563,27 @@ function renderRouteEntry(route) {
520
563
  `export { default as meta$${index} } from './${meta.importPath}';`
521
564
  );
522
565
  }
523
- if (!page || verbs.length > 1) {
524
- writer.writeLines("").writeBlockStart(`function create204Response() {`).writeBlockStart(`return new Response(null, {`).writeLines(`status: 204`).writeBlockEnd(`})`).writeBlockEnd(`}`);
525
- }
526
566
  for (const verb of verbs) {
527
567
  writeRouteEntryHandler(writer, route, verb);
528
568
  }
529
569
  return writer.end();
530
570
  }
531
- function writePageResponseContinuation(writer) {
571
+ function writePageResponse(writer, wrapFn) {
532
572
  writer.writeBlock(
533
- `return new Response(page.stream(ctx), {`,
573
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} new Response(page.stream(buildInput()), {`,
534
574
  ["status: 200,", 'headers: { "content-type": "text/html;charset=UTF-8" }'],
535
575
  "});"
536
576
  );
537
577
  }
578
+ function writeMiddleware(writer, middleware, next, wrapFn) {
579
+ if (wrapFn) {
580
+ writer.writeLines(
581
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
582
+ );
583
+ } else {
584
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
585
+ }
586
+ }
538
587
  function writeRouteEntryHandler(writer, route, verb) {
539
588
  var _a;
540
589
  const { key, index, page, handler, middleware } = route;
@@ -542,47 +591,38 @@ function writeRouteEntryHandler(writer, route, verb) {
542
591
  let nextName;
543
592
  let currentName;
544
593
  let hasBody = false;
545
- writer.writeLines("").writeBlockStart(`export async function ${verb}$${index}(ctx) {`);
594
+ writer.writeLines("").writeBlockStart(
595
+ `export async function ${verb}$${index}(context, buildInput) {`
596
+ );
546
597
  const continuations = writer.branch("cont");
547
598
  if (page && verb === "get") {
548
- currentName = "createPageResponse";
599
+ currentName = "__page";
549
600
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
550
- continuations.writeBlockStart(`async function ${currentName}() {`);
551
- writePageResponseContinuation(continuations);
552
- continuations.writeBlockEnd("}");
601
+ const name = `handler$${verb}`;
602
+ writePageResponse(continuations, currentName);
553
603
  if (len) {
554
604
  nextName = currentName;
555
- currentName = `__handler$${verb}`;
556
- continuations.writeBlock(
557
- `async function ${currentName}() {`,
558
- [`return await handler$${verb}(ctx, ${nextName});`],
559
- "}"
560
- );
605
+ currentName = `__${name}`;
606
+ writeMiddleware(continuations, name, nextName, currentName);
561
607
  } else {
562
- writer.writeLines(`return await handler$${verb}(ctx, ${currentName})`);
608
+ writeMiddleware(writer, name, currentName);
563
609
  hasBody = true;
564
610
  }
565
611
  } else if (len) {
566
- continuations.writeBlockStart(`async function ${currentName}() {`);
567
- writePageResponseContinuation(continuations);
568
- continuations.writeBlockEnd("}");
612
+ writePageResponse(continuations, currentName);
569
613
  nextName = currentName;
570
614
  } else {
571
- writePageResponseContinuation(continuations);
615
+ writePageResponse(continuations);
572
616
  hasBody = true;
573
617
  }
574
618
  } else if (handler) {
575
- currentName = `__handler$${verb}`;
619
+ const name = `handler$${verb}`;
620
+ currentName = `__${name}`;
621
+ nextName = "noContent";
576
622
  if (len) {
577
- continuations.writeBlock(
578
- `async function ${currentName}() {`,
579
- [`return await handler$${verb}(ctx, create204Response);`],
580
- "}"
581
- );
623
+ writeMiddleware(continuations, name, nextName, currentName);
582
624
  } else {
583
- writer.writeLines(
584
- `return await handler$${verb}(ctx, create204Response);`
585
- );
625
+ writeMiddleware(writer, name, nextName);
586
626
  hasBody = true;
587
627
  }
588
628
  } else {
@@ -590,24 +630,26 @@ function writeRouteEntryHandler(writer, route, verb) {
590
630
  }
591
631
  if (!hasBody) {
592
632
  let i = len;
593
- while (--i) {
633
+ while (i--) {
634
+ const { id } = middleware[i];
635
+ const name = `mware$${id}`;
594
636
  nextName = currentName;
595
- currentName = `__middleware${i + 1}`;
596
- continuations.writeLines("").writeBlock(
597
- `async function ${currentName}() {`,
598
- [`return await middleware$${i + 1}(ctx, ${nextName});`],
599
- "}"
600
- );
637
+ currentName = i ? `__${name}` : "";
638
+ writeMiddleware(continuations, name, nextName, currentName);
601
639
  }
602
- writer.writeLines(`return await middleware$1(ctx, ${currentName});`);
603
640
  }
604
641
  continuations.join();
605
642
  writer.writeBlockEnd("}");
606
643
  }
607
- function renderRouter(routes, options = DefaultCodegenOptions) {
644
+ function renderRouter(routes, options = {
645
+ trailingSlashes: "RedirectWithout"
646
+ }) {
608
647
  const writer = createStringWriter();
609
648
  writer.writeLines(`// @marko/run/router`);
610
649
  const imports = writer.branch("imports");
650
+ imports.writeLines(
651
+ `import { RequestNotHandled, RequestNotMatched, createInput } from 'virtual:marko-run/internal';`
652
+ );
611
653
  for (const route of routes.list) {
612
654
  const verbs = getVerbs(route);
613
655
  const names = verbs.map((verb) => `${verb}$${route.index}`);
@@ -615,15 +657,15 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
615
657
  imports.writeLines(
616
658
  `import { ${names.join(
617
659
  ", "
618
- )} } from '${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.js';`
660
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.js';`
619
661
  );
620
662
  }
621
663
  for (const { key } of Object.values(routes.special)) {
622
664
  imports.writeLines(
623
- `import page$${key} from '${virtualFilePrefix}/${markoServeFilePrefix}special__${key}.marko${serverEntryQuery}';`
665
+ `import page$${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
624
666
  );
625
667
  }
626
- writer.writeLines("").writeBlockStart(`function matchRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
668
+ writer.writeLines(``).writeBlockStart(`function findRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
627
669
  for (const verb of httpVerbs) {
628
670
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
629
671
  if (filteredRoutes.length) {
@@ -633,120 +675,105 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
633
675
  writer.writeBlockEnd("}");
634
676
  }
635
677
  }
636
- writer.writeBlockEnd("}").writeBlockEnd("}");
637
- writer.writeLines("").writeBlockStart(`async function invokeRoute(route, url, request) {`);
638
- const errorRoute = routes.special[RoutableFileTypes.Error];
639
- if (errorRoute) {
640
- writer.writeBlockStart(`try {`);
678
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
679
+ writer.write(`
680
+ export function matchRoute(method, pathname) {
681
+ if (!pathname) {
682
+ pathname = '/';
683
+ } else if (pathname.charAt(0) !== '/') {
684
+ pathname = '/' + pathname;
641
685
  }
642
- writer.writeBlock(
643
- `if (route) {`,
644
- [
645
- `const [handler, params = {}, meta] = route;`,
646
- `const response = await handler({ request, url, params, meta });`,
647
- `if (response) return response;`
648
- ],
649
- `}`
650
- );
651
- const notFoundRoute = routes.special[RoutableFileTypes.NotFound];
652
- if (notFoundRoute) {
653
- writer.writeBlockStart(
654
- `if (request.headers.get('Accept')?.includes('text/html')) {`
655
- ).writeBlock(
656
- `return new Response(page$404.stream({ request, url, params: {} }), {`,
657
- [
658
- `status: 404,`,
659
- `headers: { "content-type": "text/html;charset=UTF-8" },`
660
- ],
661
- `});`
662
- ).writeBlockEnd("}");
686
+ return findRoute(method, pathname);
687
+ }
688
+
689
+ export async function invokeRoute(route, context) {
690
+ try {
691
+ const buildInput = createInput(context);
692
+ if (route) {
693
+ context.params = route.params;
694
+ context.meta = route.meta;
695
+ try {
696
+ const response = await route.handler(context, buildInput);
697
+ if (response) return response;
698
+ } catch (error) {
699
+ if (error === RequestNotHandled) {
700
+ return;
701
+ } else if (error !== RequestNotMatched) {
702
+ throw error;
703
+ }
704
+ }
705
+ `).indent = 2;
706
+ if (routes.special[RoutableFileTypes.NotFound]) {
707
+ writer.write(
708
+ ` } else {
709
+ context.params = {};
710
+ context.meta = {};
711
+ }
712
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
713
+ return new Response(page$404.stream(buildInput()), {
714
+ status: 404,
715
+ headers: { "content-type": "text/html;charset=UTF-8" },
716
+ });
717
+ }
718
+ `
719
+ );
720
+ } else {
721
+ writer.writeBlockEnd("}");
663
722
  }
664
- writer.writeLines(`return null;`);
665
- if (errorRoute) {
666
- writer.indent--;
667
- writer.writeBlockStart(`} catch (err) {`).writeBlockStart(
668
- `if (request.headers.get('Accept')?.includes('text/html')) {`
723
+ writer.indent--;
724
+ writer.writeBlockStart(`} catch (error) {`);
725
+ if (routes.special[RoutableFileTypes.Error]) {
726
+ writer.writeBlockStart(
727
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
669
728
  ).writeBlock(
670
- `return new Response(page$500.stream({ request, url, params: {}, error: err }), {`,
729
+ `return new Response(page$500.stream(buildInput({ error })), {`,
671
730
  [
672
731
  `status: 500,`,
673
732
  `headers: { "content-type": "text/html;charset=UTF-8" },`
674
733
  ],
675
734
  `});`
676
- ).writeBlockEnd("}").writeLines(`throw err;`).writeBlockEnd("}");
677
- }
678
- writer.writeBlockEnd("}");
679
- writer.write(`
680
- export function getMatchedRoute(method, url) {
681
- const route = matchRoute(method, url.pathname);
682
- if (route) {
683
- const [handler, params = {}, meta] = route;
684
- return {
685
- handler,
686
- params,
687
- meta,
688
- async invoke(request) {
689
- return await invokeRoute(route, url, request);
690
- }
691
- }
735
+ ).writeBlockEnd("}");
692
736
  }
693
- return null;
694
- }
695
-
696
- export async function handler(context) {
697
- const response = await router(context.request);
698
- //if (response.body) {
699
- // context.waitUntil?.(once(Readable.from(response.body), "end"));
700
- //}
701
- return response;
702
- }
703
-
704
- export async function router(request) {
737
+ writer.writeLines(`throw error;`).writeBlockEnd("}").writeBlockEnd("}").write(`
738
+ export async function router(context) {
705
739
  try {
706
- const url = new URL(request.url);
707
- let { pathname } = url;
708
-
709
- if (pathname !== '/') {
710
- if (pathname.endsWith('/')) {
711
- pathname = pathname.replace(/\\/+$/, '');
712
- `);
740
+ const { url, method } = context;
741
+ let { pathname } = url;`);
713
742
  switch (options.trailingSlashes) {
714
743
  case "RedirectWithout":
715
744
  writer.write(`
716
- url.pathname = pathname;
717
- return Response.redirect(url.href);
718
- `);
745
+ if (pathname !== '/' && pathname.endsWith('/')) {
746
+ url.pathname = pathname.slice(0, -1);
747
+ return Response.redirect(url);
748
+ }`);
719
749
  break;
720
750
  case "RedirectWith":
721
751
  writer.write(`
722
- } else {
723
- url.pathname = pathname + '/';
724
- return Response.redirect(url.href);
725
- `);
752
+ if (pathname !== '/' && !pathname.endsWith('/')) {
753
+ url.pathname = pathname + '/';
754
+ return Response.redirect(url);
755
+ }`);
726
756
  break;
727
757
  case "RewriteWithout":
728
758
  writer.write(`
729
- url.pathname = pathname;
730
- `);
759
+ if (pathname !== '/' && pathname.endsWith('/')) {
760
+ url.pathname = pathname = pathname.slice(0, -1);
761
+ }`);
731
762
  break;
732
763
  case "RewriteWith":
733
764
  writer.write(`
734
- } else {
735
- url.pathname = pathname + '/';
736
- `);
765
+ if (pathname !== '/' && !pathname.endsWith('/')) {
766
+ url.pathname = pathname = pathname + '/';
767
+ }`);
737
768
  break;
738
769
  }
739
- writer.write(`
740
- }
741
- }
742
- const route = matchRoute(request.method, pathname);
743
- return await invokeRoute(route, url, request) || new Response(null, {
744
- statusText: \`Not Found (No route matched \${pathname})\`,
745
- status: 404
746
- });
747
- } catch (err) {
770
+ writer.write(`
771
+
772
+ const route = matchRoute(method, pathname);
773
+ return await invokeRoute(route, context);
774
+ } catch (error) {
748
775
  const message = import.meta.env.DEV
749
- ? \`Internal Server Error (\${err.message})\`
776
+ ? \`Internal Server Error (\${error.message})\`
750
777
  : "Internal Server Error";
751
778
 
752
779
  return new Response(
@@ -754,7 +781,7 @@ export async function router(request) {
754
781
  error: {
755
782
  message,
756
783
  stack: import.meta.env.DEV
757
- ? \`This will only be seen in development mode\\n\\n\${err.stack}\`
784
+ ? \`This will only be seen in development mode\\n\\n\${error.stack}\`
758
785
  : ""
759
786
  }
760
787
  }),
@@ -767,101 +794,259 @@ export async function router(request) {
767
794
  }`);
768
795
  return writer.end();
769
796
  }
770
- function writeRouterVerb(writer, trie, verb, level = 0, pathIndex = 0, useSwitch) {
771
- const { key, route: value, static: children, dynamic, catchAll } = trie;
772
- pathIndex += key.length;
773
- if (level <= 0) {
774
- level = 0;
775
- if (value) {
776
- writer.writeLines(
777
- `if (pathname === '/') return ${renderMatch(verb, value)}; // ${value.path}`
778
- );
779
- }
780
- if (children || dynamic) {
781
- writer.writeLines(
782
- `const segments = pathname.split('/');`,
783
- `const len = segments.length;`
784
- );
785
- }
786
- } else {
787
- if (!key) {
788
- writer.writeBlockStart(`if (segments[${level}]) {`);
789
- } else if (useSwitch) {
790
- writer.writeBlockStart(`case '${key}':`);
791
- } else {
792
- writer.writeBlockStart(
793
- `if (segments[${level}]?.toLowerCase() === '${key}') {`
794
- );
795
- }
796
- if (value) {
797
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
798
+ const { route, dynamic, catchAll } = trie;
799
+ let closeCount = 0;
800
+ if (level === 0) {
801
+ writer.writeLines(`const len = pathname.length;`);
802
+ if (route) {
797
803
  writer.writeLines(
798
- `if (len === ${level + 1}) return ${renderMatch(verb, value)}; // ${value.path}`
804
+ `if (len === 1) return ${renderMatch(verb, route)}; // ${route.path}`
799
805
  );
806
+ } else if (trie.static || dynamic) {
807
+ writer.writeBlockStart(`if (len > 1) {`);
808
+ closeCount++;
800
809
  }
801
810
  }
802
- if (children || dynamic) {
803
- if (children) {
804
- if (children.size > 1) {
805
- writer.writeBlockStart(
806
- `switch(segments[${level + 1}]?.toLowerCase()) {`
807
- );
808
- for (const child of children.values()) {
809
- writeRouterVerb(writer, child, verb, level + 1, pathIndex, true);
811
+ if (trie.static || dynamic) {
812
+ const next = level + 1;
813
+ const index = `i${next}`;
814
+ let terminal;
815
+ let children;
816
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
817
+ if (trie.static) {
818
+ for (const child of trie.static.values()) {
819
+ if (child.route) {
820
+ (terminal ?? (terminal = [])).push(child);
810
821
  }
811
- writer.writeBlockEnd("}");
812
- } else {
813
- for (const child of children.values()) {
814
- writeRouterVerb(writer, child, verb, level + 1, pathIndex);
822
+ if (child.static || child.dynamic || child.catchAll) {
823
+ (children ?? (children = [])).push(child);
824
+ }
825
+ }
826
+ }
827
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
828
+ closeCount++;
829
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
830
+ let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
831
+ if (dynamic == null ? void 0 : dynamic.route) {
832
+ const segment = `s${next}`;
833
+ writer.writeLines(`const ${segment} = ${value};`);
834
+ value = segment;
835
+ }
836
+ if (terminal) {
837
+ const useSwitch = terminal.length > 1;
838
+ if (useSwitch) {
839
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
840
+ }
841
+ for (const { key, route: route2 } of terminal) {
842
+ if (useSwitch) {
843
+ writer.write(`case '${key}': `, true);
844
+ } else {
845
+ writer.write(`if (${value}.toLowerCase() === '${key}') `, true);
846
+ }
847
+ writer.write(
848
+ `return ${renderMatch(verb, route2)}; // ${route2.path}
849
+ `
850
+ );
851
+ }
852
+ if (useSwitch) {
853
+ writer.writeBlockEnd("}");
815
854
  }
816
855
  }
856
+ if (dynamic == null ? void 0 : dynamic.route) {
857
+ writer.writeLines(
858
+ `if (${value}) return ${renderMatch(verb, dynamic.route)}; // ${dynamic.route.path}`
859
+ );
860
+ }
817
861
  }
818
- if (dynamic) {
819
- writeRouterVerb(writer, dynamic, verb, level + 1, pathIndex);
862
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
863
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
864
+ writer.writeBlockEnd("} else {").indent++;
865
+ } else {
866
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
867
+ closeCount++;
868
+ }
869
+ let value = `pathname.slice(${offset}, ${index} - 1)`;
870
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic)) {
871
+ const segment = `s${next}`;
872
+ writer.writeLines(`const ${segment} = ${value};`);
873
+ value = segment;
874
+ }
875
+ if (children) {
876
+ const useSwitch = children.length > 1;
877
+ if (useSwitch) {
878
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
879
+ }
880
+ for (const child of children) {
881
+ if (useSwitch) {
882
+ writer.writeBlockStart(`case '${child.key}': {`);
883
+ } else {
884
+ writer.writeBlockStart(
885
+ `if (${value}.toLowerCase() === '${child.key}') {`
886
+ );
887
+ }
888
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
889
+ writeRouterVerb(writer, child, verb, next, nextOffset);
890
+ writer.writeBlockEnd("}");
891
+ }
892
+ if (useSwitch) {
893
+ writer.writeBlockEnd("}");
894
+ }
895
+ }
896
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
897
+ writer.writeBlockStart(`if (${value}) {`);
898
+ writeRouterVerb(writer, dynamic, verb, next, index);
899
+ writer.writeBlockEnd(`}`);
900
+ }
820
901
  }
821
902
  }
903
+ while (closeCount--) {
904
+ writer.writeBlockEnd("}");
905
+ }
822
906
  if (catchAll) {
823
907
  writer.writeLines(
824
- `return ${renderMatch(verb, catchAll, pathIndex)}; // ${catchAll.path}`
908
+ `return ${renderMatch(verb, catchAll, String(offset))}; // ${catchAll.path}`
825
909
  );
826
- if (level > 0) {
827
- writer.indent--;
828
- }
829
910
  } else if (level === 0) {
830
- writer.writeLines("return;");
831
- } else if (useSwitch) {
832
- writer.writeLines("break;").indent--;
833
- } else {
834
- writer.writeBlockEnd("}");
911
+ writer.writeLines("return null;");
835
912
  }
836
913
  }
914
+ function wrapPropertyName(name) {
915
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
916
+ }
837
917
  function renderParamsInfo(params, pathIndex) {
918
+ if (!params.length) {
919
+ return "{}";
920
+ }
838
921
  let result = "";
839
922
  let catchAll = "";
840
- let dynamicLength = "";
923
+ let sep = "{";
841
924
  for (const { name, index } of params) {
842
925
  if (index >= 0) {
843
- result += result ? ", " : "{ ";
844
- result += `'${name}': segments[${index + 1}]`;
845
- dynamicLength += ` + segments[${index + 1}].length`;
846
- } else if (pathIndex && pathIndex >= 0) {
926
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
927
+ sep || (sep = ",");
928
+ } else if (pathIndex) {
847
929
  catchAll = name;
848
930
  }
849
931
  }
850
932
  if (catchAll) {
851
- result += result ? ", " : "{ ";
852
- result += `'${catchAll}': pathname.slice(${pathIndex}${dynamicLength})`;
933
+ result += `${sep} ${wrapPropertyName(
934
+ catchAll
935
+ )}: pathname.slice(${pathIndex})`;
853
936
  }
854
937
  return result ? result + " }" : "{}";
855
938
  }
856
- function renderMatch(verb, { index, params, meta }, pathIndex) {
857
- const tuple = [`${verb}$${index}`];
858
- if (params == null ? void 0 : params.length) {
859
- tuple[1] = renderParamsInfo(params, pathIndex);
939
+ function renderParamsInfoType(params) {
940
+ if (!params.length) {
941
+ return "{}";
860
942
  }
861
- if (meta) {
862
- tuple[2] = `meta$${index}`;
943
+ let result = "{";
944
+ for (const { name } of params) {
945
+ result += ` ${wrapPropertyName(name)}: string;`;
946
+ }
947
+ return result + " }";
948
+ }
949
+ function renderMatch(verb, route, pathIndex) {
950
+ var _a;
951
+ const handler = `${verb}$${route.index}`;
952
+ const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
953
+ const meta = route.meta ? `meta$${route.index}` : "{}";
954
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
955
+ }
956
+ function renderMiddleware(middleware) {
957
+ const writer = createStringWriter();
958
+ writer.writeLines(
959
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
960
+ );
961
+ const imports = writer.branch("imports");
962
+ imports.writeLines(`import { normalize } from 'virtual:marko-run/internal';`);
963
+ writer.writeLines("");
964
+ for (const { id, importPath } of middleware) {
965
+ const importName = `mware${id}`;
966
+ imports.writeLines(`import ${importName} from './${importPath}';`);
967
+ writer.writeLines(`export const mware$${id} = normalize(${importName});`);
968
+ }
969
+ imports.join();
970
+ return writer.end();
971
+ }
972
+ function stripTsExtension(path3) {
973
+ const index = path3.lastIndexOf(".");
974
+ if (index !== -1) {
975
+ const ext = path3.slice(index + 1);
976
+ if (ext.toLowerCase() === "ts") {
977
+ return path3.slice(0, index);
978
+ }
979
+ }
980
+ return path3;
981
+ }
982
+ function renderRouteTypeInfo(routes, pathPrefix = ".") {
983
+ const writer = createStringWriter();
984
+ writer.writeLines(
985
+ `/*
986
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
987
+ Do NOT manually edit this file or your changes will be lost.
988
+ */
989
+ `,
990
+ `import type { HandlerLike, Route } from "@marko/run";
991
+ `
992
+ );
993
+ const routesWriter = writer.branch("types");
994
+ const middlewareRouteTypes = /* @__PURE__ */ new Map();
995
+ for (const route of routes.list) {
996
+ const { meta, handler, params, middleware } = route;
997
+ const routeType = `Route${route.index}`;
998
+ const pathType = `\`${route.path.replace(
999
+ /\/\$(\$?)([^\/]*)/,
1000
+ (_, catchAll, name) => catchAll ? `/:${name || "rest"}*` : `/:${name}`
1001
+ )}\``;
1002
+ const paramsType = params ? renderParamsInfoType(params) : "{}";
1003
+ let metaType = "undefined";
1004
+ if (meta) {
1005
+ metaType = `typeof import('${pathPrefix}/${stripTsExtension(
1006
+ meta.relativePath
1007
+ )}')`;
1008
+ if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
1009
+ metaType += `['default']`;
1010
+ }
1011
+ }
1012
+ if (handler) {
1013
+ writeRouteTypeModule(writer, pathPrefix, handler.relativePath, routeType);
1014
+ }
1015
+ if (middleware) {
1016
+ let i = 0;
1017
+ for (const mw of middleware) {
1018
+ const existing = middlewareRouteTypes.get(mw);
1019
+ if (!existing) {
1020
+ middlewareRouteTypes.set(mw, {
1021
+ routeTypes: [routeType],
1022
+ middleware: middleware.slice(0, i)
1023
+ });
1024
+ } else {
1025
+ existing.routeTypes.push(routeType);
1026
+ }
1027
+ i++;
1028
+ }
1029
+ }
1030
+ routesWriter.writeLines(
1031
+ `interface ${routeType} extends Route<${paramsType}, ${metaType}, ${pathType}> {}`
1032
+ );
863
1033
  }
864
- return `[${tuple.join(", ")}]`;
1034
+ for (const [file, { routeTypes }] of middlewareRouteTypes) {
1035
+ const routeType = routeTypes.length > 1 ? routeTypes.join(" | ") : routeTypes[0];
1036
+ writeRouteTypeModule(writer, pathPrefix, file.relativePath, routeType);
1037
+ }
1038
+ return writer.end();
1039
+ }
1040
+ function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1041
+ writer.writeLines(`
1042
+ declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1043
+ namespace Marko {
1044
+ type CurrentRoute = ${routeType};
1045
+ type Handler<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']> = HandlerLike<CurrentRoute>;
1046
+ function route(handler: Handler): typeof handler;
1047
+ function route<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']>(handler: Handler): typeof handler;
1048
+ }
1049
+ }`);
865
1050
  }
866
1051
 
867
1052
  // src/vite/utils/ast.ts
@@ -908,7 +1093,7 @@ function getExportIdentifiers(astProgramNode) {
908
1093
  var import_cli_table3 = __toESM(require("cli-table3"), 1);
909
1094
  var import_kleur = __toESM(require("kleur"), 1);
910
1095
  var import_gzip_size = require("gzip-size");
911
- var import_pretty_bytes = __toESM(require("pretty-bytes"), 1);
1096
+ var import_human_format = __toESM(require("human-format"), 1);
912
1097
  var HttpVerbColors = {
913
1098
  get: import_kleur.default.green,
914
1099
  post: import_kleur.default.magenta,
@@ -935,7 +1120,7 @@ function logRoutesTable(routes, bundle) {
935
1120
  headings.push("Meta");
936
1121
  colAligns.push("center");
937
1122
  }
938
- headings.push("Size");
1123
+ headings.push("Size/GZip");
939
1124
  colAligns.push("right");
940
1125
  const table = new import_cli_table3.default({
941
1126
  head: headings.map((title) => import_kleur.default.bold(import_kleur.default.white(title.toUpperCase()))),
@@ -966,7 +1151,7 @@ function logRoutesTable(routes, bundle) {
966
1151
  row.push(entryType.join(" -> "));
967
1152
  hasMiddleware && row.push(route.middleware.length || "");
968
1153
  hasMeta && row.push(route.meta ? "\u2713" : "");
969
- row.push(size || { hAlign: "center", content: "-" });
1154
+ row.push(size || "");
970
1155
  table.push(row);
971
1156
  }
972
1157
  }
@@ -982,54 +1167,76 @@ function logRoutesTable(routes, bundle) {
982
1167
  function computeRouteSize(route, bundle) {
983
1168
  if (route.page) {
984
1169
  for (const chunk of Object.values(bundle)) {
985
- if (chunk.type === "chunk" && chunk.modules[route.page.filePath]) {
986
- return computeChunkSize(chunk, bundle);
1170
+ if (chunk.type === "chunk") {
1171
+ for (const key of Object.keys(chunk.modules)) {
1172
+ if (key.startsWith(route.page.filePath)) {
1173
+ return computeChunkSize(chunk, bundle);
1174
+ }
1175
+ }
987
1176
  }
988
1177
  }
989
1178
  }
990
- return 0;
1179
+ return [0, 0];
1180
+ }
1181
+ function byteSize(str) {
1182
+ return new Blob([str]).size;
991
1183
  }
992
1184
  function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
993
1185
  if (chunk.type === "asset") {
994
- return (0, import_gzip_size.gzipSizeSync)(chunk.source);
1186
+ return [
1187
+ byteSize(chunk.source),
1188
+ (0, import_gzip_size.gzipSizeSync)(chunk.source)
1189
+ ];
995
1190
  }
996
- let size = (0, import_gzip_size.gzipSizeSync)(chunk.code);
1191
+ const size = [byteSize(chunk.code), (0, import_gzip_size.gzipSizeSync)(chunk.code)];
997
1192
  for (const id of chunk.imports) {
998
1193
  if (!seen.has(id)) {
999
- size += computeChunkSize(bundle[id], bundle, seen);
1194
+ const [bytes, compBytes] = computeChunkSize(bundle[id], bundle, seen);
1195
+ size[0] += bytes;
1196
+ size[1] += compBytes;
1000
1197
  seen.add(id);
1001
1198
  }
1002
1199
  }
1003
1200
  return size;
1004
1201
  }
1005
- function prettySize(size) {
1006
- const _size = (0, import_pretty_bytes.default)(size, {
1007
- minimumFractionDigits: 1,
1008
- maximumFractionDigits: 1
1009
- }).replace(/\sB$/, " B ");
1010
- if (size < 20 * 1e3)
1011
- return import_kleur.default.green(_size);
1012
- if (size < 50 * 1e3)
1013
- return import_kleur.default.yellow(_size);
1014
- return import_kleur.default.bold(import_kleur.default.red(_size));
1202
+ function prettySize([bytes, compBytes]) {
1203
+ if (bytes <= 0) {
1204
+ return import_kleur.default.gray("0.0 kB");
1205
+ }
1206
+ const [size, prefix] = (0, import_human_format.default)(bytes, { decimals: 1 }).split(/\s+/);
1207
+ const compSize = (0, import_human_format.default)(compBytes, { decimals: 1, prefix, unit: "B" });
1208
+ let str = import_kleur.default.white(size) + import_kleur.default.gray("/");
1209
+ if (compBytes < 20 * 1e3)
1210
+ str += import_kleur.default.green(compSize);
1211
+ else if (compBytes < 50 * 1e3)
1212
+ str += import_kleur.default.yellow(compSize);
1213
+ else
1214
+ import_kleur.default.bold(import_kleur.default.red(compSize));
1215
+ return str;
1015
1216
  }
1016
1217
  function prettyPath(path3) {
1017
1218
  return path3.replace(/\/\$\$(.*)$/, (_, p) => "/" + import_kleur.default.bold(import_kleur.default.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + import_kleur.default.bold(import_kleur.default.dim(`:${p}`)));
1018
1219
  }
1019
1220
 
1020
1221
  // src/vite/utils/config.ts
1021
- var KEY = "__MARKO_SERVE_OPTIONS__";
1022
- function getMarkoServeOptions(viteConfig) {
1023
- return viteConfig[KEY];
1222
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1223
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1224
+ function getConfig(obj, key) {
1225
+ return obj[key];
1024
1226
  }
1025
- function setMarkoServeOptions(viteConfig, options) {
1026
- viteConfig[KEY] = options;
1027
- return viteConfig;
1227
+ function setConfig(obj, key, value) {
1228
+ obj[key] = value;
1229
+ return obj;
1028
1230
  }
1231
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1232
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1233
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1029
1234
 
1030
1235
  // src/vite/plugin.ts
1236
+ var import_url = require("url");
1237
+ var import_meta = {};
1238
+ var __dirname = (0, import_url.fileURLToPath)(new URL(".", import_meta.url));
1031
1239
  var markoExt = ".marko";
1032
- var markoServeFilePrefix2 = "__marko-serve__";
1033
1240
  function isMarkoFile(id) {
1034
1241
  return id.endsWith(markoExt);
1035
1242
  }
@@ -1038,8 +1245,10 @@ function markoServe(opts = {}) {
1038
1245
  let store;
1039
1246
  let root;
1040
1247
  let resolvedRoutesDir;
1248
+ let typesDir;
1041
1249
  let isBuild = false;
1042
1250
  let isSSRBuild = false;
1251
+ let tsConfigExists;
1043
1252
  let ssrEntryFiles;
1044
1253
  let devEntryFile;
1045
1254
  let devServer;
@@ -1048,6 +1257,7 @@ function markoServe(opts = {}) {
1048
1257
  let routeDataFilename = "routes.json";
1049
1258
  let extractVerbs;
1050
1259
  let resolvedConfig;
1260
+ let typesFile;
1051
1261
  let isStale = true;
1052
1262
  let isRendered = false;
1053
1263
  const virtualFiles = /* @__PURE__ */ new Map();
@@ -1055,6 +1265,20 @@ function markoServe(opts = {}) {
1055
1265
  routesBuild: 0,
1056
1266
  routesRender: 0
1057
1267
  };
1268
+ async function writeTypesFile() {
1269
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1270
+ root,
1271
+ "{.tsconfig*,tsconfig*.json}"
1272
+ ))) {
1273
+ const filepath = import_path2.default.join(typesDir, "routes.d.ts");
1274
+ const adapterTypeInfo = (adapter == null ? void 0 : adapter.writeTypeInfo) && await (adapter == null ? void 0 : adapter.writeTypeInfo());
1275
+ const data = renderRouteTypeInfo(routes, import_path2.default.relative(typesDir, routesDir)) + adapterTypeInfo;
1276
+ if (data !== typesFile || !import_fs2.default.existsSync(filepath)) {
1277
+ await ensureDir(typesDir);
1278
+ await import_fs2.default.promises.writeFile(filepath, typesFile = data);
1279
+ }
1280
+ }
1281
+ }
1058
1282
  async function setVirtualFiles(render = false) {
1059
1283
  for (const route of routes.list) {
1060
1284
  if (render && route.handler) {
@@ -1067,31 +1291,37 @@ function markoServe(opts = {}) {
1067
1291
  }
1068
1292
  if (route.page) {
1069
1293
  virtualFiles.set(
1070
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.marko`),
1294
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.marko`),
1071
1295
  render ? renderRouteTemplate(route) : ""
1072
1296
  );
1073
1297
  }
1074
1298
  virtualFiles.set(
1075
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.js`),
1299
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.js`),
1076
1300
  render ? renderRouteEntry(route) : ""
1077
1301
  );
1078
1302
  }
1079
1303
  for (const route of Object.values(routes.special)) {
1080
1304
  virtualFiles.set(
1081
- import_path2.default.join(root, `${markoServeFilePrefix2}special__${route.key}.marko`),
1305
+ import_path2.default.join(root, `${markoRunFilePrefix}special__${route.key}.marko`),
1082
1306
  render ? renderRouteTemplate(route) : ""
1083
1307
  );
1084
1308
  }
1085
1309
  virtualFiles.set(
1086
1310
  "@marko/run/router",
1087
- render ? renderRouter(routes, opts.codegen) : ""
1311
+ render ? renderRouter(routes, {
1312
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1313
+ }) : ""
1314
+ );
1315
+ virtualFiles.set(
1316
+ import_path2.default.join(root, `${markoRunFilePrefix}middleware.js`),
1317
+ render ? renderMiddleware(routes.middleware) : ""
1088
1318
  );
1089
1319
  }
1090
1320
  const buildVirtualFiles = single(async () => {
1091
1321
  const startTime = performance.now();
1092
1322
  routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1093
1323
  times.routesBuild = performance.now() - startTime;
1094
- await setVirtualFiles(false);
1324
+ await Promise.all([writeTypesFile(), setVirtualFiles(false)]);
1095
1325
  isStale = false;
1096
1326
  isRendered = false;
1097
1327
  });
@@ -1105,23 +1335,30 @@ function markoServe(opts = {}) {
1105
1335
  {
1106
1336
  name: "marko-run-vite:pre",
1107
1337
  enforce: "pre",
1108
- async config(config, env) {
1338
+ async config(config2, env) {
1109
1339
  var _a, _b, _c;
1110
- const externalPluginOptions = getMarkoServeOptions(config);
1340
+ const externalPluginOptions = getExternalPluginOptions(config2);
1111
1341
  if (externalPluginOptions) {
1112
1342
  opts = (0, import_vite.mergeConfig)(opts, externalPluginOptions);
1113
1343
  }
1114
- const adapterOptions = await ((_a = adapter == null ? void 0 : adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1115
- if (adapterOptions) {
1116
- opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1344
+ if (adapter) {
1345
+ const externalAdapterConfig = getExternalAdapterOptions(config2);
1346
+ if (externalAdapterConfig && adapter.configure) {
1347
+ adapter.configure(externalAdapterConfig);
1348
+ }
1349
+ const adapterOptions = await ((_a = adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1350
+ if (adapterOptions) {
1351
+ opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1352
+ }
1117
1353
  }
1118
- root = (0, import_vite.normalizePath)(config.root || process.cwd());
1354
+ root = (0, import_vite.normalizePath)(config2.root || process.cwd());
1119
1355
  store = opts.store || new import_vite2.FileStore(
1120
1356
  `marko-serve-vite-${import_crypto.default.createHash("SHA1").update(root).digest("hex")}`
1121
1357
  );
1122
1358
  isBuild = env.command === "build";
1123
- isSSRBuild = isBuild && Boolean((_b = config.build) == null ? void 0 : _b.ssr);
1359
+ isSSRBuild = isBuild && Boolean((_b = config2.build) == null ? void 0 : _b.ssr);
1124
1360
  resolvedRoutesDir = import_path2.default.resolve(root, routesDir);
1361
+ typesDir = import_path2.default.join(root, ".marko-run");
1125
1362
  devEntryFile = import_path2.default.join(root, "index.html");
1126
1363
  let pluginConfig = {
1127
1364
  logLevel: isBuild ? "warn" : void 0,
@@ -1132,18 +1369,18 @@ function markoServe(opts = {}) {
1132
1369
  emptyOutDir: isSSRBuild
1133
1370
  }
1134
1371
  };
1135
- const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config));
1372
+ const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config2));
1136
1373
  if (adapterConfig) {
1137
1374
  pluginConfig = (0, import_vite.mergeConfig)(pluginConfig, adapterConfig);
1138
1375
  }
1139
- return setMarkoServeOptions(pluginConfig, opts);
1376
+ return setExternalPluginOptions(pluginConfig, opts);
1140
1377
  },
1141
- configResolved(config) {
1142
- resolvedConfig = config;
1378
+ configResolved(config2) {
1379
+ resolvedConfig = config2;
1143
1380
  const {
1144
1381
  ssr,
1145
1382
  rollupOptions: { input }
1146
- } = config.build;
1383
+ } = config2.build;
1147
1384
  if (typeof ssr === "string") {
1148
1385
  ssrEntryFiles = [ssr];
1149
1386
  } else if (typeof input === "string") {
@@ -1174,6 +1411,7 @@ function markoServe(opts = {}) {
1174
1411
  if (isStale) {
1175
1412
  for (const id of virtualFiles.keys()) {
1176
1413
  devServer.watcher.emit("change", id);
1414
+ break;
1177
1415
  }
1178
1416
  }
1179
1417
  }
@@ -1203,12 +1441,18 @@ function markoServe(opts = {}) {
1203
1441
  },
1204
1442
  async resolveId(importee, importer, { ssr }) {
1205
1443
  let resolved;
1206
- if (importee.startsWith(virtualFilePrefix)) {
1444
+ if (importee.startsWith(virtualRuntimePrefix)) {
1445
+ return this.resolve(
1446
+ import_path2.default.resolve(__dirname, "../runtime/internal"),
1447
+ importer,
1448
+ { skipSelf: true }
1449
+ );
1450
+ } else if (importee.startsWith(virtualFilePrefix)) {
1207
1451
  importee = import_path2.default.resolve(
1208
1452
  root,
1209
1453
  importee.slice(virtualFilePrefix.length + 1)
1210
1454
  );
1211
- } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoServeFilePrefix2}`)) {
1455
+ } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoRunFilePrefix}`)) {
1212
1456
  importee = import_path2.default.resolve(root, "." + importee);
1213
1457
  }
1214
1458
  if (isStale) {
@@ -1330,20 +1574,49 @@ function single(fn) {
1330
1574
  return result;
1331
1575
  };
1332
1576
  }
1577
+ async function globFileExists(root, pattern) {
1578
+ return new Promise((resolve, reject) => {
1579
+ (0, import_glob.default)(pattern, { root }, (err, matches) => {
1580
+ if (err) {
1581
+ reject(err);
1582
+ }
1583
+ resolve(matches.length > 0);
1584
+ });
1585
+ });
1586
+ }
1587
+ async function ensureDir(dir) {
1588
+ if (!import_fs2.default.existsSync(dir)) {
1589
+ await import_fs2.default.promises.mkdir(dir, { recursive: true });
1590
+ }
1591
+ }
1333
1592
 
1334
1593
  // src/vite/utils/server.ts
1335
1594
  var import_net = __toESM(require("net"), 1);
1336
1595
  var import_child_process = __toESM(require("child_process"), 1);
1337
- async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
1596
+ var import_dotenv = require("dotenv");
1597
+ var import_fs3 = __toESM(require("fs"), 1);
1598
+ async function parseEnv(envFile) {
1599
+ if (import_fs3.default.existsSync(envFile)) {
1600
+ const content = await import_fs3.default.promises.readFile(envFile, "utf8");
1601
+ return (0, import_dotenv.parse)(content);
1602
+ }
1603
+ }
1604
+ function loadEnv(envFile) {
1605
+ (0, import_dotenv.config)({ path: envFile });
1606
+ }
1607
+ async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
1338
1608
  if (port <= 0) {
1339
1609
  port = await getAvailablePort();
1340
1610
  }
1611
+ if (typeof env === "string") {
1612
+ env = await parseEnv(env);
1613
+ }
1341
1614
  const proc = import_child_process.default.spawn(cmd, {
1342
1615
  cwd,
1343
1616
  shell: true,
1344
1617
  stdio: "inherit",
1345
1618
  windowsHide: true,
1346
- env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
1619
+ env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
1347
1620
  });
1348
1621
  const close = () => {
1349
1622
  proc.unref();
@@ -1390,5 +1663,7 @@ function sleep(ms) {
1390
1663
  0 && (module.exports = {
1391
1664
  getAvailablePort,
1392
1665
  isPortInUse,
1666
+ loadEnv,
1667
+ parseEnv,
1393
1668
  spawnServer
1394
1669
  });