Skip to content

Commit 2534e43

Browse files
committed
extract route matching into find_route
1 parent f4197f5 commit 2534e43

File tree

5 files changed

+98
-47
lines changed

5 files changed

+98
-47
lines changed

packages/kit/src/runtime/app/paths/server.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { base, assets, relative, initial_base } from './internal/server.js';
2-
import { resolve_route, exec } from '../../../utils/routing.js';
3-
import { decode_params, decode_pathname } from '../../../utils/url.js';
2+
import { resolve_route, find_route } from '../../../utils/routing.js';
3+
import { decode_pathname } from '../../../utils/url.js';
44
import { try_get_request_store } from '@sveltejs/kit/internal/server';
55
import { manifest } from '__sveltekit/server';
66
import { get_hooks } from '__SERVER__/internal.js';
@@ -55,18 +55,13 @@ export async function match(url) {
5555
}
5656

5757
const matchers = await manifest._.matchers();
58+
const result = find_route(resolved_path, manifest._.routes, matchers);
5859

59-
for (const route of manifest._.routes) {
60-
const match = route.pattern.exec(resolved_path);
61-
if (!match) continue;
62-
63-
const matched = exec(match, route.params, matchers);
64-
if (matched) {
65-
return {
66-
id: /** @type {import('$app/types').RouteId} */ (route.id),
67-
params: decode_params(matched)
68-
};
69-
}
60+
if (result) {
61+
return {
62+
id: /** @type {import('$app/types').RouteId} */ (result.route.id),
63+
params: result.params
64+
};
7065
}
7166

7267
return null;

packages/kit/src/runtime/server/page/server_routing.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { base, assets, relative } from '$app/paths/internal/server';
22
import { text } from '@sveltejs/kit';
33
import { s } from '../../../utils/misc.js';
4-
import { exec } from '../../../utils/routing.js';
5-
import { decode_params } from '../../../utils/url.js';
4+
import { find_route } from '../../../utils/routing.js';
65
import { get_relative_path } from '../../utils.js';
76

87
/**
@@ -69,26 +68,11 @@ export async function resolve_route(resolved_path, url, manifest) {
6968
return text('Server-side route resolution disabled', { status: 400 });
7069
}
7170

72-
/** @type {import('types').SSRClientRoute | null} */
73-
let route = null;
74-
/** @type {Record<string, string>} */
75-
let params = {};
76-
7771
const matchers = await manifest._.matchers();
72+
const result = find_route(resolved_path, manifest._.client.routes, matchers);
7873

79-
for (const candidate of manifest._.client.routes) {
80-
const match = candidate.pattern.exec(resolved_path);
81-
if (!match) continue;
82-
83-
const matched = exec(match, candidate.params, matchers);
84-
if (matched) {
85-
route = candidate;
86-
params = decode_params(matched);
87-
break;
88-
}
89-
}
90-
91-
return create_server_routing_response(route, params, url, manifest).response;
74+
return create_server_routing_response(result?.route ?? null, result?.params ?? {}, url, manifest)
75+
.response;
9276
}
9377

9478
/**

packages/kit/src/runtime/server/respond.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {
1515
method_not_allowed,
1616
redirect_response
1717
} from './utils.js';
18-
import { decode_pathname, decode_params, disable_search, normalize_path } from '../../utils/url.js';
19-
import { exec } from '../../utils/routing.js';
18+
import { decode_pathname, disable_search, normalize_path } from '../../utils/url.js';
19+
import { find_route } from '../../utils/routing.js';
2020
import { redirect_json_response, render_data } from './data/index.js';
2121
import { add_cookies_to_headers, get_cookies } from './cookie.js';
2222
import { create_fetch } from './fetch.js';
@@ -303,18 +303,12 @@ export async function internal_respond(request, options, manifest, state) {
303303
if (!state.prerendering?.fallback) {
304304
// TODO this could theoretically break — should probably be inside a try-catch
305305
const matchers = await manifest._.matchers();
306+
const result = find_route(resolved_path, manifest._.routes, matchers);
306307

307-
for (const candidate of manifest._.routes) {
308-
const match = candidate.pattern.exec(resolved_path);
309-
if (!match) continue;
310-
311-
const matched = exec(match, candidate.params, matchers);
312-
if (matched) {
313-
route = candidate;
314-
event.route = { id: route.id };
315-
event.params = decode_params(matched);
316-
break;
317-
}
308+
if (result) {
309+
route = result.route;
310+
event.route = { id: route.id };
311+
event.params = result.params;
318312
}
319313
}
320314

packages/kit/src/utils/routing.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BROWSER } from 'esm-env';
2+
import { decode_params } from './url.js';
23

34
const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
45

@@ -276,3 +277,27 @@ export function resolve_route(id, params) {
276277
export function has_server_load(node) {
277278
return node.server?.load !== undefined || node.server?.trailingSlash !== undefined;
278279
}
280+
281+
/**
282+
* Find the first route that matches the given path
283+
* @template {{pattern: RegExp, params: import('types').RouteParam[]}} Route
284+
* @param {string} path - The decoded pathname to match
285+
* @param {Route[]} routes
286+
* @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
287+
* @returns {{ route: Route, params: Record<string, string> } | null}
288+
*/
289+
export function find_route(path, routes, matchers) {
290+
for (const route of routes) {
291+
const match = route.pattern.exec(path);
292+
if (!match) continue;
293+
294+
const matched = exec(match, route.params, matchers);
295+
if (matched) {
296+
return {
297+
route,
298+
params: decode_params(matched)
299+
};
300+
}
301+
}
302+
return null;
303+
}

packages/kit/src/utils/routing.spec.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { assert, expect, test, describe } from 'vitest';
2-
import { exec, parse_route_id, resolve_route } from './routing.js';
2+
import { exec, parse_route_id, resolve_route, find_route } from './routing.js';
33

44
describe('parse_route_id', () => {
55
const tests = {
@@ -359,3 +359,56 @@ describe('resolve_route', () => {
359359
);
360360
});
361361
});
362+
363+
describe('find_route', () => {
364+
/** @param {string} id */
365+
function create_route(id) {
366+
const { pattern, params } = parse_route_id(id);
367+
return { id, pattern, params };
368+
}
369+
370+
test('finds matching route', () => {
371+
const routes = [create_route('/blog'), create_route('/blog/[slug]'), create_route('/about')];
372+
373+
const result = find_route('/blog/hello-world', routes, {});
374+
assert.equal(result?.route.id, '/blog/[slug]');
375+
assert.deepEqual(result?.params, { slug: 'hello-world' });
376+
});
377+
378+
test('returns first matching route', () => {
379+
const routes = [create_route('/blog/[slug]'), create_route('/blog/[...rest]')];
380+
381+
const result = find_route('/blog/hello', routes, {});
382+
assert.equal(result?.route.id, '/blog/[slug]');
383+
});
384+
385+
test('returns null for no match', () => {
386+
const routes = [create_route('/blog'), create_route('/about')];
387+
388+
const result = find_route('/contact', routes, {});
389+
assert.equal(result, null);
390+
});
391+
392+
test('respects matchers', () => {
393+
const routes = [create_route('/blog/[slug=word]'), create_route('/blog/[slug]')];
394+
/** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */
395+
const matchers = {
396+
word: (param) => /^\w+$/.test(param)
397+
};
398+
399+
// "hello" matches the word matcher
400+
const result1 = find_route('/blog/hello', routes, matchers);
401+
assert.equal(result1?.route.id, '/blog/[slug=word]');
402+
403+
// "hello-world" doesn't match word matcher, falls through to [slug]
404+
const result2 = find_route('/blog/hello-world', routes, matchers);
405+
assert.equal(result2?.route.id, '/blog/[slug]');
406+
});
407+
408+
test('decodes params', () => {
409+
const routes = [create_route('/blog/[slug]')];
410+
411+
const result = find_route('/blog/hello%20world', routes, {});
412+
assert.equal(result?.params.slug, 'hello world');
413+
});
414+
});

0 commit comments

Comments
 (0)