@janit/fu 0.0.2

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.
@@ -0,0 +1,246 @@
1
+ import { parseSync, transformSync } from "rolldown/experimental";
2
+ import { transform as lightning } from "lightningcss";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ /** Files a source transform must never touch. */
6
+ function isInternal(id) {
7
+ return id.includes("node_modules") ||
8
+ id.startsWith("\0") ||
9
+ id.includes("rolldown") ||
10
+ id.endsWith("hmr-runtime.js");
11
+ }
12
+ /**
13
+ * TypeScript + Preact JSX via oxc — the same native transform Vite delegates
14
+ * to. Also carries the two island-specific source rewrites, since both need the
15
+ * transformed output.
16
+ */
17
+ export function jsx(opts = {}) {
18
+ return {
19
+ name: "fu:jsx",
20
+ transform: {
21
+ filter: { id: /\.[tj]sx?$/ },
22
+ handler(code, id) {
23
+ // Mangling rolldown's own runtime module breaks its internal symbols
24
+ // (RUNTIME_MODULE_SYMBOL_NOT_FOUND), so bail on anything internal.
25
+ if (isInternal(id))
26
+ return null;
27
+ const out = transformSync(id, code, {
28
+ jsx: { runtime: "automatic", importSource: "preact" },
29
+ lang: /\.tsx$/.test(id) ? "tsx" : /\.ts$/.test(id) ? "ts" : "jsx",
30
+ });
31
+ let js = out.code;
32
+ let map = out.map ?? null;
33
+ const islandKey = islandKeyOf(id);
34
+ if (islandKey) {
35
+ if (opts.stampIslands) {
36
+ const stamped = stampIslands(js, islandKey, id);
37
+ js = stamped.code;
38
+ // Naming an anonymous default export shifts every later position.
39
+ if (stamped.rewrote)
40
+ map = null;
41
+ }
42
+ if (opts.hmr)
43
+ js += acceptSelf(islandKey);
44
+ }
45
+ return { code: js, map };
46
+ },
47
+ },
48
+ };
49
+ }
50
+ function islandKeyOf(id) {
51
+ const i = id.indexOf("/islands/");
52
+ return i === -1 ? null : "/islands/" + id.slice(i + "/islands/".length).split("?")[0];
53
+ }
54
+ const NAMED_DECLARATIONS = new Set(["FunctionDeclaration", "ClassDeclaration"]);
55
+ /**
56
+ * Mark every exported component so the SSR renderer can spot a hydration
57
+ * boundary from the component function alone.
58
+ *
59
+ * Uses oxc's parser rather than a regex: `export const X = () => {}` is the
60
+ * most natural way to write a Preact component, and a regex over `export
61
+ * function` silently misses it — the island renders and then never hydrates,
62
+ * with no error anywhere.
63
+ *
64
+ * An anonymous `export default () => {}` has no binding to stamp, so it is
65
+ * rewritten into a named const. That is the one case that shifts positions and
66
+ * therefore invalidates the sourcemap, which the caller drops.
67
+ */
68
+ export function stampIslands(js, key, id) {
69
+ const parsed = parseSync(id, js, { sourceType: "module", lang: "tsx" });
70
+ const body = parsed.program.body ?? [];
71
+ const names = [];
72
+ let rewrote = false;
73
+ let out = js;
74
+ for (const node of body) {
75
+ // `export type {...}`, `export interface`: no runtime binding.
76
+ if (node.exportKind === "type")
77
+ continue;
78
+ if (node.type === "ExportNamedDeclaration") {
79
+ // `export ... from "./x"` re-exports nothing into local scope.
80
+ if (node.source)
81
+ continue;
82
+ const decl = node.declaration;
83
+ if (decl?.type === "VariableDeclaration") {
84
+ for (const d of decl.declarations ?? []) {
85
+ if (d.id?.name)
86
+ names.push(d.id.name);
87
+ }
88
+ }
89
+ else if (decl && NAMED_DECLARATIONS.has(decl.type)) {
90
+ if (decl.id?.name)
91
+ names.push(decl.id.name);
92
+ }
93
+ else {
94
+ for (const sp of node.specifiers ?? []) {
95
+ if (sp.local?.name)
96
+ names.push(sp.local.name);
97
+ }
98
+ }
99
+ continue;
100
+ }
101
+ if (node.type === "ExportDefaultDeclaration") {
102
+ const decl = node.declaration;
103
+ if (decl && NAMED_DECLARATIONS.has(decl.type) && decl.id?.name) {
104
+ names.push(decl.id.name);
105
+ }
106
+ else if (decl) {
107
+ // `export default <expression>` — give it a name so it can be stamped.
108
+ const head = js.slice(node.start, decl.start);
109
+ if (head.includes("export default")) {
110
+ out = out.slice(0, node.start) +
111
+ head.replace("export default", "const __fu_default =") +
112
+ out.slice(decl.start);
113
+ names.push("__fu_default");
114
+ rewrote = true;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ if (names.length === 0)
120
+ return { code: out, rewrote };
121
+ const tail = names
122
+ .map((n) => `try{${n}.__island=${JSON.stringify(key)}}catch{}`)
123
+ .join("\n");
124
+ const reexport = rewrote ? "\nexport default __fu_default;" : "";
125
+ return { code: `${out}${reexport}\n${tail}\n`, rewrote };
126
+ }
127
+ /** Accept our own updates so rolldown patches the module instead of reloading. */
128
+ function acceptSelf(key) {
129
+ return `\nif (import.meta.hot) { import.meta.hot.accept((mod) => { ` +
130
+ `globalThis.__fu_hmr__ && globalThis.__fu_hmr__(${JSON.stringify(key)}, mod); ` +
131
+ `}); }\n`;
132
+ }
133
+ /**
134
+ * CSS. Rolldown removed CSS bundling and ships no CSS builtin, so the framework
135
+ * owns the pipeline via lightningcss:
136
+ * `*.module.css` -> CSS Modules (scoped names + JS exports object)
137
+ * `*.css` -> global stylesheet
138
+ * Native nesting, `@layer` and `color-mix` pass straight through.
139
+ *
140
+ * Pair this with `moduleTypes: { ".css": "js" }`, or rolldown classifies the
141
+ * module by extension and refuses to bundle it.
142
+ */
143
+ export function css(collected) {
144
+ return {
145
+ name: "fu:css",
146
+ transform: {
147
+ filter: { id: /\.css$/ },
148
+ handler(code, id) {
149
+ const isModule = /\.module\.css$/.test(id.split("?")[0]);
150
+ const out = lightning({
151
+ filename: id,
152
+ code: Buffer.from(code),
153
+ minify: true,
154
+ cssModules: isModule,
155
+ });
156
+ const text = out.code.toString();
157
+ collected.set(id, text);
158
+ // The emitted JS must change when the stylesheet does. Class names hash
159
+ // from the *filename*, so without this stamp an edit produces
160
+ // byte-identical JS, rolldown correctly emits no update, and CSS HMR
161
+ // silently never fires.
162
+ const stamp = `export const __css = ${JSON.stringify(hash(text))};`;
163
+ if (!isModule)
164
+ return { code: `export default {};\n${stamp}`, map: null };
165
+ // `composes` yields SEVERAL class names; dropping the extras loses the
166
+ // composed styles with no error.
167
+ const names = Object.fromEntries(Object.entries(out.exports ?? {}).map(([k, v]) => [
168
+ k,
169
+ [v.name, ...(v.composes ?? []).map((c) => c.name)].join(" "),
170
+ ]));
171
+ return { code: `export default ${JSON.stringify(names)};\n${stamp}`, map: null };
172
+ },
173
+ },
174
+ };
175
+ }
176
+ /** Serve generated modules by exact id. */
177
+ export function virtual(mods) {
178
+ return {
179
+ name: "fu:virtual",
180
+ resolveId: (id) => (id in mods ? "\0" + id : null),
181
+ load: (id) => (id.startsWith("\0") && id.slice(1) in mods ? mods[id.slice(1)] : null),
182
+ };
183
+ }
184
+ export function hash(s) {
185
+ let h = 7;
186
+ for (let i = 0; i < s.length; i++)
187
+ h = (Math.imul(h, 31) + s.charCodeAt(i)) >>> 0;
188
+ return h.toString(36);
189
+ }
190
+ /**
191
+ * Recursively list source files under `dir`, as root-relative "/a/b.tsx".
192
+ * Underscore-prefixed files are skipped: they are framework files
193
+ * (`routes/_app.tsx`), not routes.
194
+ */
195
+ export function walk(root, dir, out = []) {
196
+ if (!fs.existsSync(dir))
197
+ return out;
198
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
199
+ const f = path.join(dir, e.name);
200
+ if (e.isDirectory())
201
+ walk(root, f, out);
202
+ else if (/\.[tj]sx?$/.test(e.name) && !e.name.startsWith("_")) {
203
+ out.push("/" + path.relative(root, f).split(path.sep).join("/"));
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+ /** Absolute path to an optional project file, or null when absent. */
209
+ export function optional(root, ...names) {
210
+ for (const n of names) {
211
+ const p = path.resolve(root, n);
212
+ if (fs.existsSync(p))
213
+ return p;
214
+ }
215
+ return null;
216
+ }
217
+ /** Generated module bodies both drivers need. */
218
+ export function bootModule(clientPath, islandFiles, root) {
219
+ return `
220
+ import { hydrateIslands } from ${JSON.stringify(clientPath)};
221
+ hydrateIslands({
222
+ ${islandFiles.map((f) => ` ${JSON.stringify(f)}: () => import(${JSON.stringify(path.resolve(root, "." + f))})`).join(",\n")}
223
+ });
224
+ `;
225
+ }
226
+ export function ssrModule(o) {
227
+ const lines = [
228
+ `import { createHandler } from ${JSON.stringify(o.renderPath)};`,
229
+ ];
230
+ if (o.appPath)
231
+ lines.push(`import app from ${JSON.stringify(o.appPath)};`);
232
+ if (o.shellPath)
233
+ lines.push(`import Shell from ${JSON.stringify(o.shellPath)};`);
234
+ if (o.errorPath)
235
+ lines.push(`import ErrorPage from ${JSON.stringify(o.errorPath)};`);
236
+ lines.push(`const routes = {`, o.routeFiles
237
+ .map((f) => ` ${JSON.stringify(f)}: () => import(${JSON.stringify(path.resolve(o.root, "." + f))})`)
238
+ .join(",\n"), `};`, `const assets = ${o.assets};`, `const handler = createHandler({`, ` manifest: routes,`, ` assets,`, o.appPath ? ` app,` : ` app: undefined,`, o.shellPath ? ` Shell,` : ` Shell: undefined,`, o.errorPath ? ` ErrorPage,` : ` ErrorPage: undefined,`, `});`,
239
+ // Nitro invokes the handler with an H3Event, not a Request. The event
240
+ // exposes `url`/`headers` directly — which is why routing worked long
241
+ // before anyone read a body — but has no json()/text()/formData(). The
242
+ // real Request is `event.req`.
243
+ `const toRequest = (input) => input instanceof Request ? input : (input?.req ?? input);`, `export default (input) => handler(toRequest(input));`, ``);
244
+ return lines.join("\n");
245
+ }
246
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type ComponentType } from "preact";
2
+ import type { App } from "./app.ts";
3
+ import type { Assets, Ctx, Head, RouteManifest } from "./types.ts";
4
+ /** Optional root wrapper (`routes/_app.tsx`) around every page. */
5
+ export interface ShellProps<S = Record<string, unknown>> {
6
+ ctx: Ctx<S>;
7
+ children: unknown;
8
+ }
9
+ export interface HandlerParts<S> {
10
+ manifest: RouteManifest<S>;
11
+ assets: Assets;
12
+ /** Middleware chain. Omit for no middleware. */
13
+ app?: App<S>;
14
+ /** `routes/_app.tsx` default export, if the project has one. */
15
+ Shell?: ComponentType<ShellProps<S>>;
16
+ /**
17
+ * `routes/_error.tsx` default export, if the project has one. It receives
18
+ * `ctx` exactly like a page component, with `ctx.error` set.
19
+ */
20
+ ErrorPage?: (ctx: Ctx<S>) => unknown;
21
+ /** Initial state for each request. Shallow-copied per request. */
22
+ initialState?: () => S;
23
+ }
24
+ /**
25
+ * Build the request handler: middleware chain wrapped around routing + render.
26
+ *
27
+ * Middleware runs before routing so it can answer a request no route matches.
28
+ */
29
+ export declare function createHandler<S = Record<string, unknown>>(parts: HandlerParts<S>): (req: Request) => Promise<Response>;
30
+ /**
31
+ * Turn a failure into a response. Rendered through `routes/_error.tsx` when the
32
+ * project has one, so it inherits the app's markup and styling; otherwise plain
33
+ * text. Either way it goes back out through the middleware chain as a normal
34
+ * response, so security headers and logging still apply.
35
+ */
36
+ export declare function renderError<S>(ctx: Ctx<S>, err: unknown, parts: HandlerParts<S>): Promise<Response>;
37
+ /** Assemble the HTML document from the page body, head metadata and assets. */
38
+ export declare function document(body: string, head: Head, assets: Assets): string;
package/dist/render.js ADDED
@@ -0,0 +1,189 @@
1
+ import { h, options } from "preact";
2
+ import { renderToStringAsync } from "preact-render-to-string";
3
+ import { buildRoutes, match } from "./router.js";
4
+ import { HttpError, statusText, toRouteError } from "./errors.js";
5
+ /** Property the SSR transform stamps onto island exports. */
6
+ const ISLAND = "__island";
7
+ const wrapped = new WeakMap();
8
+ let hookInstalled = false;
9
+ /**
10
+ * Intercept island components during render. Preact's `options.vnode` hook
11
+ * fires for every vnode created, so an island is recognised from the stamp the
12
+ * SSR transform left on the component itself — no separate registry.
13
+ *
14
+ * The wrapper forwards to an arrow that does NOT carry the stamp, so rendering
15
+ * the island's own output does not re-enter this branch.
16
+ */
17
+ function installIslandHook() {
18
+ if (hookInstalled)
19
+ return;
20
+ hookInstalled = true;
21
+ const prev = options.vnode;
22
+ options.vnode = (vnode) => {
23
+ const type = vnode.type;
24
+ if (typeof type === "function" && type[ISLAND]) {
25
+ let w = wrapped.get(type);
26
+ if (!w) {
27
+ const key = type[ISLAND];
28
+ const Inner = ((props) => type(props));
29
+ w = ((props) => h("div", {
30
+ "data-island": key,
31
+ "data-props": JSON.stringify(props ?? {}),
32
+ }, h(Inner, props)));
33
+ wrapped.set(type, w);
34
+ }
35
+ vnode.type = w;
36
+ }
37
+ prev?.(vnode);
38
+ };
39
+ }
40
+ /**
41
+ * Build the request handler: middleware chain wrapped around routing + render.
42
+ *
43
+ * Middleware runs before routing so it can answer a request no route matches.
44
+ */
45
+ export function createHandler(parts) {
46
+ installIslandHook();
47
+ const routes = buildRoutes(parts.manifest);
48
+ const terminal = (ctx) => renderRoute(ctx, routes, parts);
49
+ const run = parts.app ? parts.app.compose(terminal) : composeBare(terminal);
50
+ return (req) => {
51
+ const ctx = {
52
+ req,
53
+ url: new URL(req.url),
54
+ params: {},
55
+ state: (parts.initialState?.() ?? {}),
56
+ head: {},
57
+ next: () => Promise.reject(new Error("fu: next() outside the chain")),
58
+ };
59
+ // Middleware runs outside renderRoute's try, so a throw up there would
60
+ // otherwise escape to the server as an unhandled 500.
61
+ return run(ctx).catch((err) => renderError(ctx, err, parts));
62
+ };
63
+ }
64
+ function composeBare(terminal) {
65
+ return (ctx) => Promise.resolve(terminal(ctx));
66
+ }
67
+ async function renderRoute(ctx, routes, parts) {
68
+ try {
69
+ const m = match(routes, ctx.url.pathname);
70
+ if (!m)
71
+ throw new HttpError(404);
72
+ ctx.params = m.params;
73
+ const mod = await m.route.load();
74
+ if (mod.handlers) {
75
+ const fn = mod.handlers[ctx.req.method];
76
+ if (!fn)
77
+ throw new HttpError(405);
78
+ const result = await fn(ctx);
79
+ if (result instanceof Response)
80
+ return result;
81
+ ctx.data = result;
82
+ }
83
+ const Page = mod.default;
84
+ if (!Page)
85
+ throw new HttpError(404);
86
+ return htmlResponse(await renderTree(ctx, h(Page, ctx), parts), ctx, parts);
87
+ }
88
+ catch (err) {
89
+ return renderError(ctx, err, parts);
90
+ }
91
+ }
92
+ /** Render the page, wrapped in `routes/_app.tsx` when the project has one. */
93
+ async function renderTree(ctx, page, parts) {
94
+ const tree = parts.Shell
95
+ ? h(parts.Shell, { ctx, children: page })
96
+ : page;
97
+ return await renderToStringAsync(tree);
98
+ }
99
+ function htmlResponse(body, ctx, parts, status = 200) {
100
+ const headers = { "content-type": "text/html; charset=utf-8" };
101
+ // No failure may be cached: during a deploy a valid URL can 404 for a few
102
+ // seconds, and a shared cache would pin that.
103
+ if (status >= 400)
104
+ headers["cache-control"] = "no-store";
105
+ // `document` is called after rendering, so a component can still set ctx.head.
106
+ return new Response(document(body, ctx.head, parts.assets), { status, headers });
107
+ }
108
+ /**
109
+ * Turn a failure into a response. Rendered through `routes/_error.tsx` when the
110
+ * project has one, so it inherits the app's markup and styling; otherwise plain
111
+ * text. Either way it goes back out through the middleware chain as a normal
112
+ * response, so security headers and logging still apply.
113
+ */
114
+ export async function renderError(ctx, err, parts) {
115
+ const error = toRouteError(err);
116
+ ctx.error = error;
117
+ // The underlying cause is logged, never rendered: a 500's message can carry
118
+ // connection strings, file paths and query text.
119
+ if (error.status >= 500)
120
+ console.error(`[fu] ${ctx.url.pathname}`, error.cause ?? err);
121
+ if (parts.ErrorPage) {
122
+ try {
123
+ ctx.head.title ??= `${error.status} ${statusText(error.status)}`;
124
+ ctx.head.robots ??= "noindex";
125
+ const body = await renderTree(ctx, h(parts.ErrorPage, ctx), parts);
126
+ return htmlResponse(body, ctx, parts, error.status);
127
+ }
128
+ catch (pageErr) {
129
+ // An error page that throws must not mask the original failure.
130
+ console.error("[fu] the error page itself threw", pageErr);
131
+ }
132
+ }
133
+ return new Response(`${error.status} ${error.message}`, {
134
+ status: error.status,
135
+ headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
136
+ });
137
+ }
138
+ const escapes = {
139
+ "&": "&amp;",
140
+ "<": "&lt;",
141
+ ">": "&gt;",
142
+ '"': "&quot;",
143
+ };
144
+ function esc(s) {
145
+ return s.replace(/[&<>"]/g, (c) => escapes[c]);
146
+ }
147
+ function tag(name, attrs) {
148
+ const a = Object.entries(attrs)
149
+ .map(([k, v]) => ` ${k}="${esc(v)}"`)
150
+ .join("");
151
+ return `<${name}${a}>`;
152
+ }
153
+ /** Assemble the HTML document from the page body, head metadata and assets. */
154
+ export function document(body, head, assets) {
155
+ const parts = [
156
+ '<meta charset="utf-8">',
157
+ '<meta name="viewport" content="width=device-width,initial-scale=1">',
158
+ ];
159
+ if (head.title)
160
+ parts.push(`<title>${esc(head.title)}</title>`);
161
+ if (head.description) {
162
+ parts.push(tag("meta", { name: "description", content: head.description }));
163
+ }
164
+ if (head.robots)
165
+ parts.push(tag("meta", { name: "robots", content: head.robots }));
166
+ if (head.canonical)
167
+ parts.push(tag("link", { rel: "canonical", href: head.canonical }));
168
+ if (head.title)
169
+ parts.push(tag("meta", { property: "og:title", content: head.title }));
170
+ if (head.description) {
171
+ parts.push(tag("meta", { property: "og:description", content: head.description }));
172
+ }
173
+ if (head.image)
174
+ parts.push(tag("meta", { property: "og:image", content: head.image }));
175
+ for (const link of head.links ?? [])
176
+ parts.push(tag("link", link));
177
+ for (const a of assets.css ?? [])
178
+ parts.push(tag("link", { rel: "stylesheet", href: a.href }));
179
+ if (head.jsonLd !== undefined) {
180
+ // `<` escaped so a string value cannot close the script element early.
181
+ const json = JSON.stringify(head.jsonLd).replace(/</g, "\\u003c");
182
+ parts.push(`<script type="application/ld+json">${json}</script>`);
183
+ }
184
+ const js = (assets.js ?? [])
185
+ .map((a) => tag("script", { type: "module", src: a.href }) + "</script>")
186
+ .join("");
187
+ return `<!doctype html><html lang="${esc(head.lang ?? "en")}"><head>${parts.join("")}</head><body>${body}${js}</body></html>`;
188
+ }
189
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { RouteManifest, RouteModule } from "./types.ts";
2
+ export interface Route<S = Record<string, unknown>> {
3
+ /** URLPattern pathname, e.g. "/blog/:slug". */
4
+ pattern: string;
5
+ urlPattern: URLPattern;
6
+ load: () => Promise<RouteModule<S>>;
7
+ /** Lower sorts first: static beats dynamic beats wildcard. */
8
+ score: number;
9
+ }
10
+ export interface Matched<S = Record<string, unknown>> {
11
+ route: Route<S>;
12
+ params: Record<string, string>;
13
+ }
14
+ /**
15
+ * `/routes/blog/[slug].tsx` -> `/blog/:slug`
16
+ * `/routes/files/[...rest].tsx` -> `/files/:rest*`
17
+ * `/routes/index.tsx` -> `/`
18
+ */
19
+ export declare function filePathToPattern(file: string): string;
20
+ export declare function buildRoutes<S = Record<string, unknown>>(manifest: RouteManifest<S>): Route<S>[];
21
+ export declare function match<S = Record<string, unknown>>(routes: Route<S>[], pathname: string): Matched<S> | null;
package/dist/router.js ADDED
@@ -0,0 +1,48 @@
1
+ if (typeof globalThis.URLPattern === "undefined") {
2
+ await import("urlpattern-polyfill");
3
+ }
4
+ /**
5
+ * `/routes/blog/[slug].tsx` -> `/blog/:slug`
6
+ * `/routes/files/[...rest].tsx` -> `/files/:rest*`
7
+ * `/routes/index.tsx` -> `/`
8
+ */
9
+ export function filePathToPattern(file) {
10
+ let p = file.replace(/^\/routes/, "").replace(/\.[tj]sx?$/, "");
11
+ if (p.endsWith("/index"))
12
+ p = p.slice(0, -"/index".length);
13
+ if (p === "/index" || p === "")
14
+ p = "/";
15
+ const out = p
16
+ .split("/")
17
+ .map((s) => s.startsWith("[...") && s.endsWith("]")
18
+ ? ":" + s.slice(4, -1) + "*"
19
+ : s.startsWith("[") && s.endsWith("]")
20
+ ? ":" + s.slice(1, -1)
21
+ : s)
22
+ .join("/");
23
+ return out || "/";
24
+ }
25
+ export function buildRoutes(manifest) {
26
+ const out = [];
27
+ for (const [file, load] of Object.entries(manifest)) {
28
+ const pattern = filePathToPattern(file);
29
+ const score = pattern.includes("*") ? 2 : pattern.includes(":") ? 1 : 0;
30
+ out.push({ pattern, urlPattern: new URLPattern({ pathname: pattern }), load, score });
31
+ }
32
+ return out.sort((a, b) => a.score - b.score || b.pattern.split("/").length - a.pattern.split("/").length);
33
+ }
34
+ export function match(routes, pathname) {
35
+ for (const route of routes) {
36
+ const m = route.urlPattern.exec({ pathname });
37
+ if (!m)
38
+ continue;
39
+ const params = {};
40
+ for (const [k, v] of Object.entries(m.pathname.groups)) {
41
+ if (v !== undefined)
42
+ params[k] = decodeURIComponent(v);
43
+ }
44
+ return { route, params };
45
+ }
46
+ return null;
47
+ }
48
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}