@marko/run 0.0.1-beta1 → 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.
Files changed (41) hide show
  1. package/dist/adapter/default-entry.mjs +9 -3
  2. package/dist/adapter/dev-server.d.ts +1 -1
  3. package/dist/adapter/index.cjs +23 -8
  4. package/dist/adapter/index.js +22 -7
  5. package/dist/adapter/middleware.cjs +200 -0
  6. package/dist/adapter/middleware.d.ts +59 -0
  7. package/dist/adapter/middleware.js +169 -0
  8. package/dist/cli/default.config.mjs +1 -1
  9. package/dist/cli/index.mjs +45 -24
  10. package/dist/runtime/index.cjs +0 -16
  11. package/dist/runtime/index.d.ts +10 -2
  12. package/dist/runtime/index.js +0 -7
  13. package/dist/runtime/internal.cjs +148 -0
  14. package/dist/runtime/internal.d.ts +10 -0
  15. package/dist/runtime/internal.js +115 -0
  16. package/dist/runtime/router.cjs +6 -6
  17. package/dist/runtime/router.d.ts +4 -4
  18. package/dist/runtime/router.js +4 -4
  19. package/dist/runtime/types.d.ts +31 -16
  20. package/dist/runtime/utils.d.ts +3 -0
  21. package/dist/vite/codegen/index.d.ts +4 -3
  22. package/dist/vite/codegen/writer.d.ts +1 -1
  23. package/dist/vite/constants.d.ts +3 -2
  24. package/dist/vite/index.cjs +542 -275
  25. package/dist/vite/index.d.ts +4 -3
  26. package/dist/vite/index.js +539 -275
  27. package/dist/vite/types.d.ts +9 -7
  28. package/dist/vite/utils/config.d.ts +2 -2
  29. package/dist/vite/utils/server.d.ts +3 -1
  30. package/package.json +18 -6
  31. package/dist/adapter/server-old.d.ts +0 -3
  32. package/dist/adapter/server.d.ts +0 -6
  33. package/dist/adapters/node/index.d.ts +0 -5
  34. package/dist/adapters/node/server.d.ts +0 -3
  35. package/dist/adapters/static/crawler.d.ts +0 -9
  36. package/dist/adapters/static/default-entry.mjs +0 -1
  37. package/dist/adapters/static/index.cjs +0 -371
  38. package/dist/adapters/static/index.d.ts +0 -5
  39. package/dist/adapters/static/index.js +0 -341
  40. package/dist/adapters/static/server.d.ts +0 -3
  41. package/dist/runtime/request.d.ts +0 -4
@@ -1,13 +1,16 @@
1
1
  // src/vite/plugin.ts
2
2
  import path2 from "path";
3
3
  import crypto from "crypto";
4
+ import fs2 from "fs";
5
+ import glob from "glob";
4
6
  import { mergeConfig, normalizePath } from "vite";
5
7
  import markoVitePlugin, { FileStore } from "@marko/vite";
6
8
 
7
9
  // src/vite/constants.ts
8
- var markoServeFilePrefix = "__marko-serve__";
9
- var virtualFilePrefix = "virtual:marko-serve";
10
+ var markoRunFilePrefix = "__marko-run__";
11
+ var virtualFilePrefix = "virtual:marko-run";
10
12
  var virtualRoutesPrefix = `${virtualFilePrefix}/routes`;
13
+ var virtualRuntimePrefix = `${virtualFilePrefix}/internal`;
11
14
  var httpVerbs = ["get", "post", "put", "delete"];
12
15
  var serverEntryQuery = "?marko-server-entry";
13
16
  var browserEntryQuery = "?marko-browser-entry";
@@ -47,15 +50,20 @@ function isSpecialType(type) {
47
50
  return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
48
51
  }
49
52
  async function buildRoutes(walk, basePath) {
50
- const dirStack = basePath ? basePath.split("/") : [];
53
+ if (basePath) {
54
+ basePath = basePath.replace(/^\/+|\/+$/g, "");
55
+ }
56
+ const dirStack = [];
51
57
  const pathStack = [];
52
58
  const paramStack = [];
53
59
  const layoutsStack = [];
54
60
  const middlewareStack = [];
55
61
  const routes = /* @__PURE__ */ new Map();
56
62
  const special = {};
63
+ const middleware = /* @__PURE__ */ new Set();
57
64
  let isRoot = true;
58
- let nextId = 1;
65
+ let nextFileId = 1;
66
+ let nextRouteIndex = 1;
59
67
  let current;
60
68
  let children = [];
61
69
  await walk({
@@ -83,10 +91,13 @@ async function buildRoutes(walk, basePath) {
83
91
  if (!entries) {
84
92
  current.files.set(type, entries = []);
85
93
  }
94
+ const relativePath = current.originalPath ? `${current.originalPath}/${entry.name}` : entry.name;
86
95
  entries.push({
96
+ id: String(nextFileId++),
87
97
  type,
88
98
  filePath: entry.path,
89
- importPath: `${current.originalPath}/${entry.name}`,
99
+ relativePath,
100
+ importPath: `${basePath}/${relativePath}`,
90
101
  name: entry.name,
91
102
  verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
92
103
  });
@@ -97,20 +108,24 @@ async function buildRoutes(walk, basePath) {
97
108
  return;
98
109
  }
99
110
  const { path: path3, files } = current;
100
- const middleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
101
- const layout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
111
+ const localMiddleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
112
+ const localLayout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
102
113
  const handler = (_c = files.get(RoutableFileTypes.Handler)) == null ? void 0 : _c[0];
103
114
  const page = (_d = files.get(RoutableFileTypes.Page)) == null ? void 0 : _d[0];
104
115
  const middlewareStackLength = middlewareStack.length;
105
116
  const layoutsStackLength = layoutsStack.length;
106
- middleware && middlewareStack.push(middleware);
107
- layout && layoutsStack.push(layout);
117
+ if (localMiddleware) {
118
+ middlewareStack.push(localMiddleware);
119
+ }
120
+ if (localLayout) {
121
+ layoutsStack.push(localLayout);
122
+ }
108
123
  if (handler || page) {
109
124
  const key = path3.replace(/(\$\$?)[^\/]*/g, "$1").replace(/^\/+/, "").replace(/[^a-z0-9_$\/]+/gi, "").replace(/\//g, "__") || "index";
110
125
  if (routes.has(key)) {
111
126
  console.warn(`Duplicate route for path ${path3} -- ignoring`, current);
112
127
  } else {
113
- const index = nextId++;
128
+ const index = nextRouteIndex++;
114
129
  routes.set(key, {
115
130
  index,
116
131
  key,
@@ -123,6 +138,9 @@ async function buildRoutes(walk, basePath) {
123
138
  handler,
124
139
  score: scorePath(path3, index)
125
140
  });
141
+ for (const mw of middlewareStack) {
142
+ middleware.add(mw);
143
+ }
126
144
  }
127
145
  }
128
146
  if (isRoot) {
@@ -158,7 +176,7 @@ async function buildRoutes(walk, basePath) {
158
176
  if (name.charCodeAt(0) === 36) {
159
177
  if (name.charCodeAt(1) === 36) {
160
178
  paramStack.push({
161
- name: name.slice(2) || "*",
179
+ name: name.slice(2) || "rest",
162
180
  index: -1
163
181
  });
164
182
  } else if (name.length > 1) {
@@ -188,7 +206,8 @@ async function buildRoutes(walk, basePath) {
188
206
  });
189
207
  return {
190
208
  list: [...routes.values()],
191
- special
209
+ special,
210
+ middleware: [...middleware]
192
211
  };
193
212
  }
194
213
 
@@ -238,6 +257,17 @@ function createWriter(sink, options) {
238
257
  let firstOpenIndex = 0;
239
258
  const branches = [];
240
259
  const openWriters = /* @__PURE__ */ new Map();
260
+ function write(data) {
261
+ if (!writer.__isActive) {
262
+ throw new Error("Cannot write to branch that has been joined");
263
+ }
264
+ if (openWriters.size) {
265
+ buffer += data;
266
+ } else {
267
+ sink(data);
268
+ }
269
+ return writer;
270
+ }
241
271
  const writer = {
242
272
  __isActive: true,
243
273
  get indent() {
@@ -254,24 +284,16 @@ function createWriter(sink, options) {
254
284
  }
255
285
  }
256
286
  },
257
- write(data) {
258
- if (!writer.__isActive) {
259
- throw new Error("Cannot write to branch that has been joined");
287
+ write(data, indent = false) {
288
+ if (indent && indentString) {
289
+ write(indentString);
260
290
  }
261
- if (openWriters.size) {
262
- buffer += data;
263
- } else {
264
- sink(data);
265
- }
266
- return writer;
291
+ return write(data);
267
292
  },
268
293
  writeLines(...lines) {
269
294
  for (const line of lines) {
270
295
  if (line) {
271
- if (indentString) {
272
- writer.write(indentString);
273
- }
274
- writer.write(line);
296
+ writer.write(line, true);
275
297
  }
276
298
  writer.write("\n");
277
299
  }
@@ -418,16 +440,13 @@ function hasVerb(route, verb) {
418
440
  }
419
441
 
420
442
  // src/vite/codegen/index.ts
421
- var DefaultCodegenOptions = {
422
- trailingSlashes: "RedirectWithout"
423
- };
424
443
  function renderRouteTemplate(route) {
425
444
  if (!route.page) {
426
445
  throw new Error(`Route ${route.key} has no page to render`);
427
446
  }
428
447
  const writer = createStringWriter();
429
448
  writer.writeLines(
430
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.marko`
449
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.marko`
431
450
  );
432
451
  writer.branch("imports");
433
452
  writer.writeLines("");
@@ -459,25 +478,47 @@ function renderRouteEntry(route) {
459
478
  }
460
479
  const writer = createStringWriter();
461
480
  writer.writeLines(
462
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.js`
481
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.js`
463
482
  );
464
483
  const imports = writer.branch("imports");
465
- let i = 1;
466
- for (const { importPath } of middleware) {
467
- imports.writeLines(`import middleware$${i} from './${importPath}';`);
468
- i++;
484
+ const runtimeImports = [];
485
+ if (handler) {
486
+ runtimeImports.push("normalize");
469
487
  }
470
- if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
471
- const names = handler.verbs.map(
472
- (verb) => `${verb === "delete" ? "del" : verb} as handler$${verb}`
488
+ if (handler || middleware.length) {
489
+ runtimeImports.push("call");
490
+ }
491
+ if (!page || verbs.length > 1) {
492
+ runtimeImports.push("noContent");
493
+ }
494
+ if (runtimeImports.length) {
495
+ imports.writeLines(
496
+ `import { ${runtimeImports.join(", ")} } from '${virtualRuntimePrefix}';`
497
+ );
498
+ }
499
+ if (middleware.length) {
500
+ const names = middleware.map((m) => `mware$${m.id}`);
501
+ imports.writeLines(
502
+ `import { ${names.join(
503
+ ", "
504
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
473
505
  );
506
+ }
507
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
508
+ writer.writeLines("");
509
+ const names = [];
510
+ for (const verb of handler.verbs) {
511
+ const name = verb === "delete" ? "del" : verb;
512
+ names.push(name);
513
+ writer.writeLines(`const handler$${name} = normalize(${name});`);
514
+ }
474
515
  imports.writeLines(
475
516
  `import { ${names.join(", ")} } from './${handler.importPath}';`
476
517
  );
477
518
  }
478
519
  if (page) {
479
520
  imports.writeLines(
480
- `import page from '${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.marko${serverEntryQuery}';`
521
+ `import page from '${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.marko${serverEntryQuery}';`
481
522
  );
482
523
  }
483
524
  if (meta) {
@@ -485,21 +526,27 @@ function renderRouteEntry(route) {
485
526
  `export { default as meta$${index} } from './${meta.importPath}';`
486
527
  );
487
528
  }
488
- if (!page || verbs.length > 1) {
489
- writer.writeLines("").writeBlockStart(`function create204Response() {`).writeBlockStart(`return new Response(null, {`).writeLines(`status: 204`).writeBlockEnd(`})`).writeBlockEnd(`}`);
490
- }
491
529
  for (const verb of verbs) {
492
530
  writeRouteEntryHandler(writer, route, verb);
493
531
  }
494
532
  return writer.end();
495
533
  }
496
- function writePageResponseContinuation(writer) {
534
+ function writePageResponse(writer, wrapFn) {
497
535
  writer.writeBlock(
498
- `return new Response(page.stream(ctx), {`,
536
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} new Response(page.stream(buildInput()), {`,
499
537
  ["status: 200,", 'headers: { "content-type": "text/html;charset=UTF-8" }'],
500
538
  "});"
501
539
  );
502
540
  }
541
+ function writeMiddleware(writer, middleware, next, wrapFn) {
542
+ if (wrapFn) {
543
+ writer.writeLines(
544
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
545
+ );
546
+ } else {
547
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
548
+ }
549
+ }
503
550
  function writeRouteEntryHandler(writer, route, verb) {
504
551
  var _a;
505
552
  const { key, index, page, handler, middleware } = route;
@@ -507,47 +554,38 @@ function writeRouteEntryHandler(writer, route, verb) {
507
554
  let nextName;
508
555
  let currentName;
509
556
  let hasBody = false;
510
- writer.writeLines("").writeBlockStart(`export async function ${verb}$${index}(ctx) {`);
557
+ writer.writeLines("").writeBlockStart(
558
+ `export async function ${verb}$${index}(context, buildInput) {`
559
+ );
511
560
  const continuations = writer.branch("cont");
512
561
  if (page && verb === "get") {
513
- currentName = "createPageResponse";
562
+ currentName = "__page";
514
563
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
515
- continuations.writeBlockStart(`async function ${currentName}() {`);
516
- writePageResponseContinuation(continuations);
517
- continuations.writeBlockEnd("}");
564
+ const name = `handler$${verb}`;
565
+ writePageResponse(continuations, currentName);
518
566
  if (len) {
519
567
  nextName = currentName;
520
- currentName = `__handler$${verb}`;
521
- continuations.writeBlock(
522
- `async function ${currentName}() {`,
523
- [`return await handler$${verb}(ctx, ${nextName});`],
524
- "}"
525
- );
568
+ currentName = `__${name}`;
569
+ writeMiddleware(continuations, name, nextName, currentName);
526
570
  } else {
527
- writer.writeLines(`return await handler$${verb}(ctx, ${currentName})`);
571
+ writeMiddleware(writer, name, currentName);
528
572
  hasBody = true;
529
573
  }
530
574
  } else if (len) {
531
- continuations.writeBlockStart(`async function ${currentName}() {`);
532
- writePageResponseContinuation(continuations);
533
- continuations.writeBlockEnd("}");
575
+ writePageResponse(continuations, currentName);
534
576
  nextName = currentName;
535
577
  } else {
536
- writePageResponseContinuation(continuations);
578
+ writePageResponse(continuations);
537
579
  hasBody = true;
538
580
  }
539
581
  } else if (handler) {
540
- currentName = `__handler$${verb}`;
582
+ const name = `handler$${verb}`;
583
+ currentName = `__${name}`;
584
+ nextName = "noContent";
541
585
  if (len) {
542
- continuations.writeBlock(
543
- `async function ${currentName}() {`,
544
- [`return await handler$${verb}(ctx, create204Response);`],
545
- "}"
546
- );
586
+ writeMiddleware(continuations, name, nextName, currentName);
547
587
  } else {
548
- writer.writeLines(
549
- `return await handler$${verb}(ctx, create204Response);`
550
- );
588
+ writeMiddleware(writer, name, nextName);
551
589
  hasBody = true;
552
590
  }
553
591
  } else {
@@ -555,24 +593,26 @@ function writeRouteEntryHandler(writer, route, verb) {
555
593
  }
556
594
  if (!hasBody) {
557
595
  let i = len;
558
- while (--i) {
596
+ while (i--) {
597
+ const { id } = middleware[i];
598
+ const name = `mware$${id}`;
559
599
  nextName = currentName;
560
- currentName = `__middleware${i + 1}`;
561
- continuations.writeLines("").writeBlock(
562
- `async function ${currentName}() {`,
563
- [`return await middleware$${i + 1}(ctx, ${nextName});`],
564
- "}"
565
- );
600
+ currentName = i ? `__${name}` : "";
601
+ writeMiddleware(continuations, name, nextName, currentName);
566
602
  }
567
- writer.writeLines(`return await middleware$1(ctx, ${currentName});`);
568
603
  }
569
604
  continuations.join();
570
605
  writer.writeBlockEnd("}");
571
606
  }
572
- function renderRouter(routes, options = DefaultCodegenOptions) {
607
+ function renderRouter(routes, options = {
608
+ trailingSlashes: "RedirectWithout"
609
+ }) {
573
610
  const writer = createStringWriter();
574
611
  writer.writeLines(`// @marko/run/router`);
575
612
  const imports = writer.branch("imports");
613
+ imports.writeLines(
614
+ `import { RequestNotHandled, RequestNotMatched, createInput } from 'virtual:marko-run/internal';`
615
+ );
576
616
  for (const route of routes.list) {
577
617
  const verbs = getVerbs(route);
578
618
  const names = verbs.map((verb) => `${verb}$${route.index}`);
@@ -580,15 +620,15 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
580
620
  imports.writeLines(
581
621
  `import { ${names.join(
582
622
  ", "
583
- )} } from '${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.js';`
623
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.js';`
584
624
  );
585
625
  }
586
626
  for (const { key } of Object.values(routes.special)) {
587
627
  imports.writeLines(
588
- `import page$${key} from '${virtualFilePrefix}/${markoServeFilePrefix}special__${key}.marko${serverEntryQuery}';`
628
+ `import page$${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
589
629
  );
590
630
  }
591
- writer.writeLines("").writeBlockStart(`function matchRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
631
+ writer.writeLines(``).writeBlockStart(`function findRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
592
632
  for (const verb of httpVerbs) {
593
633
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
594
634
  if (filteredRoutes.length) {
@@ -598,120 +638,105 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
598
638
  writer.writeBlockEnd("}");
599
639
  }
600
640
  }
601
- writer.writeBlockEnd("}").writeBlockEnd("}");
602
- writer.writeLines("").writeBlockStart(`async function invokeRoute(route, url, request) {`);
603
- const errorRoute = routes.special[RoutableFileTypes.Error];
604
- if (errorRoute) {
605
- writer.writeBlockStart(`try {`);
641
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
642
+ writer.write(`
643
+ export function matchRoute(method, pathname) {
644
+ if (!pathname) {
645
+ pathname = '/';
646
+ } else if (pathname.charAt(0) !== '/') {
647
+ pathname = '/' + pathname;
606
648
  }
607
- writer.writeBlock(
608
- `if (route) {`,
609
- [
610
- `const [handler, params = {}, meta] = route;`,
611
- `const response = await handler({ request, url, params, meta });`,
612
- `if (response) return response;`
613
- ],
614
- `}`
615
- );
616
- const notFoundRoute = routes.special[RoutableFileTypes.NotFound];
617
- if (notFoundRoute) {
618
- writer.writeBlockStart(
619
- `if (request.headers.get('Accept')?.includes('text/html')) {`
620
- ).writeBlock(
621
- `return new Response(page$404.stream({ request, url, params: {} }), {`,
622
- [
623
- `status: 404,`,
624
- `headers: { "content-type": "text/html;charset=UTF-8" },`
625
- ],
626
- `});`
627
- ).writeBlockEnd("}");
649
+ return findRoute(method, pathname);
650
+ }
651
+
652
+ export async function invokeRoute(route, context) {
653
+ try {
654
+ const buildInput = createInput(context);
655
+ if (route) {
656
+ context.params = route.params;
657
+ context.meta = route.meta;
658
+ try {
659
+ const response = await route.handler(context, buildInput);
660
+ if (response) return response;
661
+ } catch (error) {
662
+ if (error === RequestNotHandled) {
663
+ return;
664
+ } else if (error !== RequestNotMatched) {
665
+ throw error;
666
+ }
667
+ }
668
+ `).indent = 2;
669
+ if (routes.special[RoutableFileTypes.NotFound]) {
670
+ writer.write(
671
+ ` } else {
672
+ context.params = {};
673
+ context.meta = {};
674
+ }
675
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
676
+ return new Response(page$404.stream(buildInput()), {
677
+ status: 404,
678
+ headers: { "content-type": "text/html;charset=UTF-8" },
679
+ });
680
+ }
681
+ `
682
+ );
683
+ } else {
684
+ writer.writeBlockEnd("}");
628
685
  }
629
- writer.writeLines(`return null;`);
630
- if (errorRoute) {
631
- writer.indent--;
632
- writer.writeBlockStart(`} catch (err) {`).writeBlockStart(
633
- `if (request.headers.get('Accept')?.includes('text/html')) {`
686
+ writer.indent--;
687
+ writer.writeBlockStart(`} catch (error) {`);
688
+ if (routes.special[RoutableFileTypes.Error]) {
689
+ writer.writeBlockStart(
690
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
634
691
  ).writeBlock(
635
- `return new Response(page$500.stream({ request, url, params: {}, error: err }), {`,
692
+ `return new Response(page$500.stream(buildInput({ error })), {`,
636
693
  [
637
694
  `status: 500,`,
638
695
  `headers: { "content-type": "text/html;charset=UTF-8" },`
639
696
  ],
640
697
  `});`
641
- ).writeBlockEnd("}").writeLines(`throw err;`).writeBlockEnd("}");
642
- }
643
- writer.writeBlockEnd("}");
644
- writer.write(`
645
- export function getMatchedRoute(method, url) {
646
- const route = matchRoute(method, url.pathname);
647
- if (route) {
648
- const [handler, params = {}, meta] = route;
649
- return {
650
- handler,
651
- params,
652
- meta,
653
- async invoke(request) {
654
- return await invokeRoute(route, url, request);
655
- }
656
- }
698
+ ).writeBlockEnd("}");
657
699
  }
658
- return null;
659
- }
660
-
661
- export async function handler(context) {
662
- const response = await router(context.request);
663
- //if (response.body) {
664
- // context.waitUntil?.(once(Readable.from(response.body), "end"));
665
- //}
666
- return response;
667
- }
668
-
669
- export async function router(request) {
700
+ writer.writeLines(`throw error;`).writeBlockEnd("}").writeBlockEnd("}").write(`
701
+ export async function router(context) {
670
702
  try {
671
- const url = new URL(request.url);
672
- let { pathname } = url;
673
-
674
- if (pathname !== '/') {
675
- if (pathname.endsWith('/')) {
676
- pathname = pathname.replace(/\\/+$/, '');
677
- `);
703
+ const { url, method } = context;
704
+ let { pathname } = url;`);
678
705
  switch (options.trailingSlashes) {
679
706
  case "RedirectWithout":
680
707
  writer.write(`
681
- url.pathname = pathname;
682
- return Response.redirect(url.href);
683
- `);
708
+ if (pathname !== '/' && pathname.endsWith('/')) {
709
+ url.pathname = pathname.slice(0, -1);
710
+ return Response.redirect(url);
711
+ }`);
684
712
  break;
685
713
  case "RedirectWith":
686
714
  writer.write(`
687
- } else {
688
- url.pathname = pathname + '/';
689
- return Response.redirect(url.href);
690
- `);
715
+ if (pathname !== '/' && !pathname.endsWith('/')) {
716
+ url.pathname = pathname + '/';
717
+ return Response.redirect(url);
718
+ }`);
691
719
  break;
692
720
  case "RewriteWithout":
693
721
  writer.write(`
694
- url.pathname = pathname;
695
- `);
722
+ if (pathname !== '/' && pathname.endsWith('/')) {
723
+ url.pathname = pathname = pathname.slice(0, -1);
724
+ }`);
696
725
  break;
697
726
  case "RewriteWith":
698
727
  writer.write(`
699
- } else {
700
- url.pathname = pathname + '/';
701
- `);
728
+ if (pathname !== '/' && !pathname.endsWith('/')) {
729
+ url.pathname = pathname = pathname + '/';
730
+ }`);
702
731
  break;
703
732
  }
704
- writer.write(`
705
- }
706
- }
707
- const route = matchRoute(request.method, pathname);
708
- return await invokeRoute(route, url, request) || new Response(null, {
709
- statusText: \`Not Found (No route matched \${pathname})\`,
710
- status: 404
711
- });
712
- } catch (err) {
733
+ writer.write(`
734
+
735
+ const route = matchRoute(method, pathname);
736
+ return await invokeRoute(route, context);
737
+ } catch (error) {
713
738
  const message = import.meta.env.DEV
714
- ? \`Internal Server Error (\${err.message})\`
739
+ ? \`Internal Server Error (\${error.message})\`
715
740
  : "Internal Server Error";
716
741
 
717
742
  return new Response(
@@ -719,7 +744,7 @@ export async function router(request) {
719
744
  error: {
720
745
  message,
721
746
  stack: import.meta.env.DEV
722
- ? \`This will only be seen in development mode\\n\\n\${err.stack}\`
747
+ ? \`This will only be seen in development mode\\n\\n\${error.stack}\`
723
748
  : ""
724
749
  }
725
750
  }),
@@ -732,101 +757,259 @@ export async function router(request) {
732
757
  }`);
733
758
  return writer.end();
734
759
  }
735
- function writeRouterVerb(writer, trie, verb, level = 0, pathIndex = 0, useSwitch) {
736
- const { key, route: value, static: children, dynamic, catchAll } = trie;
737
- pathIndex += key.length;
738
- if (level <= 0) {
739
- level = 0;
740
- if (value) {
741
- writer.writeLines(
742
- `if (pathname === '/') return ${renderMatch(verb, value)}; // ${value.path}`
743
- );
744
- }
745
- if (children || dynamic) {
746
- writer.writeLines(
747
- `const segments = pathname.split('/');`,
748
- `const len = segments.length;`
749
- );
750
- }
751
- } else {
752
- if (!key) {
753
- writer.writeBlockStart(`if (segments[${level}]) {`);
754
- } else if (useSwitch) {
755
- writer.writeBlockStart(`case '${key}':`);
756
- } else {
757
- writer.writeBlockStart(
758
- `if (segments[${level}]?.toLowerCase() === '${key}') {`
759
- );
760
- }
761
- if (value) {
760
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
761
+ const { route, dynamic, catchAll } = trie;
762
+ let closeCount = 0;
763
+ if (level === 0) {
764
+ writer.writeLines(`const len = pathname.length;`);
765
+ if (route) {
762
766
  writer.writeLines(
763
- `if (len === ${level + 1}) return ${renderMatch(verb, value)}; // ${value.path}`
767
+ `if (len === 1) return ${renderMatch(verb, route)}; // ${route.path}`
764
768
  );
769
+ } else if (trie.static || dynamic) {
770
+ writer.writeBlockStart(`if (len > 1) {`);
771
+ closeCount++;
765
772
  }
766
773
  }
767
- if (children || dynamic) {
768
- if (children) {
769
- if (children.size > 1) {
770
- writer.writeBlockStart(
771
- `switch(segments[${level + 1}]?.toLowerCase()) {`
772
- );
773
- for (const child of children.values()) {
774
- writeRouterVerb(writer, child, verb, level + 1, pathIndex, true);
774
+ if (trie.static || dynamic) {
775
+ const next = level + 1;
776
+ const index = `i${next}`;
777
+ let terminal;
778
+ let children;
779
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
780
+ if (trie.static) {
781
+ for (const child of trie.static.values()) {
782
+ if (child.route) {
783
+ (terminal ?? (terminal = [])).push(child);
775
784
  }
776
- writer.writeBlockEnd("}");
777
- } else {
778
- for (const child of children.values()) {
779
- writeRouterVerb(writer, child, verb, level + 1, pathIndex);
785
+ if (child.static || child.dynamic || child.catchAll) {
786
+ (children ?? (children = [])).push(child);
787
+ }
788
+ }
789
+ }
790
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
791
+ closeCount++;
792
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
793
+ let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
794
+ if (dynamic == null ? void 0 : dynamic.route) {
795
+ const segment = `s${next}`;
796
+ writer.writeLines(`const ${segment} = ${value};`);
797
+ value = segment;
798
+ }
799
+ if (terminal) {
800
+ const useSwitch = terminal.length > 1;
801
+ if (useSwitch) {
802
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
803
+ }
804
+ for (const { key, route: route2 } of terminal) {
805
+ if (useSwitch) {
806
+ writer.write(`case '${key}': `, true);
807
+ } else {
808
+ writer.write(`if (${value}.toLowerCase() === '${key}') `, true);
809
+ }
810
+ writer.write(
811
+ `return ${renderMatch(verb, route2)}; // ${route2.path}
812
+ `
813
+ );
780
814
  }
815
+ if (useSwitch) {
816
+ writer.writeBlockEnd("}");
817
+ }
818
+ }
819
+ if (dynamic == null ? void 0 : dynamic.route) {
820
+ writer.writeLines(
821
+ `if (${value}) return ${renderMatch(verb, dynamic.route)}; // ${dynamic.route.path}`
822
+ );
781
823
  }
782
824
  }
783
- if (dynamic) {
784
- writeRouterVerb(writer, dynamic, verb, level + 1, pathIndex);
825
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
826
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
827
+ writer.writeBlockEnd("} else {").indent++;
828
+ } else {
829
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
830
+ closeCount++;
831
+ }
832
+ let value = `pathname.slice(${offset}, ${index} - 1)`;
833
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic)) {
834
+ const segment = `s${next}`;
835
+ writer.writeLines(`const ${segment} = ${value};`);
836
+ value = segment;
837
+ }
838
+ if (children) {
839
+ const useSwitch = children.length > 1;
840
+ if (useSwitch) {
841
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
842
+ }
843
+ for (const child of children) {
844
+ if (useSwitch) {
845
+ writer.writeBlockStart(`case '${child.key}': {`);
846
+ } else {
847
+ writer.writeBlockStart(
848
+ `if (${value}.toLowerCase() === '${child.key}') {`
849
+ );
850
+ }
851
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
852
+ writeRouterVerb(writer, child, verb, next, nextOffset);
853
+ writer.writeBlockEnd("}");
854
+ }
855
+ if (useSwitch) {
856
+ writer.writeBlockEnd("}");
857
+ }
858
+ }
859
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
860
+ writer.writeBlockStart(`if (${value}) {`);
861
+ writeRouterVerb(writer, dynamic, verb, next, index);
862
+ writer.writeBlockEnd(`}`);
863
+ }
785
864
  }
786
865
  }
866
+ while (closeCount--) {
867
+ writer.writeBlockEnd("}");
868
+ }
787
869
  if (catchAll) {
788
870
  writer.writeLines(
789
- `return ${renderMatch(verb, catchAll, pathIndex)}; // ${catchAll.path}`
871
+ `return ${renderMatch(verb, catchAll, String(offset))}; // ${catchAll.path}`
790
872
  );
791
- if (level > 0) {
792
- writer.indent--;
793
- }
794
873
  } else if (level === 0) {
795
- writer.writeLines("return;");
796
- } else if (useSwitch) {
797
- writer.writeLines("break;").indent--;
798
- } else {
799
- writer.writeBlockEnd("}");
874
+ writer.writeLines("return null;");
800
875
  }
801
876
  }
877
+ function wrapPropertyName(name) {
878
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
879
+ }
802
880
  function renderParamsInfo(params, pathIndex) {
881
+ if (!params.length) {
882
+ return "{}";
883
+ }
803
884
  let result = "";
804
885
  let catchAll = "";
805
- let dynamicLength = "";
886
+ let sep = "{";
806
887
  for (const { name, index } of params) {
807
888
  if (index >= 0) {
808
- result += result ? ", " : "{ ";
809
- result += `'${name}': segments[${index + 1}]`;
810
- dynamicLength += ` + segments[${index + 1}].length`;
811
- } else if (pathIndex && pathIndex >= 0) {
889
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
890
+ sep || (sep = ",");
891
+ } else if (pathIndex) {
812
892
  catchAll = name;
813
893
  }
814
894
  }
815
895
  if (catchAll) {
816
- result += result ? ", " : "{ ";
817
- result += `'${catchAll}': pathname.slice(${pathIndex}${dynamicLength})`;
896
+ result += `${sep} ${wrapPropertyName(
897
+ catchAll
898
+ )}: pathname.slice(${pathIndex})`;
818
899
  }
819
900
  return result ? result + " }" : "{}";
820
901
  }
821
- function renderMatch(verb, { index, params, meta }, pathIndex) {
822
- const tuple = [`${verb}$${index}`];
823
- if (params == null ? void 0 : params.length) {
824
- tuple[1] = renderParamsInfo(params, pathIndex);
902
+ function renderParamsInfoType(params) {
903
+ if (!params.length) {
904
+ return "{}";
825
905
  }
826
- if (meta) {
827
- tuple[2] = `meta$${index}`;
906
+ let result = "{";
907
+ for (const { name } of params) {
908
+ result += ` ${wrapPropertyName(name)}: string;`;
909
+ }
910
+ return result + " }";
911
+ }
912
+ function renderMatch(verb, route, pathIndex) {
913
+ var _a;
914
+ const handler = `${verb}$${route.index}`;
915
+ const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
916
+ const meta = route.meta ? `meta$${route.index}` : "{}";
917
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
918
+ }
919
+ function renderMiddleware(middleware) {
920
+ const writer = createStringWriter();
921
+ writer.writeLines(
922
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
923
+ );
924
+ const imports = writer.branch("imports");
925
+ imports.writeLines(`import { normalize } from 'virtual:marko-run/internal';`);
926
+ writer.writeLines("");
927
+ for (const { id, importPath } of middleware) {
928
+ const importName = `mware${id}`;
929
+ imports.writeLines(`import ${importName} from './${importPath}';`);
930
+ writer.writeLines(`export const mware$${id} = normalize(${importName});`);
931
+ }
932
+ imports.join();
933
+ return writer.end();
934
+ }
935
+ function stripTsExtension(path3) {
936
+ const index = path3.lastIndexOf(".");
937
+ if (index !== -1) {
938
+ const ext = path3.slice(index + 1);
939
+ if (ext.toLowerCase() === "ts") {
940
+ return path3.slice(0, index);
941
+ }
942
+ }
943
+ return path3;
944
+ }
945
+ function renderRouteTypeInfo(routes, pathPrefix = ".") {
946
+ const writer = createStringWriter();
947
+ writer.writeLines(
948
+ `/*
949
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
950
+ Do NOT manually edit this file or your changes will be lost.
951
+ */
952
+ `,
953
+ `import type { HandlerLike, Route } from "@marko/run";
954
+ `
955
+ );
956
+ const routesWriter = writer.branch("types");
957
+ const middlewareRouteTypes = /* @__PURE__ */ new Map();
958
+ for (const route of routes.list) {
959
+ const { meta, handler, params, middleware } = route;
960
+ const routeType = `Route${route.index}`;
961
+ const pathType = `\`${route.path.replace(
962
+ /\/\$(\$?)([^\/]*)/,
963
+ (_, catchAll, name) => catchAll ? `/:${name || "rest"}*` : `/:${name}`
964
+ )}\``;
965
+ const paramsType = params ? renderParamsInfoType(params) : "{}";
966
+ let metaType = "undefined";
967
+ if (meta) {
968
+ metaType = `typeof import('${pathPrefix}/${stripTsExtension(
969
+ meta.relativePath
970
+ )}')`;
971
+ if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
972
+ metaType += `['default']`;
973
+ }
974
+ }
975
+ if (handler) {
976
+ writeRouteTypeModule(writer, pathPrefix, handler.relativePath, routeType);
977
+ }
978
+ if (middleware) {
979
+ let i = 0;
980
+ for (const mw of middleware) {
981
+ const existing = middlewareRouteTypes.get(mw);
982
+ if (!existing) {
983
+ middlewareRouteTypes.set(mw, {
984
+ routeTypes: [routeType],
985
+ middleware: middleware.slice(0, i)
986
+ });
987
+ } else {
988
+ existing.routeTypes.push(routeType);
989
+ }
990
+ i++;
991
+ }
992
+ }
993
+ routesWriter.writeLines(
994
+ `interface ${routeType} extends Route<${paramsType}, ${metaType}, ${pathType}> {}`
995
+ );
996
+ }
997
+ for (const [file, { routeTypes }] of middlewareRouteTypes) {
998
+ const routeType = routeTypes.length > 1 ? routeTypes.join(" | ") : routeTypes[0];
999
+ writeRouteTypeModule(writer, pathPrefix, file.relativePath, routeType);
1000
+ }
1001
+ return writer.end();
1002
+ }
1003
+ function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1004
+ writer.writeLines(`
1005
+ declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1006
+ namespace Marko {
1007
+ type CurrentRoute = ${routeType};
1008
+ type Handler<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']> = HandlerLike<CurrentRoute>;
1009
+ function route(handler: Handler): typeof handler;
1010
+ function route<_Params = CurrentRoute['params'], _Meta = CurrentRoute['meta']>(handler: Handler): typeof handler;
828
1011
  }
829
- return `[${tuple.join(", ")}]`;
1012
+ }`);
830
1013
  }
831
1014
 
832
1015
  // src/vite/utils/ast.ts
@@ -873,7 +1056,7 @@ function getExportIdentifiers(astProgramNode) {
873
1056
  import Table from "cli-table3";
874
1057
  import kleur from "kleur";
875
1058
  import { gzipSizeSync } from "gzip-size";
876
- import prettyBytes from "pretty-bytes";
1059
+ import format from "human-format";
877
1060
  var HttpVerbColors = {
878
1061
  get: kleur.green,
879
1062
  post: kleur.magenta,
@@ -900,7 +1083,7 @@ function logRoutesTable(routes, bundle) {
900
1083
  headings.push("Meta");
901
1084
  colAligns.push("center");
902
1085
  }
903
- headings.push("Size");
1086
+ headings.push("Size/GZip");
904
1087
  colAligns.push("right");
905
1088
  const table = new Table({
906
1089
  head: headings.map((title) => kleur.bold(kleur.white(title.toUpperCase()))),
@@ -931,7 +1114,7 @@ function logRoutesTable(routes, bundle) {
931
1114
  row.push(entryType.join(" -> "));
932
1115
  hasMiddleware && row.push(route.middleware.length || "");
933
1116
  hasMeta && row.push(route.meta ? "\u2713" : "");
934
- row.push(size || { hAlign: "center", content: "-" });
1117
+ row.push(size || "");
935
1118
  table.push(row);
936
1119
  }
937
1120
  }
@@ -947,36 +1130,52 @@ function logRoutesTable(routes, bundle) {
947
1130
  function computeRouteSize(route, bundle) {
948
1131
  if (route.page) {
949
1132
  for (const chunk of Object.values(bundle)) {
950
- if (chunk.type === "chunk" && chunk.modules[route.page.filePath]) {
951
- return computeChunkSize(chunk, bundle);
1133
+ if (chunk.type === "chunk") {
1134
+ for (const key of Object.keys(chunk.modules)) {
1135
+ if (key.startsWith(route.page.filePath)) {
1136
+ return computeChunkSize(chunk, bundle);
1137
+ }
1138
+ }
952
1139
  }
953
1140
  }
954
1141
  }
955
- return 0;
1142
+ return [0, 0];
1143
+ }
1144
+ function byteSize(str) {
1145
+ return new Blob([str]).size;
956
1146
  }
957
1147
  function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
958
1148
  if (chunk.type === "asset") {
959
- return gzipSizeSync(chunk.source);
1149
+ return [
1150
+ byteSize(chunk.source),
1151
+ gzipSizeSync(chunk.source)
1152
+ ];
960
1153
  }
961
- let size = gzipSizeSync(chunk.code);
1154
+ const size = [byteSize(chunk.code), gzipSizeSync(chunk.code)];
962
1155
  for (const id of chunk.imports) {
963
1156
  if (!seen.has(id)) {
964
- size += computeChunkSize(bundle[id], bundle, seen);
1157
+ const [bytes, compBytes] = computeChunkSize(bundle[id], bundle, seen);
1158
+ size[0] += bytes;
1159
+ size[1] += compBytes;
965
1160
  seen.add(id);
966
1161
  }
967
1162
  }
968
1163
  return size;
969
1164
  }
970
- function prettySize(size) {
971
- const _size = prettyBytes(size, {
972
- minimumFractionDigits: 1,
973
- maximumFractionDigits: 1
974
- }).replace(/\sB$/, " B ");
975
- if (size < 20 * 1e3)
976
- return kleur.green(_size);
977
- if (size < 50 * 1e3)
978
- return kleur.yellow(_size);
979
- return kleur.bold(kleur.red(_size));
1165
+ function prettySize([bytes, compBytes]) {
1166
+ if (bytes <= 0) {
1167
+ return kleur.gray("0.0 kB");
1168
+ }
1169
+ const [size, prefix] = format(bytes, { decimals: 1 }).split(/\s+/);
1170
+ const compSize = format(compBytes, { decimals: 1, prefix, unit: "B" });
1171
+ let str = kleur.white(size) + kleur.gray("/");
1172
+ if (compBytes < 20 * 1e3)
1173
+ str += kleur.green(compSize);
1174
+ else if (compBytes < 50 * 1e3)
1175
+ str += kleur.yellow(compSize);
1176
+ else
1177
+ kleur.bold(kleur.red(compSize));
1178
+ return str;
980
1179
  }
981
1180
  function prettyPath(path3) {
982
1181
  return path3.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur.bold(kleur.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur.bold(kleur.dim(`:${p}`)));
@@ -984,17 +1183,18 @@ function prettyPath(path3) {
984
1183
 
985
1184
  // src/vite/utils/config.ts
986
1185
  var KEY = "__MARKO_SERVE_OPTIONS__";
987
- function getMarkoServeOptions(viteConfig) {
1186
+ function getMarkoRunOptions(viteConfig) {
988
1187
  return viteConfig[KEY];
989
1188
  }
990
- function setMarkoServeOptions(viteConfig, options) {
1189
+ function setMarkoRunOptions(viteConfig, options) {
991
1190
  viteConfig[KEY] = options;
992
1191
  return viteConfig;
993
1192
  }
994
1193
 
995
1194
  // src/vite/plugin.ts
1195
+ import { fileURLToPath } from "url";
1196
+ var __dirname = fileURLToPath(new URL(".", import.meta.url));
996
1197
  var markoExt = ".marko";
997
- var markoServeFilePrefix2 = "__marko-serve__";
998
1198
  function isMarkoFile(id) {
999
1199
  return id.endsWith(markoExt);
1000
1200
  }
@@ -1003,8 +1203,10 @@ function markoServe(opts = {}) {
1003
1203
  let store;
1004
1204
  let root;
1005
1205
  let resolvedRoutesDir;
1206
+ let typesDir;
1006
1207
  let isBuild = false;
1007
1208
  let isSSRBuild = false;
1209
+ let tsConfigExists;
1008
1210
  let ssrEntryFiles;
1009
1211
  let devEntryFile;
1010
1212
  let devServer;
@@ -1013,6 +1215,7 @@ function markoServe(opts = {}) {
1013
1215
  let routeDataFilename = "routes.json";
1014
1216
  let extractVerbs;
1015
1217
  let resolvedConfig;
1218
+ let typesFile;
1016
1219
  let isStale = true;
1017
1220
  let isRendered = false;
1018
1221
  const virtualFiles = /* @__PURE__ */ new Map();
@@ -1020,6 +1223,22 @@ function markoServe(opts = {}) {
1020
1223
  routesBuild: 0,
1021
1224
  routesRender: 0
1022
1225
  };
1226
+ async function writeTypesFile() {
1227
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1228
+ root,
1229
+ "{.tsconfig*,tsconfig*.json}"
1230
+ ))) {
1231
+ const filepath = path2.join(typesDir, "routes.d.ts");
1232
+ const data = renderRouteTypeInfo(
1233
+ routes,
1234
+ path2.relative(typesDir, routesDir)
1235
+ );
1236
+ if (data !== typesFile || !fs2.existsSync(filepath)) {
1237
+ await ensureDir(typesDir);
1238
+ await fs2.promises.writeFile(filepath, typesFile = data);
1239
+ }
1240
+ }
1241
+ }
1023
1242
  async function setVirtualFiles(render = false) {
1024
1243
  for (const route of routes.list) {
1025
1244
  if (render && route.handler) {
@@ -1032,31 +1251,37 @@ function markoServe(opts = {}) {
1032
1251
  }
1033
1252
  if (route.page) {
1034
1253
  virtualFiles.set(
1035
- path2.join(root, `${markoServeFilePrefix2}route__${route.key}.marko`),
1254
+ path2.join(root, `${markoRunFilePrefix}route__${route.key}.marko`),
1036
1255
  render ? renderRouteTemplate(route) : ""
1037
1256
  );
1038
1257
  }
1039
1258
  virtualFiles.set(
1040
- path2.join(root, `${markoServeFilePrefix2}route__${route.key}.js`),
1259
+ path2.join(root, `${markoRunFilePrefix}route__${route.key}.js`),
1041
1260
  render ? renderRouteEntry(route) : ""
1042
1261
  );
1043
1262
  }
1044
1263
  for (const route of Object.values(routes.special)) {
1045
1264
  virtualFiles.set(
1046
- path2.join(root, `${markoServeFilePrefix2}special__${route.key}.marko`),
1265
+ path2.join(root, `${markoRunFilePrefix}special__${route.key}.marko`),
1047
1266
  render ? renderRouteTemplate(route) : ""
1048
1267
  );
1049
1268
  }
1050
1269
  virtualFiles.set(
1051
1270
  "@marko/run/router",
1052
- render ? renderRouter(routes, opts.codegen) : ""
1271
+ render ? renderRouter(routes, {
1272
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1273
+ }) : ""
1274
+ );
1275
+ virtualFiles.set(
1276
+ path2.join(root, `${markoRunFilePrefix}middleware.js`),
1277
+ render ? renderMiddleware(routes.middleware) : ""
1053
1278
  );
1054
1279
  }
1055
1280
  const buildVirtualFiles = single(async () => {
1056
1281
  const startTime = performance.now();
1057
1282
  routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1058
1283
  times.routesBuild = performance.now() - startTime;
1059
- await setVirtualFiles(false);
1284
+ await Promise.all([writeTypesFile(), setVirtualFiles(false)]);
1060
1285
  isStale = false;
1061
1286
  isRendered = false;
1062
1287
  });
@@ -1070,9 +1295,9 @@ function markoServe(opts = {}) {
1070
1295
  {
1071
1296
  name: "marko-run-vite:pre",
1072
1297
  enforce: "pre",
1073
- async config(config, env) {
1298
+ async config(config2, env) {
1074
1299
  var _a, _b, _c;
1075
- const externalPluginOptions = getMarkoServeOptions(config);
1300
+ const externalPluginOptions = getMarkoRunOptions(config2);
1076
1301
  if (externalPluginOptions) {
1077
1302
  opts = mergeConfig(opts, externalPluginOptions);
1078
1303
  }
@@ -1080,13 +1305,14 @@ function markoServe(opts = {}) {
1080
1305
  if (adapterOptions) {
1081
1306
  opts = mergeConfig(opts, adapterOptions);
1082
1307
  }
1083
- root = normalizePath(config.root || process.cwd());
1308
+ root = normalizePath(config2.root || process.cwd());
1084
1309
  store = opts.store || new FileStore(
1085
1310
  `marko-serve-vite-${crypto.createHash("SHA1").update(root).digest("hex")}`
1086
1311
  );
1087
1312
  isBuild = env.command === "build";
1088
- isSSRBuild = isBuild && Boolean((_b = config.build) == null ? void 0 : _b.ssr);
1313
+ isSSRBuild = isBuild && Boolean((_b = config2.build) == null ? void 0 : _b.ssr);
1089
1314
  resolvedRoutesDir = path2.resolve(root, routesDir);
1315
+ typesDir = path2.join(root, ".marko-run");
1090
1316
  devEntryFile = path2.join(root, "index.html");
1091
1317
  let pluginConfig = {
1092
1318
  logLevel: isBuild ? "warn" : void 0,
@@ -1097,18 +1323,18 @@ function markoServe(opts = {}) {
1097
1323
  emptyOutDir: isSSRBuild
1098
1324
  }
1099
1325
  };
1100
- const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config));
1326
+ const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config2));
1101
1327
  if (adapterConfig) {
1102
1328
  pluginConfig = mergeConfig(pluginConfig, adapterConfig);
1103
1329
  }
1104
- return setMarkoServeOptions(pluginConfig, opts);
1330
+ return setMarkoRunOptions(pluginConfig, opts);
1105
1331
  },
1106
- configResolved(config) {
1107
- resolvedConfig = config;
1332
+ configResolved(config2) {
1333
+ resolvedConfig = config2;
1108
1334
  const {
1109
1335
  ssr,
1110
1336
  rollupOptions: { input }
1111
- } = config.build;
1337
+ } = config2.build;
1112
1338
  if (typeof ssr === "string") {
1113
1339
  ssrEntryFiles = [ssr];
1114
1340
  } else if (typeof input === "string") {
@@ -1139,6 +1365,7 @@ function markoServe(opts = {}) {
1139
1365
  if (isStale) {
1140
1366
  for (const id of virtualFiles.keys()) {
1141
1367
  devServer.watcher.emit("change", id);
1368
+ break;
1142
1369
  }
1143
1370
  }
1144
1371
  }
@@ -1168,12 +1395,18 @@ function markoServe(opts = {}) {
1168
1395
  },
1169
1396
  async resolveId(importee, importer, { ssr }) {
1170
1397
  let resolved;
1171
- if (importee.startsWith(virtualFilePrefix)) {
1398
+ if (importee.startsWith(virtualRuntimePrefix)) {
1399
+ return this.resolve(
1400
+ path2.resolve(__dirname, "../runtime/internal"),
1401
+ importer,
1402
+ { skipSelf: true }
1403
+ );
1404
+ } else if (importee.startsWith(virtualFilePrefix)) {
1172
1405
  importee = path2.resolve(
1173
1406
  root,
1174
1407
  importee.slice(virtualFilePrefix.length + 1)
1175
1408
  );
1176
- } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoServeFilePrefix2}`)) {
1409
+ } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoRunFilePrefix}`)) {
1177
1410
  importee = path2.resolve(root, "." + importee);
1178
1411
  }
1179
1412
  if (isStale) {
@@ -1295,20 +1528,49 @@ function single(fn) {
1295
1528
  return result;
1296
1529
  };
1297
1530
  }
1531
+ async function globFileExists(root, pattern) {
1532
+ return new Promise((resolve, reject) => {
1533
+ glob(pattern, { root }, (err, matches) => {
1534
+ if (err) {
1535
+ reject(err);
1536
+ }
1537
+ resolve(matches.length > 0);
1538
+ });
1539
+ });
1540
+ }
1541
+ async function ensureDir(dir) {
1542
+ if (!fs2.existsSync(dir)) {
1543
+ await fs2.promises.mkdir(dir, { recursive: true });
1544
+ }
1545
+ }
1298
1546
 
1299
1547
  // src/vite/utils/server.ts
1300
1548
  import net from "net";
1301
1549
  import cp from "child_process";
1302
- async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
1550
+ import { parse, config } from "dotenv";
1551
+ import fs3 from "fs";
1552
+ async function parseEnv(envFile) {
1553
+ if (fs3.existsSync(envFile)) {
1554
+ const content = await fs3.promises.readFile(envFile, "utf8");
1555
+ return parse(content);
1556
+ }
1557
+ }
1558
+ function loadEnv(envFile) {
1559
+ config({ path: envFile });
1560
+ }
1561
+ async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
1303
1562
  if (port <= 0) {
1304
1563
  port = await getAvailablePort();
1305
1564
  }
1565
+ if (typeof env === "string") {
1566
+ env = await parseEnv(env);
1567
+ }
1306
1568
  const proc = cp.spawn(cmd, {
1307
1569
  cwd,
1308
1570
  shell: true,
1309
1571
  stdio: "inherit",
1310
1572
  windowsHide: true,
1311
- env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
1573
+ env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
1312
1574
  });
1313
1575
  const close = () => {
1314
1576
  proc.unref();
@@ -1355,5 +1617,7 @@ export {
1355
1617
  markoServe as default,
1356
1618
  getAvailablePort,
1357
1619
  isPortInUse,
1620
+ loadEnv,
1621
+ parseEnv,
1358
1622
  spawnServer
1359
1623
  };