@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
@@ -29,6 +29,8 @@ __export(vite_exports, {
29
29
  default: () => markoServe,
30
30
  getAvailablePort: () => getAvailablePort,
31
31
  isPortInUse: () => isPortInUse,
32
+ loadEnv: () => loadEnv,
33
+ parseEnv: () => parseEnv,
32
34
  spawnServer: () => spawnServer
33
35
  });
34
36
  module.exports = __toCommonJS(vite_exports);
@@ -36,13 +38,16 @@ module.exports = __toCommonJS(vite_exports);
36
38
  // src/vite/plugin.ts
37
39
  var import_path2 = __toESM(require("path"), 1);
38
40
  var import_crypto = __toESM(require("crypto"), 1);
41
+ var import_fs2 = __toESM(require("fs"), 1);
42
+ var import_glob = __toESM(require("glob"), 1);
39
43
  var import_vite = require("vite");
40
44
  var import_vite2 = __toESM(require("@marko/vite"), 1);
41
45
 
42
46
  // src/vite/constants.ts
43
- var markoServeFilePrefix = "__marko-serve__";
44
- var virtualFilePrefix = "virtual:marko-serve";
47
+ var markoRunFilePrefix = "__marko-run__";
48
+ var virtualFilePrefix = "virtual:marko-run";
45
49
  var virtualRoutesPrefix = `${virtualFilePrefix}/routes`;
50
+ var virtualRuntimePrefix = `${virtualFilePrefix}/internal`;
46
51
  var httpVerbs = ["get", "post", "put", "delete"];
47
52
  var serverEntryQuery = "?marko-server-entry";
48
53
  var browserEntryQuery = "?marko-browser-entry";
@@ -82,15 +87,20 @@ function isSpecialType(type) {
82
87
  return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
83
88
  }
84
89
  async function buildRoutes(walk, basePath) {
85
- const dirStack = basePath ? basePath.split("/") : [];
90
+ if (basePath) {
91
+ basePath = basePath.replace(/^\/+|\/+$/g, "");
92
+ }
93
+ const dirStack = [];
86
94
  const pathStack = [];
87
95
  const paramStack = [];
88
96
  const layoutsStack = [];
89
97
  const middlewareStack = [];
90
98
  const routes = /* @__PURE__ */ new Map();
91
99
  const special = {};
100
+ const middleware = /* @__PURE__ */ new Set();
92
101
  let isRoot = true;
93
- let nextId = 1;
102
+ let nextFileId = 1;
103
+ let nextRouteIndex = 1;
94
104
  let current;
95
105
  let children = [];
96
106
  await walk({
@@ -118,10 +128,13 @@ async function buildRoutes(walk, basePath) {
118
128
  if (!entries) {
119
129
  current.files.set(type, entries = []);
120
130
  }
131
+ const relativePath = current.originalPath ? `${current.originalPath}/${entry.name}` : entry.name;
121
132
  entries.push({
133
+ id: String(nextFileId++),
122
134
  type,
123
135
  filePath: entry.path,
124
- importPath: `${current.originalPath}/${entry.name}`,
136
+ relativePath,
137
+ importPath: `${basePath}/${relativePath}`,
125
138
  name: entry.name,
126
139
  verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
127
140
  });
@@ -132,20 +145,31 @@ async function buildRoutes(walk, basePath) {
132
145
  return;
133
146
  }
134
147
  const { path: path3, files } = current;
135
- const middleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
136
- const layout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
148
+ const localMiddleware = (_a = files.get(RoutableFileTypes.Middleware)) == null ? void 0 : _a[0];
149
+ const localLayout = (_b = files.get(RoutableFileTypes.Layout)) == null ? void 0 : _b[0];
137
150
  const handler = (_c = files.get(RoutableFileTypes.Handler)) == null ? void 0 : _c[0];
138
151
  const page = (_d = files.get(RoutableFileTypes.Page)) == null ? void 0 : _d[0];
139
152
  const middlewareStackLength = middlewareStack.length;
140
153
  const layoutsStackLength = layoutsStack.length;
141
- middleware && middlewareStack.push(middleware);
142
- layout && layoutsStack.push(layout);
154
+ if (localMiddleware) {
155
+ middlewareStack.push(localMiddleware);
156
+ }
157
+ if (localLayout) {
158
+ layoutsStack.push(localLayout);
159
+ }
143
160
  if (handler || page) {
144
161
  const key = path3.replace(/(\$\$?)[^\/]*/g, "$1").replace(/^\/+/, "").replace(/[^a-z0-9_$\/]+/gi, "").replace(/\//g, "__") || "index";
145
162
  if (routes.has(key)) {
146
- console.warn(`Duplicate route for path ${path3} -- ignoring`, current);
163
+ const existing = routes.get(key);
164
+ const existingFiles = [existing.handler, existing.page].filter(Boolean).map((f) => f.filePath);
165
+ const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
166
+ throw new Error(`Duplicate routes for path ${path3} were defined. A route established by:
167
+ ${existingFiles}
168
+ collides with
169
+ ${currentFiles.join(" and ")}
170
+ `);
147
171
  } else {
148
- const index = nextId++;
172
+ const index = nextRouteIndex++;
149
173
  routes.set(key, {
150
174
  index,
151
175
  key,
@@ -158,6 +182,9 @@ async function buildRoutes(walk, basePath) {
158
182
  handler,
159
183
  score: scorePath(path3, index)
160
184
  });
185
+ for (const mw of middlewareStack) {
186
+ middleware.add(mw);
187
+ }
161
188
  }
162
189
  }
163
190
  if (isRoot) {
@@ -188,14 +215,16 @@ async function buildRoutes(walk, basePath) {
188
215
  const prevChildren = children;
189
216
  const prevCurrent = current;
190
217
  const prevIsRoot = isRoot;
191
- if (name.charCodeAt(0) === 95 || name.charCodeAt(0) === 40 && name.charCodeAt(name.length - 1) === 41 || name.toLowerCase() === "index") {
218
+ if (name.charCodeAt(0) === 95) {
192
219
  } else {
193
220
  if (name.charCodeAt(0) === 36) {
194
221
  if (name.charCodeAt(1) === 36) {
195
- paramStack.push({
196
- name: name.slice(2) || "*",
197
- index: -1
198
- });
222
+ if (name.length > 2) {
223
+ paramStack.push({
224
+ name: name.slice(2),
225
+ index: -1
226
+ });
227
+ }
199
228
  } else if (name.length > 1) {
200
229
  paramStack.push({
201
230
  name: name.slice(1),
@@ -223,7 +252,8 @@ async function buildRoutes(walk, basePath) {
223
252
  });
224
253
  return {
225
254
  list: [...routes.values()],
226
- special
255
+ special,
256
+ middleware: [...middleware]
227
257
  };
228
258
  }
229
259
 
@@ -273,6 +303,17 @@ function createWriter(sink, options) {
273
303
  let firstOpenIndex = 0;
274
304
  const branches = [];
275
305
  const openWriters = /* @__PURE__ */ new Map();
306
+ function write(data) {
307
+ if (!writer.__isActive) {
308
+ throw new Error("Cannot write to branch that has been joined");
309
+ }
310
+ if (openWriters.size) {
311
+ buffer += data;
312
+ } else {
313
+ sink(data);
314
+ }
315
+ return writer;
316
+ }
276
317
  const writer = {
277
318
  __isActive: true,
278
319
  get indent() {
@@ -289,24 +330,16 @@ function createWriter(sink, options) {
289
330
  }
290
331
  }
291
332
  },
292
- write(data) {
293
- if (!writer.__isActive) {
294
- throw new Error("Cannot write to branch that has been joined");
333
+ write(data, indent = false) {
334
+ if (indent && indentString) {
335
+ write(indentString);
295
336
  }
296
- if (openWriters.size) {
297
- buffer += data;
298
- } else {
299
- sink(data);
300
- }
301
- return writer;
337
+ return write(data);
302
338
  },
303
339
  writeLines(...lines) {
304
340
  for (const line of lines) {
305
341
  if (line) {
306
- if (indentString) {
307
- writer.write(indentString);
308
- }
309
- writer.write(line);
342
+ writer.write(line, true);
310
343
  }
311
344
  writer.write("\n");
312
345
  }
@@ -453,16 +486,13 @@ function hasVerb(route, verb) {
453
486
  }
454
487
 
455
488
  // src/vite/codegen/index.ts
456
- var DefaultCodegenOptions = {
457
- trailingSlashes: "RedirectWithout"
458
- };
459
489
  function renderRouteTemplate(route) {
460
490
  if (!route.page) {
461
491
  throw new Error(`Route ${route.key} has no page to render`);
462
492
  }
463
493
  const writer = createStringWriter();
464
494
  writer.writeLines(
465
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.marko`
495
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.marko`
466
496
  );
467
497
  writer.branch("imports");
468
498
  writer.writeLines("");
@@ -494,47 +524,76 @@ function renderRouteEntry(route) {
494
524
  }
495
525
  const writer = createStringWriter();
496
526
  writer.writeLines(
497
- `// ${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.js`
527
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.js`
498
528
  );
499
529
  const imports = writer.branch("imports");
500
- let i = 1;
501
- for (const { importPath } of middleware) {
502
- imports.writeLines(`import middleware$${i} from './${importPath}';`);
503
- i++;
530
+ const runtimeImports = [];
531
+ if (handler) {
532
+ runtimeImports.push("normalize");
504
533
  }
505
- if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
506
- const names = handler.verbs.map(
507
- (verb) => `${verb === "delete" ? "del" : verb} as handler$${verb}`
534
+ if (handler || middleware.length) {
535
+ runtimeImports.push("call");
536
+ }
537
+ if (!page || verbs.length > 1) {
538
+ runtimeImports.push("noContent");
539
+ }
540
+ if (page) {
541
+ runtimeImports.push("pageResponse");
542
+ }
543
+ if (runtimeImports.length) {
544
+ imports.writeLines(
545
+ `import { ${runtimeImports.join(", ")} } from '${virtualRuntimePrefix}';`
508
546
  );
547
+ }
548
+ if (middleware.length) {
549
+ const names = middleware.map((m) => `mware${m.id}`);
550
+ imports.writeLines(
551
+ `import { ${names.join(
552
+ ", "
553
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
554
+ );
555
+ }
556
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
557
+ writer.writeLines("");
558
+ const names = [];
559
+ for (const verb of handler.verbs) {
560
+ const importName = verb.toUpperCase();
561
+ names.push(importName);
562
+ writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
563
+ }
509
564
  imports.writeLines(
510
565
  `import { ${names.join(", ")} } from './${handler.importPath}';`
511
566
  );
512
567
  }
513
568
  if (page) {
514
569
  imports.writeLines(
515
- `import page from '${virtualFilePrefix}/${markoServeFilePrefix}route__${key}.marko${serverEntryQuery}';`
570
+ `import page from '${virtualFilePrefix}/${markoRunFilePrefix}route__${key}.marko${serverEntryQuery}';`
516
571
  );
517
572
  }
518
573
  if (meta) {
519
574
  imports.writeLines(
520
- `export { default as meta$${index} } from './${meta.importPath}';`
575
+ `export { default as meta${index} } from './${meta.importPath}';`
521
576
  );
522
577
  }
523
- if (!page || verbs.length > 1) {
524
- writer.writeLines("").writeBlockStart(`function create204Response() {`).writeBlockStart(`return new Response(null, {`).writeLines(`status: 204`).writeBlockEnd(`})`).writeBlockEnd(`}`);
525
- }
526
578
  for (const verb of verbs) {
527
579
  writeRouteEntryHandler(writer, route, verb);
528
580
  }
529
581
  return writer.end();
530
582
  }
531
- function writePageResponseContinuation(writer) {
532
- writer.writeBlock(
533
- `return new Response(page.stream(ctx), {`,
534
- ["status: 200,", 'headers: { "content-type": "text/html;charset=UTF-8" }'],
535
- "});"
583
+ function writePageResponse(writer, wrapFn) {
584
+ writer.writeLines(
585
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} pageResponse(page, buildInput());`
536
586
  );
537
587
  }
588
+ function writeMiddleware(writer, middleware, next, wrapFn) {
589
+ if (wrapFn) {
590
+ writer.writeLines(
591
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
592
+ );
593
+ } else {
594
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
595
+ }
596
+ }
538
597
  function writeRouteEntryHandler(writer, route, verb) {
539
598
  var _a;
540
599
  const { key, index, page, handler, middleware } = route;
@@ -542,47 +601,43 @@ function writeRouteEntryHandler(writer, route, verb) {
542
601
  let nextName;
543
602
  let currentName;
544
603
  let hasBody = false;
545
- writer.writeLines("").writeBlockStart(`export async function ${verb}$${index}(ctx) {`);
604
+ writer.writeLines("");
605
+ if (page) {
606
+ writer.writeBlockStart(
607
+ `export async function ${verb}${index}(context, buildInput) {`
608
+ );
609
+ } else {
610
+ writer.writeBlockStart(`export async function ${verb}${index}(context) {`);
611
+ }
546
612
  const continuations = writer.branch("cont");
547
613
  if (page && verb === "get") {
548
- currentName = "createPageResponse";
614
+ currentName = "__page";
549
615
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
550
- continuations.writeBlockStart(`async function ${currentName}() {`);
551
- writePageResponseContinuation(continuations);
552
- continuations.writeBlockEnd("}");
616
+ const name = `${verb}Handler`;
617
+ writePageResponse(continuations, currentName);
553
618
  if (len) {
554
619
  nextName = currentName;
555
- currentName = `__handler$${verb}`;
556
- continuations.writeBlock(
557
- `async function ${currentName}() {`,
558
- [`return await handler$${verb}(ctx, ${nextName});`],
559
- "}"
560
- );
620
+ currentName = `__${name}`;
621
+ writeMiddleware(continuations, name, nextName, currentName);
561
622
  } else {
562
- writer.writeLines(`return await handler$${verb}(ctx, ${currentName})`);
623
+ writeMiddleware(writer, name, currentName);
563
624
  hasBody = true;
564
625
  }
565
626
  } else if (len) {
566
- continuations.writeBlockStart(`async function ${currentName}() {`);
567
- writePageResponseContinuation(continuations);
568
- continuations.writeBlockEnd("}");
627
+ writePageResponse(continuations, currentName);
569
628
  nextName = currentName;
570
629
  } else {
571
- writePageResponseContinuation(continuations);
630
+ writePageResponse(continuations);
572
631
  hasBody = true;
573
632
  }
574
633
  } else if (handler) {
575
- currentName = `__handler$${verb}`;
634
+ const name = `${verb}Handler`;
635
+ currentName = `__${name}`;
636
+ nextName = "noContent";
576
637
  if (len) {
577
- continuations.writeBlock(
578
- `async function ${currentName}() {`,
579
- [`return await handler$${verb}(ctx, create204Response);`],
580
- "}"
581
- );
638
+ writeMiddleware(continuations, name, nextName, currentName);
582
639
  } else {
583
- writer.writeLines(
584
- `return await handler$${verb}(ctx, create204Response);`
585
- );
640
+ writeMiddleware(writer, name, nextName);
586
641
  hasBody = true;
587
642
  }
588
643
  } else {
@@ -590,40 +645,48 @@ function writeRouteEntryHandler(writer, route, verb) {
590
645
  }
591
646
  if (!hasBody) {
592
647
  let i = len;
593
- while (--i) {
648
+ while (i--) {
649
+ const { id } = middleware[i];
650
+ const name = `mware${id}`;
594
651
  nextName = currentName;
595
- currentName = `__middleware${i + 1}`;
596
- continuations.writeLines("").writeBlock(
597
- `async function ${currentName}() {`,
598
- [`return await middleware$${i + 1}(ctx, ${nextName});`],
599
- "}"
600
- );
652
+ currentName = i ? `__${name}` : "";
653
+ writeMiddleware(continuations, name, nextName, currentName);
601
654
  }
602
- writer.writeLines(`return await middleware$1(ctx, ${currentName});`);
603
655
  }
604
656
  continuations.join();
605
657
  writer.writeBlockEnd("}");
606
658
  }
607
- function renderRouter(routes, options = DefaultCodegenOptions) {
659
+ function renderRouter(routes, options = {
660
+ trailingSlashes: "RedirectWithout"
661
+ }) {
608
662
  const writer = createStringWriter();
609
663
  writer.writeLines(`// @marko/run/router`);
610
664
  const imports = writer.branch("imports");
665
+ imports.writeLines(
666
+ `import { NotHandled, NotMatched, createInput } from 'virtual:marko-run/internal';`
667
+ );
611
668
  for (const route of routes.list) {
612
669
  const verbs = getVerbs(route);
613
- const names = verbs.map((verb) => `${verb}$${route.index}`);
614
- route.meta && names.push(`meta$${route.index}`);
670
+ const names = verbs.map((verb) => `${verb}${route.index}`);
671
+ route.meta && names.push(`meta${route.index}`);
615
672
  imports.writeLines(
616
673
  `import { ${names.join(
617
674
  ", "
618
- )} } from '${virtualFilePrefix}/${markoServeFilePrefix}route__${route.key}.js';`
675
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}route__${route.key}.js';`
619
676
  );
620
677
  }
621
678
  for (const { key } of Object.values(routes.special)) {
622
679
  imports.writeLines(
623
- `import page$${key} from '${virtualFilePrefix}/${markoServeFilePrefix}special__${key}.marko${serverEntryQuery}';`
680
+ `import page${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
624
681
  );
625
682
  }
626
- writer.writeLines("").writeBlockStart(`function matchRoute(method, pathname) {`).writeBlockStart(`switch (method.toLowerCase()) {`);
683
+ writer.writeLines(``).writeBlockStart(`export function match(method, pathname) {`).writeLines(
684
+ `if (!pathname) {
685
+ pathname = '/';
686
+ } else if (pathname.charAt(0) !== '/') {
687
+ pathname = '/' + pathname;
688
+ }`
689
+ ).writeBlockStart(`switch (method.toLowerCase()) {`);
627
690
  for (const verb of httpVerbs) {
628
691
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
629
692
  if (filteredRoutes.length) {
@@ -633,235 +696,477 @@ function renderRouter(routes, options = DefaultCodegenOptions) {
633
696
  writer.writeBlockEnd("}");
634
697
  }
635
698
  }
636
- writer.writeBlockEnd("}").writeBlockEnd("}");
637
- writer.writeLines("").writeBlockStart(`async function invokeRoute(route, url, request) {`);
638
- const errorRoute = routes.special[RoutableFileTypes.Error];
639
- if (errorRoute) {
640
- writer.writeBlockStart(`try {`);
641
- }
642
- writer.writeBlock(
643
- `if (route) {`,
644
- [
645
- `const [handler, params = {}, meta] = route;`,
646
- `const response = await handler({ request, url, params, meta });`,
647
- `if (response) return response;`
648
- ],
649
- `}`
650
- );
651
- const notFoundRoute = routes.special[RoutableFileTypes.NotFound];
652
- if (notFoundRoute) {
653
- writer.writeBlockStart(
654
- `if (request.headers.get('Accept')?.includes('text/html')) {`
655
- ).writeBlock(
656
- `return new Response(page$404.stream({ request, url, params: {} }), {`,
657
- [
658
- `status: 404,`,
659
- `headers: { "content-type": "text/html;charset=UTF-8" },`
660
- ],
661
- `});`
662
- ).writeBlockEnd("}");
699
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
700
+ writer.write(`
701
+ export async function invoke(route, request, platform, url = new URL(request.url)) {
702
+ const context = {
703
+ url,
704
+ request,
705
+ platform
706
+ };
707
+ const buildInput = createInput(context);
708
+ try {
709
+ if (route) {
710
+ context.params = route.params;
711
+ context.meta = route.meta;
712
+ try {
713
+ const response = await route.handler(context, buildInput);
714
+ if (response) return response;
715
+ } catch (error) {
716
+ if (error === NotHandled) {
717
+ return;
718
+ } else if (error !== NotMatched) {
719
+ throw error;
720
+ }
721
+ }
722
+ `).indent = 2;
723
+ if (routes.special[RoutableFileTypes.NotFound]) {
724
+ writer.write(
725
+ ` } else {
726
+ context.params = {};
727
+ context.meta = {};
728
+ }
729
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
730
+ return new Response(page404.stream(buildInput()), {
731
+ status: 404,
732
+ headers: { "content-type": "text/html;charset=UTF-8" },
733
+ });
734
+ }
735
+ `
736
+ );
737
+ } else {
738
+ writer.writeBlockEnd("}");
663
739
  }
664
- writer.writeLines(`return null;`);
665
- if (errorRoute) {
666
- writer.indent--;
667
- writer.writeBlockStart(`} catch (err) {`).writeBlockStart(
668
- `if (request.headers.get('Accept')?.includes('text/html')) {`
740
+ writer.indent--;
741
+ writer.writeBlockStart(`} catch (error) {`);
742
+ if (routes.special[RoutableFileTypes.Error]) {
743
+ writer.writeBlockStart(
744
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
669
745
  ).writeBlock(
670
- `return new Response(page$500.stream({ request, url, params: {}, error: err }), {`,
746
+ `return new Response(page500.stream(buildInput({ error })), {`,
671
747
  [
672
748
  `status: 500,`,
673
749
  `headers: { "content-type": "text/html;charset=UTF-8" },`
674
750
  ],
675
751
  `});`
676
- ).writeBlockEnd("}").writeLines(`throw err;`).writeBlockEnd("}");
677
- }
678
- writer.writeBlockEnd("}");
679
- writer.write(`
680
- export function getMatchedRoute(method, url) {
681
- const route = matchRoute(method, url.pathname);
682
- if (route) {
683
- const [handler, params = {}, meta] = route;
684
- return {
685
- handler,
686
- params,
687
- meta,
688
- async invoke(request) {
689
- return await invokeRoute(route, url, request);
690
- }
691
- }
752
+ ).writeBlockEnd("}");
692
753
  }
693
- return null;
694
- }
695
-
696
- export async function handler(context) {
697
- const response = await router(context.request);
698
- //if (response.body) {
699
- // context.waitUntil?.(once(Readable.from(response.body), "end"));
700
- //}
701
- return response;
702
- }
703
-
704
- export async function router(request) {
754
+ writer.writeLines(`throw error;`).writeBlockEnd("}").writeBlockEnd("}").write(`
755
+ export async function fetch(request, platform) {
705
756
  try {
706
757
  const url = new URL(request.url);
707
- let { pathname } = url;
708
-
709
- if (pathname !== '/') {
710
- if (pathname.endsWith('/')) {
711
- pathname = pathname.replace(/\\/+$/, '');
712
- `);
758
+ let { pathname } = url;`);
713
759
  switch (options.trailingSlashes) {
714
760
  case "RedirectWithout":
715
761
  writer.write(`
716
- url.pathname = pathname;
717
- return Response.redirect(url.href);
718
- `);
762
+ if (pathname !== '/' && pathname.endsWith('/')) {
763
+ url.pathname = pathname.slice(0, -1);
764
+ return Response.redirect(url);
765
+ }`);
719
766
  break;
720
767
  case "RedirectWith":
721
768
  writer.write(`
722
- } else {
723
- url.pathname = pathname + '/';
724
- return Response.redirect(url.href);
725
- `);
769
+ if (pathname !== '/' && !pathname.endsWith('/')) {
770
+ url.pathname = pathname + '/';
771
+ return Response.redirect(url);
772
+ }`);
726
773
  break;
727
774
  case "RewriteWithout":
728
775
  writer.write(`
729
- url.pathname = pathname;
730
- `);
776
+ if (pathname !== '/' && pathname.endsWith('/')) {
777
+ url.pathname = pathname = pathname.slice(0, -1);
778
+ }`);
731
779
  break;
732
780
  case "RewriteWith":
733
781
  writer.write(`
734
- } else {
735
- url.pathname = pathname + '/';
736
- `);
782
+ if (pathname !== '/' && !pathname.endsWith('/')) {
783
+ url.pathname = pathname = pathname + '/';
784
+ }`);
737
785
  break;
738
786
  }
739
- writer.write(`
740
- }
741
- }
742
- const route = matchRoute(request.method, pathname);
743
- return await invokeRoute(route, url, request) || new Response(null, {
744
- statusText: \`Not Found (No route matched \${pathname})\`,
745
- status: 404
746
- });
747
- } catch (err) {
748
- const message = import.meta.env.DEV
749
- ? \`Internal Server Error (\${err.message})\`
750
- : "Internal Server Error";
787
+ writer.write(`
751
788
 
752
- return new Response(
753
- JSON.stringify({
754
- error: {
755
- message,
756
- stack: import.meta.env.DEV
757
- ? \`This will only be seen in development mode\\n\\n\${err.stack}\`
758
- : ""
759
- }
760
- }),
761
- {
762
- statusText: message,
763
- status: 500,
764
- }
765
- );
789
+ const route = match(request.method, pathname);
790
+ return await invoke(route, request, platform, url);
791
+ } catch (error) {
792
+ const body = import.meta.env.DEV
793
+ ? error.stack || error.message || "Internal Server Error"
794
+ : null;
795
+ return new Response(body, {
796
+ status: 500
797
+ });
766
798
  }
767
799
  }`);
768
800
  return writer.end();
769
801
  }
770
- function writeRouterVerb(writer, trie, verb, level = 0, pathIndex = 0, useSwitch) {
771
- const { key, route: value, static: children, dynamic, catchAll } = trie;
772
- pathIndex += key.length;
773
- if (level <= 0) {
774
- level = 0;
775
- if (value) {
802
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
803
+ const { route, dynamic, catchAll } = trie;
804
+ let closeCount = 0;
805
+ if (level === 0) {
806
+ writer.writeLines(`const len = pathname.length;`);
807
+ if (route) {
776
808
  writer.writeLines(
777
- `if (pathname === '/') return ${renderMatch(verb, value)}; // ${value.path}`
778
- );
779
- }
780
- if (children || dynamic) {
781
- writer.writeLines(
782
- `const segments = pathname.split('/');`,
783
- `const len = segments.length;`
784
- );
785
- }
786
- } else {
787
- if (!key) {
788
- writer.writeBlockStart(`if (segments[${level}]) {`);
789
- } else if (useSwitch) {
790
- writer.writeBlockStart(`case '${key}':`);
791
- } else {
792
- writer.writeBlockStart(
793
- `if (segments[${level}]?.toLowerCase() === '${key}') {`
794
- );
795
- }
796
- if (value) {
797
- writer.writeLines(
798
- `if (len === ${level + 1}) return ${renderMatch(verb, value)}; // ${value.path}`
809
+ `if (len === 1) return ${renderMatch(verb, route)}; // ${route.path}`
799
810
  );
811
+ } else if (trie.static || dynamic) {
812
+ writer.writeBlockStart(`if (len > 1) {`);
813
+ closeCount++;
800
814
  }
801
815
  }
802
- if (children || dynamic) {
803
- if (children) {
804
- if (children.size > 1) {
805
- writer.writeBlockStart(
806
- `switch(segments[${level + 1}]?.toLowerCase()) {`
807
- );
808
- for (const child of children.values()) {
809
- writeRouterVerb(writer, child, verb, level + 1, pathIndex, true);
816
+ if (trie.static || dynamic) {
817
+ const next = level + 1;
818
+ const index = `i${next}`;
819
+ let terminal;
820
+ let children;
821
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
822
+ if (trie.static) {
823
+ for (const child of trie.static.values()) {
824
+ if (child.route) {
825
+ (terminal ?? (terminal = [])).push(child);
810
826
  }
811
- writer.writeBlockEnd("}");
812
- } else {
813
- for (const child of children.values()) {
814
- writeRouterVerb(writer, child, verb, level + 1, pathIndex);
827
+ if (child.static || child.dynamic || child.catchAll) {
828
+ (children ?? (children = [])).push(child);
815
829
  }
816
830
  }
817
831
  }
818
- if (dynamic) {
819
- writeRouterVerb(writer, dynamic, verb, level + 1, pathIndex);
832
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
833
+ closeCount++;
834
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
835
+ let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
836
+ if (dynamic == null ? void 0 : dynamic.route) {
837
+ const segment = `s${next}`;
838
+ writer.writeLines(`const ${segment} = ${value};`);
839
+ value = segment;
840
+ }
841
+ if (terminal) {
842
+ const useSwitch = terminal.length > 1;
843
+ if (useSwitch) {
844
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
845
+ }
846
+ for (const { key, route: route2 } of terminal) {
847
+ if (useSwitch) {
848
+ writer.write(`case '${key}': `, true);
849
+ } else {
850
+ writer.write(`if (${value}.toLowerCase() === '${key}') `, true);
851
+ }
852
+ writer.write(
853
+ `return ${renderMatch(verb, route2)}; // ${route2.path}
854
+ `
855
+ );
856
+ }
857
+ if (useSwitch) {
858
+ writer.writeBlockEnd("}");
859
+ }
860
+ }
861
+ if (dynamic == null ? void 0 : dynamic.route) {
862
+ writer.writeLines(
863
+ `if (${value}) return ${renderMatch(verb, dynamic.route)}; // ${dynamic.route.path}`
864
+ );
865
+ }
866
+ }
867
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
868
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
869
+ writer.writeBlockEnd("} else {").indent++;
870
+ } else {
871
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
872
+ closeCount++;
873
+ }
874
+ let value = `pathname.slice(${offset}, ${index} - 1)`;
875
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic)) {
876
+ const segment = `s${next}`;
877
+ writer.writeLines(`const ${segment} = ${value};`);
878
+ value = segment;
879
+ }
880
+ if (children) {
881
+ const useSwitch = children.length > 1;
882
+ if (useSwitch) {
883
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
884
+ }
885
+ for (const child of children) {
886
+ if (useSwitch) {
887
+ writer.writeBlockStart(`case '${child.key}': {`);
888
+ } else {
889
+ writer.writeBlockStart(
890
+ `if (${value}.toLowerCase() === '${child.key}') {`
891
+ );
892
+ }
893
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
894
+ writeRouterVerb(writer, child, verb, next, nextOffset);
895
+ writer.writeBlockEnd("}");
896
+ }
897
+ if (useSwitch) {
898
+ writer.writeBlockEnd("}");
899
+ }
900
+ }
901
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
902
+ writer.writeBlockStart(`if (${value}) {`);
903
+ writeRouterVerb(writer, dynamic, verb, next, index);
904
+ writer.writeBlockEnd(`}`);
905
+ }
820
906
  }
821
907
  }
908
+ while (closeCount--) {
909
+ writer.writeBlockEnd("}");
910
+ }
822
911
  if (catchAll) {
823
912
  writer.writeLines(
824
- `return ${renderMatch(verb, catchAll, pathIndex)}; // ${catchAll.path}`
913
+ `return ${renderMatch(verb, catchAll, String(offset))}; // ${catchAll.path}`
825
914
  );
826
- if (level > 0) {
827
- writer.indent--;
828
- }
829
915
  } else if (level === 0) {
830
- writer.writeLines("return;");
831
- } else if (useSwitch) {
832
- writer.writeLines("break;").indent--;
833
- } else {
834
- writer.writeBlockEnd("}");
916
+ writer.writeLines("return null;");
835
917
  }
836
918
  }
919
+ function wrapPropertyName(name) {
920
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
921
+ }
837
922
  function renderParamsInfo(params, pathIndex) {
923
+ if (!params.length) {
924
+ return "{}";
925
+ }
838
926
  let result = "";
839
927
  let catchAll = "";
840
- let dynamicLength = "";
928
+ let sep = "{";
841
929
  for (const { name, index } of params) {
842
930
  if (index >= 0) {
843
- result += result ? ", " : "{ ";
844
- result += `'${name}': segments[${index + 1}]`;
845
- dynamicLength += ` + segments[${index + 1}].length`;
846
- } else if (pathIndex && pathIndex >= 0) {
931
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
932
+ sep || (sep = ",");
933
+ } else if (pathIndex) {
847
934
  catchAll = name;
848
935
  }
849
936
  }
850
937
  if (catchAll) {
851
- result += result ? ", " : "{ ";
852
- result += `'${catchAll}': pathname.slice(${pathIndex}${dynamicLength})`;
938
+ result += `${sep} ${wrapPropertyName(
939
+ catchAll
940
+ )}: pathname.slice(${pathIndex})`;
853
941
  }
854
942
  return result ? result + " }" : "{}";
855
943
  }
856
- function renderMatch(verb, { index, params, meta }, pathIndex) {
857
- const tuple = [`${verb}$${index}`];
858
- if (params == null ? void 0 : params.length) {
859
- tuple[1] = renderParamsInfo(params, pathIndex);
944
+ function renderParamsInfoType(params) {
945
+ if (!params.length) {
946
+ return "{}";
860
947
  }
861
- if (meta) {
862
- tuple[2] = `meta$${index}`;
948
+ let result = "{";
949
+ for (const { name } of params) {
950
+ result += ` ${wrapPropertyName(name)}: string;`;
863
951
  }
864
- return `[${tuple.join(", ")}]`;
952
+ return result + " }";
953
+ }
954
+ function renderMatch(verb, route, pathIndex) {
955
+ var _a;
956
+ const handler = `${verb}${route.index}`;
957
+ const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
958
+ const meta = route.meta ? `meta${route.index}` : "{}";
959
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
960
+ }
961
+ function renderMiddleware(middleware) {
962
+ const writer = createStringWriter();
963
+ writer.writeLines(
964
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
965
+ );
966
+ const imports = writer.branch("imports");
967
+ imports.writeLines(`import { normalize } from 'virtual:marko-run/internal';`);
968
+ writer.writeLines("");
969
+ for (const { id, importPath } of middleware) {
970
+ const importName = `middleware${id}`;
971
+ imports.writeLines(`import ${importName} from './${importPath}';`);
972
+ writer.writeLines(`export const mware${id} = normalize(${importName});`);
973
+ }
974
+ imports.join();
975
+ return writer.end();
976
+ }
977
+ function stripTsExtension(path3) {
978
+ const index = path3.lastIndexOf(".");
979
+ if (index !== -1) {
980
+ const ext = path3.slice(index + 1);
981
+ if (ext.toLowerCase() === "ts") {
982
+ return path3.slice(0, index);
983
+ }
984
+ }
985
+ return path3;
986
+ }
987
+ function renderRouteTypeInfo(routes, pathPrefix = ".", adapterTypes = "") {
988
+ var _a, _b;
989
+ const writer = createStringWriter();
990
+ writer.writeLines(
991
+ `/*
992
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
993
+ Do NOT manually edit this file or your changes will be lost.
994
+ */
995
+ `,
996
+ `import type { HandlerLike, Route as AnyRoute, Context as AnyContext, ValidatePath, ValidateHref } from "@marko/run";`,
997
+ adapterTypes,
998
+ `
999
+
1000
+ declare global {
1001
+ namespace MarkoRun {`
1002
+ );
1003
+ const pathsWriter = writer.branch("paths");
1004
+ writer.write(`
1005
+ type GetablePath<T extends string> = ValidatePath<GetPaths, T>;
1006
+ type GetableHref<T extends string> = ValidateHref<GetPaths, T>;
1007
+ type PostablePath<T extends string> = ValidatePath<PostPaths, T>;
1008
+ type PostableHref<T extends string> = ValidateHref<PostPaths, T>;
1009
+ }
1010
+ }
1011
+ `);
1012
+ const routesWriter = writer.branch("types");
1013
+ const serverWriter = writer.branch("server");
1014
+ const middlewareRouteTypes = /* @__PURE__ */ new Map();
1015
+ const layoutRouteTypes = /* @__PURE__ */ new Map();
1016
+ const getPaths = /* @__PURE__ */ new Set();
1017
+ const postPaths = /* @__PURE__ */ new Set();
1018
+ for (const route of routes.list) {
1019
+ const { meta, handler, params, middleware, page, layouts } = route;
1020
+ const routeType = `Route${route.index}`;
1021
+ const pathType = `\`${route.path.replace(
1022
+ /\/\$(\$?)([^\/]*)/,
1023
+ (_, catchAll, name) => catchAll ? `/:${name || "rest"}*` : `/:${name}`
1024
+ )}\``;
1025
+ const paramsType = params ? renderParamsInfoType(params) : "{}";
1026
+ let metaType = "undefined";
1027
+ if (page || handler) {
1028
+ const isGet = page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"));
1029
+ const isPost = (_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post");
1030
+ if (isGet || isPost) {
1031
+ const path3 = route.path.replace(
1032
+ /\$(\$?)([^/]+)/g,
1033
+ (_, s, name) => s ? `\${...${name}}` : `\${${name}}`
1034
+ );
1035
+ const splatIndex = path3.indexOf("/${...");
1036
+ if (splatIndex >= 0) {
1037
+ const path22 = path3.slice(0, splatIndex) || "/";
1038
+ isGet && getPaths.add(path22);
1039
+ isPost && postPaths.add(path22);
1040
+ }
1041
+ isGet && getPaths.add(path3);
1042
+ isPost && postPaths.add(path3);
1043
+ }
1044
+ }
1045
+ if (meta) {
1046
+ metaType = `typeof import('${pathPrefix}/${stripTsExtension(
1047
+ meta.relativePath
1048
+ )}')`;
1049
+ if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
1050
+ metaType += `['default']`;
1051
+ }
1052
+ }
1053
+ if (handler) {
1054
+ writeRouteTypeModule(
1055
+ serverWriter,
1056
+ pathPrefix,
1057
+ handler.relativePath,
1058
+ routeType
1059
+ );
1060
+ }
1061
+ if (page) {
1062
+ writer.writeLines(`
1063
+ declare module '${pathPrefix}/${page.relativePath}' {
1064
+ export interface Input {}
1065
+
1066
+ namespace MarkoRun {
1067
+ type Route = ${routeType};
1068
+ type Context = AnyContext<AnyContext['platform'], Route>;
1069
+ }
1070
+ }`);
1071
+ }
1072
+ if (middleware) {
1073
+ let i = 0;
1074
+ for (const mw of middleware) {
1075
+ const existing = middlewareRouteTypes.get(mw);
1076
+ if (!existing) {
1077
+ middlewareRouteTypes.set(mw, {
1078
+ routeTypes: [routeType],
1079
+ middleware: middleware.slice(0, i)
1080
+ });
1081
+ } else {
1082
+ existing.routeTypes.push(routeType);
1083
+ }
1084
+ i++;
1085
+ }
1086
+ }
1087
+ if (layouts) {
1088
+ for (const layout of layouts) {
1089
+ const existing = layoutRouteTypes.get(layout);
1090
+ if (!existing) {
1091
+ layoutRouteTypes.set(layout, {
1092
+ routeTypes: [routeType]
1093
+ });
1094
+ } else {
1095
+ existing.routeTypes.push(routeType);
1096
+ }
1097
+ }
1098
+ }
1099
+ routesWriter.writeLines(
1100
+ `interface ${routeType} extends AnyRoute<${paramsType}, ${metaType}, ${pathType}> {}`
1101
+ );
1102
+ }
1103
+ pathsWriter.write(" type GetPaths =");
1104
+ for (const path3 of getPaths) {
1105
+ pathsWriter.write(`
1106
+ | '${path3}'`);
1107
+ }
1108
+ pathsWriter.writeLines(";", "");
1109
+ pathsWriter.write(" type PostPaths =");
1110
+ for (const path3 of postPaths) {
1111
+ pathsWriter.write(`
1112
+ | '${path3}'`);
1113
+ }
1114
+ pathsWriter.writeLines(";");
1115
+ pathsWriter.join();
1116
+ for (const [file, { routeTypes }] of middlewareRouteTypes) {
1117
+ writeRouteTypeModule(
1118
+ serverWriter,
1119
+ pathPrefix,
1120
+ file.relativePath,
1121
+ routeTypes.join(" | ")
1122
+ );
1123
+ }
1124
+ for (const [file, { routeTypes }] of layoutRouteTypes) {
1125
+ writer.writeLines(`
1126
+ declare module '${pathPrefix}/${file.relativePath}' {
1127
+ export interface Input {
1128
+ renderBody: Marko.Body;
1129
+ }
1130
+
1131
+ namespace MarkoRun {
1132
+ type Route = ${routeTypes.join(" | ")};
1133
+ type Context = AnyContext<AnyContext['platform'], Route>;
1134
+ }
1135
+ }`);
1136
+ }
1137
+ for (const route of [routes.special["404"], routes.special["500"]]) {
1138
+ if (route && route.page) {
1139
+ writer.write(`
1140
+ declare module '${pathPrefix}/${route.page.relativePath}' {
1141
+ export interface Input {`);
1142
+ if (route.page.type === RoutableFileTypes.Error) {
1143
+ writer.write(`
1144
+ error: unknown;
1145
+ `);
1146
+ }
1147
+ writer.writeLines(`}
1148
+
1149
+ namespace MarkoRun {
1150
+ type Route = AnyRoute;
1151
+ type Context = AnyContext<AnyContext['platform'], Route>;
1152
+ }
1153
+ }`);
1154
+ }
1155
+ }
1156
+ serverWriter.join();
1157
+ return writer.end();
1158
+ }
1159
+ function writeRouteTypeModule(writer, pathPrefix, path3, routeType) {
1160
+ writer.writeLines(`
1161
+ declare module '${pathPrefix}/${stripTsExtension(path3)}' {
1162
+ namespace MarkoRun {
1163
+ type Route = ${routeType};
1164
+ type Context = AnyContext<AnyContext['platform'], Route>;
1165
+ type Handler<_Params = Route['params'], _Meta = Route['meta']> = HandlerLike<Route>;
1166
+ function route(handler: Handler): typeof handler;
1167
+ function route<_Params = Route['params'], _Meta = Route['meta']>(handler: Handler): typeof handler;
1168
+ }
1169
+ }`);
865
1170
  }
866
1171
 
867
1172
  // src/vite/utils/ast.ts
@@ -908,7 +1213,7 @@ function getExportIdentifiers(astProgramNode) {
908
1213
  var import_cli_table3 = __toESM(require("cli-table3"), 1);
909
1214
  var import_kleur = __toESM(require("kleur"), 1);
910
1215
  var import_gzip_size = require("gzip-size");
911
- var import_pretty_bytes = __toESM(require("pretty-bytes"), 1);
1216
+ var import_human_format = __toESM(require("human-format"), 1);
912
1217
  var HttpVerbColors = {
913
1218
  get: import_kleur.default.green,
914
1219
  post: import_kleur.default.magenta,
@@ -935,7 +1240,7 @@ function logRoutesTable(routes, bundle) {
935
1240
  headings.push("Meta");
936
1241
  colAligns.push("center");
937
1242
  }
938
- headings.push("Size");
1243
+ headings.push("Size/GZip");
939
1244
  colAligns.push("right");
940
1245
  const table = new import_cli_table3.default({
941
1246
  head: headings.map((title) => import_kleur.default.bold(import_kleur.default.white(title.toUpperCase()))),
@@ -966,7 +1271,7 @@ function logRoutesTable(routes, bundle) {
966
1271
  row.push(entryType.join(" -> "));
967
1272
  hasMiddleware && row.push(route.middleware.length || "");
968
1273
  hasMeta && row.push(route.meta ? "\u2713" : "");
969
- row.push(size || { hAlign: "center", content: "-" });
1274
+ row.push(size || "");
970
1275
  table.push(row);
971
1276
  }
972
1277
  }
@@ -982,54 +1287,76 @@ function logRoutesTable(routes, bundle) {
982
1287
  function computeRouteSize(route, bundle) {
983
1288
  if (route.page) {
984
1289
  for (const chunk of Object.values(bundle)) {
985
- if (chunk.type === "chunk" && chunk.modules[route.page.filePath]) {
986
- return computeChunkSize(chunk, bundle);
1290
+ if (chunk.type === "chunk") {
1291
+ for (const key of Object.keys(chunk.modules)) {
1292
+ if (key.startsWith(route.page.filePath)) {
1293
+ return computeChunkSize(chunk, bundle);
1294
+ }
1295
+ }
987
1296
  }
988
1297
  }
989
1298
  }
990
- return 0;
1299
+ return [0, 0];
1300
+ }
1301
+ function byteSize(str) {
1302
+ return new Blob([str]).size;
991
1303
  }
992
1304
  function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
993
1305
  if (chunk.type === "asset") {
994
- return (0, import_gzip_size.gzipSizeSync)(chunk.source);
1306
+ return [
1307
+ byteSize(chunk.source),
1308
+ (0, import_gzip_size.gzipSizeSync)(chunk.source)
1309
+ ];
995
1310
  }
996
- let size = (0, import_gzip_size.gzipSizeSync)(chunk.code);
1311
+ const size = [byteSize(chunk.code), (0, import_gzip_size.gzipSizeSync)(chunk.code)];
997
1312
  for (const id of chunk.imports) {
998
1313
  if (!seen.has(id)) {
999
- size += computeChunkSize(bundle[id], bundle, seen);
1314
+ const [bytes, compBytes] = computeChunkSize(bundle[id], bundle, seen);
1315
+ size[0] += bytes;
1316
+ size[1] += compBytes;
1000
1317
  seen.add(id);
1001
1318
  }
1002
1319
  }
1003
1320
  return size;
1004
1321
  }
1005
- function prettySize(size) {
1006
- const _size = (0, import_pretty_bytes.default)(size, {
1007
- minimumFractionDigits: 1,
1008
- maximumFractionDigits: 1
1009
- }).replace(/\sB$/, " B ");
1010
- if (size < 20 * 1e3)
1011
- return import_kleur.default.green(_size);
1012
- if (size < 50 * 1e3)
1013
- return import_kleur.default.yellow(_size);
1014
- return import_kleur.default.bold(import_kleur.default.red(_size));
1322
+ function prettySize([bytes, compBytes]) {
1323
+ if (bytes <= 0) {
1324
+ return import_kleur.default.gray("0.0 kB");
1325
+ }
1326
+ const [size, prefix] = (0, import_human_format.default)(bytes, { decimals: 1 }).split(/\s+/);
1327
+ const compSize = (0, import_human_format.default)(compBytes, { decimals: 1, prefix, unit: "B" });
1328
+ let str = import_kleur.default.white(size) + import_kleur.default.gray("/");
1329
+ if (compBytes < 20 * 1e3)
1330
+ str += import_kleur.default.green(compSize);
1331
+ else if (compBytes < 50 * 1e3)
1332
+ str += import_kleur.default.yellow(compSize);
1333
+ else
1334
+ import_kleur.default.bold(import_kleur.default.red(compSize));
1335
+ return str;
1015
1336
  }
1016
1337
  function prettyPath(path3) {
1017
1338
  return path3.replace(/\/\$\$(.*)$/, (_, p) => "/" + import_kleur.default.bold(import_kleur.default.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + import_kleur.default.bold(import_kleur.default.dim(`:${p}`)));
1018
1339
  }
1019
1340
 
1020
1341
  // src/vite/utils/config.ts
1021
- var KEY = "__MARKO_SERVE_OPTIONS__";
1022
- function getMarkoServeOptions(viteConfig) {
1023
- return viteConfig[KEY];
1342
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1343
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1344
+ function getConfig(obj, key) {
1345
+ return obj[key];
1024
1346
  }
1025
- function setMarkoServeOptions(viteConfig, options) {
1026
- viteConfig[KEY] = options;
1027
- return viteConfig;
1347
+ function setConfig(obj, key, value) {
1348
+ obj[key] = value;
1349
+ return obj;
1028
1350
  }
1351
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1352
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1353
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1029
1354
 
1030
1355
  // src/vite/plugin.ts
1356
+ var import_url = require("url");
1357
+ var import_meta = {};
1358
+ var __dirname = (0, import_url.fileURLToPath)(new URL(".", import_meta.url));
1031
1359
  var markoExt = ".marko";
1032
- var markoServeFilePrefix2 = "__marko-serve__";
1033
1360
  function isMarkoFile(id) {
1034
1361
  return id.endsWith(markoExt);
1035
1362
  }
@@ -1038,8 +1365,10 @@ function markoServe(opts = {}) {
1038
1365
  let store;
1039
1366
  let root;
1040
1367
  let resolvedRoutesDir;
1368
+ let typesDir;
1041
1369
  let isBuild = false;
1042
1370
  let isSSRBuild = false;
1371
+ let tsConfigExists;
1043
1372
  let ssrEntryFiles;
1044
1373
  let devEntryFile;
1045
1374
  let devServer;
@@ -1048,6 +1377,7 @@ function markoServe(opts = {}) {
1048
1377
  let routeDataFilename = "routes.json";
1049
1378
  let extractVerbs;
1050
1379
  let resolvedConfig;
1380
+ let typesFile;
1051
1381
  let isStale = true;
1052
1382
  let isRendered = false;
1053
1383
  const virtualFiles = /* @__PURE__ */ new Map();
@@ -1055,6 +1385,20 @@ function markoServe(opts = {}) {
1055
1385
  routesBuild: 0,
1056
1386
  routesRender: 0
1057
1387
  };
1388
+ async function writeTypesFile() {
1389
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1390
+ root,
1391
+ "{.tsconfig*,tsconfig*.json}"
1392
+ ))) {
1393
+ const filepath = import_path2.default.join(typesDir, "routes.d.ts");
1394
+ const adapterTypeInfo = (adapter == null ? void 0 : adapter.writeTypeInfo) && await (adapter == null ? void 0 : adapter.writeTypeInfo());
1395
+ const data = renderRouteTypeInfo(routes, import_path2.default.relative(typesDir, routesDir), adapterTypeInfo);
1396
+ if (data !== typesFile || !import_fs2.default.existsSync(filepath)) {
1397
+ await ensureDir(typesDir);
1398
+ await import_fs2.default.promises.writeFile(filepath, typesFile = data);
1399
+ }
1400
+ }
1401
+ }
1058
1402
  async function setVirtualFiles(render = false) {
1059
1403
  for (const route of routes.list) {
1060
1404
  if (render && route.handler) {
@@ -1067,24 +1411,30 @@ function markoServe(opts = {}) {
1067
1411
  }
1068
1412
  if (route.page) {
1069
1413
  virtualFiles.set(
1070
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.marko`),
1414
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.marko`),
1071
1415
  render ? renderRouteTemplate(route) : ""
1072
1416
  );
1073
1417
  }
1074
1418
  virtualFiles.set(
1075
- import_path2.default.join(root, `${markoServeFilePrefix2}route__${route.key}.js`),
1419
+ import_path2.default.join(root, `${markoRunFilePrefix}route__${route.key}.js`),
1076
1420
  render ? renderRouteEntry(route) : ""
1077
1421
  );
1078
1422
  }
1079
1423
  for (const route of Object.values(routes.special)) {
1080
1424
  virtualFiles.set(
1081
- import_path2.default.join(root, `${markoServeFilePrefix2}special__${route.key}.marko`),
1425
+ import_path2.default.join(root, `${markoRunFilePrefix}special__${route.key}.marko`),
1082
1426
  render ? renderRouteTemplate(route) : ""
1083
1427
  );
1084
1428
  }
1085
1429
  virtualFiles.set(
1086
1430
  "@marko/run/router",
1087
- render ? renderRouter(routes, opts.codegen) : ""
1431
+ render ? renderRouter(routes, {
1432
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1433
+ }) : ""
1434
+ );
1435
+ virtualFiles.set(
1436
+ import_path2.default.join(root, `${markoRunFilePrefix}middleware.js`),
1437
+ render ? renderMiddleware(routes.middleware) : ""
1088
1438
  );
1089
1439
  }
1090
1440
  const buildVirtualFiles = single(async () => {
@@ -1098,6 +1448,7 @@ function markoServe(opts = {}) {
1098
1448
  const renderVirtualFiles = single(async () => {
1099
1449
  const startTime = performance.now();
1100
1450
  await setVirtualFiles(true);
1451
+ await writeTypesFile();
1101
1452
  times.routesRender = performance.now() - startTime;
1102
1453
  isRendered = true;
1103
1454
  });
@@ -1105,23 +1456,30 @@ function markoServe(opts = {}) {
1105
1456
  {
1106
1457
  name: "marko-run-vite:pre",
1107
1458
  enforce: "pre",
1108
- async config(config, env) {
1459
+ async config(config2, env) {
1109
1460
  var _a, _b, _c;
1110
- const externalPluginOptions = getMarkoServeOptions(config);
1461
+ const externalPluginOptions = getExternalPluginOptions(config2);
1111
1462
  if (externalPluginOptions) {
1112
1463
  opts = (0, import_vite.mergeConfig)(opts, externalPluginOptions);
1113
1464
  }
1114
- const adapterOptions = await ((_a = adapter == null ? void 0 : adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1115
- if (adapterOptions) {
1116
- opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1465
+ if (adapter) {
1466
+ const externalAdapterConfig = getExternalAdapterOptions(config2);
1467
+ if (externalAdapterConfig && adapter.configure) {
1468
+ adapter.configure(externalAdapterConfig);
1469
+ }
1470
+ const adapterOptions = await ((_a = adapter.pluginOptions) == null ? void 0 : _a.call(adapter, opts));
1471
+ if (adapterOptions) {
1472
+ opts = (0, import_vite.mergeConfig)(opts, adapterOptions);
1473
+ }
1117
1474
  }
1118
- root = (0, import_vite.normalizePath)(config.root || process.cwd());
1475
+ root = (0, import_vite.normalizePath)(config2.root || process.cwd());
1119
1476
  store = opts.store || new import_vite2.FileStore(
1120
1477
  `marko-serve-vite-${import_crypto.default.createHash("SHA1").update(root).digest("hex")}`
1121
1478
  );
1122
1479
  isBuild = env.command === "build";
1123
- isSSRBuild = isBuild && Boolean((_b = config.build) == null ? void 0 : _b.ssr);
1480
+ isSSRBuild = isBuild && Boolean((_b = config2.build) == null ? void 0 : _b.ssr);
1124
1481
  resolvedRoutesDir = import_path2.default.resolve(root, routesDir);
1482
+ typesDir = import_path2.default.join(root, ".marko-run");
1125
1483
  devEntryFile = import_path2.default.join(root, "index.html");
1126
1484
  let pluginConfig = {
1127
1485
  logLevel: isBuild ? "warn" : void 0,
@@ -1132,18 +1490,18 @@ function markoServe(opts = {}) {
1132
1490
  emptyOutDir: isSSRBuild
1133
1491
  }
1134
1492
  };
1135
- const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config));
1493
+ const adapterConfig = await ((_c = adapter == null ? void 0 : adapter.viteConfig) == null ? void 0 : _c.call(adapter, config2));
1136
1494
  if (adapterConfig) {
1137
1495
  pluginConfig = (0, import_vite.mergeConfig)(pluginConfig, adapterConfig);
1138
1496
  }
1139
- return setMarkoServeOptions(pluginConfig, opts);
1497
+ return setExternalPluginOptions(pluginConfig, opts);
1140
1498
  },
1141
- configResolved(config) {
1142
- resolvedConfig = config;
1499
+ configResolved(config2) {
1500
+ resolvedConfig = config2;
1143
1501
  const {
1144
1502
  ssr,
1145
1503
  rollupOptions: { input }
1146
- } = config.build;
1504
+ } = config2.build;
1147
1505
  if (typeof ssr === "string") {
1148
1506
  ssrEntryFiles = [ssr];
1149
1507
  } else if (typeof input === "string") {
@@ -1174,6 +1532,7 @@ function markoServe(opts = {}) {
1174
1532
  if (isStale) {
1175
1533
  for (const id of virtualFiles.keys()) {
1176
1534
  devServer.watcher.emit("change", id);
1535
+ break;
1177
1536
  }
1178
1537
  }
1179
1538
  }
@@ -1203,12 +1562,18 @@ function markoServe(opts = {}) {
1203
1562
  },
1204
1563
  async resolveId(importee, importer, { ssr }) {
1205
1564
  let resolved;
1206
- if (importee.startsWith(virtualFilePrefix)) {
1565
+ if (importee.startsWith(virtualRuntimePrefix)) {
1566
+ return this.resolve(
1567
+ import_path2.default.resolve(__dirname, "../runtime/internal"),
1568
+ importer,
1569
+ { skipSelf: true }
1570
+ );
1571
+ } else if (importee.startsWith(virtualFilePrefix)) {
1207
1572
  importee = import_path2.default.resolve(
1208
1573
  root,
1209
1574
  importee.slice(virtualFilePrefix.length + 1)
1210
1575
  );
1211
- } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoServeFilePrefix2}`)) {
1576
+ } else if (!isBuild && importer === devEntryFile && importee.startsWith(`/${markoRunFilePrefix}`)) {
1212
1577
  importee = import_path2.default.resolve(root, "." + importee);
1213
1578
  }
1214
1579
  if (isStale) {
@@ -1294,8 +1659,8 @@ async function getVerbsFromFileBuild(context, filePath) {
1294
1659
  if (result) {
1295
1660
  const exportIds = getExportIdentifiers(result.ast);
1296
1661
  for (const id of exportIds) {
1297
- const verb = id === "del" ? "delete" : id;
1298
- if (httpVerbs.includes(verb)) {
1662
+ const verb = id.toLowerCase();
1663
+ if (id === verb.toUpperCase() && httpVerbs.includes(verb)) {
1299
1664
  verbs.push(verb);
1300
1665
  }
1301
1666
  }
@@ -1306,12 +1671,19 @@ async function getVerbsFromFileDev(devServer, filePath) {
1306
1671
  const verbs = [];
1307
1672
  const result = await devServer.transformRequest(filePath, { ssr: true });
1308
1673
  if (result && result.code) {
1309
- const verbMatchReg = /__vite_ssr_exports__,\s+["'](get|post|put|del)["']/g;
1674
+ const verbMatchReg = /__vite_ssr_exports__,\s+["'](GET|POST|PUT|DELETE)["']/gi;
1310
1675
  let match = verbMatchReg.exec(result.code);
1311
1676
  while (match) {
1312
- const verb = match[1] === "del" ? "delete" : match[1];
1677
+ const id = match[1];
1678
+ const verb = id.toLowerCase();
1313
1679
  if (httpVerbs.includes(verb)) {
1314
- verbs.push(verb);
1680
+ if (id === verb.toUpperCase()) {
1681
+ verbs.push(verb);
1682
+ } else {
1683
+ console.warn(
1684
+ `Found export '${id}' in handler ${filePath} which is close to '${verb.toUpperCase()}'. Exported handlers need to be uppercase: GET, POST, PUT or DELETE.`
1685
+ );
1686
+ }
1315
1687
  }
1316
1688
  match = verbMatchReg.exec(result.code);
1317
1689
  }
@@ -1330,20 +1702,49 @@ function single(fn) {
1330
1702
  return result;
1331
1703
  };
1332
1704
  }
1705
+ async function globFileExists(root, pattern) {
1706
+ return new Promise((resolve, reject) => {
1707
+ (0, import_glob.default)(pattern, { root }, (err, matches) => {
1708
+ if (err) {
1709
+ reject(err);
1710
+ }
1711
+ resolve(matches.length > 0);
1712
+ });
1713
+ });
1714
+ }
1715
+ async function ensureDir(dir) {
1716
+ if (!import_fs2.default.existsSync(dir)) {
1717
+ await import_fs2.default.promises.mkdir(dir, { recursive: true });
1718
+ }
1719
+ }
1333
1720
 
1334
1721
  // src/vite/utils/server.ts
1335
1722
  var import_net = __toESM(require("net"), 1);
1336
1723
  var import_child_process = __toESM(require("child_process"), 1);
1337
- async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
1724
+ var import_dotenv = require("dotenv");
1725
+ var import_fs3 = __toESM(require("fs"), 1);
1726
+ async function parseEnv(envFile) {
1727
+ if (import_fs3.default.existsSync(envFile)) {
1728
+ const content = await import_fs3.default.promises.readFile(envFile, "utf8");
1729
+ return (0, import_dotenv.parse)(content);
1730
+ }
1731
+ }
1732
+ function loadEnv(envFile) {
1733
+ (0, import_dotenv.config)({ path: envFile });
1734
+ }
1735
+ async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
1338
1736
  if (port <= 0) {
1339
1737
  port = await getAvailablePort();
1340
1738
  }
1739
+ if (typeof env === "string") {
1740
+ env = await parseEnv(env);
1741
+ }
1341
1742
  const proc = import_child_process.default.spawn(cmd, {
1342
1743
  cwd,
1343
1744
  shell: true,
1344
1745
  stdio: "inherit",
1345
1746
  windowsHide: true,
1346
- env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
1747
+ env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
1347
1748
  });
1348
1749
  const close = () => {
1349
1750
  proc.unref();
@@ -1390,5 +1791,7 @@ function sleep(ms) {
1390
1791
  0 && (module.exports = {
1391
1792
  getAvailablePort,
1392
1793
  isPortInUse,
1794
+ loadEnv,
1795
+ parseEnv,
1393
1796
  spawnServer
1394
1797
  });