@markless/router 0.1.0

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.
package/dist/vite.js ADDED
@@ -0,0 +1,1355 @@
1
+ import { t as buildRouteManifestFromFileIds } from "./route-manifest-OTaWQ_TC.js";
2
+ import { r as transformRequestFileSource } from "./request-files-DTkeaKxU.js";
3
+ import { dirname, extname, isAbsolute, join, normalize, relative } from "pathe";
4
+ import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withoutLeadingSlash } from "ufo";
5
+ import { existsSync } from "node:fs";
6
+ import { nitro } from "nitro/vite";
7
+ import { sortUserPlugins } from "vite";
8
+ import { symbolVirtualModuleSourceFile } from "@markless/bundler/preload";
9
+ import { parseModule } from "@tsrx/core";
10
+ import { toHtml } from "hast-util-to-html";
11
+ import { HastReader, createMdxHastHandle, defineHastPlugin, dropHandle, materializeHastTree, resolveHastSubscriptions, serializeHandle, visitHastHandle } from "satteri";
12
+ //#region src/route-types.ts
13
+ const routeTypesEnvPath = "markless-router-env.d.ts";
14
+ const routeTypesOutputPath = ".output/markless/router/types/routes.d.ts";
15
+ const routeTypesEnvDeclaration = "/// <reference path=\"./.output/markless/router/types/routes.d.ts\" />\n";
16
+ function createRouteTypesDeclaration(manifest) {
17
+ const routes = manifest.routes;
18
+ const staticHrefs = routes.filter((route) => route.params.length === 0).map((route) => stringLiteral(route.pathname));
19
+ const routePatterns = routes.filter((route) => route.params.length > 0).map((route) => stringLiteral(route.pattern));
20
+ return [
21
+ "/* This file is generated by Markless Router. Do not edit. */",
22
+ "",
23
+ createUnion("MarklessRouterStaticPageHref", staticHrefs),
24
+ "",
25
+ createUnion("MarklessRouterConcretePageHref", routes.map((route) => hrefType(route))),
26
+ "",
27
+ createUnion("MarklessRouterRoutePattern", routePatterns),
28
+ "",
29
+ createRouteParamsInterface(routes),
30
+ "",
31
+ createUnion("MarklessRouterExternalHref", [
32
+ "`http://${string}`",
33
+ "`https://${string}`",
34
+ "`mailto:${string}`",
35
+ "`tel:${string}`",
36
+ "`#${string}`",
37
+ "`?${string}`"
38
+ ]),
39
+ "",
40
+ "export type MarklessRouterAssetHref = `/${string}.${string}`;",
41
+ "",
42
+ createLinkTypes(),
43
+ ""
44
+ ].join("\n");
45
+ }
46
+ function createUnion(name, members) {
47
+ if (members.length === 0) return `export type ${name} = never;`;
48
+ return [`export type ${name} =`, ...members.map((member, index) => `\t| ${member}${index === members.length - 1 ? ";" : ""}`)].join("\n");
49
+ }
50
+ function createRouteParamsInterface(routes) {
51
+ const dynamicRoutes = routes.filter((route) => route.params.length > 0);
52
+ if (dynamicRoutes.length === 0) return "export interface MarklessRouterRouteParams {}";
53
+ return [
54
+ "export interface MarklessRouterRouteParams {",
55
+ ...dynamicRoutes.flatMap((route) => [
56
+ `\t${stringLiteral(route.pattern)}: {`,
57
+ ...route.params.map((param) => `\t\treadonly ${param.name}: ${paramType(param.kind)};`),
58
+ " };"
59
+ ]),
60
+ "}"
61
+ ].join("\n");
62
+ }
63
+ function hrefType(route) {
64
+ if (route.params.length === 0) return stringLiteral(route.pathname);
65
+ return templateLiteralHref(route.pathname);
66
+ }
67
+ function templateLiteralHref(pathname) {
68
+ return `\`/${splitPathname(pathname).map((segment) => {
69
+ if (segment === "**" || segment.startsWith(":")) return "${string}";
70
+ return segment;
71
+ }).join("/")}\``;
72
+ }
73
+ function splitPathname(pathname) {
74
+ return pathname === "/" ? [] : pathname.slice(1).split("/");
75
+ }
76
+ function paramType(kind) {
77
+ return kind === "catch-all" ? "string | number | readonly (string | number)[]" : "string | number";
78
+ }
79
+ function stringLiteral(value) {
80
+ return JSON.stringify(value);
81
+ }
82
+ function createLinkTypes() {
83
+ return [
84
+ "export type MarklessRouterRoutePatternLinkProps = {",
85
+ " [Pattern in MarklessRouterRoutePattern]: {",
86
+ " readonly href: Pattern;",
87
+ " readonly params: MarklessRouterRouteParams[Pattern];",
88
+ " };",
89
+ "}[MarklessRouterRoutePattern];",
90
+ "",
91
+ "export type MarklessRouterConcreteLinkProps = {",
92
+ " readonly href?: MarklessRouterStaticPageHref | MarklessRouterExternalHref | MarklessRouterAssetHref;",
93
+ " readonly params?: never;",
94
+ "};",
95
+ "",
96
+ "export type MarklessRouterNavigationProps = {",
97
+ " readonly prefetch?: boolean | \"intent\" | \"viewport\";",
98
+ " readonly replace?: boolean;",
99
+ " readonly scroll?: boolean;",
100
+ "};",
101
+ "",
102
+ "export type MarklessRouterLinkProps = MarklessRouterNavigationProps &",
103
+ " (MarklessRouterConcreteLinkProps | MarklessRouterRoutePatternLinkProps);",
104
+ "",
105
+ "declare module \"@markless/router\" {",
106
+ " interface MarklessRouterGeneratedRoutes {",
107
+ " readonly link: MarklessRouterLinkProps;",
108
+ " }",
109
+ "}"
110
+ ].join("\n");
111
+ }
112
+ //#endregion
113
+ //#region src/vite/route-typegen.ts
114
+ const PAGE_EXTENSIONS = new Set([".tsrx", ".mdx"]);
115
+ function routeTypegenPlugin() {
116
+ let root = "";
117
+ return {
118
+ name: "markless-router:typegen",
119
+ configResolved(config) {
120
+ root = config.root;
121
+ },
122
+ async buildStart() {
123
+ await writeRouteTypes(this.fs, root);
124
+ },
125
+ async watchChange() {
126
+ await writeRouteTypes(this.fs, root);
127
+ }
128
+ };
129
+ }
130
+ async function writeRouteTypes(fs, root) {
131
+ const manifest = buildRouteManifestFromFileIds(await discoverPageFiles(fs, root));
132
+ await writeIfChanged(fs, join(root, routeTypesEnvPath), routeTypesEnvDeclaration);
133
+ await writeIfChanged(fs, join(root, routeTypesOutputPath), createRouteTypesDeclaration(manifest));
134
+ }
135
+ async function discoverPageFiles(fs, root) {
136
+ const pagesDir = join(root, "pages");
137
+ const files = [];
138
+ await collectPageFiles(fs, root, pagesDir, files);
139
+ return files.toSorted((left, right) => left.localeCompare(right));
140
+ }
141
+ async function collectPageFiles(fs, root, dir, files) {
142
+ const entries = await readdirIfExists(fs, dir);
143
+ await Promise.all(entries.map((entry) => collectPageFile(fs, root, dir, files, entry)));
144
+ }
145
+ async function collectPageFile(fs, root, dir, files, entry) {
146
+ const path = join(dir, entry.name);
147
+ if (entry.isDirectory()) {
148
+ await collectPageFiles(fs, root, path, files);
149
+ return;
150
+ }
151
+ if (entry.isFile() && PAGE_EXTENSIONS.has(extname(path))) files.push(withLeadingSlash(relative(root, path)));
152
+ }
153
+ async function readdirIfExists(fs, dir) {
154
+ try {
155
+ return await fs.readdir(dir, { withFileTypes: true });
156
+ } catch (error) {
157
+ if (isNotFoundError(error)) return [];
158
+ throw error;
159
+ }
160
+ }
161
+ async function writeIfChanged(fs, path, contents) {
162
+ if (await readExistingText(fs, path) === contents) return;
163
+ await fs.mkdir(dirname(path), { recursive: true });
164
+ await fs.writeFile(path, contents, { encoding: "utf8" });
165
+ }
166
+ async function readExistingText(fs, path) {
167
+ try {
168
+ return await fs.readFile(path, { encoding: "utf8" });
169
+ } catch (error) {
170
+ if (isNotFoundError(error)) return;
171
+ throw error;
172
+ }
173
+ }
174
+ function isNotFoundError(error) {
175
+ return typeof error === "object" && error !== null && "code" in error ? error.code === "ENOENT" : false;
176
+ }
177
+ //#endregion
178
+ //#region src/vite/anchor-transform.ts
179
+ const ROUTE_HREF_HELPER_ID = "virtual:markless-router/route-href";
180
+ const JSX_FILE_FILTER = /\.(?:[jt]sx|tsrx)(?:$|\?)/;
181
+ const ROUTER_IMPORT_SOURCES = new Set(["@markless/router", "@markless/core/router"]);
182
+ function anchorTransformPlugin() {
183
+ let root = "";
184
+ let routePatterns = /* @__PURE__ */ new Map();
185
+ let routesLoaded = false;
186
+ const refreshRoutes = async (fs) => {
187
+ routePatterns = routePatternMap(buildRouteManifestFromFileIds(await discoverPageFiles(fs, root)));
188
+ routesLoaded = true;
189
+ };
190
+ return {
191
+ name: "markless-router:anchors",
192
+ transform: {
193
+ order: "pre",
194
+ filter: { id: JSX_FILE_FILTER },
195
+ async handler(code, id) {
196
+ if (!isTransformCandidateCode(code)) return;
197
+ if (!routesLoaded) await refreshRoutes(this.fs);
198
+ const transformed = transformAnchorSource(code, parseTransformAst(code, id, (source, options) => this.parse(source, options)), routePatterns);
199
+ return transformed === code ? void 0 : {
200
+ code: transformed,
201
+ map: null
202
+ };
203
+ }
204
+ },
205
+ configResolved(config) {
206
+ root = config.root;
207
+ },
208
+ async buildStart() {
209
+ await refreshRoutes(this.fs);
210
+ },
211
+ async watchChange() {
212
+ await refreshRoutes(this.fs);
213
+ }
214
+ };
215
+ }
216
+ function parseTransformAst(code, id, parse) {
217
+ if (id.includes(".tsrx")) return parseModule(code, id);
218
+ return parse(code, {
219
+ astType: "ts",
220
+ lang: id.includes(".jsx") ? "jsx" : "tsx",
221
+ range: true
222
+ });
223
+ }
224
+ function isTransformCandidateCode(code) {
225
+ return code.includes("<a") || [...ROUTER_IMPORT_SOURCES].some((importSource) => code.includes(importSource));
226
+ }
227
+ function transformAnchorSource(code, astInput, routePatterns) {
228
+ const ast = node$1(astInput);
229
+ if (!ast) return code;
230
+ const edits = [];
231
+ const linkNames = marklessRouterLinkImportNames(ast);
232
+ walk(ast, (current) => {
233
+ if (current.type !== "JSXOpeningElement") return;
234
+ const name = jsxName$1(node$1(current.name));
235
+ if (name !== "a" && (!name || !linkNames.has(name))) return;
236
+ const attributes = nodes$1(current.attributes);
237
+ const href = findJsxAttribute(attributes, "href");
238
+ const hrefValue = href ? stringLiteralValue(node$1(href.value)) : void 0;
239
+ if (!href || !hrefValue || !isRoutePatternHref(hrefValue)) return;
240
+ const routeParams = routePatterns.get(hrefValue);
241
+ if (!routeParams) fail$1(`Typed route error: ${hrefValue} does not match any route in pages/.`);
242
+ const params = findJsxAttribute(attributes, "params");
243
+ if (!params) fail$1(missingParamsMessage(hrefValue, routeParams));
244
+ const paramsExpression = jsxExpression(node$1(params.value));
245
+ if (!paramsExpression) fail$1(missingParamsMessage(hrefValue, routeParams));
246
+ validateObjectLiteralParams(hrefValue, routeParams, paramsExpression);
247
+ edits.push({
248
+ ...rangeOf(href),
249
+ text: `href={__marklessRouteHref(${JSON.stringify(hrefValue)}, ${slice$1(code, paramsExpression)})}`
250
+ });
251
+ edits.push({
252
+ ...attributeRemovalRange(code, params),
253
+ text: ""
254
+ });
255
+ });
256
+ if (edits.length === 0) return code;
257
+ return `${helperImport()}${applyEdits(code, edits)}`;
258
+ }
259
+ function marklessRouterLinkImportNames(ast) {
260
+ const names = /* @__PURE__ */ new Set();
261
+ walk(ast, (current) => {
262
+ if (current.type !== "ImportDeclaration" || !ROUTER_IMPORT_SOURCES.has(literalString(node$1(current.source)) ?? "")) return;
263
+ for (const specifier of nodes$1(current.specifiers)) if (specifier.type === "ImportSpecifier" && identifierName(node$1(specifier.imported)) === "Link") {
264
+ const localName = identifierName(node$1(specifier.local));
265
+ if (localName) names.add(localName);
266
+ }
267
+ });
268
+ return names;
269
+ }
270
+ function routePatternMap(manifest) {
271
+ return new Map(manifest.routes.filter((route) => route.params.length > 0).map((route) => [route.pattern, route.params]));
272
+ }
273
+ function validateObjectLiteralParams(pattern, routeParams, paramsExpression) {
274
+ if (paramsExpression.type !== "ObjectExpression") return;
275
+ const expected = new Set(routeParams.map((param) => param.name));
276
+ const actual = objectLiteralKeys(paramsExpression);
277
+ const unknown = actual.filter((param) => !expected.has(param));
278
+ if (unknown.length > 0) fail$1(`Typed route error: ${pattern} does not define param:\n${list(unknown)}`);
279
+ const missing = routeParams.map((param) => param.name).filter((param) => !actual.includes(param));
280
+ if (missing.length > 0) fail$1(`Typed route error: ${pattern} requires params:\n${list(missing)}`);
281
+ }
282
+ function objectLiteralKeys(expression) {
283
+ return nodes$1(expression.properties).flatMap((property) => {
284
+ if (property.type !== "Property") return [];
285
+ const key = node$1(property.key);
286
+ const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
287
+ return name ? [name] : [];
288
+ });
289
+ }
290
+ function helperImport() {
291
+ return `import { __marklessRouteHref } from "${ROUTE_HREF_HELPER_ID}";\n`;
292
+ }
293
+ function missingParamsMessage(pattern, routeParams) {
294
+ return `Typed route error: ${pattern} requires params:\n${list(routeParams.map((param) => param.name))}`;
295
+ }
296
+ function list(items) {
297
+ return items.map((item) => `- ${item}`).join("\n");
298
+ }
299
+ function findJsxAttribute(attributes, name) {
300
+ return attributes.find((attr) => attr.type === "JSXAttribute" && jsxName$1(node$1(attr.name)) === name);
301
+ }
302
+ function stringLiteralValue(value) {
303
+ return value?.type === "Literal" ? string$1(value.value) : void 0;
304
+ }
305
+ function literalString(value) {
306
+ return value?.type === "Literal" ? string$1(value.value) : void 0;
307
+ }
308
+ function identifierName(value) {
309
+ return value?.type === "Identifier" ? string$1(value.name) : void 0;
310
+ }
311
+ function jsxExpression(value) {
312
+ if (value?.type !== "JSXExpressionContainer") return;
313
+ const expression = node$1(value.expression);
314
+ return expression?.type === "JSXEmptyExpression" ? void 0 : expression;
315
+ }
316
+ function isRoutePatternHref(href) {
317
+ return href.includes("[") && href.includes("]");
318
+ }
319
+ function attributeRemovalRange(code, attribute) {
320
+ const range = rangeOf(attribute);
321
+ let start = range.start;
322
+ while (start > 0 && /\s/.test(code[start - 1] ?? "")) start--;
323
+ return {
324
+ start,
325
+ end: range.end
326
+ };
327
+ }
328
+ function applyEdits(code, edits) {
329
+ let transformed = code;
330
+ for (const edit of [...edits].toSorted((left, right) => right.start - left.start)) transformed = transformed.slice(0, edit.start) + edit.text + transformed.slice(edit.end);
331
+ return transformed;
332
+ }
333
+ function walk(root, visit) {
334
+ const stack = [root];
335
+ while (stack.length > 0) {
336
+ const current = node$1(stack.pop());
337
+ if (!current) continue;
338
+ visit(current);
339
+ for (const child of Object.values(current)) if (Array.isArray(child)) stack.push(...child);
340
+ else stack.push(child);
341
+ }
342
+ }
343
+ function jsxName$1(name) {
344
+ return name?.type === "JSXIdentifier" ? string$1(name.name) : void 0;
345
+ }
346
+ function slice$1(code, value) {
347
+ const range = rangeOf(value);
348
+ return code.slice(range.start, range.end);
349
+ }
350
+ function rangeOf(value) {
351
+ const range = value.range;
352
+ if (range) return {
353
+ start: range[0],
354
+ end: range[1]
355
+ };
356
+ return {
357
+ start: value.start,
358
+ end: value.end
359
+ };
360
+ }
361
+ function nodes$1(value) {
362
+ return Array.isArray(value) ? value.filter((item) => !!node$1(item)) : [];
363
+ }
364
+ function node$1(value) {
365
+ return typeof value === "object" && value !== null ? value : void 0;
366
+ }
367
+ function string$1(value) {
368
+ return typeof value === "string" ? value : void 0;
369
+ }
370
+ function fail$1(message) {
371
+ throw new Error(message);
372
+ }
373
+ //#endregion
374
+ //#region src/vite/html-transform.ts
375
+ const HELPER = "__marklessRouterHtmlAttributes";
376
+ const SAFE_GLOBALS = new Set([
377
+ "String",
378
+ "Number",
379
+ "Boolean",
380
+ "Math",
381
+ "JSON"
382
+ ]);
383
+ const DOCUMENT_FILE_LABEL = "document.tsx or document.jsx";
384
+ const DOCUMENT_FILE_FILTER = /(?:^|\/)document\.[tj]sx(?:$|\?)/;
385
+ function htmlTransformPlugin() {
386
+ return {
387
+ name: "markless-router:html",
388
+ transform: {
389
+ order: "pre",
390
+ filter: { id: DOCUMENT_FILE_FILTER },
391
+ handler(code, id) {
392
+ return transformHtmlSource(code, this.parse(code, {
393
+ astType: "ts",
394
+ lang: id.includes("document.jsx") ? "jsx" : "tsx",
395
+ range: true
396
+ }));
397
+ }
398
+ }
399
+ };
400
+ }
401
+ function transformHtmlSource(code, astInput) {
402
+ const ast = node(astInput);
403
+ if (!ast) fail(`MarklessRouter could not read ${DOCUMENT_FILE_LABEL}. Check it for syntax errors.`);
404
+ const fn = findDefaultDocumentFunction(ast);
405
+ if (fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression") fail(`MarklessRouter needs to see your document body in ${DOCUMENT_FILE_LABEL}. Write \`export default (props) => <Html>...</Html>\` instead of passing a named function.`);
406
+ const props = first(nodes(fn.params));
407
+ const propsName = props?.type === "Identifier" ? string(props.name) : void 0;
408
+ const propsSource = props ? slice(code, props) : "";
409
+ const attrs = nodes(node(findReturnedHtml(fn).openingElement)?.attributes).map((attr) => htmlAttr(code, attr, propsName));
410
+ const helper = `export function ${HELPER}(${propsSource}) {\n return ${attrs.length ? `{\n${attrs.map(([name, value]) => ` ${JSON.stringify(name)}: ${value}`).join(",\n")}\n }` : "{}"};\n}\n`;
411
+ return code.endsWith("\n") ? `${code}\n${helper}` : `${code}\n\n${helper}`;
412
+ }
413
+ function findDefaultDocumentFunction(ast) {
414
+ for (const statement of nodes(ast.body)) {
415
+ const declaration = node(statement.declaration);
416
+ if (statement.type !== "ExportDefaultDeclaration") continue;
417
+ if (declaration?.type === "ArrowFunctionExpression" || declaration?.type === "FunctionExpression") return declaration;
418
+ }
419
+ fail(`MarklessRouter could not find the document function. ${DOCUMENT_FILE_LABEL} must default export \`() => <Html>...</Html>\`.`);
420
+ }
421
+ function findReturnedHtml(fn) {
422
+ const body = unwrap(node(fn.body));
423
+ const returned = body?.type === "BlockStatement" ? unwrap(node(nodes(body.body).find((item) => item.type === "ReturnStatement")?.argument)) : body;
424
+ if (returned?.type !== "JSXElement") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Wrap your document shell in \`<Html>...</Html>\`.`);
425
+ if (jsxName(node(node(returned.openingElement)?.name)) !== "Html") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Put <head> and <body> inside \`<Html>...</Html>\`.`);
426
+ return returned;
427
+ }
428
+ function htmlAttr(code, attr, propsName) {
429
+ if (attr.type === "JSXSpreadAttribute") fail("MarklessRouter does not support spreading props onto <Html> yet. Write each html attribute directly, like `<Html lang=\"en\">`.");
430
+ const name = jsxName(node(attr.name));
431
+ if (!name) fail("MarklessRouter found an unsupported <Html> attribute name. Use plain html attributes like `lang`, `class`, or `data-theme`.");
432
+ const value = node(attr.value);
433
+ if (!value) return [name, "true"];
434
+ if (value.type === "Literal") return [name, JSON.stringify(value.value)];
435
+ if (value.type !== "JSXExpressionContainer") fail(`MarklessRouter could not understand the <Html> "${name}" attribute. Use a string or JSX expression, like \`${name}="value"\` or \`${name}={props.url.pathname}\`.`);
436
+ const expression = unwrap(node(value.expression));
437
+ if (!expression || expression.type === "JSXEmptyExpression") fail(`MarklessRouter found an empty <Html> "${name}" attribute expression. Add a value or remove the attribute.`);
438
+ const ref = unsupportedIdentifier(expression, propsName);
439
+ if (ref) fail(`MarklessRouter cannot use "${ref}" in <Html ${name}={...}> because html attributes are read before Markless renders ${DOCUMENT_FILE_LABEL}. Use props directly, like \`${name}={props.url.pathname}\`, or a literal value.`);
440
+ return [name, slice(code, expression)];
441
+ }
442
+ function unsupportedIdentifier(root, propsName) {
443
+ const stack = [root];
444
+ while (stack.length > 0) {
445
+ const current = node(stack.pop());
446
+ if (!current) continue;
447
+ if (current.type === "Identifier") {
448
+ const name = string(current.name);
449
+ if (name && name !== propsName && !SAFE_GLOBALS.has(name)) return name;
450
+ continue;
451
+ }
452
+ if (current.type === "MemberExpression") {
453
+ stack.push(current.object);
454
+ if (current.computed === true) stack.push(current.property);
455
+ continue;
456
+ }
457
+ if (current.type === "Property") {
458
+ stack.push(current.value);
459
+ if (current.computed === true) stack.push(current.key);
460
+ continue;
461
+ }
462
+ for (const child of Object.values(current)) if (Array.isArray(child)) stack.push(...child);
463
+ else stack.push(child);
464
+ }
465
+ }
466
+ function jsxName(name) {
467
+ return name?.type === "JSXIdentifier" ? string(name.name) : void 0;
468
+ }
469
+ function unwrap(value) {
470
+ let current = value;
471
+ while (current?.type === "ParenthesizedExpression" || current?.type === "TSAsExpression" || current?.type === "TSNonNullExpression") current = node(current.expression);
472
+ return current;
473
+ }
474
+ function slice(code, value) {
475
+ const range = value.range;
476
+ if (range) return code.slice(range[0], range[1]);
477
+ return code.slice(value.start, value.end);
478
+ }
479
+ function nodes(value) {
480
+ return Array.isArray(value) ? value.filter((item) => !!node(item)) : [];
481
+ }
482
+ function node(value) {
483
+ return typeof value === "object" && value !== null ? value : void 0;
484
+ }
485
+ function first(items) {
486
+ return items[0];
487
+ }
488
+ function string(value) {
489
+ return typeof value === "string" ? value : void 0;
490
+ }
491
+ function fail(message) {
492
+ throw new Error(message);
493
+ }
494
+ //#endregion
495
+ //#region src/vite/mdx.ts
496
+ function mdxTransformPlugin() {
497
+ return {
498
+ name: "markless-router:mdx",
499
+ enforce: "pre",
500
+ transform: {
501
+ order: "pre",
502
+ async handler(code, id) {
503
+ if (!isMdxFile(id)) return;
504
+ return {
505
+ code: await transformMdxRoute(code, id),
506
+ map: null
507
+ };
508
+ }
509
+ }
510
+ };
511
+ }
512
+ async function transformMdxRoute(source, id) {
513
+ const route = parseMdxRoute(source, id);
514
+ const html = route.parts.map((part) => part.kind === "html" ? part.html : "").join("");
515
+ if (route.components.length === 0) return [
516
+ "const marklessMdxPage = {",
517
+ " renderSsr() {",
518
+ ` return { html: ${JSON.stringify(html)} };`,
519
+ " }",
520
+ "};",
521
+ "export default marklessMdxPage;",
522
+ ""
523
+ ].join("\n");
524
+ return emitComposedMdxRoute(route);
525
+ }
526
+ function emitComposedMdxRoute(route) {
527
+ return [
528
+ `import { resumeFromPayloadDocument } from '@markless/core/web/resume';`,
529
+ `import { composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild, replaceMdxChild, rootFromMdxHtml } from '@markless/router/vite/runtime/mdx-route';`,
530
+ ...route.imports,
531
+ "",
532
+ `const marklessMdxParts = ${JSON.stringify(route.parts)};`,
533
+ `const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
534
+ "",
535
+ "const marklessMdxPage = {",
536
+ " async renderSsr(props = {}) {",
537
+ " const marklessMdxChildren = [];",
538
+ ` const html = ${renderHtmlExpression(route, "ssr")};`,
539
+ " const state = composeMdxState(marklessMdxChildren);",
540
+ " const view = composeMdxView(marklessMdxParts, marklessMdxChildren, 0);",
541
+ " return { html, ...(state ? { state } : {}), ...(view ? { view } : {}) };",
542
+ " },",
543
+ " renderCsr(props = {}) {",
544
+ " const marklessMdxChildren = [];",
545
+ ` const root = rootFromMdxHtml(${renderCsrHtml(route)});`,
546
+ ...route.components.flatMap((component, index) => [
547
+ ` const marklessMdxChild${index} = ${component.localName}.renderCsr?.(${componentPropsExpression(component)});`,
548
+ ` replaceMdxChild(root, ${index}, marklessMdxChild${index}?.root);`,
549
+ ` if (marklessMdxChild${index}) marklessMdxChildren.push({ componentIndex: ${index}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}, output: marklessMdxChild${index} });`
550
+ ]),
551
+ " const state = composeMdxState(marklessMdxChildren);",
552
+ " const view = composeMdxView(marklessMdxParts, marklessMdxChildren, 1);",
553
+ " return {",
554
+ " root,",
555
+ " ...(state ? { state } : {}),",
556
+ " ...(view ? { view } : {}),",
557
+ " loadSymbol(symbolId) { return marklessMdxLoadSymbol(symbolId, marklessMdxChildren); },",
558
+ " connectRuntime(context) { for (const child of marklessMdxChildren) child.output?.connectRuntime?.(context); },",
559
+ " };",
560
+ " },",
561
+ ...renderMdxPreload(route.components),
562
+ "};",
563
+ "export default marklessMdxPage;",
564
+ "",
565
+ "export async function resumeContainerEvent(input) {",
566
+ " input.root.__asyncResumeRuntimeStarted = true;",
567
+ " const { runtime } = await resumeFromPayloadDocument({",
568
+ " document: input.root,",
569
+ " root: input.root,",
570
+ " loadSymbol: marklessMdxLoadSymbol,",
571
+ " });",
572
+ " await runtime.dispatch(input.event, { syncPolicyAlreadyApplied: true });",
573
+ "}",
574
+ "",
575
+ "function marklessMdxLoadSymbol(symbolId, children = []) {",
576
+ " return loadMdxSymbol(symbolId, children, marklessMdxSymbolLoaders);",
577
+ "}",
578
+ ""
579
+ ].join("\n");
580
+ }
581
+ function renderMdxPreload(components) {
582
+ return [
583
+ " preload() {",
584
+ ` return Promise.all([${[...new Set(components.map((component) => component.localName))].map((localName) => `${localName}.preload?.()`).join(", ")}].filter(Boolean));`,
585
+ " },"
586
+ ];
587
+ }
588
+ function isMdxFile(id) {
589
+ return extname(decodePath(parseURL(id).pathname)) === ".mdx";
590
+ }
591
+ function parseMdxRoute(source, id) {
592
+ const imports = [];
593
+ const importedComponents = /* @__PURE__ */ new Map();
594
+ const components = [];
595
+ const handle = createMdxHastHandle(source);
596
+ let root;
597
+ try {
598
+ const importCollector = defineHastPlugin({
599
+ name: "markless-router-mdx-imports",
600
+ mdxjsEsm(node) {
601
+ for (const imported of tsrxImportsFromProgram(node.parseExpression(), id)) if (!importedComponents.has(imported.localName)) {
602
+ importedComponents.set(imported.localName, imported);
603
+ imports.push(`import ${imported.localName} from ${JSON.stringify(imported.specifier)};`);
604
+ }
605
+ }
606
+ });
607
+ visitHastHandle(handle, importCollector, resolveHastSubscriptions(importCollector), source, id);
608
+ root = materializeHastTree(new HastReader(serializeHandle(handle)));
609
+ } finally {
610
+ dropHandle(handle);
611
+ }
612
+ return {
613
+ imports,
614
+ components,
615
+ parts: routePartsFromHast(root.children ?? [], {
616
+ components,
617
+ id,
618
+ importedComponents
619
+ })
620
+ };
621
+ }
622
+ function tsrxImportsFromProgram(program, id) {
623
+ const imports = [];
624
+ if (!program) throw new Error(`Markless Router MDX could not parse ESM import syntax: ${id}`);
625
+ for (const statement of program.body) {
626
+ if (statement.type !== "ImportDeclaration") throw new Error(`Markless Router MDX only supports importing TSRX components before content: ${id}`);
627
+ const specifier = statement.source.value;
628
+ const defaultImport = statement.specifiers.find((importSpecifier) => importSpecifier.type === "ImportDefaultSpecifier");
629
+ if (typeof specifier !== "string" || !specifier.endsWith(".tsrx") || !defaultImport || statement.specifiers.length !== 1) throw new Error(`Markless Router MDX currently supports default imports from .tsrx files only: ${id}`);
630
+ imports.push({
631
+ localName: defaultImport.local.name,
632
+ specifier
633
+ });
634
+ }
635
+ return imports;
636
+ }
637
+ function routePartsFromHast(nodes, context) {
638
+ const parts = [];
639
+ let staticNodes = [];
640
+ for (const node of nodes) {
641
+ if (isMdxEsm(node)) continue;
642
+ if (isMdxJsxFlowElement(node)) {
643
+ appendHtmlPart(parts, staticNodes, context.id);
644
+ staticNodes = [];
645
+ parts.push(componentPart(node, context));
646
+ continue;
647
+ }
648
+ staticNodes.push(node);
649
+ }
650
+ appendHtmlPart(parts, staticNodes, context.id);
651
+ return parts;
652
+ }
653
+ function componentPart(node, context) {
654
+ const name = node.name;
655
+ const imported = name ? context.importedComponents.get(name) : void 0;
656
+ if (!imported) throw new Error(`Markless Router MDX component is not imported from .tsrx: ${name}`);
657
+ const componentIndex = context.components.length;
658
+ const props = componentProps(node, context.id);
659
+ const children = renderStaticHtml(node.children, context.id);
660
+ context.components.push({
661
+ ...imported,
662
+ prefix: `m${componentIndex}:`,
663
+ props: children ? [...props, {
664
+ name: "children",
665
+ valueExpression: JSON.stringify(children)
666
+ }] : props
667
+ });
668
+ return {
669
+ kind: "component",
670
+ componentIndex
671
+ };
672
+ }
673
+ function appendHtmlPart(parts, nodes, id) {
674
+ if (nodes.length === 0) return;
675
+ const staticNodes = normalizeStaticHastNodes(nodes, id);
676
+ const html = toHtml({
677
+ type: "root",
678
+ children: staticNodes
679
+ }, { allowDangerousHtml: true });
680
+ if (html) parts.push({
681
+ kind: "html",
682
+ html,
683
+ elementCount: countHastElements(staticNodes)
684
+ });
685
+ }
686
+ function renderStaticHtml(nodes, id) {
687
+ if (nodes.length === 0) return "";
688
+ return toHtml({
689
+ type: "root",
690
+ children: normalizeStaticHastNodes(nodes, id)
691
+ }, { allowDangerousHtml: true });
692
+ }
693
+ function normalizeStaticHastNodes(nodes, id) {
694
+ return nodes.flatMap((node) => normalizeStaticHastNode(node, id));
695
+ }
696
+ function normalizeStaticHastNode(node, id) {
697
+ if (isMdxEsm(node)) return [];
698
+ if (isMdxJsxFlowElement(node) || isMdxJsxTextElement(node)) throw new Error(`Markless Router MDX only supports imported TSRX components as route-level MDX elements or static markdown children: ${id}`);
699
+ if (isMdxExpression(node)) {
700
+ const value = literalSafeExpressionValue(node.value, id);
701
+ return [{
702
+ type: "text",
703
+ value: value == null ? "" : String(value)
704
+ }];
705
+ }
706
+ if ("children" in node && Array.isArray(node.children)) return [{
707
+ ...node,
708
+ children: normalizeStaticHastNodes(node.children, id)
709
+ }];
710
+ return [node];
711
+ }
712
+ function componentProps(node, id) {
713
+ return node.attributes.map((attribute) => componentProp(attribute, id));
714
+ }
715
+ function componentProp(attribute, id) {
716
+ if (attribute.type === "mdxJsxExpressionAttribute") throw new Error(`Markless Router MDX cannot lower spread attributes because they depend on MDX JavaScript scope that Markless does not execute during resume: ${id}`);
717
+ const value = attribute.value;
718
+ if (value == null) return {
719
+ name: attribute.name,
720
+ valueExpression: "true"
721
+ };
722
+ if (typeof value === "string") return {
723
+ name: attribute.name,
724
+ valueExpression: JSON.stringify(value)
725
+ };
726
+ return {
727
+ name: attribute.name,
728
+ valueExpression: literalValueExpression(literalSafeExpressionValue(value.value, id), id)
729
+ };
730
+ }
731
+ function literalSafeExpressionValue(source, id) {
732
+ const program = parseMdxExpressionValue(source, id);
733
+ if (!program) throw new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
734
+ const statement = program.body[0];
735
+ if (program.body.length !== 1 || statement?.type !== "ExpressionStatement") throw new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
736
+ return evaluateLiteralExpression(statement.expression, id);
737
+ }
738
+ function parseMdxExpressionValue(source, id) {
739
+ const handle = createMdxHastHandle(`{${source}}`);
740
+ let program = null;
741
+ try {
742
+ const expressionParser = defineHastPlugin({
743
+ name: "markless-router-mdx-expression",
744
+ mdxFlowExpression(node) {
745
+ program = node.parseExpression();
746
+ },
747
+ mdxTextExpression(node) {
748
+ program = node.parseExpression();
749
+ }
750
+ });
751
+ visitHastHandle(handle, expressionParser, resolveHastSubscriptions(expressionParser), `{${source}}`, id);
752
+ return program;
753
+ } finally {
754
+ dropHandle(handle);
755
+ }
756
+ }
757
+ function evaluateLiteralExpression(node, id) {
758
+ if (!node) throw literalExpressionError(id);
759
+ if (node.type === "Literal") {
760
+ const value = node.value;
761
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) return value;
762
+ throw literalExpressionError(id);
763
+ }
764
+ if (node.type === "UnaryExpression") {
765
+ const value = evaluateLiteralExpression(node.argument, id);
766
+ if (node.operator === "-" && typeof value === "number") return -value;
767
+ if (node.operator === "+" && typeof value === "number") return value;
768
+ if (node.operator === "!") return !value;
769
+ throw literalExpressionError(id);
770
+ }
771
+ if (node.type === "ArrayExpression") return node.elements.map((element) => evaluateLiteralExpression(element ?? void 0, id));
772
+ if (node.type === "ObjectExpression") {
773
+ const value = {};
774
+ for (const property of node.properties) {
775
+ if (property.type !== "Property" || property.kind !== "init" || property.method || property.computed) throw literalExpressionError(id);
776
+ const key = property.key;
777
+ const name = key.type === "Identifier" ? key.name : key.type === "Literal" && typeof key.value === "string" ? key.value : void 0;
778
+ if (!name) throw literalExpressionError(id);
779
+ value[name] = evaluateLiteralExpression(property.value, id);
780
+ }
781
+ return value;
782
+ }
783
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis.map((quasi) => quasi.value?.cooked ?? "").join("");
784
+ throw literalExpressionError(id);
785
+ }
786
+ function literalValueExpression(value, id) {
787
+ if (typeof value === "number" && !Number.isFinite(value)) throw literalExpressionError(id);
788
+ const json = JSON.stringify(value);
789
+ if (json === void 0) throw literalExpressionError(id);
790
+ return json;
791
+ }
792
+ function literalExpressionError(id) {
793
+ return /* @__PURE__ */ new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
794
+ }
795
+ function renderHtmlExpression(route, mode) {
796
+ return joinExpressions(route.parts.map((part) => {
797
+ if (part.kind === "html") return JSON.stringify(part.html);
798
+ if (mode === "csr") return JSON.stringify(`<span data-markless-mdx-child="${part.componentIndex}"></span>`);
799
+ const component = route.components[part.componentIndex];
800
+ return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)} }))`;
801
+ }));
802
+ }
803
+ function componentPropsExpression(component) {
804
+ if (component.props.length === 0) return "{}";
805
+ return `{ ${component.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.valueExpression}`).join(", ")} }`;
806
+ }
807
+ function renderCsrHtml(route) {
808
+ return `"<main data-markless-mdx-root>" + ${renderHtmlExpression(route, "csr")} + "</main>"`;
809
+ }
810
+ function joinExpressions(expressions) {
811
+ const filtered = expressions.filter((expression) => expression !== "\"\"");
812
+ return filtered.length === 0 ? "\"\"" : filtered.join(" + ");
813
+ }
814
+ function countHastElements(nodes) {
815
+ let count = 0;
816
+ for (const node of nodes) {
817
+ if (node.type === "element") count += 1;
818
+ if ("children" in node && Array.isArray(node.children)) count += countHastElements(node.children);
819
+ }
820
+ return count;
821
+ }
822
+ function renderSymbolLoaders(components) {
823
+ return `[${components.map((component) => `{ prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return import(${JSON.stringify(`${component.specifier}?markless-symbols`)}).then((mod) => mod.loadSymbol(symbolId.slice(${component.prefix.length}))); } }`).join(", ")}]`;
824
+ }
825
+ function isMdxEsm(node) {
826
+ return node.type === "mdxjsEsm";
827
+ }
828
+ function isMdxJsxFlowElement(node) {
829
+ return node.type === "mdxJsxFlowElement";
830
+ }
831
+ function isMdxJsxTextElement(node) {
832
+ return node.type === "mdxJsxTextElement";
833
+ }
834
+ function isMdxExpression(node) {
835
+ return node.type === "mdxFlowExpression" || node.type === "mdxTextExpression";
836
+ }
837
+ //#endregion
838
+ //#region src/vite/index.ts
839
+ const ROUTE_DISCOVERY_ID = "virtual:markless-router/routes";
840
+ const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
841
+ const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
842
+ const RESUME_ENTRY_ORIGIN = "/entries/resume-entry.ts";
843
+ const RESUME_ENTRY_PATH_ID = "virtual:markless-router/resume-entry-path";
844
+ const NAVIGATION_ENTRY_ID = "virtual:markless-router/navigation-entry";
845
+ const NAVIGATION_ENTRY_ORIGIN = "/entries/client-entry.ts";
846
+ const NAVIGATION_ENTRY_PATH_ID = "virtual:markless-router/navigation-entry-path";
847
+ const ROUTE_PRELOADS_ID = "virtual:markless-router/route-preloads";
848
+ const ROUTE_PRELOADS_PLACEHOLDER = "__MARKLESS_ROUTER_ROUTE_PRELOADS__";
849
+ const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
850
+ const ROUTE_HREF_ID = "virtual:markless-router/route-href";
851
+ const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
852
+ const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|navigation-entry|navigation-entry-path|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
853
+ const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
854
+ const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "entries");
855
+ const DEFAULT_WATCH_IGNORES = [
856
+ "**/.nitro/**",
857
+ "**/.output/**",
858
+ "**/node_modules/**",
859
+ "**/dist/**"
860
+ ];
861
+ const virtualEntryFiles = {
862
+ [ROUTE_DISCOVERY_ID]: "route-discovery.ts",
863
+ [CLIENT_ENTRY_ID]: "client-entry.ts",
864
+ [RESUME_ENTRY_ID]: "resume-entry.ts",
865
+ [NAVIGATION_ENTRY_ID]: "client-entry.ts",
866
+ [SERVER_ENTRY_ID]: "server-entry.ts",
867
+ [ROUTE_HREF_ID]: "route-href.ts"
868
+ };
869
+ function router(options = {}) {
870
+ if (options.nitro === false) return [
871
+ mdxTransformPlugin(),
872
+ requestFileTransformPlugin(),
873
+ routeTypegenPlugin(),
874
+ anchorTransformPlugin(),
875
+ htmlTransformPlugin(),
876
+ virtualModulesPlugin()
877
+ ];
878
+ const nitroPlugins = nitro();
879
+ const resumeEntry = resumeEntryState();
880
+ const navigationEntry = resumeEntryState();
881
+ const routePreloads = routePreloadState();
882
+ return [
883
+ routerConfigPlugin(nitroPlugins, resumeEntry, navigationEntry, routePreloads),
884
+ devSourceModuleRequestPlugin(),
885
+ mdxTransformPlugin(),
886
+ requestFileTransformPlugin(),
887
+ routeTypegenPlugin(),
888
+ anchorTransformPlugin(),
889
+ htmlTransformPlugin(),
890
+ virtualModulesPlugin(resumeEntry, navigationEntry, routePreloads),
891
+ nitroPlugins
892
+ ];
893
+ }
894
+ function devSourceModuleRequestPlugin() {
895
+ let root = "";
896
+ return {
897
+ name: "markless-router:dev-source-module-requests",
898
+ apply: "serve",
899
+ configResolved(config) {
900
+ root = config.root;
901
+ },
902
+ configureServer(server) {
903
+ server.middlewares.use((req, _res, next) => {
904
+ const url = req.url ?? "";
905
+ const queryIndex = url.indexOf("?");
906
+ const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);
907
+ const query = queryIndex === -1 ? "" : url.slice(queryIndex + 1);
908
+ if (root !== "" && /\.(?:tsrx|mdx)$/.test(pathname) && /(?:^|&)(?:import(?:=|&|$)|markless-)/.test(query) && existsSync(join(root, decodePath(pathname)))) req.url = `/@fs${join(root, decodePath(pathname))}?${query}`;
909
+ next();
910
+ });
911
+ }
912
+ };
913
+ }
914
+ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry, routePreloads) {
915
+ return {
916
+ name: "markless-router:vite",
917
+ enforce: "pre",
918
+ config(config) {
919
+ throwIfUserAddedNitro(config.plugins, nitroPluginsFromRouter);
920
+ config.environments ??= {};
921
+ const ssrEnvironment = config.environments.ssr ??= {};
922
+ ssrEnvironment.build ??= {};
923
+ ssrEnvironment.build.rolldownOptions ??= {};
924
+ ssrEnvironment.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, config.root);
925
+ const serverWatch = withWatchIgnores(config.server?.watch);
926
+ return {
927
+ nitro: createNitroConfig(config.nitro, config.root, serverWatch.ignored),
928
+ server: {
929
+ ...config.server,
930
+ watch: serverWatch
931
+ }
932
+ };
933
+ },
934
+ configResolved(config) {
935
+ for (const entry of [
936
+ resumeEntry,
937
+ navigationEntry,
938
+ routePreloads
939
+ ]) entry.base = config.base;
940
+ routePreloads.root = config.root;
941
+ },
942
+ configEnvironment(_name, config) {
943
+ configureRouteInputs(config);
944
+ },
945
+ generateBundle(_options, bundle) {
946
+ if (this.environment?.config.consumer !== "client") return;
947
+ const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
948
+ if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
949
+ const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
950
+ if (navigationChunk) navigationEntry.fileName = navigationChunk.fileName;
951
+ routePreloads.routes = routeModulePreloadsFromBundle({
952
+ base: routePreloads.base,
953
+ bundle,
954
+ navigationChunk,
955
+ resumeChunk,
956
+ root: routePreloads.root
957
+ });
958
+ patchRoutePreloadsInBundle(bundle, routePreloads.routes);
959
+ }
960
+ };
961
+ }
962
+ function configureRouteInputs(config) {
963
+ config.build ??= {};
964
+ config.build.rolldownOptions ??= {};
965
+ if (config.consumer === "client") {
966
+ config.build.rolldownOptions.input = routerClientInput(config.build.rolldownOptions.input, configRoot(config));
967
+ config.build.rolldownOptions.preserveEntrySignatures ??= "exports-only";
968
+ } else {
969
+ config.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, configRoot(config));
970
+ config.build.rolldownOptions.external = withExternal(config.build.rolldownOptions.external, "nitro");
971
+ }
972
+ }
973
+ function withExternal(external, id) {
974
+ if (Array.isArray(external)) return external.includes(id) ? external : [...external, id];
975
+ if (typeof external === "string") return external === id ? external : [external, id];
976
+ if (external === void 0) return [id];
977
+ return external;
978
+ }
979
+ function requestFileTransformPlugin() {
980
+ let root = "";
981
+ return {
982
+ name: "markless-router:request-files",
983
+ enforce: "pre",
984
+ configResolved(config) {
985
+ root = config.root;
986
+ },
987
+ transform(code, id) {
988
+ const fileId = requestFileIdForTransform(root, id);
989
+ if (!fileId) return;
990
+ const transform = transformRequestFileSource(fileId, code);
991
+ return transform ? {
992
+ code: transform.code,
993
+ map: null
994
+ } : void 0;
995
+ }
996
+ };
997
+ }
998
+ function requestFileIdForTransform(root, id) {
999
+ if (id.startsWith("\0")) return;
1000
+ const pathname = decodePath(parseURL(id).pathname);
1001
+ if (!isAbsolute(pathname)) return;
1002
+ const relativeFileId = root ? withoutLeadingSlash(normalize(relative(root, pathname))) : withoutLeadingSlash(normalize(pathname));
1003
+ if (relativeFileId.startsWith("api/") || relativeFileId.startsWith("middleware/")) return relativeFileId;
1004
+ return relativeFileId.match(/(?:^|\/)((?:api|middleware)\/.+\.ts)$/)?.[1];
1005
+ }
1006
+ function requestFileBuildTransformPlugin(root) {
1007
+ return {
1008
+ name: "markless-router:nitro-request-files",
1009
+ transform(code, id) {
1010
+ const fileId = requestFileIdForTransform(root, id);
1011
+ if (!fileId) return;
1012
+ const transform = transformRequestFileSource(fileId, code);
1013
+ return transform ? {
1014
+ code: transform.code,
1015
+ map: null
1016
+ } : void 0;
1017
+ }
1018
+ };
1019
+ }
1020
+ function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = []) {
1021
+ const scanDirs = Array.isArray(nitroConfig?.scanDirs) ? nitroConfig.scanDirs.filter((dir) => typeof dir === "string") : [];
1022
+ const watchOptions = withWatchIgnores(nitroConfig?.watchOptions, serverWatchIgnored);
1023
+ const publicAssets = Array.isArray(nitroConfig?.publicAssets) ? nitroConfig.publicAssets : [];
1024
+ return {
1025
+ ...nitroConfig,
1026
+ apiDir: nitroConfig?.apiDir ?? "api",
1027
+ devServer: nitroConfig?.devServer,
1028
+ publicAssets: [
1029
+ {
1030
+ baseURL: "/assets",
1031
+ dir: join(root, "dist/assets")
1032
+ },
1033
+ {
1034
+ baseURL: "/assets",
1035
+ dir: join(root, "node_modules/.nitro/vite/services/ssr/assets")
1036
+ },
1037
+ ...publicAssets
1038
+ ],
1039
+ routesDir: nitroConfig?.routesDir ?? ".output/markless/router/nitro-routes",
1040
+ rolldownConfig: withRequestFileBuildPlugin(nitroConfig?.rolldownConfig, root),
1041
+ rollupConfig: withRequestFileBuildPlugin(nitroConfig?.rollupConfig, root),
1042
+ scanDirs: [...new Set([".", ...scanDirs])],
1043
+ watchOptions
1044
+ };
1045
+ }
1046
+ function withWatchIgnores(watchOptions, extraIgnored = []) {
1047
+ const watchOptionsObject = isRecord(watchOptions) ? watchOptions : {};
1048
+ return {
1049
+ ...watchOptionsObject,
1050
+ followSymlinks: watchOptionsObject.followSymlinks ?? false,
1051
+ ignored: [
1052
+ ...toArray(watchOptionsObject.ignored),
1053
+ ...extraIgnored,
1054
+ ...DEFAULT_WATCH_IGNORES
1055
+ ]
1056
+ };
1057
+ }
1058
+ function toArray(value) {
1059
+ if (value === void 0) return [];
1060
+ return Array.isArray(value) ? value : [value];
1061
+ }
1062
+ function withRequestFileBuildPlugin(config, root) {
1063
+ const configObject = isRecord(config) ? config : {};
1064
+ const plugins = Array.isArray(configObject.plugins) ? configObject.plugins : configObject.plugins ? [configObject.plugins] : [];
1065
+ return {
1066
+ ...configObject,
1067
+ plugins: [requestFileBuildTransformPlugin(root), ...plugins]
1068
+ };
1069
+ }
1070
+ function throwIfUserAddedNitro(plugins, nitroPluginsFromRouter) {
1071
+ const routerNitroPluginSet = new Set(nitroPluginsFromRouter);
1072
+ if (sortUserPlugins(plugins).flat().filter(Boolean).find((plugin) => typeof plugin.name === "string" && plugin.name.startsWith("nitro:") && !routerNitroPluginSet.has(plugin))) throw new Error("Markless Router wires Nitro internally. Remove nitro() from vite.config.ts and keep plugins: [markless(), router()].");
1073
+ }
1074
+ function isRecord(input) {
1075
+ return typeof input === "object" && input !== null;
1076
+ }
1077
+ function resumeEntryState() {
1078
+ return {
1079
+ base: "/",
1080
+ fileName: void 0
1081
+ };
1082
+ }
1083
+ function routePreloadState() {
1084
+ return {
1085
+ base: "/",
1086
+ root: ".",
1087
+ routes: {
1088
+ navigation: {},
1089
+ ssr: {}
1090
+ }
1091
+ };
1092
+ }
1093
+ function routerClientInput(input, root) {
1094
+ const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
1095
+ const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
1096
+ if (input === void 0) return [resumeEntryId, navigationEntryId];
1097
+ if (typeof input === "string" || Array.isArray(input)) return [
1098
+ resumeEntryId,
1099
+ navigationEntryId,
1100
+ ...Array.isArray(input) ? input : [input]
1101
+ ];
1102
+ if (isRecord(input)) return {
1103
+ ...input,
1104
+ "markless-router-resume": resumeEntryId,
1105
+ "markless-router-navigation": navigationEntryId
1106
+ };
1107
+ return input;
1108
+ }
1109
+ function isVirtualEntryChunk(chunk, origin) {
1110
+ return [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id ? decodePath(parseURL(id).pathname).endsWith(origin) : false);
1111
+ }
1112
+ function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
1113
+ let root = "";
1114
+ return {
1115
+ name: "markless-router:routes",
1116
+ configResolved(config) {
1117
+ root = config.root;
1118
+ },
1119
+ resolveId: {
1120
+ filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
1121
+ handler(id) {
1122
+ const baseId = virtualModuleBaseId(id);
1123
+ if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID || baseId === ROUTER_OPTIONS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
1124
+ const entryFile = virtualEntryFiles[baseId];
1125
+ if (!entryFile) return;
1126
+ return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
1127
+ }
1128
+ },
1129
+ load(id) {
1130
+ if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root);
1131
+ if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
1132
+ if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
1133
+ if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads);
1134
+ if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) return "export const routerMode = \"path\"; // retained for compat; hash paths are always first-class";
1135
+ }
1136
+ };
1137
+ }
1138
+ function entryPathSource(exportName, entry, entryId, root) {
1139
+ const path = entry.fileName ? joinURL(entry.base, entry.fileName) : joinURL(entry.base, "@id", scopedVirtualEntryId(entryId, root));
1140
+ return `export const ${exportName} = ${JSON.stringify(path)};`;
1141
+ }
1142
+ function serverEntrySource(root) {
1143
+ const query = rootScopeQuery(root);
1144
+ return [
1145
+ `import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';`,
1146
+ `import { resumeEntryPath } from '${RESUME_ENTRY_PATH_ID}${query}';`,
1147
+ `import { navigationEntryPath } from '${NAVIGATION_ENTRY_PATH_ID}${query}';`,
1148
+ `import { routeModulePreloads, routeSsrModulePreloads } from '${ROUTE_PRELOADS_ID}${query}';`,
1149
+ `import { pageModuleLoaders, routeFileIds } from '${ROUTE_DISCOVERY_ID}${query}';`,
1150
+ `const documentModuleLoaders = import.meta.glob(['/document.tsrx']);`,
1151
+ `const entry = createServerEntry({`,
1152
+ ` resumeEntryPath,`,
1153
+ ` navigationEntryPath,`,
1154
+ ` routeModulePreloads,`,
1155
+ ` routeSsrModulePreloads,`,
1156
+ ` documentModuleLoader: documentModuleLoaders['/document.tsrx'],`,
1157
+ ` pageModuleLoaders,`,
1158
+ ` routeFileIds,`,
1159
+ `});`,
1160
+ `export const fetch = entry.fetch;`,
1161
+ `export default entry;`
1162
+ ].join("\n");
1163
+ }
1164
+ function rootScopeQuery(root, id = "") {
1165
+ const query = stringifyQuery({
1166
+ ...parseQuery(parsePath(id).search),
1167
+ "markless-router-root": root || "."
1168
+ });
1169
+ return query ? `?${query}` : "";
1170
+ }
1171
+ function routePreloadsSource(state) {
1172
+ return [
1173
+ `const routePreloadsJson = globalThis.__marklessRouterRoutePreloadsJson ?? ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)};`,
1174
+ `const routePreloadData = routePreloadsJson === ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)} ? ${JSON.stringify(state.routes)} : JSON.parse(routePreloadsJson);`,
1175
+ `export const routeModulePreloads = routePreloadData.navigation ?? {};`,
1176
+ `export const routeSsrModulePreloads = routePreloadData.ssr ?? {};`,
1177
+ `export function preloadRouteModule(file, document = globalThis.document) {`,
1178
+ ` const hrefs = routeModulePreloads[file];`,
1179
+ ` if (!hrefs?.length || !document?.head) return [];`,
1180
+ ` const seen = new Set([...document.querySelectorAll('link[rel="modulepreload"]')].map((link) => link.getAttribute?.('href') || link.href).filter(Boolean));`,
1181
+ ` const appended = [];`,
1182
+ ` for (const href of hrefs) {`,
1183
+ ` if (!href || seen.has(href)) continue;`,
1184
+ ` const link = document.createElement('link');`,
1185
+ ` link.rel = 'modulepreload';`,
1186
+ ` link.href = href;`,
1187
+ ` link.crossOrigin = 'anonymous';`,
1188
+ ` document.head.appendChild(link);`,
1189
+ ` seen.add(href);`,
1190
+ ` appended.push(href);`,
1191
+ ` }`,
1192
+ ` return appended;`,
1193
+ `}`
1194
+ ].join("\n");
1195
+ }
1196
+ function patchRoutePreloadsInBundle(bundle, routes) {
1197
+ const replacement = jsStringLiteralContent(JSON.stringify(routes));
1198
+ for (const chunk of outputChunks(bundle)) {
1199
+ if (!chunk.code?.includes(ROUTE_PRELOADS_PLACEHOLDER)) continue;
1200
+ chunk.code = chunk.code.replace(ROUTE_PRELOADS_PLACEHOLDER, replacement);
1201
+ }
1202
+ }
1203
+ function jsStringLiteralContent(value) {
1204
+ return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("'", "\\'").replaceAll("`", "\\`").replaceAll("${", "\\${");
1205
+ }
1206
+ function routeModulePreloadsFromBundle(input) {
1207
+ const chunks = outputChunks(input.bundle);
1208
+ const chunksByFileName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
1209
+ const routeChunks = /* @__PURE__ */ new Map();
1210
+ const routeChunkFileNames = /* @__PURE__ */ new Set();
1211
+ for (const chunk of chunks) {
1212
+ const routeFile = routeFileForChunk(input.root, chunk);
1213
+ if (!routeFile) continue;
1214
+ routeChunks.set(routeFile, chunk);
1215
+ routeChunkFileNames.add(chunk.fileName);
1216
+ }
1217
+ const symbolChunks = symbolChunksBySourceFile(chunks);
1218
+ const navigation = {};
1219
+ const ssr = {};
1220
+ for (const [routeFile, routeChunk] of routeChunks) {
1221
+ const navigationFileNames = /* @__PURE__ */ new Set();
1222
+ if (input.navigationChunk) {
1223
+ includeChunk(navigationFileNames, chunksByFileName, input.navigationChunk.fileName);
1224
+ for (const fileName of input.navigationChunk.dynamicImports) if (!routeChunkFileNames.has(fileName)) includeChunk(navigationFileNames, chunksByFileName, fileName);
1225
+ }
1226
+ includeChunk(navigationFileNames, chunksByFileName, routeChunk.fileName);
1227
+ includeChunk(navigationFileNames, chunksByFileName, routeChunk.fileName, true);
1228
+ if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames, chunksByFileName, fileName, true);
1229
+ const ssrFileNames = /* @__PURE__ */ new Set();
1230
+ if (input.resumeChunk) {
1231
+ includeChunk(ssrFileNames, chunksByFileName, input.resumeChunk.fileName);
1232
+ for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
1233
+ }
1234
+ includeChunk(ssrFileNames, chunksByFileName, routeChunk.fileName, true);
1235
+ const routeSourceFiles = sourceFilesForChunks(new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
1236
+ for (const [sourceFile, symbolFileNames] of symbolChunks) {
1237
+ if (!routeSourceFiles.has(sourceFile)) continue;
1238
+ for (const fileName of symbolFileNames) {
1239
+ includeChunk(navigationFileNames, chunksByFileName, fileName, true);
1240
+ includeChunk(ssrFileNames, chunksByFileName, fileName, true);
1241
+ }
1242
+ }
1243
+ navigation[routeFile] = [...navigationFileNames].map((fileName) => joinURL(input.base, fileName));
1244
+ ssr[routeFile] = [...ssrFileNames].map((fileName) => joinURL(input.base, fileName));
1245
+ }
1246
+ return {
1247
+ navigation,
1248
+ ssr
1249
+ };
1250
+ }
1251
+ function symbolChunksBySourceFile(chunks) {
1252
+ const bySourceFile = /* @__PURE__ */ new Map();
1253
+ for (const chunk of chunks) for (const moduleId of chunk.moduleIds ?? []) {
1254
+ const sourceFile = symbolVirtualModuleSourceFile(moduleId);
1255
+ if (!sourceFile) continue;
1256
+ const key = sourceModulePathname(sourceFile);
1257
+ const fileNames = bySourceFile.get(key) ?? [];
1258
+ if (!fileNames.includes(chunk.fileName)) fileNames.push(chunk.fileName);
1259
+ bySourceFile.set(key, fileNames);
1260
+ }
1261
+ return bySourceFile;
1262
+ }
1263
+ function sourceFilesForChunks(fileNames, chunksByFileName) {
1264
+ const sourceFiles = /* @__PURE__ */ new Set();
1265
+ for (const fileName of fileNames) for (const moduleId of chunksByFileName.get(fileName)?.moduleIds ?? []) {
1266
+ if (moduleId.startsWith("\0")) continue;
1267
+ const pathname = sourceModulePathname(moduleId);
1268
+ if (/\.(?:tsrx|mdx)$/.test(pathname)) sourceFiles.add(pathname);
1269
+ }
1270
+ return sourceFiles;
1271
+ }
1272
+ function sourceModulePathname(moduleId) {
1273
+ return decodePath(parseURL(moduleId).pathname);
1274
+ }
1275
+ function includeChunk(fileNames, chunksByFileName, fileName, includeDynamic = false, visitedDynamic = /* @__PURE__ */ new Set()) {
1276
+ if (!fileNames.has(fileName)) fileNames.add(fileName);
1277
+ const chunk = chunksByFileName.get(fileName);
1278
+ if (!chunk) return;
1279
+ for (const imported of chunk.imports) includeChunk(fileNames, chunksByFileName, imported, includeDynamic, visitedDynamic);
1280
+ for (const imported of codeStaticImports(chunk)) {
1281
+ if (!chunksByFileName.has(imported)) continue;
1282
+ includeChunk(fileNames, chunksByFileName, imported, includeDynamic, visitedDynamic);
1283
+ }
1284
+ if (!includeDynamic || visitedDynamic.has(fileName)) return;
1285
+ visitedDynamic.add(fileName);
1286
+ for (const imported of chunk.dynamicImports) includeChunk(fileNames, chunksByFileName, imported, true, visitedDynamic);
1287
+ for (const imported of codeDynamicImports(chunk)) {
1288
+ if (!chunksByFileName.has(imported)) continue;
1289
+ includeChunk(fileNames, chunksByFileName, imported, true, visitedDynamic);
1290
+ }
1291
+ }
1292
+ function routeScopedDynamicImports(chunk, routeFile) {
1293
+ if (!chunk.code) return [];
1294
+ const imports = /* @__PURE__ */ new Set();
1295
+ const routeLiteralIndex = chunk.code.indexOf(routeFile);
1296
+ if (routeLiteralIndex === -1) return [];
1297
+ const nextRouteLiteralIndex = chunk.code.slice(routeLiteralIndex + routeFile.length).search(/["'`]\/?pages\/[^"'`]+\.(?:tsrx|mdx)["'`]/);
1298
+ const routeBlockEnd = nextRouteLiteralIndex === -1 ? chunk.code.length : routeLiteralIndex + routeFile.length + nextRouteLiteralIndex;
1299
+ const routeBlock = chunk.code.slice(routeLiteralIndex, routeBlockEnd);
1300
+ for (const specifier of codeDynamicImportSpecifiers(routeBlock)) imports.add(normalize(join(dirname(chunk.fileName), specifier)));
1301
+ return [...imports];
1302
+ }
1303
+ function codeStaticImports(chunk) {
1304
+ return codeImportSpecifiers(chunk, /(?:import\s*(?:[^('"`]*?\bfrom\s*)?|export\s*[^('"`]*?\bfrom\s*)["'](\.\/[^"']+\.js)["']/g);
1305
+ }
1306
+ function codeDynamicImports(chunk) {
1307
+ return codeDynamicImportSpecifiers(chunk.code ?? "").map((specifier) => normalize(join(dirname(chunk.fileName), specifier)));
1308
+ }
1309
+ function codeDynamicImportSpecifiers(code) {
1310
+ const imports = /* @__PURE__ */ new Set();
1311
+ for (const match of code.matchAll(/import\(\s*["'`](\.\/[^"'`]+\.js)["'`]\s*\)/g)) {
1312
+ const specifier = match[1];
1313
+ if (specifier) imports.add(specifier);
1314
+ }
1315
+ return [...imports];
1316
+ }
1317
+ function codeImportSpecifiers(chunk, pattern) {
1318
+ if (!chunk.code) return [];
1319
+ const imports = /* @__PURE__ */ new Set();
1320
+ for (const match of chunk.code.matchAll(pattern)) {
1321
+ const specifier = match[1];
1322
+ if (specifier) imports.add(normalize(join(dirname(chunk.fileName), specifier)));
1323
+ }
1324
+ return [...imports];
1325
+ }
1326
+ function outputChunks(bundle) {
1327
+ return Object.values(bundle).filter((item) => {
1328
+ return isRecord(item) && item.type === "chunk" && typeof item.fileName === "string" && Array.isArray(item.imports) && Array.isArray(item.dynamicImports);
1329
+ });
1330
+ }
1331
+ function routeFileForChunk(root, chunk) {
1332
+ for (const moduleId of [chunk.facadeModuleId, ...chunk.moduleIds ?? []]) {
1333
+ const routeFile = routeFileForModuleId(root, moduleId);
1334
+ if (routeFile) return routeFile;
1335
+ }
1336
+ }
1337
+ function routeFileForModuleId(root, moduleId) {
1338
+ if (!moduleId || moduleId.startsWith("\0")) return void 0;
1339
+ const pathname = decodePath(parseURL(moduleId).pathname);
1340
+ if (!isAbsolute(pathname)) return void 0;
1341
+ const routeFile = withoutLeadingSlash(normalize(relative(root, pathname)));
1342
+ return /(?:^|\/)pages\/.+\.(?:tsrx|mdx)$/.test(routeFile) ? routeFile : void 0;
1343
+ }
1344
+ function scopedVirtualEntryId(id, root) {
1345
+ return `${virtualModuleBaseId(id)}${rootScopeQuery(root ?? ".", id)}`;
1346
+ }
1347
+ function virtualModuleBaseId(id) {
1348
+ return parsePath(id).pathname;
1349
+ }
1350
+ function configRoot(config) {
1351
+ const root = config.root;
1352
+ return typeof root === "string" ? root : ".";
1353
+ }
1354
+ //#endregion
1355
+ export { router };