@markless/router 0.5.0 → 0.7.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/create-server-entry-yboa-2m6.js +1333 -0
- package/dist/index-BjxpxRL7.d.ts +248 -0
- package/dist/index.d.ts +2 -233
- package/dist/index.js +64 -15
- package/dist/request-locals-BDAhNpEa.js +94 -0
- package/dist/route-manifest-DMizh42B.js +456 -0
- package/dist/typescript-plugin.cjs +1 -1
- package/dist/typescript-plugin.js +1 -1
- package/dist/vite/runtime/create-route-discovery.js +1 -1
- package/dist/vite/runtime/create-server-entry.d.ts +16 -0
- package/dist/vite/runtime/create-server-entry.js +2 -658
- package/dist/vite/runtime/mdx-route.d.ts +21 -2
- package/dist/vite/runtime/mdx-route.js +65 -3
- package/dist/vite.js +239 -39
- package/package.json +4 -4
- package/src/vite/entries/fragment-entry.ts +290 -32
- package/dist/fragment-navigation-CIiNnFFc.js +0 -323
- package/dist/link-attributes-nY1Qkmx2.js +0 -7
- package/dist/request-files-Ca8E0FOo.js +0 -233
- package/dist/request-locals-BFPF6Vdm.js +0 -28
- package/dist/route-manifest-Bw_nFohL.js +0 -189
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
//#region src/vite/link-intent.ts
|
|
2
|
-
function listenForLinkIntent(root, linkAttribute, preload, prefetchAttribute) {
|
|
3
|
-
const listener = (event) => {
|
|
4
|
-
if (event.defaultPrevented) return;
|
|
5
|
-
const target = event.composedPath()[0] ?? event.target;
|
|
6
|
-
const anchor = (target instanceof Element ? target : target?.parentElement)?.closest("a[href]");
|
|
7
|
-
if (!anchor?.hasAttribute(linkAttribute) || anchor.hasAttribute("download")) return;
|
|
8
|
-
if (prefetchAttribute && anchor.getAttribute(prefetchAttribute) === "none") return;
|
|
9
|
-
if (anchor.target && anchor.target !== "_self") return;
|
|
10
|
-
if (anchor.relList.contains("external")) return;
|
|
11
|
-
const url = new URL(anchor.href, anchor.ownerDocument.baseURI);
|
|
12
|
-
if (url.origin !== new URL(anchor.ownerDocument.URL).origin) return;
|
|
13
|
-
preload(url);
|
|
14
|
-
};
|
|
15
|
-
const events = [
|
|
16
|
-
"pointerover",
|
|
17
|
-
"focusin",
|
|
18
|
-
"pointerdown"
|
|
19
|
-
];
|
|
20
|
-
for (const event of events) root.addEventListener(event, listener, { passive: true });
|
|
21
|
-
return () => {
|
|
22
|
-
for (const event of events) root.removeEventListener(event, listener);
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function appendModulePreloads(document, items) {
|
|
26
|
-
if (!document?.head || !items?.length) return [];
|
|
27
|
-
const absolute = (href) => new URL(href, document.baseURI).href;
|
|
28
|
-
const seen = new Set([...document.querySelectorAll("link[rel=\"modulepreload\"]")].map((link) => absolute(link.getAttribute("href") || link.href)));
|
|
29
|
-
const appended = [];
|
|
30
|
-
for (const item of items) {
|
|
31
|
-
const href = typeof item === "string" ? item : item.href;
|
|
32
|
-
if (!href || seen.has(absolute(href))) continue;
|
|
33
|
-
const link = document.createElement("link");
|
|
34
|
-
link.rel = "modulepreload";
|
|
35
|
-
link.href = href;
|
|
36
|
-
link.crossOrigin = typeof item === "string" ? "anonymous" : item.crossOrigin ?? "anonymous";
|
|
37
|
-
if (typeof item !== "string" && item.fetchPriority) link.fetchPriority = item.fetchPriority;
|
|
38
|
-
document.head.appendChild(link);
|
|
39
|
-
seen.add(absolute(href));
|
|
40
|
-
appended.push(href);
|
|
41
|
-
}
|
|
42
|
-
return appended;
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/vite/fragment-navigation.ts
|
|
46
|
-
function startFragmentNavigation(d, configOf, routes, importModule) {
|
|
47
|
-
const w = d.defaultView;
|
|
48
|
-
if (!w) return;
|
|
49
|
-
const host = d;
|
|
50
|
-
const known = host.__marklessFragmentNav?.routes ?? host.__marklessFragmentWake;
|
|
51
|
-
if (known) return void Object.assign(known, routes);
|
|
52
|
-
host.__marklessFragmentWake = routes;
|
|
53
|
-
const scrolled = /* @__PURE__ */ new Set();
|
|
54
|
-
let traverse;
|
|
55
|
-
const wake = () => traverse ??= start();
|
|
56
|
-
const listen = (event) => {
|
|
57
|
-
const target = event.composedPath()[0] ?? event.target;
|
|
58
|
-
if (event.type === "scroll") {
|
|
59
|
-
if (target !== d) scrolled.add(target);
|
|
60
|
-
} else if (event.type === "popstate") wake()(event);
|
|
61
|
-
else if (!traverse && target.closest?.("a[href]")) wake();
|
|
62
|
-
};
|
|
63
|
-
for (const type of [
|
|
64
|
-
"scroll",
|
|
65
|
-
"pointerover",
|
|
66
|
-
"pointerdown",
|
|
67
|
-
"touchstart",
|
|
68
|
-
"focusin",
|
|
69
|
-
"click",
|
|
70
|
-
"popstate"
|
|
71
|
-
]) w.addEventListener(type, listen, {
|
|
72
|
-
capture: true,
|
|
73
|
-
passive: true
|
|
74
|
-
});
|
|
75
|
-
if (Object.keys(routes).length) {
|
|
76
|
-
const idle = () => w.requestIdleCallback ? w.requestIdleCallback(wake, { timeout: 3e3 }) : w.setTimeout(wake, 500);
|
|
77
|
-
if (d.readyState === "complete") idle();
|
|
78
|
-
else w.addEventListener("load", idle, { once: true });
|
|
79
|
-
}
|
|
80
|
-
const start = () => {
|
|
81
|
-
delete host.__marklessFragmentWake;
|
|
82
|
-
const config = configOf();
|
|
83
|
-
const location = w.location;
|
|
84
|
-
const connection = w.navigator.connection;
|
|
85
|
-
const pageKey = (url) => url.pathname + url.search;
|
|
86
|
-
let module;
|
|
87
|
-
const load = () => {
|
|
88
|
-
module ??= importModule(config.entry);
|
|
89
|
-
module.catch(() => module = void 0);
|
|
90
|
-
return module;
|
|
91
|
-
};
|
|
92
|
-
const pages = config.pages.map((page) => page.split("/").filter(Boolean));
|
|
93
|
-
const isPage = (pathname) => {
|
|
94
|
-
if (config.endpointPrefixes.some((prefix) => (pathname + "/").startsWith(prefix + "/"))) return false;
|
|
95
|
-
const parts = pathname.split("/").filter(Boolean);
|
|
96
|
-
return pages.some((page) => {
|
|
97
|
-
const rest = page.indexOf("**");
|
|
98
|
-
if (rest === -1 ? page.length !== parts.length : parts.length <= rest) return false;
|
|
99
|
-
return page.every((segment, index) => segment === "**" || segment === ":" || segment === parts[index]);
|
|
100
|
-
});
|
|
101
|
-
};
|
|
102
|
-
const eligible = (anchor, forClick = false) => {
|
|
103
|
-
if (!anchor || anchor.localName !== "a") return;
|
|
104
|
-
const link = anchor;
|
|
105
|
-
if (link.hasAttribute("download") || link.relList.contains("external")) return;
|
|
106
|
-
if (link.target && link.target !== "_self") return;
|
|
107
|
-
if (!forClick && link.getAttribute(config.prefetchAttribute) === "none") return;
|
|
108
|
-
let url;
|
|
109
|
-
try {
|
|
110
|
-
url = new URL(link.href, d.baseURI);
|
|
111
|
-
} catch {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (url.origin !== location.origin || url.hash.startsWith("#/")) return;
|
|
115
|
-
if (!isPage(url.pathname)) return;
|
|
116
|
-
const here = forClick ? pageKey(new URL(location.href)) : state.shown;
|
|
117
|
-
if (pageKey(url) === here && (!forClick || url.href.includes("#"))) return;
|
|
118
|
-
return url;
|
|
119
|
-
};
|
|
120
|
-
const anchorOf = (event) => {
|
|
121
|
-
const target = event.composedPath?.()[0] ?? event.target;
|
|
122
|
-
return (target && target.nodeType === 1 ? target : target?.parentElement)?.closest("a[href]");
|
|
123
|
-
};
|
|
124
|
-
const preloadCode = (url, low) => {
|
|
125
|
-
const present = new Set([...d.querySelectorAll("link[rel=\"modulepreload\"]")].map((link) => link.href));
|
|
126
|
-
for (const item of [config.entry, ...state.routes[url.pathname] ?? []]) {
|
|
127
|
-
const entry = typeof item === "string" ? { href: item } : item;
|
|
128
|
-
const href = new URL(entry.href, d.baseURI).href;
|
|
129
|
-
if (present.has(href)) continue;
|
|
130
|
-
present.add(href);
|
|
131
|
-
const link = d.createElement("link");
|
|
132
|
-
link.rel = "modulepreload";
|
|
133
|
-
link.href = href;
|
|
134
|
-
link.crossOrigin = entry.crossOrigin ?? "anonymous";
|
|
135
|
-
if (low) link.fetchPriority = "low";
|
|
136
|
-
d.head.appendChild(link);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
const parse = (url, text) => {
|
|
140
|
-
const at = text.indexOf(config.regionEndMarker);
|
|
141
|
-
const prefix = at === -1 ? text : text.slice(0, at);
|
|
142
|
-
return {
|
|
143
|
-
url,
|
|
144
|
-
prefix,
|
|
145
|
-
doc: new DOMParser().parseFromString(prefix, "text/html"),
|
|
146
|
-
tail: at === -1 ? "" : text.slice(at + config.regionEndMarker.length)
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
const request = (url, speculative) => {
|
|
150
|
-
const controller = new AbortController();
|
|
151
|
-
const read = async () => {
|
|
152
|
-
const response = await w.fetch(url.href, {
|
|
153
|
-
credentials: "same-origin",
|
|
154
|
-
headers: speculative ? {
|
|
155
|
-
[config.requestHeader]: "1",
|
|
156
|
-
purpose: "prefetch"
|
|
157
|
-
} : { [config.requestHeader]: "1" },
|
|
158
|
-
priority: speculative ? "auto" : "high",
|
|
159
|
-
signal: controller.signal
|
|
160
|
-
});
|
|
161
|
-
const type = response.headers.get("content-type") ?? "";
|
|
162
|
-
if (!response.ok || !type.includes("text/html") || !response.body) throw new Error(`MARKLESS_ROUTER_FRAGMENT_UNAVAILABLE: ${response.status}`);
|
|
163
|
-
const finalUrl = new URL(response.url || url.href);
|
|
164
|
-
if (finalUrl.origin !== location.origin) throw new Error("MARKLESS_ROUTER_FRAGMENT_OFF_ORIGIN");
|
|
165
|
-
finalUrl.hash = url.hash;
|
|
166
|
-
const reader = response.body.getReader();
|
|
167
|
-
const decoder = new TextDecoder();
|
|
168
|
-
let text = "";
|
|
169
|
-
for (;;) {
|
|
170
|
-
if (text.includes(config.regionEndMarker)) return {
|
|
171
|
-
...parse(finalUrl.href, text),
|
|
172
|
-
rest: reader
|
|
173
|
-
};
|
|
174
|
-
const chunk = await reader.read();
|
|
175
|
-
if (chunk.done) {
|
|
176
|
-
entry.open = false;
|
|
177
|
-
return parse(finalUrl.href, text + decoder.decode());
|
|
178
|
-
}
|
|
179
|
-
text += decoder.decode(chunk.value, { stream: true });
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
const entry = {
|
|
183
|
-
at: w.performance.now(),
|
|
184
|
-
result: read(),
|
|
185
|
-
open: true,
|
|
186
|
-
abort: () => {
|
|
187
|
-
entry.open = false;
|
|
188
|
-
controller.abort();
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
entry.result.catch(() => entry.open = false);
|
|
192
|
-
return entry;
|
|
193
|
-
};
|
|
194
|
-
const prefetch = (url) => {
|
|
195
|
-
const key = pageKey(url);
|
|
196
|
-
if (!config.prefetch || key === state.shown) return;
|
|
197
|
-
const existing = state.prefetches.get(key);
|
|
198
|
-
if (existing && w.performance.now() - existing.at < config.prefetchTtlMs) return;
|
|
199
|
-
existing?.abort();
|
|
200
|
-
const open = [...state.prefetches].filter(([, entry]) => entry.open);
|
|
201
|
-
for (const [stale, entry] of open.slice(0, Math.max(0, open.length + 1 - config.prefetchConnections))) {
|
|
202
|
-
state.prefetches.delete(stale);
|
|
203
|
-
entry.abort();
|
|
204
|
-
}
|
|
205
|
-
const entry = request(url, true);
|
|
206
|
-
state.prefetches.set(key, entry);
|
|
207
|
-
w.setTimeout(() => {
|
|
208
|
-
if (state.prefetches.get(key) !== entry) return;
|
|
209
|
-
state.prefetches.delete(key);
|
|
210
|
-
entry.abort();
|
|
211
|
-
}, config.prefetchTtlMs);
|
|
212
|
-
preloadCode(url, true);
|
|
213
|
-
};
|
|
214
|
-
let scanning = false;
|
|
215
|
-
const rescan = () => {
|
|
216
|
-
if (!state.idleDownloads || scanning) return;
|
|
217
|
-
scanning = true;
|
|
218
|
-
const run = () => {
|
|
219
|
-
scanning = false;
|
|
220
|
-
const visible = /* @__PURE__ */ new Map();
|
|
221
|
-
for (const anchor of d.querySelectorAll("a[href]")) {
|
|
222
|
-
const url = eligible(anchor);
|
|
223
|
-
if (!url || !state.routes[url.pathname]) continue;
|
|
224
|
-
const box = anchor.getBoundingClientRect();
|
|
225
|
-
if (!box.width || box.bottom < 0 || box.top > w.innerHeight) continue;
|
|
226
|
-
if (box.right < 0 || box.left > w.innerWidth) continue;
|
|
227
|
-
const hit = d.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2);
|
|
228
|
-
if (!hit || hit !== anchor && !anchor.contains(hit)) continue;
|
|
229
|
-
visible.set(pageKey(url), url);
|
|
230
|
-
}
|
|
231
|
-
const fragments = visible.size <= config.idleFragments * 2 ? config.idleFragments : 0;
|
|
232
|
-
let fetched = 0;
|
|
233
|
-
for (const [key, url] of visible) {
|
|
234
|
-
preloadCode(url, true);
|
|
235
|
-
if (fetched >= fragments || state.prefetches.has(key)) continue;
|
|
236
|
-
fetched += 1;
|
|
237
|
-
state.prefetch(url);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
const idle = () => w.requestIdleCallback ? w.requestIdleCallback(run, { timeout: 3e3 }) : w.setTimeout(run, 500);
|
|
241
|
-
if (d.readyState === "complete") idle();
|
|
242
|
-
else w.addEventListener("load", idle, { once: true });
|
|
243
|
-
};
|
|
244
|
-
const state = {
|
|
245
|
-
config,
|
|
246
|
-
routes,
|
|
247
|
-
prefetches: /* @__PURE__ */ new Map(),
|
|
248
|
-
visited: /* @__PURE__ */ new Map(),
|
|
249
|
-
shown: pageKey(new URL(location.href)),
|
|
250
|
-
eligible,
|
|
251
|
-
preloadCode,
|
|
252
|
-
request,
|
|
253
|
-
prefetch,
|
|
254
|
-
parse,
|
|
255
|
-
idleDownloads: config.prefetch && (connection ? connection.effectiveType === "4g" && connection.saveData !== true : config.idleWithoutConnectionInfo),
|
|
256
|
-
rescan,
|
|
257
|
-
scrolled
|
|
258
|
-
};
|
|
259
|
-
host.__marklessFragmentNav = state;
|
|
260
|
-
let restTimer = 0;
|
|
261
|
-
let previous;
|
|
262
|
-
d.addEventListener("pointermove", (event) => {
|
|
263
|
-
const pointer = event;
|
|
264
|
-
if (pointer.pointerType !== "mouse") return;
|
|
265
|
-
const point = {
|
|
266
|
-
x: pointer.clientX,
|
|
267
|
-
y: pointer.clientY,
|
|
268
|
-
t: pointer.timeStamp
|
|
269
|
-
};
|
|
270
|
-
const gap = previous ? point.t - previous.t : 0;
|
|
271
|
-
const creeping = !!previous && gap > 0 && Math.hypot(point.x - previous.x, point.y - previous.y) / gap < .1;
|
|
272
|
-
previous = point;
|
|
273
|
-
w.clearTimeout(restTimer);
|
|
274
|
-
const url = eligible(anchorOf(event));
|
|
275
|
-
if (!url) return;
|
|
276
|
-
if (creeping) prefetch(url);
|
|
277
|
-
else restTimer = w.setTimeout(() => prefetch(url), Math.max(config.hoverDwellMs, gap * 1.5));
|
|
278
|
-
}, { passive: true });
|
|
279
|
-
d.addEventListener("pointerout", () => w.clearTimeout(restTimer), { passive: true });
|
|
280
|
-
for (const type of [
|
|
281
|
-
"pointerdown",
|
|
282
|
-
"touchstart",
|
|
283
|
-
"focusin"
|
|
284
|
-
]) d.addEventListener(type, (event) => {
|
|
285
|
-
w.clearTimeout(restTimer);
|
|
286
|
-
const url = eligible(anchorOf(event));
|
|
287
|
-
if (url) prefetch(url);
|
|
288
|
-
}, { passive: true });
|
|
289
|
-
const follow = (routedPhase) => (event) => {
|
|
290
|
-
const mouse = event;
|
|
291
|
-
if (mouse.defaultPrevented || mouse.button !== 0) return;
|
|
292
|
-
if (mouse.metaKey || mouse.altKey || mouse.ctrlKey || mouse.shiftKey) return;
|
|
293
|
-
const anchor = anchorOf(event);
|
|
294
|
-
if (!anchor || anchor.hasAttribute(config.linkAttribute) !== routedPhase) return;
|
|
295
|
-
const url = eligible(anchor, true);
|
|
296
|
-
if (!url) return;
|
|
297
|
-
const current = new URL(location.href);
|
|
298
|
-
w.clearTimeout(restTimer);
|
|
299
|
-
event.preventDefault();
|
|
300
|
-
const replace = anchor.hasAttribute(config.replaceAttribute) || url.href === current.href;
|
|
301
|
-
const manual = anchor.getAttribute(config.scrollAttribute) === "manual";
|
|
302
|
-
load().then((entry) => entry.navigate(d, url, replace ? "replace" : "push", manual ? "manual" : "reset"), () => location.assign(url.href));
|
|
303
|
-
};
|
|
304
|
-
d.addEventListener("click", follow(true), true);
|
|
305
|
-
d.addEventListener("click", follow(false));
|
|
306
|
-
rescan();
|
|
307
|
-
return (event) => {
|
|
308
|
-
const url = new URL(location.href);
|
|
309
|
-
if (url.hash.startsWith("#/") || pageKey(url) === state.shown) return;
|
|
310
|
-
const saved = event.state?.__marklessFragment;
|
|
311
|
-
load().then((entry) => entry.navigate(d, url, "traverse", saved ?? {
|
|
312
|
-
x: 0,
|
|
313
|
-
y: 0
|
|
314
|
-
}), () => location.reload());
|
|
315
|
-
};
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
const FRAGMENT_REQUEST_HEADER = "x-markless-fragment";
|
|
319
|
-
const FRAGMENT_REGION_END = "<!--markless:region-end-->";
|
|
320
|
-
const FRAGMENT_APPEND_END = "<!--markless:append-end-->";
|
|
321
|
-
const FRAGMENT_PREFETCH_TTL_MS = 3e4;
|
|
322
|
-
//#endregion
|
|
323
|
-
export { startFragmentNavigation as a, FRAGMENT_REQUEST_HEADER as i, FRAGMENT_PREFETCH_TTL_MS as n, appendModulePreloads as o, FRAGMENT_REGION_END as r, listenForLinkIntent as s, FRAGMENT_APPEND_END as t };
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
//#region src/link-attributes.ts
|
|
2
|
-
const LINK_ATTRIBUTE = "data-markless-router-link";
|
|
3
|
-
const REPLACE_ATTRIBUTE = "data-markless-router-replace";
|
|
4
|
-
const SCROLL_ATTRIBUTE = "data-markless-router-scroll";
|
|
5
|
-
const PREFETCH_ATTRIBUTE = "data-markless-router-prefetch";
|
|
6
|
-
//#endregion
|
|
7
|
-
export { SCROLL_ATTRIBUTE as i, PREFETCH_ATTRIBUTE as n, REPLACE_ATTRIBUTE as r, LINK_ATTRIBUTE as t };
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { basename, extname, join, normalize, relative } from "pathe";
|
|
2
|
-
import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo";
|
|
3
|
-
//#region src/request-files.ts
|
|
4
|
-
const API_DIR = "api";
|
|
5
|
-
const MIDDLEWARE_DIR = "middleware";
|
|
6
|
-
const REQUEST_FILE_EXTENSION = ".ts";
|
|
7
|
-
const HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
8
|
-
"connect",
|
|
9
|
-
"delete",
|
|
10
|
-
"get",
|
|
11
|
-
"head",
|
|
12
|
-
"options",
|
|
13
|
-
"patch",
|
|
14
|
-
"post",
|
|
15
|
-
"put",
|
|
16
|
-
"trace"
|
|
17
|
-
]);
|
|
18
|
-
function parseRequestFile(fileId, sourceText) {
|
|
19
|
-
const file = normalizeRequestFileId(fileId);
|
|
20
|
-
const apiFile = apiRelativeFile(file);
|
|
21
|
-
if (apiFile) return parseApiRequestFile(file, apiFile, sourceText);
|
|
22
|
-
if (middlewareRelativeFile(file)) return parseMiddlewareRequestFile(file, sourceText);
|
|
23
|
-
return {
|
|
24
|
-
diagnostics: [],
|
|
25
|
-
file,
|
|
26
|
-
kind: "none"
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function normalizeRequestFileId(fileId) {
|
|
30
|
-
return withoutLeadingSlash(normalize(fileId));
|
|
31
|
-
}
|
|
32
|
-
function transformRequestFileSource(fileId, sourceText) {
|
|
33
|
-
const requestFile = parseRequestFile(fileId, sourceText);
|
|
34
|
-
if (requestFile.kind === "none" || requestFile.diagnostics.length > 0 || !requestFile.defaultExport) return;
|
|
35
|
-
const handler = localizeDefaultExport(sourceText);
|
|
36
|
-
if (!handler) return;
|
|
37
|
-
return {
|
|
38
|
-
code: requestFileWrapperSource(requestFile, handler.code, handler.name),
|
|
39
|
-
file: requestFile.file,
|
|
40
|
-
kind: requestFile.kind
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function parseApiRequestFile(file, apiFile, sourceText) {
|
|
44
|
-
const defaultExport = findDefaultFunctionExport(sourceText);
|
|
45
|
-
const cache = parseCacheMetadata(sourceText);
|
|
46
|
-
const diagnostics = [
|
|
47
|
-
...missingDefaultFunctionDiagnostics("api", defaultExport),
|
|
48
|
-
...httpMethodExportDiagnostics(sourceText),
|
|
49
|
-
...cacheMetadataDiagnostics(sourceText, cache)
|
|
50
|
-
];
|
|
51
|
-
return {
|
|
52
|
-
...cache ? { cache } : {},
|
|
53
|
-
...defaultExport ? { defaultExport } : {},
|
|
54
|
-
diagnostics,
|
|
55
|
-
file,
|
|
56
|
-
kind: "api",
|
|
57
|
-
...apiRouteFromFile(apiFile)
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function parseMiddlewareRequestFile(file, sourceText) {
|
|
61
|
-
const defaultExport = findDefaultFunctionExport(sourceText);
|
|
62
|
-
const diagnostics = missingDefaultFunctionDiagnostics("middleware", defaultExport);
|
|
63
|
-
return {
|
|
64
|
-
...defaultExport ? { defaultExport } : {},
|
|
65
|
-
diagnostics,
|
|
66
|
-
file,
|
|
67
|
-
kind: "middleware"
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function apiRouteFromFile(apiFile) {
|
|
71
|
-
const withoutExtension = apiFile.slice(0, -3);
|
|
72
|
-
const rawSegments = normalize(join(withoutExtension)).split("/");
|
|
73
|
-
const finalSegment = rawSegments.at(-1);
|
|
74
|
-
const method = methodSuffix(finalSegment);
|
|
75
|
-
if (method !== "all") rawSegments[rawSegments.length - 1] = finalSegment.slice(0, -(method.length + 1));
|
|
76
|
-
if (rawSegments.at(-1) === "index") rawSegments.pop();
|
|
77
|
-
const normalizedSegments = rawSegments.filter(Boolean).map(normalizeRouteSegment);
|
|
78
|
-
const pathnameSegments = normalizedSegments.map((segment) => segment.pathname);
|
|
79
|
-
const patternSegments = normalizedSegments.map((segment) => segment.pattern);
|
|
80
|
-
return {
|
|
81
|
-
method,
|
|
82
|
-
route: {
|
|
83
|
-
params: normalizedSegments.flatMap((segment) => segment.params),
|
|
84
|
-
pathname: requestPathname([API_DIR, ...pathnameSegments]),
|
|
85
|
-
pattern: requestPathname([API_DIR, ...patternSegments])
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function normalizeRouteSegment(segment) {
|
|
90
|
-
const catchAll = segment.match(/^\[\.\.\.([A-Za-z_$][\w$]*)\]$/);
|
|
91
|
-
if (catchAll) return {
|
|
92
|
-
params: [{
|
|
93
|
-
kind: "catch-all",
|
|
94
|
-
name: catchAll[1]
|
|
95
|
-
}],
|
|
96
|
-
pathname: "**",
|
|
97
|
-
pattern: `[...${catchAll[1]}]`
|
|
98
|
-
};
|
|
99
|
-
const dynamic = segment.match(/^\[([A-Za-z_$][\w$]*)\]$/);
|
|
100
|
-
if (dynamic) return {
|
|
101
|
-
params: [{
|
|
102
|
-
kind: "dynamic",
|
|
103
|
-
name: dynamic[1]
|
|
104
|
-
}],
|
|
105
|
-
pathname: `:${dynamic[1]}`,
|
|
106
|
-
pattern: `[${dynamic[1]}]`
|
|
107
|
-
};
|
|
108
|
-
return {
|
|
109
|
-
params: [],
|
|
110
|
-
pathname: segment,
|
|
111
|
-
pattern: segment
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function requestPathname(segments) {
|
|
115
|
-
if (segments.length === 0) return "/";
|
|
116
|
-
const [base, ...rest] = segments;
|
|
117
|
-
return withLeadingSlash(joinURL(base, ...rest));
|
|
118
|
-
}
|
|
119
|
-
function methodSuffix(finalSegment) {
|
|
120
|
-
const suffix = finalSegment.split(".").at(-1);
|
|
121
|
-
return suffix && HTTP_METHODS.has(suffix) ? suffix : "all";
|
|
122
|
-
}
|
|
123
|
-
function findDefaultFunctionExport(sourceText) {
|
|
124
|
-
const directFunction = sourceText.match(/\bexport\s+default\s+(?:async\s+)?function(?:\s+[A-Za-z_$][\w$]*)?\s*\(\s*([A-Za-z_$][\w$]*)?/);
|
|
125
|
-
if (directFunction) return {
|
|
126
|
-
kind: "function",
|
|
127
|
-
parameterName: directFunction[1]
|
|
128
|
-
};
|
|
129
|
-
const directArrow = sourceText.match(/\bexport\s+default\s+(?:async\s+)?(?:\(\s*([A-Za-z_$][\w$]*)?\s*\)|([A-Za-z_$][\w$]*))\s*=>/);
|
|
130
|
-
if (directArrow) return {
|
|
131
|
-
kind: "function",
|
|
132
|
-
parameterName: directArrow[1] ?? directArrow[2]
|
|
133
|
-
};
|
|
134
|
-
const defaultIdentifier = sourceText.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;/)?.[1];
|
|
135
|
-
if (!defaultIdentifier) return;
|
|
136
|
-
const escapedIdentifier = escapeRegExp(defaultIdentifier);
|
|
137
|
-
const constArrow = sourceText.match(new RegExp(`\\bconst\\s+${escapedIdentifier}\\s*=\\s*(?:async\\s+)?(?:\\(\\s*([A-Za-z_$][\\w$]*)?\\s*\\)|([A-Za-z_$][\\w$]*))\\s*=>`));
|
|
138
|
-
if (constArrow) return {
|
|
139
|
-
kind: "function",
|
|
140
|
-
parameterName: constArrow[1] ?? constArrow[2]
|
|
141
|
-
};
|
|
142
|
-
const functionDeclaration = sourceText.match(new RegExp(`\\b(?:async\\s+)?function\\s+${escapedIdentifier}\\s*\\(\\s*([A-Za-z_$][\\w$]*)?`));
|
|
143
|
-
if (functionDeclaration) return {
|
|
144
|
-
kind: "function",
|
|
145
|
-
parameterName: functionDeclaration[1]
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function parseCacheMetadata(sourceText) {
|
|
149
|
-
const objectLiteral = sourceText.match(/\bexport\s+const\s+cache\s*=\s*(\{[\s\S]*?\})\s*;?/)?.[1];
|
|
150
|
-
if (!objectLiteral) return;
|
|
151
|
-
const maxAge = objectLiteral.match(/^\{\s*maxAge\s*:\s*(\d+(?:\.\d+)?)\s*,?\s*\}$/)?.[1];
|
|
152
|
-
return maxAge ? { maxAge: Number(maxAge) } : void 0;
|
|
153
|
-
}
|
|
154
|
-
function missingDefaultFunctionDiagnostics(kind, defaultExport) {
|
|
155
|
-
if (defaultExport) return [];
|
|
156
|
-
return [{
|
|
157
|
-
code: "missing-default-function",
|
|
158
|
-
message: kind === "api" ? "API files must default export a function." : "Middleware files must default export a function."
|
|
159
|
-
}];
|
|
160
|
-
}
|
|
161
|
-
function httpMethodExportDiagnostics(sourceText) {
|
|
162
|
-
const exportedMethods = /* @__PURE__ */ new Set();
|
|
163
|
-
for (const pattern of [
|
|
164
|
-
/\bexport\s+(?:async\s+)?function\s+([A-Z]+)\b/g,
|
|
165
|
-
/\bexport\s+const\s+([A-Z]+)\b/g,
|
|
166
|
-
/\bexport\s*\{([^}]+)\}/g
|
|
167
|
-
]) for (const match of sourceText.matchAll(pattern)) {
|
|
168
|
-
const exportClause = match[1];
|
|
169
|
-
if (!exportClause) continue;
|
|
170
|
-
if (exportClause.includes(",")) {
|
|
171
|
-
for (const exportName of exportClause.split(",")) addHttpMethodExport(exportedMethods, exportName);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
addHttpMethodExport(exportedMethods, exportClause);
|
|
175
|
-
}
|
|
176
|
-
return Array.from(exportedMethods).toSorted().map((method) => ({
|
|
177
|
-
code: "http-method-export",
|
|
178
|
-
message: `Do not export ${method}; the HTTP method comes from the filename.`
|
|
179
|
-
}));
|
|
180
|
-
}
|
|
181
|
-
function addHttpMethodExport(methods, exportName) {
|
|
182
|
-
const method = exportName.trim().split(/\s+as\s+/i).at(-1)?.trim();
|
|
183
|
-
if (method && HTTP_METHODS.has(method.toLowerCase())) methods.add(method.toUpperCase());
|
|
184
|
-
}
|
|
185
|
-
function cacheMetadataDiagnostics(sourceText, cache) {
|
|
186
|
-
if (!/\bexport\s+const\s+cache\b/.test(sourceText) || cache) return [];
|
|
187
|
-
return [{
|
|
188
|
-
code: "invalid-cache-metadata",
|
|
189
|
-
message: "Use export const cache for endpoint cache metadata."
|
|
190
|
-
}];
|
|
191
|
-
}
|
|
192
|
-
function apiRelativeFile(file) {
|
|
193
|
-
if (!isRequestModuleFile(file) || file === API_DIR || !file.startsWith(`${API_DIR}/`)) return;
|
|
194
|
-
return relative(API_DIR, file);
|
|
195
|
-
}
|
|
196
|
-
function middlewareRelativeFile(file) {
|
|
197
|
-
if (!isRequestModuleFile(file) || file === MIDDLEWARE_DIR || !file.startsWith(`${MIDDLEWARE_DIR}/`)) return;
|
|
198
|
-
return relative(MIDDLEWARE_DIR, file);
|
|
199
|
-
}
|
|
200
|
-
function localizeDefaultExport(sourceText) {
|
|
201
|
-
const defaultIdentifierMatch = sourceText.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;/);
|
|
202
|
-
if (defaultIdentifierMatch) return {
|
|
203
|
-
code: sourceText.replace(defaultIdentifierMatch[0], ""),
|
|
204
|
-
name: defaultIdentifierMatch[1]
|
|
205
|
-
};
|
|
206
|
-
const directDefaultMatch = sourceText.match(/\bexport\s+default\b/);
|
|
207
|
-
if (!directDefaultMatch || directDefaultMatch.index === void 0) return;
|
|
208
|
-
return {
|
|
209
|
-
code: `${sourceText.slice(0, directDefaultMatch.index)}const __markless_request_handler__ =${sourceText.slice(directDefaultMatch.index + directDefaultMatch[0].length)}`,
|
|
210
|
-
name: "__markless_request_handler__"
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function requestFileWrapperSource(requestFile, sourceText, handlerName) {
|
|
214
|
-
const defineImport = requestFile.kind === "api" && requestFile.cache ? "import { defineCachedHandler as __markless_define_handler__ } from \"nitro/cache\";" : "import { defineHandler as __markless_define_handler__ } from \"nitro\";";
|
|
215
|
-
const wrappedHandler = `(event) =>
|
|
216
|
-
${handlerName}(__markless_create_http_context__(event))`;
|
|
217
|
-
const wrappedDefault = requestFile.kind === "api" && requestFile.cache ? `__markless_define_handler__(${wrappedHandler}, cache)` : `__markless_define_handler__(${wrappedHandler})`;
|
|
218
|
-
return `${defineImport}
|
|
219
|
-
import { __marklessCreateHttpContext as __markless_create_http_context__ } from "@markless/router";
|
|
220
|
-
|
|
221
|
-
${sourceText.trimEnd()}
|
|
222
|
-
|
|
223
|
-
export default ${wrappedDefault};
|
|
224
|
-
`;
|
|
225
|
-
}
|
|
226
|
-
function isRequestModuleFile(file) {
|
|
227
|
-
return extname(file) === REQUEST_FILE_EXTENSION && basename(file) !== REQUEST_FILE_EXTENSION;
|
|
228
|
-
}
|
|
229
|
-
function escapeRegExp(value) {
|
|
230
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
231
|
-
}
|
|
232
|
-
//#endregion
|
|
233
|
-
export { parseRequestFile as n, transformRequestFileSource as r, normalizeRequestFileId as t };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
//#region src/route-dom.ts
|
|
2
|
-
const ROUTE_SCRIPT_TYPE = "@markless/core/route";
|
|
3
|
-
const HTML_ATTRIBUTES_SLOT = "__marklessRouterHtmlAttributes";
|
|
4
|
-
function routeMountTarget(document, currentRoot) {
|
|
5
|
-
const root = currentRoot ?? document.querySelector(`script[type="@markless/core/route"]`)?.parentElement;
|
|
6
|
-
if (!root) return document.body;
|
|
7
|
-
return {
|
|
8
|
-
ownerDocument: document,
|
|
9
|
-
replaceChildren(...children) {
|
|
10
|
-
root.replaceWith(...children);
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/request-locals.ts
|
|
16
|
-
const REQUEST_LOCALS = Symbol.for("@markless/router/request-locals");
|
|
17
|
-
function table() {
|
|
18
|
-
const scope = globalThis;
|
|
19
|
-
return scope[REQUEST_LOCALS] ??= /* @__PURE__ */ new WeakMap();
|
|
20
|
-
}
|
|
21
|
-
function rememberRequestLocals(request, locals) {
|
|
22
|
-
table().set(request, locals);
|
|
23
|
-
}
|
|
24
|
-
function requestLocals(request) {
|
|
25
|
-
return table().get(request);
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
|
-
export { routeMountTarget as a, ROUTE_SCRIPT_TYPE as i, requestLocals as n, HTML_ATTRIBUTES_SLOT as r, rememberRequestLocals as t };
|