@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.16
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/package.json +7 -4
- package/src/cli.js +8 -5
- package/src/core/adapt/builder.js +20 -10
- package/src/core/config/index.js +4 -5
- package/src/core/env.js +3 -3
- package/src/core/generate_manifest/index.js +6 -0
- package/src/core/postbuild/entities.js +8 -2
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +18 -13
- package/src/core/sync/create_manifest_data/conflict.js +1 -1
- package/src/core/sync/create_manifest_data/index.js +33 -2
- package/src/core/sync/sync.js +3 -2
- package/src/core/sync/utils.js +2 -2
- package/src/core/sync/write_app_types.js +72 -27
- package/src/core/sync/write_tsconfig/index.js +73 -54
- package/src/core/sync/write_tsconfig/utils.js +0 -61
- package/src/core/sync/write_tsconfig/validate.js +128 -0
- package/src/core/sync/write_types/index.js +7 -4
- package/src/exports/node/index.js +2 -7
- package/src/exports/public.d.ts +25 -18
- package/src/exports/vite/build/build_server.js +2 -3
- package/src/exports/vite/dev/index.js +44 -36
- package/src/exports/vite/index.js +91 -16
- package/src/exports/vite/utils.js +62 -15
- package/src/runner.js +4 -2
- package/src/runtime/app/forms.js +4 -7
- package/src/runtime/app/internal/transport.js +53 -0
- package/src/runtime/app/paths/client.js +2 -2
- package/src/runtime/app/paths/internal/client.js +2 -2
- package/src/runtime/app/server/remote/prerender.js +6 -10
- package/src/runtime/app/server/remote/query.js +3 -9
- package/src/runtime/app/server/remote/requested.js +2 -2
- package/src/runtime/app/server/remote/shared.js +1 -16
- package/src/runtime/client/client.js +177 -63
- package/src/runtime/client/fetcher.js +2 -13
- package/src/runtime/client/parse.js +1 -1
- package/src/runtime/client/remote-functions/command.svelte.js +1 -2
- package/src/runtime/client/remote-functions/form.svelte.js +3 -7
- package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query/index.js +1 -1
- package/src/runtime/client/remote-functions/query/proxy.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
- package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
- package/src/runtime/client/utils.js +1 -0
- package/src/runtime/form-utils.js +4 -6
- package/src/runtime/pathname.js +44 -1
- package/src/runtime/server/data/index.js +5 -8
- package/src/runtime/server/dev.js +22 -0
- package/src/runtime/server/endpoint.js +3 -4
- package/src/runtime/server/fetch.js +25 -30
- package/src/runtime/server/index.js +64 -46
- package/src/runtime/server/internal.js +12 -5
- package/src/runtime/server/page/actions.js +18 -43
- package/src/runtime/server/page/crypto.js +2 -2
- package/src/runtime/server/page/csp.js +2 -1
- package/src/runtime/server/page/data_serializer.js +12 -13
- package/src/runtime/server/page/index.js +16 -32
- package/src/runtime/server/page/load_data.js +28 -44
- package/src/runtime/server/page/render.js +28 -12
- package/src/runtime/server/page/respond_with_error.js +8 -20
- package/src/runtime/server/page/serialize_data.js +2 -13
- package/src/runtime/server/page/server_routing.js +58 -9
- package/src/runtime/server/remote-functions.js +11 -15
- package/src/runtime/server/respond.js +44 -56
- package/src/runtime/server/state.js +60 -0
- package/src/runtime/server/utils.js +0 -20
- package/src/runtime/shared.js +20 -40
- package/src/runtime/utils.js +3 -0
- package/src/types/ambient-private.d.ts +1 -1
- package/src/types/ambient.d.ts +58 -7
- package/src/types/global-private.d.ts +1 -1
- package/src/types/internal.d.ts +47 -53
- package/src/utils/filesystem.js +1 -23
- package/src/utils/hash.js +21 -0
- package/src/utils/http.js +8 -7
- package/src/utils/import.js +2 -1
- package/src/utils/params.js +1 -1
- package/src/utils/regex.js +9 -0
- package/src/utils/routing.js +52 -27
- package/src/utils/url.js +1 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +98 -28
- package/types/index.d.ts.map +1 -1
- package/src/exports/node/polyfills.js +0 -30
- package/src/runtime/server/app.js +0 -9
package/src/types/internal.d.ts
CHANGED
|
@@ -158,7 +158,7 @@ export interface ServerHooks {
|
|
|
158
158
|
handleError: HandleServerError;
|
|
159
159
|
handleValidationError: HandleValidationError;
|
|
160
160
|
reroute: Reroute;
|
|
161
|
-
transport
|
|
161
|
+
transport?: Transport;
|
|
162
162
|
init?: ServerInit;
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -174,24 +174,23 @@ export interface Env {
|
|
|
174
174
|
public: Record<string, string>;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export interface InternalRequestOptions extends RequestOptions {
|
|
178
|
+
prerendering?: PrerenderOptions;
|
|
179
|
+
/** @internal for saving dependencies during prerendering and generating fallback pages */
|
|
180
|
+
read: (file: string) => Buffer<ArrayBuffer>;
|
|
181
|
+
/** @internal used during development to check feature availability depending on the current route */
|
|
182
|
+
before_handle?: (
|
|
183
|
+
event: RequestEvent,
|
|
184
|
+
config: any,
|
|
185
|
+
prerender: PrerenderOption,
|
|
186
|
+
handle: () => Promise<Response>
|
|
187
|
+
) => Promise<Response>;
|
|
188
|
+
emulator?: Emulator;
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
export class InternalServer extends Server {
|
|
178
192
|
init(options: ServerInitOptions): Promise<void>;
|
|
179
|
-
respond(
|
|
180
|
-
request: Request,
|
|
181
|
-
options: RequestOptions & {
|
|
182
|
-
prerendering?: PrerenderOptions;
|
|
183
|
-
/** @internal for saving dependencies during prerendering and generating fallback pages */
|
|
184
|
-
read: (file: string) => Buffer<ArrayBuffer>;
|
|
185
|
-
/** @internal used during development to check feature availability depending on the current route */
|
|
186
|
-
before_handle?: (
|
|
187
|
-
event: RequestEvent,
|
|
188
|
-
config: any,
|
|
189
|
-
prerender: PrerenderOption,
|
|
190
|
-
handle: () => Promise<Response>
|
|
191
|
-
) => Promise<Response>;
|
|
192
|
-
emulator?: Emulator;
|
|
193
|
-
}
|
|
194
|
-
): Promise<Response>;
|
|
193
|
+
respond(request: Request, options: InternalRequestOptions): Promise<Response>;
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
export interface ManifestData {
|
|
@@ -239,6 +238,8 @@ export interface PrerenderOptions {
|
|
|
239
238
|
dependencies: Map<string, PrerenderDependency>;
|
|
240
239
|
/** Results of remote `prerender` functions, shared across the whole prerender run so that each only executes once */
|
|
241
240
|
remote_responses: Map<string, Promise<any>>;
|
|
241
|
+
/** Route IDs whose resolution module has been emitted, shared across the whole prerender run so that each only generates once */
|
|
242
|
+
resolved_route_ids: Set<string>;
|
|
242
243
|
/** True for the duration of a call to the `reroute` hook */
|
|
243
244
|
inside_reroute?: boolean;
|
|
244
245
|
}
|
|
@@ -535,39 +536,6 @@ export interface SSRClientRoute {
|
|
|
535
536
|
leaf: [has_server_load: boolean, node_id: number];
|
|
536
537
|
}
|
|
537
538
|
|
|
538
|
-
export interface SSRState {
|
|
539
|
-
fallback?: string;
|
|
540
|
-
getClientAddress(): string;
|
|
541
|
-
/**
|
|
542
|
-
* True if we're currently attempting to render an error page.
|
|
543
|
-
*/
|
|
544
|
-
error: boolean;
|
|
545
|
-
/**
|
|
546
|
-
* Allows us to prevent `event.fetch` from making infinitely looping internal requests.
|
|
547
|
-
*/
|
|
548
|
-
depth: number;
|
|
549
|
-
platform?: any;
|
|
550
|
-
prerendering?: PrerenderOptions;
|
|
551
|
-
/**
|
|
552
|
-
* When fetching data from a +server.js endpoint in `load`, the page's
|
|
553
|
-
* prerender option is inherited by the endpoint, unless overridden.
|
|
554
|
-
*/
|
|
555
|
-
prerender_default?: PrerenderOption;
|
|
556
|
-
/** @internal reads from the filesystem when user code tries to fetch a static asset */
|
|
557
|
-
read?: (file: string) => Buffer<ArrayBuffer>;
|
|
558
|
-
/**
|
|
559
|
-
* Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
|
|
560
|
-
* E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.
|
|
561
|
-
*/
|
|
562
|
-
before_handle?: (
|
|
563
|
-
event: RequestEvent,
|
|
564
|
-
config: Record<string, any>,
|
|
565
|
-
prerender: PrerenderOption,
|
|
566
|
-
handle: () => Promise<Response>
|
|
567
|
-
) => Promise<Response>;
|
|
568
|
-
emulator?: Emulator;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
539
|
export type StrictBody = string | ArrayBufferView;
|
|
572
540
|
|
|
573
541
|
export interface Uses {
|
|
@@ -681,8 +649,35 @@ export type RecordSpan = <T>(options: {
|
|
|
681
649
|
* used for tracking things like remote function calls
|
|
682
650
|
*/
|
|
683
651
|
export interface RequestState {
|
|
684
|
-
readonly
|
|
685
|
-
readonly
|
|
652
|
+
readonly getClientAddress: () => string;
|
|
653
|
+
readonly platform?: any;
|
|
654
|
+
/** @internal reads from the filesystem when user code tries to fetch a static asset */
|
|
655
|
+
readonly read?: (file: string) => Buffer<ArrayBuffer>;
|
|
656
|
+
/**
|
|
657
|
+
* Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
|
|
658
|
+
* E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.
|
|
659
|
+
*/
|
|
660
|
+
readonly before_handle?: (
|
|
661
|
+
event: RequestEvent,
|
|
662
|
+
config: Record<string, any>,
|
|
663
|
+
prerender: PrerenderOption,
|
|
664
|
+
handle: () => Promise<Response>
|
|
665
|
+
) => Promise<Response>;
|
|
666
|
+
readonly emulator?: Emulator;
|
|
667
|
+
readonly prerendering?: PrerenderOptions;
|
|
668
|
+
/**
|
|
669
|
+
* When fetching data from a +server.js endpoint in `load`, the page's
|
|
670
|
+
* prerender option is inherited by the endpoint, unless overridden.
|
|
671
|
+
*/
|
|
672
|
+
prerender_default?: PrerenderOption;
|
|
673
|
+
/**
|
|
674
|
+
* True if we're currently attempting to render an error page.
|
|
675
|
+
*/
|
|
676
|
+
error: boolean;
|
|
677
|
+
/**
|
|
678
|
+
* Allows us to prevent `event.fetch` from making infinitely looping internal requests.
|
|
679
|
+
*/
|
|
680
|
+
readonly depth: number;
|
|
686
681
|
readonly handleValidationError: ServerHooks['handleValidationError'];
|
|
687
682
|
readonly tracing: {
|
|
688
683
|
record_span: RecordSpan;
|
|
@@ -737,7 +732,6 @@ export interface RequestState {
|
|
|
737
732
|
readonly is_in_remote_query: boolean;
|
|
738
733
|
readonly is_in_remote_prerender: boolean;
|
|
739
734
|
readonly is_in_render: boolean;
|
|
740
|
-
readonly is_in_universal_load: boolean;
|
|
741
735
|
/**
|
|
742
736
|
* The event before `derive_remote_function_event` hid or stubbed properties.
|
|
743
737
|
* Hooks like `handleValidationError` receive this so `url` etc. stay accessible
|
package/src/utils/filesystem.js
CHANGED
|
@@ -2,28 +2,6 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { posixify } from './os.js';
|
|
4
4
|
|
|
5
|
-
/** @param {string} dir */
|
|
6
|
-
export function mkdirp(dir) {
|
|
7
|
-
try {
|
|
8
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
9
|
-
} catch (/** @type {any} */ e) {
|
|
10
|
-
if (e.code === 'EEXIST') {
|
|
11
|
-
if (!fs.statSync(dir).isDirectory()) {
|
|
12
|
-
throw new Error(`Cannot create directory ${dir}, a file already exists at this position`, {
|
|
13
|
-
cause: e
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
throw e;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** @param {string} path */
|
|
23
|
-
export function rimraf(path) {
|
|
24
|
-
fs.rmSync(path, { force: true, recursive: true });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
5
|
/**
|
|
28
6
|
* @param {string} source
|
|
29
7
|
* @param {string} target
|
|
@@ -58,7 +36,7 @@ export function copy(source, target, opts = {}) {
|
|
|
58
36
|
go(path.join(from, file), path.join(to, file));
|
|
59
37
|
});
|
|
60
38
|
} else {
|
|
61
|
-
|
|
39
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
62
40
|
|
|
63
41
|
if (opts.replace) {
|
|
64
42
|
const data = fs.readFileSync(from, 'utf-8');
|
package/src/utils/hash.js
CHANGED
|
@@ -20,3 +20,24 @@ export function hash(...values) {
|
|
|
20
20
|
|
|
21
21
|
return (hash >>> 0).toString(36);
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Hash of the headers and body a `fetch` was called with. The server-side serializer and the
|
|
26
|
+
* client-side cache lookup must produce identical values for cached responses to be found.
|
|
27
|
+
* @param {HeadersInit | undefined} headers
|
|
28
|
+
* @param {import('types').StrictBody | null | undefined} body
|
|
29
|
+
*/
|
|
30
|
+
export function hash_request(headers, body) {
|
|
31
|
+
/** @type {import('types').StrictBody[]} */
|
|
32
|
+
const values = [];
|
|
33
|
+
|
|
34
|
+
if (headers) {
|
|
35
|
+
values.push([...new Headers(headers)].join(','));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (body) {
|
|
39
|
+
values.push(body);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return hash(...values);
|
|
43
|
+
}
|
package/src/utils/http.js
CHANGED
|
@@ -11,7 +11,7 @@ export function negotiate(accept, types) {
|
|
|
11
11
|
const parts = [];
|
|
12
12
|
|
|
13
13
|
accept.split(',').forEach((str, i) => {
|
|
14
|
-
const match =
|
|
14
|
+
const match = /^[ \t]*([^/ \t]+)\/([^; \t]+)[ \t]*(?:;[ \t]*q=([0-9.]+))?/.exec(str);
|
|
15
15
|
|
|
16
16
|
// no match equals invalid header — ignore
|
|
17
17
|
if (match) {
|
|
@@ -57,12 +57,13 @@ export function negotiate(accept, types) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Returns `true` if
|
|
61
|
-
*
|
|
60
|
+
* Returns `true` if a `content-type` header value is one of the given types, ignoring
|
|
61
|
+
* parameters such as `charset` and comparing case-insensitively
|
|
62
|
+
* @param {string | null | undefined} header
|
|
62
63
|
* @param {...string} types
|
|
63
64
|
*/
|
|
64
|
-
function
|
|
65
|
-
const type =
|
|
65
|
+
export function matches_content_type(header, ...types) {
|
|
66
|
+
const type = header?.split(';', 1)[0].trim() ?? '';
|
|
66
67
|
return types.includes(type.toLowerCase());
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -72,8 +73,8 @@ function is_content_type(request, ...types) {
|
|
|
72
73
|
export function is_form_content_type(request) {
|
|
73
74
|
// These content types must be protected against CSRF
|
|
74
75
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
|
|
75
|
-
return
|
|
76
|
-
request,
|
|
76
|
+
return matches_content_type(
|
|
77
|
+
request.headers.get('content-type'),
|
|
77
78
|
'application/x-www-form-urlencoded',
|
|
78
79
|
'multipart/form-data',
|
|
79
80
|
'text/plain',
|
package/src/utils/import.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Resolves a peer dependency relative to the current working directory.
|
|
@@ -51,7 +52,7 @@ function resolve_peer(dependency, root) {
|
|
|
51
52
|
*/
|
|
52
53
|
export async function import_peer(dependency, root) {
|
|
53
54
|
try {
|
|
54
|
-
return await import(/* @vite-ignore */ resolve_peer(dependency, root));
|
|
55
|
+
return await import(/* @vite-ignore */ pathToFileURL(resolve_peer(dependency, root)).href);
|
|
55
56
|
} catch {
|
|
56
57
|
return await import(/* @vite-ignore */ dependency);
|
|
57
58
|
}
|
package/src/utils/params.js
CHANGED
|
@@ -25,7 +25,7 @@ export function collect_matcher_names(routes) {
|
|
|
25
25
|
*/
|
|
26
26
|
export function validate_param_matchers(params, names, file) {
|
|
27
27
|
for (const name of names) {
|
|
28
|
-
if (!(name
|
|
28
|
+
if (!Object.hasOwn(params, name)) {
|
|
29
29
|
throw new Error(`No matcher found for parameter '${name}'${file ? ` in ${file}` : ''}`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes characters that have special meaning in a regular expression.
|
|
3
|
+
* @param {string} str
|
|
4
|
+
* @returns {string} escaped string
|
|
5
|
+
*/
|
|
6
|
+
export function escape_for_regexp(str) {
|
|
7
|
+
// TODO replace with `RegExp.escape(str)` when we require Node >= 24
|
|
8
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
|
|
9
|
+
}
|
package/src/utils/routing.js
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { BROWSER } from 'esm-env';
|
|
2
|
+
import { escape_for_regexp } from './regex.js';
|
|
2
3
|
|
|
3
4
|
const param_pattern = /^(\[)?(\.\.\.)?([\w-]+)(?:=([\w-]+))?(\])?$/;
|
|
4
5
|
|
|
5
6
|
const root_group_pattern = /^\/\((?:[^)]+)\)$/;
|
|
6
7
|
|
|
8
|
+
const escape_sequence_pattern = /\[([ux])\+([^\]]+)\]/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Decodes the codepoints of an `[x+nn]` or `[u+nnnn]` escape sequence
|
|
12
|
+
* @param {string} code the sequence without its `[x+`/`[u+` prefix or `]` suffix
|
|
13
|
+
*/
|
|
14
|
+
export function decode_escape_sequence(code) {
|
|
15
|
+
return String.fromCodePoint(...code.split('-').map((codepoint) => parseInt(codepoint, 16)));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Encodes the characters that `decode_pathname` leaves untouched, so that a decoded
|
|
20
|
+
* escape sequence still matches the pattern `parse_route_id` builds for it
|
|
21
|
+
* @param {string} str
|
|
22
|
+
*/
|
|
23
|
+
export function encode_pathname_chars(str) {
|
|
24
|
+
return str.replace(
|
|
25
|
+
/[%/?#]/g,
|
|
26
|
+
(char) => '%' + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
/**
|
|
8
31
|
* Creates the regex pattern, extracts parameter names, and generates types for a route
|
|
9
32
|
* @param {string} id
|
|
@@ -51,19 +74,8 @@ export function parse_route_id(id) {
|
|
|
51
74
|
const result = parts
|
|
52
75
|
.map((content, i) => {
|
|
53
76
|
if (i % 2) {
|
|
54
|
-
if (content.startsWith('x+')) {
|
|
55
|
-
return escape(
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (content.startsWith('u+')) {
|
|
59
|
-
return escape(
|
|
60
|
-
String.fromCharCode(
|
|
61
|
-
...content
|
|
62
|
-
.slice(2)
|
|
63
|
-
.split('-')
|
|
64
|
-
.map((code) => parseInt(code, 16))
|
|
65
|
-
)
|
|
66
|
-
);
|
|
77
|
+
if (content.startsWith('x+') || content.startsWith('u+')) {
|
|
78
|
+
return escape(decode_escape_sequence(content.slice(2)));
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
// We know the match cannot be null in the browser because manifest generation
|
|
@@ -243,25 +255,36 @@ export function exec(match, params, matchers) {
|
|
|
243
255
|
return result;
|
|
244
256
|
}
|
|
245
257
|
|
|
258
|
+
/**
|
|
259
|
+
* `decode_pathname` leaves these characters untouched, so routes have to match their encoded forms
|
|
260
|
+
* @type {Record<string, string>}
|
|
261
|
+
*/
|
|
262
|
+
const encoded = {
|
|
263
|
+
'%': '%25',
|
|
264
|
+
'/': '%2[Ff]',
|
|
265
|
+
'?': '%3[Ff]',
|
|
266
|
+
'#': '%23'
|
|
267
|
+
};
|
|
268
|
+
|
|
246
269
|
/** @param {string} str */
|
|
247
270
|
function escape(str) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
.replace(/%/g, '%25')
|
|
255
|
-
.replace(/\//g, '%2[Ff]')
|
|
256
|
-
.replace(/\?/g, '%3[Ff]')
|
|
257
|
-
.replace(/#/g, '%23')
|
|
258
|
-
// escape characters that have special meaning in regex
|
|
259
|
-
.replace(/[.*+?^${}()|\\]/g, '\\$&')
|
|
260
|
-
);
|
|
271
|
+
// the replacements in `encoded` are regex source themselves, so they must not be escaped again
|
|
272
|
+
return str
|
|
273
|
+
.normalize()
|
|
274
|
+
.split(/([%/?#])/)
|
|
275
|
+
.map((part, i) => (i % 2 ? encoded[part] : escape_for_regexp(part)))
|
|
276
|
+
.join('');
|
|
261
277
|
}
|
|
262
278
|
|
|
263
279
|
const basic_param_pattern = /\[(\[)?(\.\.\.)?([\w-]+?)(?:=([\w-]+))?\]\]?/g;
|
|
264
280
|
|
|
281
|
+
// escape sequences are expanded in the same pass as the params, so that a param
|
|
282
|
+
// value containing `[x+2f]` is not itself expanded
|
|
283
|
+
export const segment_pattern = new RegExp(
|
|
284
|
+
`${escape_sequence_pattern.source}|${basic_param_pattern.source}`,
|
|
285
|
+
'g'
|
|
286
|
+
);
|
|
287
|
+
|
|
265
288
|
/**
|
|
266
289
|
* Populate a route ID with params to resolve a pathname.
|
|
267
290
|
* @example
|
|
@@ -286,7 +309,9 @@ export function resolve_route(id, params) {
|
|
|
286
309
|
'/' +
|
|
287
310
|
segments
|
|
288
311
|
.map((segment) =>
|
|
289
|
-
segment.replace(
|
|
312
|
+
segment.replace(segment_pattern, (_, escape_type, escape_code, optional, rest, name) => {
|
|
313
|
+
if (escape_type) return encode_pathname_chars(decode_escape_sequence(escape_code));
|
|
314
|
+
|
|
290
315
|
const value = params[name];
|
|
291
316
|
|
|
292
317
|
if (value === undefined || value === '') {
|
package/src/utils/url.js
CHANGED
|
@@ -46,6 +46,7 @@ export function relative_pathname(from, to) {
|
|
|
46
46
|
export function matches_external_allowlist_entry(location, allowed) {
|
|
47
47
|
if (location === allowed) return true;
|
|
48
48
|
|
|
49
|
+
// TODO replace the try/catch with `URL.parse` when browser support allows (Chrome 126, Firefox 126, Safari 18)
|
|
49
50
|
try {
|
|
50
51
|
const allow = new URL(allowed);
|
|
51
52
|
const loc = new URL(location, allow);
|
package/src/version.js
CHANGED