@sveltejs/kit 3.0.0-next.10 → 3.0.0-next.12
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 +1 -1
- package/src/cli.js +8 -8
- package/src/constants.js +3 -1
- package/src/core/adapt/builder.js +3 -6
- package/src/core/config/index.js +6 -1
- package/src/core/config/options.js +54 -104
- package/src/core/env.js +25 -11
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +75 -28
- package/src/core/postbuild/queue.js +2 -1
- package/src/core/sync/sync.js +11 -14
- package/src/core/sync/utils.js +21 -1
- package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
- package/src/core/sync/write_client_manifest.js +4 -12
- package/src/core/sync/write_env.js +6 -4
- package/src/core/sync/write_server.js +12 -17
- package/src/core/sync/write_tsconfig/index.js +245 -0
- package/src/core/sync/write_tsconfig/utils.js +162 -0
- package/src/core/sync/write_types/index.js +80 -88
- package/src/core/utils.js +28 -3
- package/src/exports/env/index.js +68 -3
- package/src/exports/internal/env.js +6 -3
- package/src/exports/internal/server/index.js +3 -1
- package/src/exports/params.js +9 -4
- package/src/exports/public.d.ts +69 -71
- package/src/exports/vite/dev/index.js +112 -56
- package/src/exports/vite/index.js +483 -258
- package/src/exports/vite/module_ids.js +2 -2
- package/src/exports/vite/preview/index.js +3 -2
- package/src/exports/vite/utils.js +21 -10
- package/src/runner.js +13 -0
- package/src/runtime/app/forms.js +4 -0
- package/src/runtime/app/manifest/index.js +1 -0
- package/src/runtime/app/paths/client.js +30 -30
- package/src/runtime/app/paths/internal/client.js +26 -0
- package/src/runtime/app/paths/server.js +23 -10
- package/src/runtime/app/paths/types.d.ts +11 -19
- package/src/runtime/app/server/index.js +1 -1
- package/src/runtime/app/server/remote/command.js +12 -7
- package/src/runtime/app/server/remote/form.js +29 -26
- package/src/runtime/app/server/remote/prerender.js +9 -2
- package/src/runtime/app/server/remote/query.js +6 -5
- package/src/runtime/app/server/remote/shared.js +48 -30
- package/src/runtime/app/service-worker/index.js +24 -0
- package/src/runtime/app/state/index.js +1 -1
- package/src/runtime/client/client.js +46 -9
- package/src/runtime/client/ndjson.js +1 -1
- package/src/runtime/client/remote-functions/form.svelte.js +43 -51
- package/src/runtime/client/remote-functions/query/index.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
- package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
- package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
- package/src/runtime/client/state.svelte.js +54 -21
- package/src/runtime/client/stream.js +3 -2
- package/src/runtime/form-utils.js +89 -54
- package/src/runtime/server/cookie.js +1 -1
- package/src/runtime/server/data/index.js +32 -29
- package/src/runtime/server/errors.js +135 -0
- package/src/runtime/server/fetch.js +1 -1
- package/src/runtime/server/index.js +20 -10
- package/src/runtime/server/internal.js +25 -0
- package/src/runtime/server/page/actions.js +4 -3
- package/src/runtime/server/page/data_serializer.js +10 -10
- package/src/runtime/server/page/index.js +3 -2
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +22 -35
- package/src/runtime/server/page/respond_with_error.js +6 -5
- package/src/runtime/server/{remote.js → remote-functions.js} +67 -36
- package/src/runtime/server/respond.js +58 -20
- package/src/runtime/server/sourcemaps.js +183 -0
- package/src/runtime/server/utils.js +12 -157
- package/src/types/ambient-private.d.ts +10 -0
- package/src/types/ambient.d.ts +22 -27
- package/src/types/global-private.d.ts +12 -5
- package/src/types/internal.d.ts +19 -9
- package/src/types/private.d.ts +8 -0
- package/src/utils/escape.js +9 -25
- package/src/utils/features.js +1 -1
- package/src/utils/fork.js +7 -2
- package/src/utils/page_nodes.js +3 -5
- package/src/utils/url.js +12 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +152 -120
- package/types/index.d.ts.map +6 -1
- package/src/core/sync/write_ambient.js +0 -18
- package/src/core/sync/write_tsconfig.js +0 -258
- package/src/runtime/shared-server.js +0 -7
- /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
|
@@ -7,7 +7,8 @@ import { compact } from '../../../utils/array.js';
|
|
|
7
7
|
import { get_status, normalize_error } from '../../../utils/error.js';
|
|
8
8
|
import { noop } from '../../../utils/functions.js';
|
|
9
9
|
import { add_data_suffix } from '../../pathname.js';
|
|
10
|
-
import { redirect_response
|
|
10
|
+
import { redirect_response } from '../utils.js';
|
|
11
|
+
import { static_error_page, handle_error_and_jsonify } from '../errors.js';
|
|
11
12
|
import {
|
|
12
13
|
handle_action_json_request,
|
|
13
14
|
handle_action_request,
|
|
@@ -19,7 +20,7 @@ import { load_data, load_server_data } from './load_data.js';
|
|
|
19
20
|
import { render_response } from './render.js';
|
|
20
21
|
import { respond_with_error } from './respond_with_error.js';
|
|
21
22
|
import { DEV } from 'esm-env';
|
|
22
|
-
import { get_remote_action, handle_remote_form_post } from '../remote.js';
|
|
23
|
+
import { get_remote_action, handle_remote_form_post } from '../remote-functions.js';
|
|
23
24
|
import { PageNodes } from '../../../utils/page_nodes.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -465,7 +465,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
465
465
|
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
|
|
466
466
|
if (!included) {
|
|
467
467
|
throw new Error(
|
|
468
|
-
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://svelte.dev/docs/kit/hooks#
|
|
468
|
+
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://svelte.dev/docs/kit/hooks#handle (at ${event.route.id})`
|
|
469
469
|
);
|
|
470
470
|
}
|
|
471
471
|
}
|
|
@@ -14,14 +14,10 @@ import { create_server_routing_response, generate_route_object } from './server_
|
|
|
14
14
|
import { add_data_suffix, add_resolution_suffix } from '../../pathname.js';
|
|
15
15
|
import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server';
|
|
16
16
|
import { text_encoder } from '../../utils.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
create_replacer,
|
|
20
|
-
get_global_name,
|
|
21
|
-
handle_error_and_jsonify
|
|
22
|
-
} from '../utils.js';
|
|
17
|
+
import { count_non_ssi_comments, create_replacer, get_global_name } from '../utils.js';
|
|
18
|
+
import { handle_error_and_jsonify } from '../errors.js';
|
|
23
19
|
import * as env from '__sveltekit/env';
|
|
24
|
-
import { collect_remote_data } from '../remote.js';
|
|
20
|
+
import { collect_remote_data } from '../remote-functions.js';
|
|
25
21
|
import Root from '../../components/root.svelte';
|
|
26
22
|
import { render } from 'svelte/server';
|
|
27
23
|
|
|
@@ -308,6 +304,19 @@ export async function render_response({
|
|
|
308
304
|
head.add_style(style, attributes);
|
|
309
305
|
}
|
|
310
306
|
|
|
307
|
+
/**
|
|
308
|
+
* see the `output.linkHeaderPreload` option for details on why we have multiple options here
|
|
309
|
+
* @param {string} path
|
|
310
|
+
* @param {string[]} attributes
|
|
311
|
+
*/
|
|
312
|
+
const add_preload = (path, attributes) => {
|
|
313
|
+
if (options.link_header_preload && !state.prerendering) {
|
|
314
|
+
link_headers.add(`<${encodeURI(path)}>; ${attributes.join('; ')}; nopush`);
|
|
315
|
+
} else {
|
|
316
|
+
head.add_link_tag(path, attributes);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
311
320
|
for (const dep of stylesheets) {
|
|
312
321
|
const path = prefixed(dep);
|
|
313
322
|
|
|
@@ -332,18 +341,7 @@ export async function render_response({
|
|
|
332
341
|
if (resolve_opts.preload({ type: 'font', path })) {
|
|
333
342
|
const ext = dep.slice(dep.lastIndexOf('.') + 1);
|
|
334
343
|
|
|
335
|
-
|
|
336
|
-
link_headers.add(
|
|
337
|
-
`<${encodeURI(path)}>; rel="preload"; as="font"; type="font/${ext}"; crossorigin; nopush`
|
|
338
|
-
);
|
|
339
|
-
} else {
|
|
340
|
-
head.add_link_tag(path, [
|
|
341
|
-
'rel="preload"',
|
|
342
|
-
'as="font"',
|
|
343
|
-
`type="font/${ext}"`,
|
|
344
|
-
'crossorigin'
|
|
345
|
-
]);
|
|
346
|
-
}
|
|
344
|
+
add_preload(path, ['rel="preload"', 'as="font"', `type="font/${ext}"`, 'crossorigin']);
|
|
347
345
|
}
|
|
348
346
|
}
|
|
349
347
|
|
|
@@ -372,23 +370,12 @@ export async function render_response({
|
|
|
372
370
|
}
|
|
373
371
|
|
|
374
372
|
if (!client.inline) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
/** @type {(path: string) => void} */
|
|
380
|
-
let add_preload;
|
|
373
|
+
for (const dep of modulepreloads) {
|
|
374
|
+
const path = prefixed(dep);
|
|
381
375
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
|
|
386
|
-
} else {
|
|
387
|
-
add_preload = (path) => head.add_link_tag(path, ['rel="modulepreload"']);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
for (const path of included_modulepreloads) {
|
|
391
|
-
add_preload(path);
|
|
376
|
+
if (resolve_opts.preload({ type: 'js', path })) {
|
|
377
|
+
add_preload(path, ['rel="modulepreload"']);
|
|
378
|
+
}
|
|
392
379
|
}
|
|
393
380
|
}
|
|
394
381
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Redirect } from '@sveltejs/kit/internal';
|
|
2
2
|
import { render_response } from './render.js';
|
|
3
3
|
import { load_data, load_server_data } from './load_data.js';
|
|
4
|
-
import {
|
|
4
|
+
import { redirect_response } from '../utils.js';
|
|
5
|
+
import { handle_error_and_jsonify, static_error_page } from '../errors.js';
|
|
5
6
|
import { PageNodes } from '../../../utils/page_nodes.js';
|
|
6
7
|
import { server_data_serializer } from './data_serializer.js';
|
|
7
8
|
|
|
@@ -33,12 +34,12 @@ export async function respond_with_error({
|
|
|
33
34
|
}) {
|
|
34
35
|
// reroute to the fallback page to prevent an infinite chain of requests.
|
|
35
36
|
if (event.request.headers.get('x-sveltekit-error')) {
|
|
36
|
-
|
|
37
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
38
|
+
return static_error_page(options, status, transformed.message);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** @type {import('./types.js').Fetched[]} */
|
|
40
42
|
const fetched = [];
|
|
41
|
-
|
|
42
43
|
try {
|
|
43
44
|
const branch = [];
|
|
44
45
|
const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
|
|
@@ -46,6 +47,8 @@ export async function respond_with_error({
|
|
|
46
47
|
const ssr = nodes.ssr();
|
|
47
48
|
const csr = nodes.csr();
|
|
48
49
|
const data_serializer = server_data_serializer(event, event_state, options);
|
|
50
|
+
// Do this here first in case the awaits below before rendering themselves error
|
|
51
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
49
52
|
|
|
50
53
|
if (ssr) {
|
|
51
54
|
state.error = true;
|
|
@@ -89,8 +92,6 @@ export async function respond_with_error({
|
|
|
89
92
|
);
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
93
|
-
|
|
94
95
|
return await render_response({
|
|
95
96
|
options,
|
|
96
97
|
manifest,
|
|
@@ -7,12 +7,13 @@ import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server
|
|
|
7
7
|
import { app_dir, base } from '$app/paths/internal/server';
|
|
8
8
|
import { is_form_content_type } from '../../utils/http.js';
|
|
9
9
|
import { create_remote_key, parse_remote_arg, split_remote_key, stringify } from '../shared.js';
|
|
10
|
-
import { handle_error_and_jsonify } from './
|
|
10
|
+
import { handle_error_and_jsonify } from './errors.js';
|
|
11
11
|
import { normalize_error } from '../../utils/error.js';
|
|
12
12
|
import { check_incorrect_fail_use } from './page/actions.js';
|
|
13
13
|
import { DEV } from 'esm-env';
|
|
14
14
|
import { record_span } from '../telemetry/record_span.js';
|
|
15
15
|
import { deserialize_binary_form } from '../form-utils.js';
|
|
16
|
+
import { with_version_header } from './utils.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* How long (in milliseconds) to wait after the last message was sent before
|
|
@@ -28,11 +29,12 @@ export async function handle_remote_call(event, state, options, manifest, id) {
|
|
|
28
29
|
attributes: {
|
|
29
30
|
'sveltekit.remote.call.id': id
|
|
30
31
|
},
|
|
31
|
-
fn: (current) => {
|
|
32
|
+
fn: async (current) => {
|
|
32
33
|
const traced_event = merge_tracing(event, current);
|
|
33
|
-
|
|
34
|
+
const response = await with_request_store({ event: traced_event, state }, () =>
|
|
34
35
|
handle_remote_call_internal(traced_event, state, options, manifest, id)
|
|
35
36
|
);
|
|
37
|
+
return with_version_header(response);
|
|
36
38
|
}
|
|
37
39
|
});
|
|
38
40
|
}
|
|
@@ -240,7 +242,11 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
240
242
|
);
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
const {
|
|
245
|
+
const {
|
|
246
|
+
data: input,
|
|
247
|
+
meta,
|
|
248
|
+
form_data
|
|
249
|
+
} = await deserialize_binary_form(event.request, internals.id);
|
|
244
250
|
state.remote.requested = create_requested_map(meta.remote_refreshes);
|
|
245
251
|
|
|
246
252
|
// If this is a keyed form instance (created via form.for(key)), add the key to the form data (unless already set)
|
|
@@ -368,43 +374,64 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
368
374
|
/** @type {Promise<any>[]} */
|
|
369
375
|
const promises = [];
|
|
370
376
|
|
|
377
|
+
// Keys the explicit pass has serialized. Invoking a query's `fn` there can, as a
|
|
378
|
+
// side effect, register the same query in `state.remote.implicit` (via
|
|
379
|
+
// `get_response`), so we skip those keys in the implicit pass below to avoid
|
|
380
|
+
// processing them twice.
|
|
381
|
+
/** @type {Set<string>} */
|
|
382
|
+
const processed = new Set();
|
|
383
|
+
|
|
371
384
|
if (state.remote.explicit) {
|
|
372
|
-
|
|
373
|
-
// there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
|
|
374
|
-
// so the client should apply these single-flight updates instead of calling `invalidateAll()`
|
|
375
|
-
data.r = true;
|
|
385
|
+
const { explicit } = state.remote;
|
|
376
386
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
/** @type {Promise<void>[]} */
|
|
388
|
+
const inflight = [];
|
|
389
|
+
|
|
390
|
+
const drain = () => {
|
|
391
|
+
for (const [remote_key, { internals, fn }] of explicit) {
|
|
392
|
+
explicit.delete(remote_key);
|
|
393
|
+
if (processed.has(remote_key)) continue;
|
|
394
|
+
processed.add(remote_key);
|
|
395
|
+
|
|
396
|
+
// there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
|
|
397
|
+
// so the client should apply these single-flight updates instead of calling `invalidateAll()`
|
|
398
|
+
data.r = true;
|
|
380
399
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
400
|
+
const type = /** @type {'p' | 'q' | 'l'} */ (
|
|
401
|
+
internals.type === 'query_live' ? 'l' : internals.type[0]
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// `fn` is deferred until now so the query runs after any state mutations
|
|
405
|
+
// in the command/form body. If the query was re-awaited in the meantime,
|
|
406
|
+
// `fn` returns the existing (fresh) cache entry rather than re-running.
|
|
407
|
+
inflight.push(
|
|
408
|
+
fn().then(
|
|
409
|
+
(v) => {
|
|
410
|
+
// a fresh value replaces the node entirely, so a re-run can't leave
|
|
411
|
+
// a stale error from a previous run alongside the new value
|
|
412
|
+
(data[type] ??= {})[remote_key] = { v };
|
|
413
|
+
drain();
|
|
414
|
+
},
|
|
415
|
+
async (e) => {
|
|
416
|
+
if (!(e instanceof Redirect)) {
|
|
417
|
+
// (a Redirect is already handled elsewhere)
|
|
418
|
+
(data[type] ??= {})[remote_key] = { e: await convert_error(e) };
|
|
419
|
+
}
|
|
420
|
+
drain();
|
|
397
421
|
}
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
398
426
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
427
|
+
drain();
|
|
428
|
+
|
|
429
|
+
// `inflight` grows as settles drain newly-refreshed queries
|
|
430
|
+
for (const promise of inflight) {
|
|
431
|
+
await promise;
|
|
403
432
|
}
|
|
404
433
|
}
|
|
405
434
|
|
|
406
|
-
await Promise.all(promises);
|
|
407
|
-
|
|
408
435
|
if (state.remote.implicit) {
|
|
409
436
|
for (const [internals, record] of state.remote.implicit) {
|
|
410
437
|
// Private (non-exported) remote functions have no `id` and must never be
|
|
@@ -416,6 +443,10 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
416
443
|
// form outputs are registered under the client-side action id directly
|
|
417
444
|
const remote_key = internals.type === 'form' ? key : create_remote_key(internals.id, key);
|
|
418
445
|
|
|
446
|
+
// already serialized by the explicit pass (which always awaits and wins),
|
|
447
|
+
// so don't reprocess it here with the implicit "still loading" heuristic
|
|
448
|
+
if (processed.has(remote_key)) continue;
|
|
449
|
+
|
|
419
450
|
const type = /** @type {'p' | 'q' | 'l' | 'f'} */ (
|
|
420
451
|
internals.type === 'query_live' ? 'l' : internals.type[0]
|
|
421
452
|
);
|
|
@@ -540,9 +571,9 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
|
540
571
|
}
|
|
541
572
|
|
|
542
573
|
try {
|
|
543
|
-
const
|
|
574
|
+
const __ = /** @type {RemoteFormInternals} */ (/** @type {any} */ (form).__);
|
|
544
575
|
|
|
545
|
-
const { data, meta, form_data } = await deserialize_binary_form(event.request);
|
|
576
|
+
const { data, meta, form_data } = await deserialize_binary_form(event.request, __.id);
|
|
546
577
|
|
|
547
578
|
if (action_id && !('id' in data)) {
|
|
548
579
|
data.id = JSON.parse(decodeURIComponent(action_id));
|
|
@@ -550,7 +581,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
|
550
581
|
|
|
551
582
|
await with_request_store(
|
|
552
583
|
{ event, state: { ...state, is_in_remote_form_or_command: true } },
|
|
553
|
-
() => fn(data, meta, form_data)
|
|
584
|
+
() => __.fn(data, meta, form_data)
|
|
554
585
|
);
|
|
555
586
|
|
|
556
587
|
// We don't want the data to appear on `let { form } = $props()`, which is why we're not returning it.
|
|
@@ -9,13 +9,14 @@ import { render_page } from './page/index.js';
|
|
|
9
9
|
import { render_response } from './page/render.js';
|
|
10
10
|
import { respond_with_error } from './page/respond_with_error.js';
|
|
11
11
|
import { get_self_origin, is_csrf_forbidden, is_remote_forbidden } from './csrf.js';
|
|
12
|
+
import { has_prerendered_path, method_not_allowed, redirect_response } from './utils.js';
|
|
13
|
+
import { handle_fatal_error } from './errors.js';
|
|
12
14
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from '
|
|
18
|
-
import { decode_pathname, disable_search, normalize_path } from '../../utils/url.js';
|
|
15
|
+
decode_pathname,
|
|
16
|
+
disable_search,
|
|
17
|
+
normalize_path,
|
|
18
|
+
relative_pathname
|
|
19
|
+
} from '../../utils/url.js';
|
|
19
20
|
import { find_route } from '../../utils/routing.js';
|
|
20
21
|
import { redirect_json_response, render_data } from './data/index.js';
|
|
21
22
|
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
strip_resolution_suffix
|
|
37
38
|
} from '../pathname.js';
|
|
38
39
|
import { server_data_serializer } from './page/data_serializer.js';
|
|
39
|
-
import { get_remote_id, handle_remote_call } from './remote.js';
|
|
40
|
+
import { get_remote_id, handle_remote_call } from './remote-functions.js';
|
|
40
41
|
import { record_span } from '../telemetry/record_span.js';
|
|
41
42
|
import { otel } from '../telemetry/otel.js';
|
|
42
43
|
|
|
@@ -49,6 +50,28 @@ const default_filter = () => false;
|
|
|
49
50
|
/** @type {import('types').RequiredResolveOptions['preload']} */
|
|
50
51
|
const default_preload = ({ type }) => type === 'js' || type === 'css';
|
|
51
52
|
|
|
53
|
+
// `Sec-Fetch-Dest` values for subresource requests that can never render an HTML error page
|
|
54
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Dest
|
|
55
|
+
const non_html_fetch_destinations = new Set([
|
|
56
|
+
'audio',
|
|
57
|
+
'audioworklet',
|
|
58
|
+
'font',
|
|
59
|
+
'image',
|
|
60
|
+
'json',
|
|
61
|
+
'manifest',
|
|
62
|
+
'paintworklet',
|
|
63
|
+
'report',
|
|
64
|
+
'script',
|
|
65
|
+
'serviceworker',
|
|
66
|
+
'sharedworker',
|
|
67
|
+
'style',
|
|
68
|
+
'track',
|
|
69
|
+
'video',
|
|
70
|
+
'webidentity',
|
|
71
|
+
'worker',
|
|
72
|
+
'xslt'
|
|
73
|
+
]);
|
|
74
|
+
|
|
52
75
|
const page_methods = new Set(['GET', 'HEAD', 'POST']);
|
|
53
76
|
|
|
54
77
|
const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
@@ -113,6 +136,8 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
113
136
|
/** @type {boolean[] | undefined} */
|
|
114
137
|
let invalidated_data_nodes;
|
|
115
138
|
|
|
139
|
+
let skip_route_resolution = false;
|
|
140
|
+
|
|
116
141
|
if (is_route_resolution_request) {
|
|
117
142
|
/**
|
|
118
143
|
* If the request is for a route resolution, first modify the URL, then continue as normal
|
|
@@ -130,8 +155,15 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
130
155
|
.map((node) => node === '1');
|
|
131
156
|
url.searchParams.delete(INVALIDATED_PARAM);
|
|
132
157
|
} else if (remote_id) {
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
// query clients don't send these headers, leaving `event.url` as the endpoint URL
|
|
159
|
+
const pathname = request.headers.get('x-sveltekit-pathname');
|
|
160
|
+
|
|
161
|
+
if (pathname === null) {
|
|
162
|
+
skip_route_resolution = true;
|
|
163
|
+
} else {
|
|
164
|
+
url.pathname = pathname;
|
|
165
|
+
url.search = request.headers.get('x-sveltekit-search') ?? '';
|
|
166
|
+
}
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
/** @type {Record<string, string>} */
|
|
@@ -162,6 +194,7 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
162
194
|
is_in_remote_function: false,
|
|
163
195
|
is_in_remote_form_or_command: false,
|
|
164
196
|
is_in_remote_query: false,
|
|
197
|
+
is_in_remote_prerender: false,
|
|
165
198
|
is_in_render: false,
|
|
166
199
|
is_in_universal_load: false
|
|
167
200
|
};
|
|
@@ -335,7 +368,7 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
335
368
|
return text('Not found', { status: 404, headers });
|
|
336
369
|
}
|
|
337
370
|
|
|
338
|
-
if (!state.prerendering?.fallback) {
|
|
371
|
+
if (!state.prerendering?.fallback && !skip_route_resolution) {
|
|
339
372
|
try {
|
|
340
373
|
const matchers = await manifest._.matchers();
|
|
341
374
|
const result = find_route(resolved_path, manifest._.routes, matchers);
|
|
@@ -382,10 +415,9 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
382
415
|
status: 308,
|
|
383
416
|
headers: {
|
|
384
417
|
'x-sveltekit-normalize': '1',
|
|
418
|
+
// relative so (possibly invisible) path prefixes are preserved
|
|
385
419
|
location:
|
|
386
|
-
|
|
387
|
-
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
|
|
388
|
-
(url.search === '?' ? '' : url.search)
|
|
420
|
+
relative_pathname(url.pathname, normalized) + (url.search === '?' ? '' : url.search)
|
|
389
421
|
}
|
|
390
422
|
});
|
|
391
423
|
}
|
|
@@ -635,13 +667,12 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
635
667
|
) {
|
|
636
668
|
endpoint = await route.endpoint();
|
|
637
669
|
|
|
638
|
-
// Prefer rendering the page if the endpoint can't handle this GET or
|
|
639
|
-
if (route.page && (method === 'GET' || method === 'HEAD')) {
|
|
640
|
-
const endpoint_can_handle =
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
);
|
|
670
|
+
// Prefer rendering the page if the endpoint can't handle this GET, HEAD, or POST request
|
|
671
|
+
if (route.page && (method === 'GET' || method === 'HEAD' || method === 'POST')) {
|
|
672
|
+
const endpoint_can_handle =
|
|
673
|
+
method === 'POST'
|
|
674
|
+
? !!(endpoint.POST || endpoint.fallback)
|
|
675
|
+
: !!(endpoint.GET || endpoint.fallback || (method === 'HEAD' && endpoint.HEAD));
|
|
645
676
|
if (!endpoint_can_handle) {
|
|
646
677
|
endpoint = undefined;
|
|
647
678
|
}
|
|
@@ -737,6 +768,13 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
737
768
|
// if this request came direct from the user, rather than
|
|
738
769
|
// via our own `fetch`, render a 404 page
|
|
739
770
|
if (state.depth === 0) {
|
|
771
|
+
if (non_html_fetch_destinations.has(event.request.headers.get('sec-fetch-dest') ?? '')) {
|
|
772
|
+
return text('Not Found', {
|
|
773
|
+
status: 404,
|
|
774
|
+
headers: { vary: 'Sec-Fetch-Dest' }
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
740
778
|
return await respond_with_error({
|
|
741
779
|
event,
|
|
742
780
|
event_state,
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/* eslint-disable n/prefer-global/process */
|
|
2
|
+
|
|
3
|
+
// using `getBuiltinModule` rather than `import` makes this safe to run in non-Node-compatible environments
|
|
4
|
+
const fs = globalThis.process?.getBuiltinModule?.('node:fs');
|
|
5
|
+
const url = globalThis.process?.getBuiltinModule?.('node:url');
|
|
6
|
+
const path = globalThis.process?.getBuiltinModule?.('node:path');
|
|
7
|
+
const module = globalThis.process?.getBuiltinModule?.('node:module');
|
|
8
|
+
|
|
9
|
+
const cwd = globalThis.process?.cwd?.();
|
|
10
|
+
|
|
11
|
+
/** @type {(file: string) => string} */
|
|
12
|
+
const relative = cwd ? (file) => path.relative(cwd, file) : (file) => file;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Applies sourcemaps, makes paths relative to the cwd, and truncates
|
|
16
|
+
* non-user code from the bottom of the stack
|
|
17
|
+
* @param {Error} error
|
|
18
|
+
* @returns void
|
|
19
|
+
*/
|
|
20
|
+
export let fix_stack_trace = (error) => {
|
|
21
|
+
if (!error.stack || !fs) return;
|
|
22
|
+
|
|
23
|
+
let end = 0;
|
|
24
|
+
|
|
25
|
+
error.stack = error.stack
|
|
26
|
+
.split('\n')
|
|
27
|
+
.map((line, i) => {
|
|
28
|
+
const match = line.match(/^ {4}at.+(file:\/\/\/.*):(\d+):(\d+)(\)?)$/);
|
|
29
|
+
|
|
30
|
+
if (!match) {
|
|
31
|
+
if (!line.includes('node:internal/')) {
|
|
32
|
+
end = i + 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return line;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const file = url.fileURLToPath(match[1]);
|
|
39
|
+
const traced = trace(file, Number(match[2]) - 1, Number(match[3]) - 1);
|
|
40
|
+
|
|
41
|
+
// truncate non-user code from the bottom of the stack (but leave it in otherwise)
|
|
42
|
+
if (!/[\\/]node_modules[\\/]/.test(traced?.file ?? file)) {
|
|
43
|
+
end = i + 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (traced?.line) {
|
|
47
|
+
const location = `${match[1]}:${match[2]}:${match[3]}`;
|
|
48
|
+
const original = `${relative(traced.file)}:${traced.line}:${traced.column}`;
|
|
49
|
+
|
|
50
|
+
return line.replace(location, original);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (traced) {
|
|
54
|
+
return `${line.replace(match[1], relative(file))} [${traced.file}]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return line;
|
|
58
|
+
})
|
|
59
|
+
.slice(0, end)
|
|
60
|
+
.join('\n');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Override the implementation of fix_stack_trace (for using during dev)
|
|
65
|
+
* @param {(error: Error) => void} fn
|
|
66
|
+
*/
|
|
67
|
+
export function set_fix_stack_trace(fn) {
|
|
68
|
+
fix_stack_trace = fn;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @type {Map<string, { map: import('node:module').SourceMap; directory: string } | null>} */
|
|
72
|
+
const source_maps = new Map();
|
|
73
|
+
|
|
74
|
+
/** @type {Map<string, Array<string | undefined>>} */
|
|
75
|
+
const source_regions = new Map();
|
|
76
|
+
|
|
77
|
+
/** @param {string} file */
|
|
78
|
+
function get_source_map(file) {
|
|
79
|
+
if (source_maps.has(file)) {
|
|
80
|
+
return source_maps.get(file);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
let source;
|
|
85
|
+
let directory = path.dirname(file);
|
|
86
|
+
|
|
87
|
+
const code = fs.readFileSync(file, 'utf8');
|
|
88
|
+
const matches = Array.from(code.matchAll(/\/\/[#@]\s*sourceMappingURL=(\S+)/g));
|
|
89
|
+
const url = matches.at(-1)?.[1];
|
|
90
|
+
|
|
91
|
+
if (url?.startsWith('data:')) {
|
|
92
|
+
const comma = url.indexOf(',');
|
|
93
|
+
const metadata = url.slice(5, comma);
|
|
94
|
+
const data = url.slice(comma + 1);
|
|
95
|
+
source = metadata.endsWith(';base64')
|
|
96
|
+
? Buffer.from(data, 'base64').toString()
|
|
97
|
+
: decodeURIComponent(data);
|
|
98
|
+
} else {
|
|
99
|
+
const map_file = url
|
|
100
|
+
? path.resolve(path.dirname(file), decodeURIComponent(url))
|
|
101
|
+
: `${file}.map`;
|
|
102
|
+
if (fs.existsSync(map_file)) {
|
|
103
|
+
directory = path.dirname(map_file);
|
|
104
|
+
source = fs.readFileSync(map_file, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (source) {
|
|
109
|
+
const source_map = { map: new module.SourceMap(JSON.parse(source)), directory };
|
|
110
|
+
source_maps.set(file, source_map);
|
|
111
|
+
|
|
112
|
+
return source_map;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// failure could be for any reason and this is best-effort, ignore
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
source_maps.set(file, null);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
*
|
|
124
|
+
* @param {string} file
|
|
125
|
+
* @param {number} line
|
|
126
|
+
* @param {number} column
|
|
127
|
+
* @returns {null | { file: string, line?: number, column?: number }}
|
|
128
|
+
*/
|
|
129
|
+
function trace(file, line, column) {
|
|
130
|
+
const source_map = get_source_map(file);
|
|
131
|
+
if (!source_map) return null;
|
|
132
|
+
|
|
133
|
+
const entry = source_map.map.findEntry(line, column);
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
entry &&
|
|
137
|
+
'originalSource' in entry &&
|
|
138
|
+
entry.originalSource &&
|
|
139
|
+
typeof entry.originalLine === 'number' &&
|
|
140
|
+
typeof entry.originalColumn === 'number'
|
|
141
|
+
) {
|
|
142
|
+
const source = entry.originalSource.startsWith('file:')
|
|
143
|
+
? url.fileURLToPath(entry.originalSource)
|
|
144
|
+
: path.resolve(source_map.directory, entry.originalSource);
|
|
145
|
+
|
|
146
|
+
const traced = {
|
|
147
|
+
file: source,
|
|
148
|
+
line: entry.originalLine + 1,
|
|
149
|
+
column: entry.originalColumn + 1
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// keep going, in case we are running code that was bundled a second time by an adapter
|
|
153
|
+
return trace(traced.file, traced.line - 1, traced.column - 1) ?? traced;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let regions = source_regions.get(file);
|
|
157
|
+
if (!regions) {
|
|
158
|
+
/** @type {string | undefined} */
|
|
159
|
+
let source;
|
|
160
|
+
regions = fs
|
|
161
|
+
.readFileSync(file, 'utf8')
|
|
162
|
+
.split('\n')
|
|
163
|
+
.map((line) => {
|
|
164
|
+
// Vite will merge multiple files into one but add region markers
|
|
165
|
+
// with the original file names, which we try to extract here.
|
|
166
|
+
const start = line.match(/^\/\/#region (.+)$/);
|
|
167
|
+
if (start) source = start[1];
|
|
168
|
+
if (line === '//#endregion') source = undefined;
|
|
169
|
+
return source;
|
|
170
|
+
});
|
|
171
|
+
source_regions.set(file, regions);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const source = regions[line];
|
|
175
|
+
|
|
176
|
+
if (source) {
|
|
177
|
+
return {
|
|
178
|
+
file: source
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|