@marko/run 0.0.1-beta2 → 0.0.1-beta3

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");
324
+ write(data, indent = false) {
325
+ if (indent && indentString) {
326
+ write(indentString);
295
327
  }
296
- if (openWriters.size) {
297
- buffer += data;
298
- } else {
299
- sink(data);
300
- }
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}';`
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';`
508
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
+ );
815
851
  }
852
+ if (useSwitch) {
853
+ writer.writeBlockEnd("}");
854
+ }
855
+ }
856
+ if (dynamic == null ? void 0 : dynamic.route) {
857
+ writer.writeLines(
858
+ `if (${value}) return ${renderMatch(verb, dynamic.route)}; // ${dynamic.route.path}`
859
+ );
816
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
+ );
1033
+ }
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;
863
1048
  }
864
- return `[${tuple.join(", ")}]`;
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,36 +1167,52 @@ 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}`)));
@@ -1019,17 +1220,19 @@ function prettyPath(path3) {
1019
1220
 
1020
1221
  // src/vite/utils/config.ts
1021
1222
  var KEY = "__MARKO_SERVE_OPTIONS__";
1022
- function getMarkoServeOptions(viteConfig) {
1223
+ function getMarkoRunOptions(viteConfig) {
1023
1224
  return viteConfig[KEY];
1024
1225
  }
1025
- function setMarkoServeOptions(viteConfig, options) {
1226
+ function setMarkoRunOptions(viteConfig, options) {
1026
1227
  viteConfig[KEY] = options;
1027
1228
  return viteConfig;
1028
1229
  }
1029
1230
 
1030
1231
  // src/vite/plugin.ts
1232
+ var import_url = require("url");
1233
+ var import_meta = {};
1234
+ var __dirname = (0, import_url.fileURLToPath)(new URL(".", import_meta.url));
1031
1235
  var markoExt = ".marko";
1032
- var markoServeFilePrefix2 = "__marko-serve__";
1033
1236
  function isMarkoFile(id) {
1034
1237
  return id.endsWith(markoExt);
1035
1238
  }
@@ -1038,8 +1241,10 @@ function markoServe(opts = {}) {
1038
1241
  let store;
1039
1242
  let root;
1040
1243
  let resolvedRoutesDir;
1244
+ let typesDir;
1041
1245
  let isBuild = false;
1042
1246
  let isSSRBuild = false;
1247
+ let tsConfigExists;
1043
1248
  let ssrEntryFiles;
1044
1249
  let devEntryFile;
1045
1250
  let devServer;
@@ -1048,6 +1253,7 @@ function markoServe(opts = {}) {
1048
1253
  let routeDataFilename = "routes.json";
1049
1254
  let extractVerbs;
1050
1255
  let resolvedConfig;
1256
+ let typesFile;
1051
1257
  let isStale = true;
1052
1258
  let isRendered = false;
1053
1259
  const virtualFiles = /* @__PURE__ */ new Map();
@@ -1055,6 +1261,22 @@ function markoServe(opts = {}) {
1055
1261
  routesBuild: 0,
1056
1262
  routesRender: 0
1057
1263
  };
1264
+ async function writeTypesFile() {
1265
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1266
+ root,
1267
+ "{.tsconfig*,tsconfig*.json}"
1268
+ ))) {
1269
+ const filepath = import_path2.default.join(typesDir, "routes.d.ts");
1270
+ const data = renderRouteTypeInfo(
1271
+ routes,
1272
+ import_path2.default.relative(typesDir, routesDir)
1273
+ );
1274
+ if (data !== typesFile || !import_fs2.default.existsSync(filepath)) {
1275
+ await ensureDir(typesDir);
1276
+ await import_fs2.default.promises.writeFile(filepath, typesFile = data);
1277
+ }
1278
+ }
1279
+ }
1058
1280
  async function setVirtualFiles(render = false) {
1059
1281
  for (const route of routes.list) {
1060
1282
  if (render && route.handler) {
@@ -1067,31 +1289,37 @@ function markoServe(opts = {}) {
1067
1289
  }
1068
1290
  if (route.page) {
1069
1291
  virtualFiles.set(
1070
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.marko`),
1292
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.marko`),
1071
1293
  render ? renderRouteTemplate(route) : ""
1072
1294
  );
1073
1295
  }
1074
1296
  virtualFiles.set(
1075
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.js`),
1297
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.js`),
1076
1298
  render ? renderRouteEntry(route) : ""
1077
1299
  );
1078
1300
  }
1079
1301
  for (const route of Object.values(routes.special)) {
1080
1302
  virtualFiles.set(
1081
- import_path2.default.join(root, `${markoServeFilePrefix2}special__${route.key}.marko`),
1303
+ import_path2.default.join(root, `${markoRunFilePrefix}special__${route.key}.marko`),
1082
1304
  render ? renderRouteTemplate(route) : ""
1083
1305
  );
1084
1306
  }
1085
1307
  virtualFiles.set(
1086
1308
  "@marko/run/router",
1087
- render ? renderRouter(routes, opts.codegen) : ""
1309
+ render ? renderRouter(routes, {
1310
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1311
+ }) : ""
1312
+ );
1313
+ virtualFiles.set(
1314
+ import_path2.default.join(root, `${markoRunFilePrefix}middleware.js`),
1315
+ render ? renderMiddleware(routes.middleware) : ""
1088
1316
  );
1089
1317
  }
1090
1318
  const buildVirtualFiles = single(async () => {
1091
1319
  const startTime = performance.now();
1092
1320
  routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1093
1321
  times.routesBuild = performance.now() - startTime;
1094
- await setVirtualFiles(false);
1322
+ await Promise.all([writeTypesFile(), setVirtualFiles(false)]);
1095
1323
  isStale = false;
1096
1324
  isRendered = false;
1097
1325
  });
@@ -1105,9 +1333,9 @@ function markoServe(opts = {}) {
1105
1333
  {
1106
1334
  name: "marko-run-vite:pre",
1107
1335
  enforce: "pre",
1108
- async config(config, env) {
1336
+ async config(config2, env) {
1109
1337
  var _a, _b, _c;
1110
- const externalPluginOptions = getMarkoServeOptions(config);
1338
+ const externalPluginOptions = getMarkoRunOptions(config2);
1111
1339
  if (externalPluginOptions) {
1112
1340
  opts = (0, import_vite.mergeConfig)(opts, externalPluginOptions);
1113
1341
  }
@@ -1115,13 +1343,14 @@ function markoServe(opts = {}) {
1115
1343
  if (adapterOptions) {
1116
1344
  opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1117
1345
  }
1118
- root = (0, import_vite.normalizePath)(config.root || process.cwd());
1346
+ root = (0, import_vite.normalizePath)(config2.root || process.cwd());
1119
1347
  store = opts.store || new import_vite2.FileStore(
1120
1348
  `marko-serve-vite-${import_crypto.default.createHash("SHA1").update(root).digest("hex")}`
1121
1349
  );
1122
1350
  isBuild = env.command === "build";
1123
- isSSRBuild = isBuild && Boolean((_b = config.build) == null ? void 0 : _b.ssr);
1351
+ isSSRBuild = isBuild && Boolean((_b = config2.build) == null ? void 0 : _b.ssr);
1124
1352
  resolvedRoutesDir = import_path2.default.resolve(root, routesDir);
1353
+ typesDir = import_path2.default.join(root, ".marko-run");
1125
1354
  devEntryFile = import_path2.default.join(root, "index.html");
1126
1355
  let pluginConfig = {
1127
1356
  logLevel: isBuild ? "warn" : void 0,
@@ -1132,18 +1361,18 @@ function markoServe(opts = {}) {
1132
1361
  emptyOutDir: isSSRBuild
1133
1362
  }
1134
1363
  };
1135
- const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config));
1364
+ const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config2));
1136
1365
  if (adapterConfig) {
1137
1366
  pluginConfig = (0, import_vite.mergeConfig)(pluginConfig, adapterConfig);
1138
1367
  }
1139
- return setMarkoServeOptions(pluginConfig, opts);
1368
+ return setMarkoRunOptions(pluginConfig, opts);
1140
1369
  },
1141
- configResolved(config) {
1142
- resolvedConfig = config;
1370
+ configResolved(config2) {
1371
+ resolvedConfig = config2;
1143
1372
  const {
1144
1373
  ssr,
1145
1374
  rollupOptions: { input }
1146
- } = config.build;
1375
+ } = config2.build;
1147
1376
  if (typeof ssr === "string") {
1148
1377
  ssrEntryFiles = [ssr];
1149
1378
  } else if (typeof input === "string") {
@@ -1174,6 +1403,7 @@ function markoServe(opts = {}) {
1174
1403
  if (isStale) {
1175
1404
  for (const id of virtualFiles.keys()) {
1176
1405
  devServer.watcher.emit("change", id);
1406
+ break;
1177
1407
  }
1178
1408
  }
1179
1409
  }
@@ -1203,12 +1433,18 @@ function markoServe(opts = {}) {
1203
1433
  },
1204
1434
  async resolveId(importee, importer, { ssr }) {
1205
1435
  let resolved;
1206
- if (importee.startsWith(virtualFilePrefix)) {
1436
+ if (importee.startsWith(virtualRuntimePrefix)) {
1437
+ return this.resolve(
1438
+ import_path2.default.resolve(__dirname, "../runtime/internal"),
1439
+ importer,
1440
+ { skipSelf: true }
1441
+ );
1442
+ } else if (importee.startsWith(virtualFilePrefix)) {
1207
1443
  importee = import_path2.default.resolve(
1208
1444
  root,
1209
1445
  importee.slice(virtualFilePrefix.length + 1)
1210
1446
  );
1211
- } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoServeFilePrefix2}`)) {
1447
+ } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoRunFilePrefix}`)) {
1212
1448
  importee = import_path2.default.resolve(root, "." + importee);
1213
1449
  }
1214
1450
  if (isStale) {
@@ -1330,20 +1566,49 @@ function single(fn) {
1330
1566
  return result;
1331
1567
  };
1332
1568
  }
1569
+ async function globFileExists(root, pattern) {
1570
+ return new Promise((resolve, reject) => {
1571
+ (0, import_glob.default)(pattern, { root }, (err, matches) => {
1572
+ if (err) {
1573
+ reject(err);
1574
+ }
1575
+ resolve(matches.length > 0);
1576
+ });
1577
+ });
1578
+ }
1579
+ async function ensureDir(dir) {
1580
+ if (!import_fs2.default.existsSync(dir)) {
1581
+ await import_fs2.default.promises.mkdir(dir, { recursive: true });
1582
+ }
1583
+ }
1333
1584
 
1334
1585
  // src/vite/utils/server.ts
1335
1586
  var import_net = __toESM(require("net"), 1);
1336
1587
  var import_child_process = __toESM(require("child_process"), 1);
1337
- async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
1588
+ var import_dotenv = require("dotenv");
1589
+ var import_fs3 = __toESM(require("fs"), 1);
1590
+ async function parseEnv(envFile) {
1591
+ if (import_fs3.default.existsSync(envFile)) {
1592
+ const content = await import_fs3.default.promises.readFile(envFile, "utf8");
1593
+ return (0, import_dotenv.parse)(content);
1594
+ }
1595
+ }
1596
+ function loadEnv(envFile) {
1597
+ (0, import_dotenv.config)({ path: envFile });
1598
+ }
1599
+ async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
1338
1600
  if (port <= 0) {
1339
1601
  port = await getAvailablePort();
1340
1602
  }
1603
+ if (typeof env === "string") {
1604
+ env = await parseEnv(env);
1605
+ }
1341
1606
  const proc = import_child_process.default.spawn(cmd, {
1342
1607
  cwd,
1343
1608
  shell: true,
1344
1609
  stdio: "inherit",
1345
1610
  windowsHide: true,
1346
- env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
1611
+ env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
1347
1612
  });
1348
1613
  const close = () => {
1349
1614
  proc.unref();
@@ -1390,5 +1655,7 @@ function sleep(ms) {
1390
1655
  0 && (module.exports = {
1391
1656
  getAvailablePort,
1392
1657
  isPortInUse,
1658
+ loadEnv,
1659
+ parseEnv,
1393
1660
  spawnServer
1394
1661
  });