@marko/run 0.0.1-beta1 → 0.0.1-beta10

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 (45) hide show
  1. package/README.md +385 -131
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/adapter/default-entry.mjs +14 -5
  4. package/dist/adapter/dev-server.d.ts +1 -1
  5. package/dist/adapter/index.cjs +34 -10
  6. package/dist/adapter/index.d.ts +1 -0
  7. package/dist/adapter/index.js +34 -10
  8. package/dist/adapter/middleware.cjs +237 -0
  9. package/dist/adapter/middleware.d.ts +55 -0
  10. package/dist/adapter/middleware.js +209 -0
  11. package/dist/adapter/polyfill.d.ts +6 -0
  12. package/dist/cli/default.config.mjs +2 -2
  13. package/dist/cli/index.mjs +90 -59
  14. package/dist/runtime/index.cjs +0 -16
  15. package/dist/runtime/index.d.ts +21 -2
  16. package/dist/runtime/index.js +0 -7
  17. package/dist/runtime/internal.cjs +160 -0
  18. package/dist/runtime/internal.d.ts +11 -0
  19. package/dist/runtime/internal.js +126 -0
  20. package/dist/runtime/router.cjs +12 -10
  21. package/dist/runtime/router.d.ts +3 -4
  22. package/dist/runtime/router.js +9 -7
  23. package/dist/runtime/types.d.ts +39 -16
  24. package/dist/vite/codegen/index.d.ts +4 -3
  25. package/dist/vite/codegen/writer.d.ts +1 -1
  26. package/dist/vite/constants.d.ts +3 -2
  27. package/dist/vite/index.cjs +715 -312
  28. package/dist/vite/index.d.ts +4 -3
  29. package/dist/vite/index.js +712 -312
  30. package/dist/vite/types.d.ts +14 -7
  31. package/dist/vite/utils/config.d.ts +5 -3
  32. package/dist/vite/utils/route.d.ts +1 -1
  33. package/dist/vite/utils/server.d.ts +3 -1
  34. package/package.json +39 -17
  35. package/dist/adapter/server-old.d.ts +0 -3
  36. package/dist/adapter/server.d.ts +0 -6
  37. package/dist/adapters/node/index.d.ts +0 -5
  38. package/dist/adapters/node/server.d.ts +0 -3
  39. package/dist/adapters/static/crawler.d.ts +0 -9
  40. package/dist/adapters/static/default-entry.mjs +0 -1
  41. package/dist/adapters/static/index.cjs +0 -371
  42. package/dist/adapters/static/index.d.ts +0 -5
  43. package/dist/adapters/static/index.js +0 -341
  44. package/dist/adapters/static/server.d.ts +0 -3
  45. 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,31 @@ 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
- console.warn(`Duplicate route for path ${path3} -- ignoring`, current);
126
+ const existing = routes.get(key);
127
+ const existingFiles = [existing.handler, existing.page].filter(Boolean).map((f) => f.filePath);
128
+ const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
129
+ throw new Error(`Duplicate routes for path ${path3} were defined. A route established by:
130
+ ${existingFiles}
131
+ collides with
132
+ ${currentFiles.join(" and ")}
133
+ `);
112
134
  } else {
113
- const index = nextId++;
135
+ const index = nextRouteIndex++;
114
136
  routes.set(key, {
115
137
  index,
116
138
  key,
@@ -123,6 +145,9 @@ async function buildRoutes(walk, basePath) {
123
145
  handler,
124
146
  score: scorePath(path3, index)
125
147
  });
148
+ for (const mw of middlewareStack) {
149
+ middleware.add(mw);
150
+ }
126
151
  }
127
152
  }
128
153
  if (isRoot) {
@@ -153,14 +178,16 @@ async function buildRoutes(walk, basePath) {
153
178
  const prevChildren = children;
154
179
  const prevCurrent = current;
155
180
  const prevIsRoot = isRoot;
156
- if (name.charCodeAt(0) === 95 || name.charCodeAt(0) === 40 && name.charCodeAt(name.length - 1) === 41 || name.toLowerCase() === "index") {
181
+ if (name.charCodeAt(0) === 95) {
157
182
  } else {
158
183
  if (name.charCodeAt(0) === 36) {
159
184
  if (name.charCodeAt(1) === 36) {
160
- paramStack.push({
161
- name: name.slice(2) || "*",
162
- index: -1
163
- });
185
+ if (name.length > 2) {
186
+ paramStack.push({
187
+ name: name.slice(2),
188
+ index: -1
189
+ });
190
+ }
164
191
  } else if (name.length > 1) {
165
192
  paramStack.push({
166
193
  name: name.slice(1),
@@ -188,7 +215,8 @@ async function buildRoutes(walk, basePath) {
188
215
  });
189
216
  return {
190
217
  list: [...routes.values()],
191
- special
218
+ special,
219
+ middleware: [...middleware]
192
220
  };
193
221
  }
194
222
 
@@ -238,6 +266,17 @@ function createWriter(sink, options) {
238
266
  let firstOpenIndex = 0;
239
267
  const branches = [];
240
268
  const openWriters = /* @__PURE__ */ new Map();
269
+ function write(data) {
270
+ if (!writer.__isActive) {
271
+ throw new Error("Cannot write to branch that has been joined");
272
+ }
273
+ if (openWriters.size) {
274
+ buffer += data;
275
+ } else {
276
+ sink(data);
277
+ }
278
+ return writer;
279
+ }
241
280
  const writer = {
242
281
  __isActive: true,
243
282
  get indent() {
@@ -254,24 +293,16 @@ function createWriter(sink, options) {
254
293
  }
255
294
  }
256
295
  },
257
- write(data) {
258
- if (!writer.__isActive) {
259
- throw new Error("Cannot write to branch that has been joined");
296
+ write(data, indent = false) {
297
+ if (indent && indentString) {
298
+ write(indentString);
260
299
  }
261
- if (openWriters.size) {
262
- buffer += data;
263
- } else {
264
- sink(data);
265
- }
266
- return writer;
300
+ return write(data);
267
301
  },
268
302
  writeLines(...lines) {
269
303
  for (const line of lines) {
270
304
  if (line) {
271
- if (indentString) {
272
- writer.write(indentString);
273
- }
274
- writer.write(line);
305
+ writer.write(line, true);
275
306
  }
276
307
  writer.write("\n");
277
308
  }
@@ -418,16 +449,13 @@ function hasVerb(route, verb) {
418
449
  }
419
450
 
420
451
  // src/vite/codegen/index.ts
421
- var DefaultCodegenOptions = {
422
- trailingSlashes: "RedirectWithout"
423
- };
424
452
  function renderRouteTemplate(route) {
425
453
  if (!route.page) {
426
454
  throw new Error(`Route ${route.key} has no page to render`);
427
455
  }
428
456
  const writer = createStringWriter();
429
457
  writer.writeLines(
430
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.marko`
458
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.marko`
431
459
  );
432
460
  writer.branch("imports");
433
461
  writer.writeLines("");
@@ -459,47 +487,76 @@ function renderRouteEntry(route) {
459
487
  }
460
488
  const writer = createStringWriter();
461
489
  writer.writeLines(
462
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.js`
490
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.js`
463
491
  );
464
492
  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++;
493
+ const runtimeImports = [];
494
+ if (handler) {
495
+ runtimeImports.push("normalize");
469
496
  }
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}`
497
+ if (handler || middleware.length) {
498
+ runtimeImports.push("call");
499
+ }
500
+ if (!page || verbs.length > 1) {
501
+ runtimeImports.push("noContent");
502
+ }
503
+ if (page) {
504
+ runtimeImports.push("pageResponse");
505
+ }
506
+ if (runtimeImports.length) {
507
+ imports.writeLines(
508
+ `import { ${runtimeImports.join(", ")} } from '${virtualRuntimePrefix}';`
473
509
  );
510
+ }
511
+ if (middleware.length) {
512
+ const names = middleware.map((m) => `mware${m.id}`);
513
+ imports.writeLines(
514
+ `import { ${names.join(
515
+ ", "
516
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
517
+ );
518
+ }
519
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
520
+ writer.writeLines("");
521
+ const names = [];
522
+ for (const verb of handler.verbs) {
523
+ const importName = verb.toUpperCase();
524
+ names.push(importName);
525
+ writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
526
+ }
474
527
  imports.writeLines(
475
528
  `import { ${names.join(", ")} } from './${handler.importPath}';`
476
529
  );
477
530
  }
478
531
  if (page) {
479
532
  imports.writeLines(
480
- `import page from '${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.marko${serverEntryQuery}';`
533
+ `import page from '${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.marko${serverEntryQuery}';`
481
534
  );
482
535
  }
483
536
  if (meta) {
484
537
  imports.writeLines(
485
- `export { default as meta$${index} } from './${meta.importPath}';`
538
+ `export { default as meta${index} } from './${meta.importPath}';`
486
539
  );
487
540
  }
488
- if (!page || verbs.length > 1) {
489
- writer.writeLines("").writeBlockStart(`function create204Response() {`).writeBlockStart(`return new Response(null, {`).writeLines(`status: 204`).writeBlockEnd(`})`).writeBlockEnd(`}`);
490
- }
491
541
  for (const verb of verbs) {
492
542
  writeRouteEntryHandler(writer, route, verb);
493
543
  }
494
544
  return writer.end();
495
545
  }
496
- function writePageResponseContinuation(writer) {
497
- writer.writeBlock(
498
- `return new Response(page.stream(ctx), {`,
499
- ["status: 200,", 'headers: { "content-type": "text/html;charset=UTF-8" }'],
500
- "});"
546
+ function writePageResponse(writer, wrapFn) {
547
+ writer.writeLines(
548
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} pageResponse(page, buildInput());`
501
549
  );
502
550
  }
551
+ function writeMiddleware(writer, middleware, next, wrapFn) {
552
+ if (wrapFn) {
553
+ writer.writeLines(
554
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
555
+ );
556
+ } else {
557
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
558
+ }
559
+ }
503
560
  function writeRouteEntryHandler(writer, route, verb) {
504
561
  var _a;
505
562
  const { key, index, page, handler, middleware } = route;
@@ -507,47 +564,43 @@ function writeRouteEntryHandler(writer, route, verb) {
507
564
  let nextName;
508
565
  let currentName;
509
566
  let hasBody = false;
510
- writer.writeLines("").writeBlockStart(`export async function ${verb}$${index}(ctx) {`);
567
+ writer.writeLines("");
568
+ if (page) {
569
+ writer.writeBlockStart(
570
+ `export async function ${verb}${index}(context, buildInput) {`
571
+ );
572
+ } else {
573
+ writer.writeBlockStart(`export async function ${verb}${index}(context) {`);
574
+ }
511
575
  const continuations = writer.branch("cont");
512
576
  if (page && verb === "get") {
513
- currentName = "createPageResponse";
577
+ currentName = "__page";
514
578
  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("}");
579
+ const name = `${verb}Handler`;
580
+ writePageResponse(continuations, currentName);
518
581
  if (len) {
519
582
  nextName = currentName;
520
- currentName = `__handler$${verb}`;
521
- continuations.writeBlock(
522
- `async function ${currentName}() {`,
523
- [`return await handler$${verb}(ctx, ${nextName});`],
524
- "}"
525
- );
583
+ currentName = `__${name}`;
584
+ writeMiddleware(continuations, name, nextName, currentName);
526
585
  } else {
527
- writer.writeLines(`return await handler$${verb}(ctx, ${currentName})`);
586
+ writeMiddleware(writer, name, currentName);
528
587
  hasBody = true;
529
588
  }
530
589
  } else if (len) {
531
- continuations.writeBlockStart(`async function ${currentName}() {`);
532
- writePageResponseContinuation(continuations);
533
- continuations.writeBlockEnd("}");
590
+ writePageResponse(continuations, currentName);
534
591
  nextName = currentName;
535
592
  } else {
536
- writePageResponseContinuation(continuations);
593
+ writePageResponse(continuations);
537
594
  hasBody = true;
538
595
  }
539
596
  } else if (handler) {
540
- currentName = `__handler$${verb}`;
597
+ const name = `${verb}Handler`;
598
+ currentName = `__${name}`;
599
+ nextName = "noContent";
541
600
  if (len) {
542
- continuations.writeBlock(
543
- `async function ${currentName}() {`,
544
- [`return await handler$${verb}(ctx, create204Response);`],
545
- "}"
546
- );
601
+ writeMiddleware(continuations, name, nextName, currentName);
547
602
  } else {
548
- writer.writeLines(
549
- `return await handler$${verb}(ctx, create204Response);`
550
- );
603
+ writeMiddleware(writer, name, nextName);
551
604
  hasBody = true;
552
605
  }
553
606
  } else {
@@ -555,40 +608,48 @@ function writeRouteEntryHandler(writer, route, verb) {
555
608
  }
556
609
  if (!hasBody) {
557
610
  let i = len;
558
- while (--i) {
611
+ while (i--) {
612
+ const { id } = middleware[i];
613
+ const name = `mware${id}`;
559
614
  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
- );
615
+ currentName = i ? `__${name}` : "";
616
+ writeMiddleware(continuations, name, nextName, currentName);
566
617
  }
567
- writer.writeLines(`return await middleware$1(ctx, ${currentName});`);
568
618
  }
569
619
  continuations.join();
570
620
  writer.writeBlockEnd("}");
571
621
  }
572
- function renderRouter(routes, options = DefaultCodegenOptions) {
622
+ function renderRouter(routes, options = {
623
+ trailingSlashes: "RedirectWithout"
624
+ }) {
573
625
  const writer = createStringWriter();
574
626
  writer.writeLines(`// @marko/run/router`);
575
627
  const imports = writer.branch("imports");
628
+ imports.writeLines(
629
+ `import { NotHandled, NotMatched, createInput } from 'virtual:marko-run/internal';`
630
+ );
576
631
  for (const route of routes.list) {
577
632
  const verbs = getVerbs(route);
578
- const names = verbs.map((verb) => `${verb}$${route.index}`);
579
- route.meta && names.push(`meta$${route.index}`);
633
+ const names = verbs.map((verb) => `${verb}${route.index}`);
634
+ route.meta && names.push(`meta${route.index}`);
580
635
  imports.writeLines(
581
636
  `import { ${names.join(
582
637
  ", "
583
- )} } from '${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.js';`
638
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.js';`
584
639
  );
585
640
  }
586
641
  for (const { key } of Object.values(routes.special)) {
587
642
  imports.writeLines(
588
- `import page$${key} from '${virtualFilePrefix}/${markoServeFilePrefix}special__${key}.marko${serverEntryQuery}';`
643
+ `import page${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
589
644
  );
590
645
  }
591
- writer.writeLines("").writeBlockStart(`function matchRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
646
+ writer.writeLines(``).writeBlockStart(`export function match(method, pathname) {`).writeLines(
647
+ `if (!pathname) {
648
+ pathname = '/';
649
+ } else if (pathname.charAt(0) !== '/') {
650
+ pathname = '/' + pathname;
651
+ }`
652
+ ).writeBlockStart(`switch (method.toLowerCase()) {`);
592
653
  for (const verb of httpVerbs) {
593
654
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
594
655
  if (filteredRoutes.length) {
@@ -598,235 +659,477 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
598
659
  writer.writeBlockEnd("}");
599
660
  }
600
661
  }
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 {`);
606
- }
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("}");
662
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
663
+ writer.write(`
664
+ export async function invoke(route, request, platform, url = new URL(request.url)) {
665
+ const context = {
666
+ url,
667
+ request,
668
+ platform
669
+ };
670
+ const buildInput = createInput(context);
671
+ try {
672
+ if (route) {
673
+ context.params = route.params;
674
+ context.meta = route.meta;
675
+ try {
676
+ const response = await route.handler(context, buildInput);
677
+ if (response) return response;
678
+ } catch (error) {
679
+ if (error === NotHandled) {
680
+ return;
681
+ } else if (error !== NotMatched) {
682
+ throw error;
683
+ }
684
+ }
685
+ `).indent = 2;
686
+ if (routes.special[RoutableFileTypes.NotFound]) {
687
+ writer.write(
688
+ ` } else {
689
+ context.params = {};
690
+ context.meta = {};
691
+ }
692
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
693
+ return new Response(page404.stream(buildInput()), {
694
+ status: 404,
695
+ headers: { "content-type": "text/html;charset=UTF-8" },
696
+ });
697
+ }
698
+ `
699
+ );
700
+ } else {
701
+ writer.writeBlockEnd("}");
628
702
  }
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')) {`
703
+ writer.indent--;
704
+ writer.writeBlockStart(`} catch (error) {`);
705
+ if (routes.special[RoutableFileTypes.Error]) {
706
+ writer.writeBlockStart(
707
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
634
708
  ).writeBlock(
635
- `return new Response(page$500.stream({ request, url, params: {}, error: err }), {`,
709
+ `return new Response(page500.stream(buildInput({ error })), {`,
636
710
  [
637
711
  `status: 500,`,
638
712
  `headers: { "content-type": "text/html;charset=UTF-8" },`
639
713
  ],
640
714
  `});`
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
- }
715
+ ).writeBlockEnd("}");
657
716
  }
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) {
717
+ writer.writeLines(`throw error;`).writeBlockEnd("}").writeBlockEnd("}").write(`
718
+ export async function fetch(request, platform) {
670
719
  try {
671
720
  const url = new URL(request.url);
672
- let { pathname } = url;
673
-
674
- if (pathname !== '/') {
675
- if (pathname.endsWith('/')) {
676
- pathname = pathname.replace(/\\/+$/, '');
677
- `);
721
+ let { pathname } = url;`);
678
722
  switch (options.trailingSlashes) {
679
723
  case "RedirectWithout":
680
724
  writer.write(`
681
- url.pathname = pathname;
682
- return Response.redirect(url.href);
683
- `);
725
+ if (pathname !== '/' && pathname.endsWith('/')) {
726
+ url.pathname = pathname.slice(0, -1);
727
+ return Response.redirect(url);
728
+ }`);
684
729
  break;
685
730
  case "RedirectWith":
686
731
  writer.write(`
687
- } else {
688
- url.pathname = pathname + '/';
689
- return Response.redirect(url.href);
690
- `);
732
+ if (pathname !== '/' && !pathname.endsWith('/')) {
733
+ url.pathname = pathname + '/';
734
+ return Response.redirect(url);
735
+ }`);
691
736
  break;
692
737
  case "RewriteWithout":
693
738
  writer.write(`
694
- url.pathname = pathname;
695
- `);
739
+ if (pathname !== '/' && pathname.endsWith('/')) {
740
+ url.pathname = pathname = pathname.slice(0, -1);
741
+ }`);
696
742
  break;
697
743
  case "RewriteWith":
698
744
  writer.write(`
699
- } else {
700
- url.pathname = pathname + '/';
701
- `);
745
+ if (pathname !== '/' && !pathname.endsWith('/')) {
746
+ url.pathname = pathname = pathname + '/';
747
+ }`);
702
748
  break;
703
749
  }
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) {
713
- const message = import.meta.env.DEV
714
- ? \`Internal Server Error (\${err.message})\`
715
- : "Internal Server Error";
750
+ writer.write(`
716
751
 
717
- return new Response(
718
- JSON.stringify({
719
- error: {
720
- message,
721
- stack: import.meta.env.DEV
722
- ? \`This will only be seen in development mode\\n\\n\${err.stack}\`
723
- : ""
724
- }
725
- }),
726
- {
727
- statusText: message,
728
- status: 500,
729
- }
730
- );
752
+ const route = match(request.method, pathname);
753
+ return await invoke(route, request, platform, url);
754
+ } catch (error) {
755
+ const body = import.meta.env.DEV
756
+ ? error.stack || error.message || "Internal Server Error"
757
+ : null;
758
+ return new Response(body, {
759
+ status: 500
760
+ });
731
761
  }
732
762
  }`);
733
763
  return writer.end();
734
764
  }
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) {
765
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
766
+ const { route, dynamic, catchAll } = trie;
767
+ let closeCount = 0;
768
+ if (level === 0) {
769
+ writer.writeLines(`const len = pathname.length;`);
770
+ if (route) {
741
771
  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) {
762
- writer.writeLines(
763
- `if (len === ${level + 1}) return ${renderMatch(verb, value)}; // ${value.path}`
772
+ `if (len === 1) return ${renderMatch(verb, route)}; // ${route.path}`
764
773
  );
774
+ } else if (trie.static || dynamic) {
775
+ writer.writeBlockStart(`if (len > 1) {`);
776
+ closeCount++;
765
777
  }
766
778
  }
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);
779
+ if (trie.static || dynamic) {
780
+ const next = level + 1;
781
+ const index = `i${next}`;
782
+ let terminal;
783
+ let children;
784
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
785
+ if (trie.static) {
786
+ for (const child of trie.static.values()) {
787
+ if (child.route) {
788
+ (terminal ?? (terminal = [])).push(child);
775
789
  }
776
- writer.writeBlockEnd("}");
777
- } else {
778
- for (const child of children.values()) {
779
- writeRouterVerb(writer, child, verb, level + 1, pathIndex);
790
+ if (child.static || child.dynamic || child.catchAll) {
791
+ (children ?? (children = [])).push(child);
780
792
  }
781
793
  }
782
794
  }
783
- if (dynamic) {
784
- writeRouterVerb(writer, dynamic, verb, level + 1, pathIndex);
795
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
796
+ closeCount++;
797
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
798
+ let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
799
+ if (dynamic == null ? void 0 : dynamic.route) {
800
+ const segment = `s${next}`;
801
+ writer.writeLines(`const ${segment} = ${value};`);
802
+ value = segment;
803
+ }
804
+ if (terminal) {
805
+ const useSwitch = terminal.length > 1;
806
+ if (useSwitch) {
807
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
808
+ }
809
+ for (const { key, route: route2 } of terminal) {
810
+ if (useSwitch) {
811
+ writer.write(`case '${key}': `, true);
812
+ } else {
813
+ writer.write(`if (${value}.toLowerCase() === '${key}') `, true);
814
+ }
815
+ writer.write(
816
+ `return ${renderMatch(verb, route2)}; // ${route2.path}
817
+ `
818
+ );
819
+ }
820
+ if (useSwitch) {
821
+ writer.writeBlockEnd("}");
822
+ }
823
+ }
824
+ if (dynamic == null ? void 0 : dynamic.route) {
825
+ writer.writeLines(
826
+ `if (${value}) return ${renderMatch(verb, dynamic.route)}; // ${dynamic.route.path}`
827
+ );
828
+ }
829
+ }
830
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
831
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
832
+ writer.writeBlockEnd("} else {").indent++;
833
+ } else {
834
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
835
+ closeCount++;
836
+ }
837
+ let value = `pathname.slice(${offset}, ${index} - 1)`;
838
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic)) {
839
+ const segment = `s${next}`;
840
+ writer.writeLines(`const ${segment} = ${value};`);
841
+ value = segment;
842
+ }
843
+ if (children) {
844
+ const useSwitch = children.length > 1;
845
+ if (useSwitch) {
846
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
847
+ }
848
+ for (const child of children) {
849
+ if (useSwitch) {
850
+ writer.writeBlockStart(`case '${child.key}': {`);
851
+ } else {
852
+ writer.writeBlockStart(
853
+ `if (${value}.toLowerCase() === '${child.key}') {`
854
+ );
855
+ }
856
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
857
+ writeRouterVerb(writer, child, verb, next, nextOffset);
858
+ writer.writeBlockEnd("}");
859
+ }
860
+ if (useSwitch) {
861
+ writer.writeBlockEnd("}");
862
+ }
863
+ }
864
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
865
+ writer.writeBlockStart(`if (${value}) {`);
866
+ writeRouterVerb(writer, dynamic, verb, next, index);
867
+ writer.writeBlockEnd(`}`);
868
+ }
785
869
  }
786
870
  }
871
+ while (closeCount--) {
872
+ writer.writeBlockEnd("}");
873
+ }
787
874
  if (catchAll) {
788
875
  writer.writeLines(
789
- `return ${renderMatch(verb, catchAll, pathIndex)}; // ${catchAll.path}`
876
+ `return ${renderMatch(verb, catchAll, String(offset))}; // ${catchAll.path}`
790
877
  );
791
- if (level > 0) {
792
- writer.indent--;
793
- }
794
878
  } else if (level === 0) {
795
- writer.writeLines("return;");
796
- } else if (useSwitch) {
797
- writer.writeLines("break;").indent--;
798
- } else {
799
- writer.writeBlockEnd("}");
879
+ writer.writeLines("return null;");
800
880
  }
801
881
  }
882
+ function wrapPropertyName(name) {
883
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
884
+ }
802
885
  function renderParamsInfo(params, pathIndex) {
886
+ if (!params.length) {
887
+ return "{}";
888
+ }
803
889
  let result = "";
804
890
  let catchAll = "";
805
- let dynamicLength = "";
891
+ let sep = "{";
806
892
  for (const { name, index } of params) {
807
893
  if (index >= 0) {
808
- result += result ? ", " : "{ ";
809
- result += `'${name}': segments[${index + 1}]`;
810
- dynamicLength += ` + segments[${index + 1}].length`;
811
- } else if (pathIndex && pathIndex >= 0) {
894
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
895
+ sep || (sep = ",");
896
+ } else if (pathIndex) {
812
897
  catchAll = name;
813
898
  }
814
899
  }
815
900
  if (catchAll) {
816
- result += result ? ", " : "{ ";
817
- result += `'${catchAll}': pathname.slice(${pathIndex}${dynamicLength})`;
901
+ result += `${sep} ${wrapPropertyName(
902
+ catchAll
903
+ )}: pathname.slice(${pathIndex})`;
818
904
  }
819
905
  return result ? result + " }" : "{}";
820
906
  }
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);
907
+ function renderParamsInfoType(params) {
908
+ if (!params.length) {
909
+ return "{}";
825
910
  }
826
- if (meta) {
827
- tuple[2] = `meta$${index}`;
911
+ let result = "{";
912
+ for (const { name } of params) {
913
+ result += ` ${wrapPropertyName(name)}: string;`;
828
914
  }
829
- return `[${tuple.join(", ")}]`;
915
+ return result + " }";
916
+ }
917
+ function renderMatch(verb, route, pathIndex) {
918
+ var _a;
919
+ const handler = `${verb}${route.index}`;
920
+ const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
921
+ const meta = route.meta ? `meta${route.index}` : "{}";
922
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
923
+ }
924
+ function renderMiddleware(middleware) {
925
+ const writer = createStringWriter();
926
+ writer.writeLines(
927
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
928
+ );
929
+ const imports = writer.branch("imports");
930
+ imports.writeLines(`import { normalize } from 'virtual:marko-run/internal';`);
931
+ writer.writeLines("");
932
+ for (const { id, importPath } of middleware) {
933
+ const importName = `middleware${id}`;
934
+ imports.writeLines(`import ${importName} from './${importPath}';`);
935
+ writer.writeLines(`export const mware${id} = normalize(${importName});`);
936
+ }
937
+ imports.join();
938
+ return writer.end();
939
+ }
940
+ function stripTsExtension(path3) {
941
+ const index = path3.lastIndexOf(".");
942
+ if (index !== -1) {
943
+ const ext = path3.slice(index + 1);
944
+ if (ext.toLowerCase() === "ts") {
945
+ return path3.slice(0, index);
946
+ }
947
+ }
948
+ return path3;
949
+ }
950
+ function renderRouteTypeInfo(routes, pathPrefix = ".", adapterTypes = "") {
951
+ var _a, _b;
952
+ const writer = createStringWriter();
953
+ writer.writeLines(
954
+ `/*
955
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
956
+ Do NOT manually edit this file or your changes will be lost.
957
+ */
958
+ `,
959
+ `import type { HandlerLike, Route as AnyRoute, Context as AnyContext, ValidatePath, ValidateHref } from "@marko/run";`,
960
+ adapterTypes,
961
+ `
962
+
963
+ declare global {
964
+ namespace MarkoRun {`
965
+ );
966
+ const pathsWriter = writer.branch("paths");
967
+ writer.write(`
968
+ type GetablePath<T extends string> = ValidatePath<GetPaths, T>;
969
+ type GetableHref<T extends string> = ValidateHref<GetPaths, T>;
970
+ type PostablePath<T extends string> = ValidatePath<PostPaths, T>;
971
+ type PostableHref<T extends string> = ValidateHref<PostPaths, T>;
972
+ }
973
+ }
974
+ `);
975
+ const routesWriter = writer.branch("types");
976
+ const serverWriter = writer.branch("server");
977
+ const middlewareRouteTypes = /* @__PURE__ */ new Map();
978
+ const layoutRouteTypes = /* @__PURE__ */ new Map();
979
+ const getPaths = /* @__PURE__ */ new Set();
980
+ const postPaths = /* @__PURE__ */ new Set();
981
+ for (const route of routes.list) {
982
+ const { meta, handler, params, middleware, page, layouts } = route;
983
+ const routeType = `Route${route.index}`;
984
+ const pathType = `\`${route.path.replace(
985
+ /\/\$(\$?)([^\/]*)/,
986
+ (_, catchAll, name) => catchAll ? `/:${name || "rest"}*` : `/:${name}`
987
+ )}\``;
988
+ const paramsType = params ? renderParamsInfoType(params) : "{}";
989
+ let metaType = "undefined";
990
+ if (page || handler) {
991
+ const isGet = page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"));
992
+ const isPost = (_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post");
993
+ if (isGet || isPost) {
994
+ const path3 = route.path.replace(
995
+ /\$(\$?)([^/]+)/g,
996
+ (_, s, name) => s ? `\${...${name}}` : `\${${name}}`
997
+ );
998
+ const splatIndex = path3.indexOf("/${...");
999
+ if (splatIndex >= 0) {
1000
+ const path22 = path3.slice(0, splatIndex) || "/";
1001
+ isGet && getPaths.add(path22);
1002
+ isPost && postPaths.add(path22);
1003
+ }
1004
+ isGet && getPaths.add(path3);
1005
+ isPost && postPaths.add(path3);
1006
+ }
1007
+ }
1008
+ if (meta) {
1009
+ metaType = `typeof import('${pathPrefix}/${stripTsExtension(
1010
+ meta.relativePath
1011
+ )}')`;
1012
+ if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
1013
+ metaType += `['default']`;
1014
+ }
1015
+ }
1016
+ if (handler) {
1017
+ writeRouteTypeModule(
1018
+ serverWriter,
1019
+ pathPrefix,
1020
+ handler.relativePath,
1021
+ routeType
1022
+ );
1023
+ }
1024
+ if (page) {
1025
+ writer.writeLines(`
1026
+ declare module '${pathPrefix}/${page.relativePath}' {
1027
+ export interface Input {}
1028
+
1029
+ namespace MarkoRun {
1030
+ type Route = ${routeType};
1031
+ type Context = AnyContext<AnyContext['platform'], Route>;
1032
+ }
1033
+ }`);
1034
+ }
1035
+ if (middleware) {
1036
+ let i = 0;
1037
+ for (const mw of middleware) {
1038
+ const existing = middlewareRouteTypes.get(mw);
1039
+ if (!existing) {
1040
+ middlewareRouteTypes.set(mw, {
1041
+ routeTypes: [routeType],
1042
+ middleware: middleware.slice(0, i)
1043
+ });
1044
+ } else {
1045
+ existing.routeTypes.push(routeType);
1046
+ }
1047
+ i++;
1048
+ }
1049
+ }
1050
+ if (layouts) {
1051
+ for (const layout of layouts) {
1052
+ const existing = layoutRouteTypes.get(layout);
1053
+ if (!existing) {
1054
+ layoutRouteTypes.set(layout, {
1055
+ routeTypes: [routeType]
1056
+ });
1057
+ } else {
1058
+ existing.routeTypes.push(routeType);
1059
+ }
1060
+ }
1061
+ }
1062
+ routesWriter.writeLines(
1063
+ `interface ${routeType} extends AnyRoute<${paramsType}, ${metaType}, ${pathType}> {}`
1064
+ );
1065
+ }
1066
+ pathsWriter.write(" type GetPaths =");
1067
+ for (const path3 of getPaths) {
1068
+ pathsWriter.write(`
1069
+ | '${path3}'`);
1070
+ }
1071
+ pathsWriter.writeLines(";", "");
1072
+ pathsWriter.write(" type PostPaths =");
1073
+ for (const path3 of postPaths) {
1074
+ pathsWriter.write(`
1075
+ | '${path3}'`);
1076
+ }
1077
+ pathsWriter.writeLines(";");
1078
+ pathsWriter.join();
1079
+ for (const [file, { routeTypes }] of middlewareRouteTypes) {
1080
+ writeRouteTypeModule(
1081
+ serverWriter,
1082
+ pathPrefix,
1083
+ file.relativePath,
1084
+ routeTypes.join(" | ")
1085
+ );
1086
+ }
1087
+ for (const [file, { routeTypes }] of layoutRouteTypes) {
1088
+ writer.writeLines(`
1089
+ declare module '${pathPrefix}/${file.relativePath}' {
1090
+ export interface Input {
1091
+ renderBody: Marko.Body;
1092
+ }
1093
+
1094
+ namespace MarkoRun {
1095
+ type Route = ${routeTypes.join(" | ")};
1096
+ type Context = AnyContext<AnyContext['platform'], Route>;
1097
+ }
1098
+ }`);
1099
+ }
1100
+ for (const route of [routes.special["404"], routes.special["500"]]) {
1101
+ if (route && route.page) {
1102
+ writer.write(`
1103
+ declare module '${pathPrefix}/${route.page.relativePath}' {
1104
+ export interface Input {`);
1105
+ if (route.page.type === RoutableFileTypes.Error) {
1106
+ writer.write(`
1107
+ error: unknown;
1108
+ `);
1109
+ }
1110
+ writer.writeLines(`}
1111
+
1112
+ namespace MarkoRun {
1113
+ type Route = AnyRoute;
1114
+ type Context = AnyContext<AnyContext['platform'], Route>;
1115
+ }
1116
+ }`);
1117
+ }
1118
+ }
1119
+ serverWriter.join();
1120
+ return writer.end();
1121
+ }
1122
+ function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1123
+ writer.writeLines(`
1124
+ declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1125
+ namespace MarkoRun {
1126
+ type Route = ${routeType};
1127
+ type Context = AnyContext<AnyContext['platform'], Route>;
1128
+ type Handler<_Params = Route['params'], _Meta = Route['meta']> = HandlerLike<Route>;
1129
+ function route(handler: Handler): typeof handler;
1130
+ function route<_Params = Route['params'], _Meta = Route['meta']>(handler: Handler): typeof handler;
1131
+ }
1132
+ }`);
830
1133
  }
831
1134
 
832
1135
  // src/vite/utils/ast.ts
@@ -873,7 +1176,7 @@ function getExportIdentifiers(astProgramNode) {
873
1176
  import Table from "cli-table3";
874
1177
  import kleur from "kleur";
875
1178
  import { gzipSizeSync } from "gzip-size";
876
- import prettyBytes from "pretty-bytes";
1179
+ import format from "human-format";
877
1180
  var HttpVerbColors = {
878
1181
  get: kleur.green,
879
1182
  post: kleur.magenta,
@@ -900,7 +1203,7 @@ function logRoutesTable(routes, bundle) {
900
1203
  headings.push("Meta");
901
1204
  colAligns.push("center");
902
1205
  }
903
- headings.push("Size");
1206
+ headings.push("Size/GZip");
904
1207
  colAligns.push("right");
905
1208
  const table = new Table({
906
1209
  head: headings.map((title) => kleur.bold(kleur.white(title.toUpperCase()))),
@@ -931,7 +1234,7 @@ function logRoutesTable(routes, bundle) {
931
1234
  row.push(entryType.join(" -> "));
932
1235
  hasMiddleware && row.push(route.middleware.length || "");
933
1236
  hasMeta && row.push(route.meta ? "\u2713" : "");
934
- row.push(size || { hAlign: "center", content: "-" });
1237
+ row.push(size || "");
935
1238
  table.push(row);
936
1239
  }
937
1240
  }
@@ -947,54 +1250,75 @@ function logRoutesTable(routes, bundle) {
947
1250
  function computeRouteSize(route, bundle) {
948
1251
  if (route.page) {
949
1252
  for (const chunk of Object.values(bundle)) {
950
- if (chunk.type === "chunk" && chunk.modules[route.page.filePath]) {
951
- return computeChunkSize(chunk, bundle);
1253
+ if (chunk.type === "chunk") {
1254
+ for (const key of Object.keys(chunk.modules)) {
1255
+ if (key.startsWith(route.page.filePath)) {
1256
+ return computeChunkSize(chunk, bundle);
1257
+ }
1258
+ }
952
1259
  }
953
1260
  }
954
1261
  }
955
- return 0;
1262
+ return [0, 0];
1263
+ }
1264
+ function byteSize(str) {
1265
+ return new Blob([str]).size;
956
1266
  }
957
1267
  function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
958
1268
  if (chunk.type === "asset") {
959
- return gzipSizeSync(chunk.source);
1269
+ return [
1270
+ byteSize(chunk.source),
1271
+ gzipSizeSync(chunk.source)
1272
+ ];
960
1273
  }
961
- let size = gzipSizeSync(chunk.code);
1274
+ const size = [byteSize(chunk.code), gzipSizeSync(chunk.code)];
962
1275
  for (const id of chunk.imports) {
963
1276
  if (!seen.has(id)) {
964
- size += computeChunkSize(bundle[id], bundle, seen);
1277
+ const [bytes, compBytes] = computeChunkSize(bundle[id], bundle, seen);
1278
+ size[0] += bytes;
1279
+ size[1] += compBytes;
965
1280
  seen.add(id);
966
1281
  }
967
1282
  }
968
1283
  return size;
969
1284
  }
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));
1285
+ function prettySize([bytes, compBytes]) {
1286
+ if (bytes <= 0) {
1287
+ return kleur.gray("0.0 kB");
1288
+ }
1289
+ const [size, prefix] = format(bytes, { decimals: 1 }).split(/\s+/);
1290
+ const compSize = format(compBytes, { decimals: 1, prefix, unit: "B" });
1291
+ let str = kleur.white(size) + kleur.gray("/");
1292
+ if (compBytes < 20 * 1e3)
1293
+ str += kleur.green(compSize);
1294
+ else if (compBytes < 50 * 1e3)
1295
+ str += kleur.yellow(compSize);
1296
+ else
1297
+ kleur.bold(kleur.red(compSize));
1298
+ return str;
980
1299
  }
981
1300
  function prettyPath(path3) {
982
1301
  return path3.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur.bold(kleur.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur.bold(kleur.dim(`:${p}`)));
983
1302
  }
984
1303
 
985
1304
  // src/vite/utils/config.ts
986
- var KEY = "__MARKO_SERVE_OPTIONS__";
987
- function getMarkoServeOptions(viteConfig) {
988
- return viteConfig[KEY];
1305
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1306
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1307
+ function getConfig(obj, key) {
1308
+ return obj[key];
989
1309
  }
990
- function setMarkoServeOptions(viteConfig, options) {
991
- viteConfig[KEY] = options;
992
- return viteConfig;
1310
+ function setConfig(obj, key, value) {
1311
+ obj[key] = value;
1312
+ return obj;
993
1313
  }
1314
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1315
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1316
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
994
1317
 
995
1318
  // src/vite/plugin.ts
1319
+ import { fileURLToPath } from "url";
1320
+ var __dirname = fileURLToPath(new URL(".", import.meta.url));
996
1321
  var markoExt = ".marko";
997
- var markoServeFilePrefix2 = "__marko-serve__";
998
1322
  function isMarkoFile(id) {
999
1323
  return id.endsWith(markoExt);
1000
1324
  }
@@ -1003,8 +1327,10 @@ function markoServe(opts = {}) {
1003
1327
  let store;
1004
1328
  let root;
1005
1329
  let resolvedRoutesDir;
1330
+ let typesDir;
1006
1331
  let isBuild = false;
1007
1332
  let isSSRBuild = false;
1333
+ let tsConfigExists;
1008
1334
  let ssrEntryFiles;
1009
1335
  let devEntryFile;
1010
1336
  let devServer;
@@ -1013,6 +1339,7 @@ function markoServe(opts = {}) {
1013
1339
  let routeDataFilename = "routes.json";
1014
1340
  let extractVerbs;
1015
1341
  let resolvedConfig;
1342
+ let typesFile;
1016
1343
  let isStale = true;
1017
1344
  let isRendered = false;
1018
1345
  const virtualFiles = /* @__PURE__ */ new Map();
@@ -1020,6 +1347,20 @@ function markoServe(opts = {}) {
1020
1347
  routesBuild: 0,
1021
1348
  routesRender: 0
1022
1349
  };
1350
+ async function writeTypesFile() {
1351
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1352
+ root,
1353
+ "{.tsconfig*,tsconfig*.json}"
1354
+ ))) {
1355
+ const filepath = path2.join(typesDir, "routes.d.ts");
1356
+ const adapterTypeInfo = (adapter == null ? void 0 : adapter.writeTypeInfo) && await (adapter == null ? void 0 : adapter.writeTypeInfo());
1357
+ const data = renderRouteTypeInfo(routes, path2.relative(typesDir, routesDir), adapterTypeInfo);
1358
+ if (data !== typesFile || !fs2.existsSync(filepath)) {
1359
+ await ensureDir(typesDir);
1360
+ await fs2.promises.writeFile(filepath, typesFile = data);
1361
+ }
1362
+ }
1363
+ }
1023
1364
  async function setVirtualFiles(render = false) {
1024
1365
  for (const route of routes.list) {
1025
1366
  if (render && route.handler) {
@@ -1032,24 +1373,30 @@ function markoServe(opts = {}) {
1032
1373
  }
1033
1374
  if (route.page) {
1034
1375
  virtualFiles.set(
1035
- path2.join(root, `${markoServeFilePrefix2}route__${route.key}.marko`),
1376
+ path2.join(root, `${markoRunFilePrefix}route__${route.key}.marko`),
1036
1377
  render ? renderRouteTemplate(route) : ""
1037
1378
  );
1038
1379
  }
1039
1380
  virtualFiles.set(
1040
- path2.join(root, `${markoServeFilePrefix2}route__${route.key}.js`),
1381
+ path2.join(root, `${markoRunFilePrefix}route__${route.key}.js`),
1041
1382
  render ? renderRouteEntry(route) : ""
1042
1383
  );
1043
1384
  }
1044
1385
  for (const route of Object.values(routes.special)) {
1045
1386
  virtualFiles.set(
1046
- path2.join(root, `${markoServeFilePrefix2}special__${route.key}.marko`),
1387
+ path2.join(root, `${markoRunFilePrefix}special__${route.key}.marko`),
1047
1388
  render ? renderRouteTemplate(route) : ""
1048
1389
  );
1049
1390
  }
1050
1391
  virtualFiles.set(
1051
1392
  "@marko/run/router",
1052
- render ? renderRouter(routes, opts.codegen) : ""
1393
+ render ? renderRouter(routes, {
1394
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1395
+ }) : ""
1396
+ );
1397
+ virtualFiles.set(
1398
+ path2.join(root, `${markoRunFilePrefix}middleware.js`),
1399
+ render ? renderMiddleware(routes.middleware) : ""
1053
1400
  );
1054
1401
  }
1055
1402
  const buildVirtualFiles = single(async () => {
@@ -1063,6 +1410,7 @@ function markoServe(opts = {}) {
1063
1410
  const renderVirtualFiles = single(async () => {
1064
1411
  const startTime = performance.now();
1065
1412
  await setVirtualFiles(true);
1413
+ await writeTypesFile();
1066
1414
  times.routesRender = performance.now() - startTime;
1067
1415
  isRendered = true;
1068
1416
  });
@@ -1070,23 +1418,30 @@ function markoServe(opts = {}) {
1070
1418
  {
1071
1419
  name: "marko-run-vite:pre",
1072
1420
  enforce: "pre",
1073
- async config(config, env) {
1421
+ async config(config2, env) {
1074
1422
  var _a, _b, _c;
1075
- const externalPluginOptions = getMarkoServeOptions(config);
1423
+ const externalPluginOptions = getExternalPluginOptions(config2);
1076
1424
  if (externalPluginOptions) {
1077
1425
  opts = mergeConfig(opts, externalPluginOptions);
1078
1426
  }
1079
- const adapterOptions = await ((_a = adapter == null ? void 0 : adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1080
- if (adapterOptions) {
1081
- opts = mergeConfig(opts, adapterOptions);
1427
+ if (adapter) {
1428
+ const externalAdapterConfig = getExternalAdapterOptions(config2);
1429
+ if (externalAdapterConfig && adapter.configure) {
1430
+ adapter.configure(externalAdapterConfig);
1431
+ }
1432
+ const adapterOptions = await ((_a = adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1433
+ if (adapterOptions) {
1434
+ opts = mergeConfig(opts, adapterOptions);
1435
+ }
1082
1436
  }
1083
- root = normalizePath(config.root || process.cwd());
1437
+ root = normalizePath(config2.root || process.cwd());
1084
1438
  store = opts.store || new FileStore(
1085
1439
  `marko-serve-vite-${crypto.createHash("SHA1").update(root).digest("hex")}`
1086
1440
  );
1087
1441
  isBuild = env.command === "build";
1088
- isSSRBuild = isBuild && Boolean((_b = config.build) == null ? void 0 : _b.ssr);
1442
+ isSSRBuild = isBuild && Boolean((_b = config2.build) == null ? void 0 : _b.ssr);
1089
1443
  resolvedRoutesDir = path2.resolve(root, routesDir);
1444
+ typesDir = path2.join(root, ".marko-run");
1090
1445
  devEntryFile = path2.join(root, "index.html");
1091
1446
  let pluginConfig = {
1092
1447
  logLevel: isBuild ? "warn" : void 0,
@@ -1097,18 +1452,18 @@ function markoServe(opts = {}) {
1097
1452
  emptyOutDir: isSSRBuild
1098
1453
  }
1099
1454
  };
1100
- const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config));
1455
+ const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config2));
1101
1456
  if (adapterConfig) {
1102
1457
  pluginConfig = mergeConfig(pluginConfig, adapterConfig);
1103
1458
  }
1104
- return setMarkoServeOptions(pluginConfig, opts);
1459
+ return setExternalPluginOptions(pluginConfig, opts);
1105
1460
  },
1106
- configResolved(config) {
1107
- resolvedConfig = config;
1461
+ configResolved(config2) {
1462
+ resolvedConfig = config2;
1108
1463
  const {
1109
1464
  ssr,
1110
1465
  rollupOptions: { input }
1111
- } = config.build;
1466
+ } = config2.build;
1112
1467
  if (typeof ssr === "string") {
1113
1468
  ssrEntryFiles = [ssr];
1114
1469
  } else if (typeof input === "string") {
@@ -1139,6 +1494,7 @@ function markoServe(opts = {}) {
1139
1494
  if (isStale) {
1140
1495
  for (const id of virtualFiles.keys()) {
1141
1496
  devServer.watcher.emit("change", id);
1497
+ break;
1142
1498
  }
1143
1499
  }
1144
1500
  }
@@ -1168,12 +1524,18 @@ function markoServe(opts = {}) {
1168
1524
  },
1169
1525
  async resolveId(importee, importer, { ssr }) {
1170
1526
  let resolved;
1171
- if (importee.startsWith(virtualFilePrefix)) {
1527
+ if (importee.startsWith(virtualRuntimePrefix)) {
1528
+ return this.resolve(
1529
+ path2.resolve(__dirname, "../runtime/internal"),
1530
+ importer,
1531
+ { skipSelf: true }
1532
+ );
1533
+ } else if (importee.startsWith(virtualFilePrefix)) {
1172
1534
  importee = path2.resolve(
1173
1535
  root,
1174
1536
  importee.slice(virtualFilePrefix.length + 1)
1175
1537
  );
1176
- } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoServeFilePrefix2}`)) {
1538
+ } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoRunFilePrefix}`)) {
1177
1539
  importee = path2.resolve(root, "." + importee);
1178
1540
  }
1179
1541
  if (isStale) {
@@ -1259,8 +1621,8 @@ async function getVerbsFromFileBuild(context, filePath) {
1259
1621
  if (result) {
1260
1622
  const exportIds = getExportIdentifiers(result.ast);
1261
1623
  for (const id of exportIds) {
1262
- const verb = id === "del" ? "delete" : id;
1263
- if (httpVerbs.includes(verb)) {
1624
+ const verb = id.toLowerCase();
1625
+ if (id === verb.toUpperCase() && httpVerbs.includes(verb)) {
1264
1626
  verbs.push(verb);
1265
1627
  }
1266
1628
  }
@@ -1271,12 +1633,19 @@ async function getVerbsFromFileDev(devServer, filePath) {
1271
1633
  const verbs = [];
1272
1634
  const result = await devServer.transformRequest(filePath, { ssr: true });
1273
1635
  if (result && result.code) {
1274
- const verbMatchReg = /__vite_ssr_exports__,\s+["'](get|post|put|del)["']/g;
1636
+ const verbMatchReg = /__vite_ssr_exports__,\s+["'](GET|POST|PUT|DELETE)["']/gi;
1275
1637
  let match = verbMatchReg.exec(result.code);
1276
1638
  while (match) {
1277
- const verb = match[1] === "del" ? "delete" : match[1];
1639
+ const id = match[1];
1640
+ const verb = id.toLowerCase();
1278
1641
  if (httpVerbs.includes(verb)) {
1279
- verbs.push(verb);
1642
+ if (id === verb.toUpperCase()) {
1643
+ verbs.push(verb);
1644
+ } else {
1645
+ console.warn(
1646
+ `Found export '${id}' in handler ${filePath} which is close to '${verb.toUpperCase()}'. Exported handlers need to be uppercase: GET, POST, PUT or DELETE.`
1647
+ );
1648
+ }
1280
1649
  }
1281
1650
  match = verbMatchReg.exec(result.code);
1282
1651
  }
@@ -1295,20 +1664,49 @@ function single(fn) {
1295
1664
  return result;
1296
1665
  };
1297
1666
  }
1667
+ async function globFileExists(root, pattern) {
1668
+ return new Promise((resolve, reject) => {
1669
+ glob(pattern, { root }, (err, matches) => {
1670
+ if (err) {
1671
+ reject(err);
1672
+ }
1673
+ resolve(matches.length > 0);
1674
+ });
1675
+ });
1676
+ }
1677
+ async function ensureDir(dir) {
1678
+ if (!fs2.existsSync(dir)) {
1679
+ await fs2.promises.mkdir(dir, { recursive: true });
1680
+ }
1681
+ }
1298
1682
 
1299
1683
  // src/vite/utils/server.ts
1300
1684
  import net from "net";
1301
1685
  import cp from "child_process";
1302
- async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
1686
+ import { parse, config } from "dotenv";
1687
+ import fs3 from "fs";
1688
+ async function parseEnv(envFile) {
1689
+ if (fs3.existsSync(envFile)) {
1690
+ const content = await fs3.promises.readFile(envFile, "utf8");
1691
+ return parse(content);
1692
+ }
1693
+ }
1694
+ function loadEnv(envFile) {
1695
+ config({ path: envFile });
1696
+ }
1697
+ async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
1303
1698
  if (port <= 0) {
1304
1699
  port = await getAvailablePort();
1305
1700
  }
1701
+ if (typeof env === "string") {
1702
+ env = await parseEnv(env);
1703
+ }
1306
1704
  const proc = cp.spawn(cmd, {
1307
1705
  cwd,
1308
1706
  shell: true,
1309
1707
  stdio: "inherit",
1310
1708
  windowsHide: true,
1311
- env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
1709
+ env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
1312
1710
  });
1313
1711
  const close = () => {
1314
1712
  proc.unref();
@@ -1355,5 +1753,7 @@ export {
1355
1753
  markoServe as default,
1356
1754
  getAvailablePort,
1357
1755
  isPortInUse,
1756
+ loadEnv,
1757
+ parseEnv,
1358
1758
  spawnServer
1359
1759
  };