@sveltejs/kit 2.65.1 → 2.66.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/package.json +3 -3
- package/src/core/adapt/builder.js +33 -6
- package/src/core/config/index.js +60 -2
- package/src/core/config/options.js +12 -3
- package/src/core/env.js +7 -3
- package/src/core/postbuild/analyse.js +4 -1
- package/src/core/postbuild/prerender.js +13 -4
- package/src/core/sync/create_manifest_data/index.js +13 -2
- package/src/core/sync/write_client_manifest.js +2 -0
- package/src/core/sync/write_server.js +12 -10
- package/src/core/sync/write_tsconfig.js +1 -2
- package/src/exports/internal/env.js +1 -1
- package/src/exports/public.d.ts +13 -1
- package/src/exports/vite/build/build_server.js +6 -1
- package/src/exports/vite/build/build_service_worker.js +8 -2
- package/src/exports/vite/index.js +44 -16
- package/src/exports/vite/preview/index.js +13 -4
- package/src/exports/vite/static_analysis/index.js +8 -3
- package/src/runtime/app/paths/server.js +7 -1
- package/src/runtime/app/server/remote/form.js +2 -2
- package/src/runtime/client/client.js +30 -8
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/form.svelte.js +16 -6
- package/src/runtime/client/remote-functions/prerender.svelte.js +5 -0
- package/src/runtime/client/remote-functions/query/instance.svelte.js +8 -1
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +25 -8
- package/src/runtime/client/remote-functions/shared.svelte.js +7 -1
- package/src/runtime/client/types.d.ts +6 -0
- package/src/runtime/form-utils.js +1 -0
- package/src/runtime/server/page/render.js +15 -13
- package/src/runtime/server/remote.js +9 -3
- package/src/runtime/server/respond.js +4 -1
- package/src/types/internal.d.ts +3 -1
- package/src/types/private.d.ts +24 -1
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/url.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +54 -11
- package/types/index.d.ts.map +2 -1
|
@@ -51,10 +51,19 @@ export async function preview(vite, vite_config, svelte_config) {
|
|
|
51
51
|
set_assets(assets);
|
|
52
52
|
|
|
53
53
|
const server = new Server(manifest);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await server.init({
|
|
57
|
+
env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''),
|
|
58
|
+
read: (file) => createReadableStream(`${dir}/${file}`)
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Vite erases the error message when starting the preview server so we store
|
|
62
|
+
// it in the stack instead. This ensures errors thrown using `stackless`
|
|
63
|
+
// are still readable
|
|
64
|
+
if (error instanceof Error) error.stack = error.message;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
const emulator = await svelte_config.kit.adapter?.emulate?.();
|
|
60
69
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** @import { PageOptions } from './types.js' */
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
3
5
|
import { Parser } from 'acorn';
|
|
4
6
|
import { read } from '../../../utils/filesystem.js';
|
|
@@ -227,7 +229,10 @@ export function get_page_options(filepath) {
|
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} cwd
|
|
234
|
+
*/
|
|
235
|
+
export function create_node_analyser(cwd = process.cwd()) {
|
|
231
236
|
const static_exports = new Map();
|
|
232
237
|
|
|
233
238
|
/**
|
|
@@ -271,7 +276,7 @@ export function create_node_analyser() {
|
|
|
271
276
|
}
|
|
272
277
|
|
|
273
278
|
if (node.server) {
|
|
274
|
-
const server_page_options = get_page_options(node.server);
|
|
279
|
+
const server_page_options = get_page_options(path.join(cwd, node.server));
|
|
275
280
|
if (server_page_options === null) {
|
|
276
281
|
cache(key, null);
|
|
277
282
|
return null;
|
|
@@ -280,7 +285,7 @@ export function create_node_analyser() {
|
|
|
280
285
|
}
|
|
281
286
|
|
|
282
287
|
if (node.universal) {
|
|
283
|
-
const universal_page_options = get_page_options(node.universal);
|
|
288
|
+
const universal_page_options = get_page_options(path.join(cwd, node.universal));
|
|
284
289
|
if (universal_page_options === null) {
|
|
285
290
|
cache(key, null);
|
|
286
291
|
return null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { base, assets, relative, initial_base } from './internal/server.js';
|
|
2
2
|
import { resolve_route, find_route } from '../../../utils/routing.js';
|
|
3
3
|
import { decode_pathname } from '../../../utils/url.js';
|
|
4
|
+
import { add_data_suffix } from '../../pathname.js';
|
|
4
5
|
import { try_get_request_store } from '@sveltejs/kit/internal/server';
|
|
5
6
|
import { manifest } from '__sveltekit/server';
|
|
6
7
|
import { get_hooks } from '__SERVER__/internal.js';
|
|
@@ -26,7 +27,12 @@ export function resolve(id, params) {
|
|
|
26
27
|
const store = try_get_request_store();
|
|
27
28
|
|
|
28
29
|
if (store && !store.state.prerendering?.fallback) {
|
|
29
|
-
|
|
30
|
+
// the relative path depth must reflect the URL the browser is actually at, which
|
|
31
|
+
// for a data request includes the `__data.json` suffix that was stripped during routing
|
|
32
|
+
const pathname = store.event.isDataRequest
|
|
33
|
+
? add_data_suffix(store.event.url.pathname)
|
|
34
|
+
: store.event.url.pathname;
|
|
35
|
+
const after_base = pathname.slice(initial_base.length);
|
|
30
36
|
const segments = after_base.split('/').slice(2);
|
|
31
37
|
const prefix = segments.map(() => '..').join('/') || '.';
|
|
32
38
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @import { RemoteFormInput, RemoteForm, InvalidField } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { InternalRemoteFormIssue, MaybePromise, RemoteFormInternals } from 'types' */
|
|
2
|
+
/** @import { InternalRemoteFormIssue, MaybePromise, HasNonOptionalBoolean, RemoteFormInternals } from 'types' */
|
|
3
3
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
4
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
5
5
|
import { DEV } from 'esm-env';
|
|
@@ -46,7 +46,7 @@ import { ValidationError } from '@sveltejs/kit/internal';
|
|
|
46
46
|
* @template {StandardSchemaV1<RemoteFormInput, Record<string, any>>} Schema
|
|
47
47
|
* @template Output
|
|
48
48
|
* @overload
|
|
49
|
-
* @param {Schema} validate
|
|
49
|
+
* @param {true extends HasNonOptionalBoolean<StandardSchemaV1.InferInput<Schema>> ? 'Error: All booleans in form schemas must be optional (e.g. `v.optional(v.boolean(), false)`) because checkbox inputs do not send a false value when unchecked.' : Schema} validate
|
|
50
50
|
* @param {(data: StandardSchemaV1.InferOutput<Schema>, issue: InvalidField<StandardSchemaV1.InferInput<Schema>>) => MaybePromise<Output>} fn
|
|
51
51
|
* @returns {RemoteForm<StandardSchemaV1.InferInput<Schema>, Output>}
|
|
52
52
|
* @since 2.27
|
|
@@ -718,8 +718,6 @@ async function initialize(result, target, hydrate) {
|
|
|
718
718
|
// which causes component script blocks to run asynchronously
|
|
719
719
|
void (await Promise.resolve());
|
|
720
720
|
|
|
721
|
-
restore_snapshot(current_navigation_index);
|
|
722
|
-
|
|
723
721
|
if (hydrate) {
|
|
724
722
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
725
723
|
const navigation = {
|
|
@@ -736,6 +734,8 @@ async function initialize(result, target, hydrate) {
|
|
|
736
734
|
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
737
735
|
}
|
|
738
736
|
|
|
737
|
+
restore_snapshot(current_navigation_index);
|
|
738
|
+
|
|
739
739
|
started = true;
|
|
740
740
|
}
|
|
741
741
|
|
|
@@ -1488,7 +1488,17 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1488
1488
|
return _goto(new URL(error.location, location.href), {}, 0);
|
|
1489
1489
|
}
|
|
1490
1490
|
|
|
1491
|
-
|
|
1491
|
+
const error_template = await app.get_error_template();
|
|
1492
|
+
const handled = await handle_error(error, { url, params, route });
|
|
1493
|
+
const message = String(handled?.message ?? '')
|
|
1494
|
+
.replace(/&/g, '&')
|
|
1495
|
+
.replace(/</g, '<')
|
|
1496
|
+
.replace(/>/g, '>');
|
|
1497
|
+
const html = error_template({ status, message });
|
|
1498
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
1499
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.head), document.head);
|
|
1500
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.body), document.body);
|
|
1501
|
+
|
|
1492
1502
|
throw error;
|
|
1493
1503
|
}
|
|
1494
1504
|
}
|
|
@@ -1907,6 +1917,16 @@ async function navigate({
|
|
|
1907
1917
|
navigation_result.props.page.url = url;
|
|
1908
1918
|
}
|
|
1909
1919
|
|
|
1920
|
+
// Remove focus before updating the component tree, so that blur/focusout
|
|
1921
|
+
// handlers fire while the old component's data is still valid (#14575)
|
|
1922
|
+
if (
|
|
1923
|
+
!keepfocus &&
|
|
1924
|
+
document.activeElement instanceof HTMLElement &&
|
|
1925
|
+
document.activeElement !== document.body
|
|
1926
|
+
) {
|
|
1927
|
+
document.activeElement.blur();
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1910
1930
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1911
1931
|
|
|
1912
1932
|
if (fork) {
|
|
@@ -1985,10 +2005,6 @@ async function navigate({
|
|
|
1985
2005
|
|
|
1986
2006
|
is_navigating = false;
|
|
1987
2007
|
|
|
1988
|
-
if (type === 'popstate') {
|
|
1989
|
-
restore_snapshot(current_navigation_index);
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
2008
|
nav.fulfil(undefined);
|
|
1993
2009
|
|
|
1994
2010
|
// Update to.scroll to the actual scroll position after navigation completed
|
|
@@ -2000,6 +2016,10 @@ async function navigate({
|
|
|
2000
2016
|
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
2001
2017
|
);
|
|
2002
2018
|
|
|
2019
|
+
if (type === 'popstate') {
|
|
2020
|
+
restore_snapshot(current_navigation_index);
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2003
2023
|
stores.navigating.set((navigating.current = null));
|
|
2004
2024
|
|
|
2005
2025
|
updating = false;
|
|
@@ -2419,7 +2439,9 @@ export async function preloadCode(pathname) {
|
|
|
2419
2439
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2420
2440
|
}
|
|
2421
2441
|
|
|
2422
|
-
|
|
2442
|
+
// `current.url` is null until the first navigation/hydration completes, so fall back
|
|
2443
|
+
// to `location` to support calling `preloadCode` during initial page load (#13297)
|
|
2444
|
+
const url = new URL(pathname, current.url ?? location.href);
|
|
2423
2445
|
|
|
2424
2446
|
if (DEV) {
|
|
2425
2447
|
if (!pathname.startsWith('/')) {
|
|
@@ -94,8 +94,6 @@ export function initial_fetch(resource, opts) {
|
|
|
94
94
|
script.remove(); // In case multiple script tags match the same selector
|
|
95
95
|
let { body, ...init } = JSON.parse(script.textContent);
|
|
96
96
|
|
|
97
|
-
const ttl = script.getAttribute('data-ttl');
|
|
98
|
-
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
|
|
99
97
|
const b64 = script.getAttribute('data-b64');
|
|
100
98
|
if (b64 !== null) {
|
|
101
99
|
// Can't use native_fetch('data:...;base64,${body}')
|
|
@@ -103,6 +101,9 @@ export function initial_fetch(resource, opts) {
|
|
|
103
101
|
body = base64_decode(body);
|
|
104
102
|
}
|
|
105
103
|
|
|
104
|
+
const ttl = script.getAttribute('data-ttl');
|
|
105
|
+
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
|
|
106
|
+
|
|
106
107
|
return Promise.resolve(new Response(body, init));
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -53,6 +53,9 @@ export function form(id) {
|
|
|
53
53
|
/** @type {Map<any, { count: number, instance: RemoteForm<T, U> }>} */
|
|
54
54
|
const instances = new Map();
|
|
55
55
|
|
|
56
|
+
/** @type {StandardSchemaV1 | null} */
|
|
57
|
+
let shared_preflight_schema = null;
|
|
58
|
+
|
|
56
59
|
/** @param {string | number | boolean} [key] */
|
|
57
60
|
function create_instance(key) {
|
|
58
61
|
const action_id_without_key = id;
|
|
@@ -90,6 +93,7 @@ export function form(id) {
|
|
|
90
93
|
*/
|
|
91
94
|
let enhance_callback = async (instance) => {
|
|
92
95
|
if (await instance.submit()) {
|
|
96
|
+
await tick();
|
|
93
97
|
instance.element.reset();
|
|
94
98
|
}
|
|
95
99
|
};
|
|
@@ -319,7 +323,8 @@ export function form(id) {
|
|
|
319
323
|
*/
|
|
320
324
|
async function preflight(form_data) {
|
|
321
325
|
const data = convert(form_data);
|
|
322
|
-
const
|
|
326
|
+
const schema = preflight_schema ?? shared_preflight_schema;
|
|
327
|
+
const validated = await schema?.['~standard'].validate(data);
|
|
323
328
|
|
|
324
329
|
if (validated?.issues) {
|
|
325
330
|
raw_issues = merge_with_server_issues(
|
|
@@ -520,7 +525,6 @@ export function form(id) {
|
|
|
520
525
|
form.removeEventListener('input', handle_input);
|
|
521
526
|
form.removeEventListener('reset', handle_reset);
|
|
522
527
|
element = null;
|
|
523
|
-
preflight_schema = undefined;
|
|
524
528
|
};
|
|
525
529
|
};
|
|
526
530
|
|
|
@@ -565,9 +569,10 @@ export function form(id) {
|
|
|
565
569
|
|
|
566
570
|
const submission = submit(form_data, true);
|
|
567
571
|
|
|
568
|
-
|
|
572
|
+
const decrement = () => {
|
|
569
573
|
pending_count--;
|
|
570
|
-
}
|
|
574
|
+
};
|
|
575
|
+
void submission.then(decrement, decrement);
|
|
571
576
|
|
|
572
577
|
return submission;
|
|
573
578
|
}
|
|
@@ -611,6 +616,11 @@ export function form(id) {
|
|
|
611
616
|
/** @type {RemoteForm<T, U>['preflight']} */
|
|
612
617
|
value: (schema) => {
|
|
613
618
|
preflight_schema = schema;
|
|
619
|
+
|
|
620
|
+
if (key === undefined) {
|
|
621
|
+
shared_preflight_schema = schema;
|
|
622
|
+
}
|
|
623
|
+
|
|
614
624
|
return instance;
|
|
615
625
|
}
|
|
616
626
|
},
|
|
@@ -634,8 +644,8 @@ export function form(id) {
|
|
|
634
644
|
let array = [];
|
|
635
645
|
|
|
636
646
|
const data = convert(form_data);
|
|
637
|
-
|
|
638
|
-
const validated = await
|
|
647
|
+
const schema = preflight_schema ?? shared_preflight_schema;
|
|
648
|
+
const validated = await schema?.['~standard'].validate(data);
|
|
639
649
|
|
|
640
650
|
if (validate_id !== id) {
|
|
641
651
|
return;
|
|
@@ -5,6 +5,7 @@ import * as devalue from 'devalue';
|
|
|
5
5
|
import { app, goto, prerender_responses } from '../client.js';
|
|
6
6
|
import { get_remote_request_headers, remote_request, unwrap_node } from './shared.svelte.js';
|
|
7
7
|
import { create_remote_key, stringify_remote_arg } from '../../shared.js';
|
|
8
|
+
import { noop } from '../../../utils/functions.js';
|
|
8
9
|
|
|
9
10
|
// Initialize Cache API for prerender functions
|
|
10
11
|
const CACHE_NAME = __SVELTEKIT_DEV__ ? `sveltekit:${Date.now()}` : `sveltekit:${version}`;
|
|
@@ -166,6 +167,10 @@ class Prerender {
|
|
|
166
167
|
throw error;
|
|
167
168
|
}
|
|
168
169
|
);
|
|
170
|
+
|
|
171
|
+
// rejections are surfaced via `.error` for reactive consumers — make sure the
|
|
172
|
+
// stored promise (consumed without `await`) never becomes an unhandled rejection
|
|
173
|
+
this.#promise.catch(noop);
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
/**
|
|
@@ -88,7 +88,9 @@ export class Query {
|
|
|
88
88
|
// every time you see this comment, try removing the `tick.then` here and see
|
|
89
89
|
// if all the tests still pass with the latest svelte version
|
|
90
90
|
// if they do, congrats, you can remove tick.then
|
|
91
|
-
void tick()
|
|
91
|
+
void tick()
|
|
92
|
+
.then(() => this.#get_promise())
|
|
93
|
+
.catch(noop);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
#clear_pending() {
|
|
@@ -101,6 +103,11 @@ export class Query {
|
|
|
101
103
|
|
|
102
104
|
const { promise, resolve, reject } = with_resolvers();
|
|
103
105
|
|
|
106
|
+
// the rejection is surfaced via `.error` / the `then` getter for awaiting
|
|
107
|
+
// consumers — a purely reactive consumer (`.current`) attaches no handler,
|
|
108
|
+
// so make sure the stored promise can never become an unhandled rejection
|
|
109
|
+
promise.catch(noop);
|
|
110
|
+
|
|
104
111
|
this.#latest.push(resolve);
|
|
105
112
|
|
|
106
113
|
Promise.resolve(this.#fn())
|
|
@@ -124,6 +124,7 @@ export class LiveQuery {
|
|
|
124
124
|
|
|
125
125
|
/** @type {PromiseWithResolvers<void>} */
|
|
126
126
|
const { promise: stopped, resolve: on_stop } = with_resolvers();
|
|
127
|
+
let connected = false;
|
|
127
128
|
|
|
128
129
|
while (!this.#done) {
|
|
129
130
|
const controller = new AbortController();
|
|
@@ -136,16 +137,22 @@ export class LiveQuery {
|
|
|
136
137
|
const generator = create_live_iterator(this.#id, this.#payload, controller, () => {
|
|
137
138
|
this.#connected = true;
|
|
138
139
|
this.#attempt = 0;
|
|
140
|
+
connected = true;
|
|
139
141
|
on_connect();
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
try {
|
|
143
145
|
const { done, value } = await generator.next();
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
if (done) {
|
|
148
|
+
if (!this.#ready) {
|
|
149
|
+
throw new Error('Live query completed before yielding a value');
|
|
150
|
+
}
|
|
151
|
+
// stream completed without yielding (e.g. the generator returned
|
|
152
|
+
// immediately on reconnect) — keep the last good value
|
|
153
|
+
this.#done = true;
|
|
154
|
+
this.#fan_out.done();
|
|
155
|
+
break;
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
this.set(value);
|
|
@@ -202,6 +209,12 @@ export class LiveQuery {
|
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
this.#interrupt = null;
|
|
212
|
+
// If the loop exited without ever successfully connecting, settle the
|
|
213
|
+
// reconnect handshake so callers (e.g. `invalidateAll()`) never await
|
|
214
|
+
// a forever-pending promise.
|
|
215
|
+
if (!connected) {
|
|
216
|
+
on_connect_failed(this.#error ?? new Error('Live query connection was interrupted'));
|
|
217
|
+
}
|
|
205
218
|
on_stop();
|
|
206
219
|
}
|
|
207
220
|
|
|
@@ -345,10 +358,13 @@ export class LiveQuery {
|
|
|
345
358
|
promise.catch(noop);
|
|
346
359
|
this.#done = false;
|
|
347
360
|
this.#attempt = 0;
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
361
|
+
// Keep the existing fan-out open so active `for await` consumers
|
|
362
|
+
// continue receiving values from the new connection without interruption.
|
|
363
|
+
// Only replace it if it was already closed by a prior `done()`/`fail()`
|
|
364
|
+
// (e.g. reconnecting after a finite or hard-failed stream)
|
|
365
|
+
if (this.#fan_out.closed) {
|
|
366
|
+
this.#fan_out = new SharedIterator();
|
|
367
|
+
}
|
|
352
368
|
this.#main({ on_connect, on_connect_failed }).catch(noop);
|
|
353
369
|
await promise;
|
|
354
370
|
}
|
|
@@ -383,6 +399,7 @@ export class LiveQuery {
|
|
|
383
399
|
void this.#interrupt?.();
|
|
384
400
|
|
|
385
401
|
if (this.#reject_first) {
|
|
402
|
+
this.#promise.catch(noop);
|
|
386
403
|
this.#reject_first(error);
|
|
387
404
|
this.#resolve_first = null;
|
|
388
405
|
this.#reject_first = null;
|
|
@@ -114,7 +114,13 @@ export async function remote_request(url, init) {
|
|
|
114
114
|
const response = await fetch(url, init);
|
|
115
115
|
|
|
116
116
|
if (!response.ok) {
|
|
117
|
-
|
|
117
|
+
const result = await response.json().catch(() => ({
|
|
118
|
+
type: 'error',
|
|
119
|
+
status: response.status,
|
|
120
|
+
error: response.statusText
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
throw new HttpError(result.status ?? response.status ?? 500, result.error);
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
@@ -58,6 +58,12 @@ export interface SvelteKitApp {
|
|
|
58
58
|
hash: boolean;
|
|
59
59
|
|
|
60
60
|
root: typeof SvelteComponent;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Lazily loads the contents of src/error.html, used as a last-resort
|
|
64
|
+
* error page when the root layout's load function throws during client-side rendering.
|
|
65
|
+
*/
|
|
66
|
+
get_error_template: () => Promise<(data: { status: number; message: string }) => string>;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export type NavigationIntent = {
|
|
@@ -12,7 +12,7 @@ import { public_env } from '../../shared-server.js';
|
|
|
12
12
|
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
|
13
13
|
import { SCHEME } from '../../../utils/url.js';
|
|
14
14
|
import { create_server_routing_response, generate_route_object } from './server_routing.js';
|
|
15
|
-
import { add_resolution_suffix } from '../../pathname.js';
|
|
15
|
+
import { add_data_suffix, add_resolution_suffix } from '../../pathname.js';
|
|
16
16
|
import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server';
|
|
17
17
|
import { text_encoder } from '../../utils.js';
|
|
18
18
|
import {
|
|
@@ -120,7 +120,12 @@ export async function render_response({
|
|
|
120
120
|
// if appropriate, use relative paths for greater portability
|
|
121
121
|
if (paths.relative) {
|
|
122
122
|
if (!state.prerendering?.fallback) {
|
|
123
|
-
|
|
123
|
+
// the relative path depth must reflect the URL the browser is actually at, which
|
|
124
|
+
// for a data request includes the `__data.json` suffix that was stripped during routing
|
|
125
|
+
const pathname = event.isDataRequest
|
|
126
|
+
? add_data_suffix(event.url.pathname)
|
|
127
|
+
: event.url.pathname;
|
|
128
|
+
const segments = pathname.slice(paths.base.length).split('/').slice(2);
|
|
124
129
|
|
|
125
130
|
base = segments.map(() => '..').join('/') || '.';
|
|
126
131
|
|
|
@@ -368,7 +373,13 @@ export async function render_response({
|
|
|
368
373
|
if (page_config.csr && client) {
|
|
369
374
|
const route = client.routes?.find((r) => r.id === event.route.id) ?? null;
|
|
370
375
|
|
|
371
|
-
|
|
376
|
+
// when serving a prerendered page in an app that uses runtime public env vars, we must
|
|
377
|
+
// import the env.js module so that it evaluates before any user code can evaluate.
|
|
378
|
+
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
|
|
379
|
+
// https://github.com/sveltejs/kit/pull/11601
|
|
380
|
+
const load_env_eagerly = client.uses_env_dynamic_public && !!state.prerendering;
|
|
381
|
+
|
|
382
|
+
if (load_env_eagerly) {
|
|
372
383
|
modulepreloads.add(`${paths.app_dir}/env.js`);
|
|
373
384
|
}
|
|
374
385
|
|
|
@@ -401,15 +412,6 @@ export async function render_response({
|
|
|
401
412
|
|
|
402
413
|
const blocks = [];
|
|
403
414
|
|
|
404
|
-
// when serving a prerendered page in an app that uses $env/dynamic/public, we must
|
|
405
|
-
// import the env.js module so that it evaluates before any user code can evaluate.
|
|
406
|
-
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
|
|
407
|
-
// https://github.com/sveltejs/kit/pull/11601
|
|
408
|
-
const load_env_eagerly =
|
|
409
|
-
(__SVELTEKIT_EXPERIMENTAL_EXPLICIT_ENVIRONMENT_VARIABLES__ ||
|
|
410
|
-
client.uses_env_dynamic_public) &&
|
|
411
|
-
state.prerendering;
|
|
412
|
-
|
|
413
415
|
const properties = [`base: ${base_expression}`];
|
|
414
416
|
|
|
415
417
|
if (paths.assets) {
|
|
@@ -433,7 +435,7 @@ export async function render_response({
|
|
|
433
435
|
|
|
434
436
|
if (Object.keys(options.hooks.transport).length > 0) {
|
|
435
437
|
if (client.inline) {
|
|
436
|
-
app_declaration = `const app =
|
|
438
|
+
app_declaration = `const app = ${global}.app.app;`;
|
|
437
439
|
} else if (client.app) {
|
|
438
440
|
app_declaration = `const app = await import(${s(prefixed(client.app))});`;
|
|
439
441
|
} else {
|
|
@@ -57,6 +57,9 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
57
57
|
'sveltekit.remote.call.name': internals.name
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
/** @type {HeadersInit | undefined} */
|
|
61
|
+
const headers = state.prerendering ? undefined : { 'cache-control': 'private, no-store' };
|
|
62
|
+
|
|
60
63
|
try {
|
|
61
64
|
/** @type {RemoteFunctionData} */
|
|
62
65
|
const data = {};
|
|
@@ -226,7 +229,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
226
229
|
/** @type {RemoteFunctionResponse} */ ({
|
|
227
230
|
type: 'result',
|
|
228
231
|
data: stringify(data, transport)
|
|
229
|
-
})
|
|
232
|
+
}),
|
|
233
|
+
{ headers }
|
|
230
234
|
);
|
|
231
235
|
}
|
|
232
236
|
|
|
@@ -275,7 +279,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
275
279
|
/** @type {RemoteFunctionResponse} */ ({
|
|
276
280
|
type: 'result',
|
|
277
281
|
data: stringify(data, transport)
|
|
278
|
-
})
|
|
282
|
+
}),
|
|
283
|
+
{ headers }
|
|
279
284
|
);
|
|
280
285
|
} catch (error) {
|
|
281
286
|
if (error instanceof Redirect) {
|
|
@@ -285,7 +290,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
285
290
|
/** @type {RemoteFunctionResponse} */ ({
|
|
286
291
|
type: 'result',
|
|
287
292
|
data: stringify(data, transport)
|
|
288
|
-
})
|
|
293
|
+
}),
|
|
294
|
+
{ headers }
|
|
289
295
|
);
|
|
290
296
|
}
|
|
291
297
|
|
|
@@ -619,7 +619,10 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
619
619
|
invalidated_data_nodes,
|
|
620
620
|
trailing_slash
|
|
621
621
|
);
|
|
622
|
-
} else if (
|
|
622
|
+
} else if (
|
|
623
|
+
route.endpoint &&
|
|
624
|
+
(!route.page || (!state.prerendering && is_endpoint_request(event)))
|
|
625
|
+
) {
|
|
623
626
|
response = await render_endpoint(event, event_state, await route.endpoint(), state);
|
|
624
627
|
} else if (route.page) {
|
|
625
628
|
if (!page_nodes) {
|
package/src/types/internal.d.ts
CHANGED
|
@@ -44,7 +44,6 @@ export interface ServerModule {
|
|
|
44
44
|
export interface ServerInternalModule {
|
|
45
45
|
set_assets(path: string): void;
|
|
46
46
|
set_building(): void;
|
|
47
|
-
set_env(environment: Record<string, string>): void;
|
|
48
47
|
set_manifest(manifest: SSRManifest): void;
|
|
49
48
|
set_prerendering(): void;
|
|
50
49
|
set_private_env(environment: Record<string, string>): void;
|
|
@@ -103,6 +102,9 @@ export interface BuildData {
|
|
|
103
102
|
routes?: SSRClientRoute[];
|
|
104
103
|
stylesheets: string[];
|
|
105
104
|
fonts: string[];
|
|
105
|
+
/**
|
|
106
|
+
* Whether the client uses public dynamic env vars — `$env/dynamic/public` or `$app/env/public`.
|
|
107
|
+
*/
|
|
106
108
|
uses_env_dynamic_public: boolean;
|
|
107
109
|
/** Only set in case of `bundleStrategy === 'inline'`. */
|
|
108
110
|
inline?: {
|
package/src/types/private.d.ts
CHANGED
|
@@ -61,7 +61,10 @@ export namespace Csp {
|
|
|
61
61
|
| 'unsafe-eval'
|
|
62
62
|
| 'unsafe-hashes'
|
|
63
63
|
| 'unsafe-inline'
|
|
64
|
+
| 'unsafe-allow-redirects'
|
|
65
|
+
| 'unsafe-webtransport-hashes'
|
|
64
66
|
| 'wasm-unsafe-eval'
|
|
67
|
+
| 'trusted-types-eval'
|
|
65
68
|
| 'none';
|
|
66
69
|
type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
|
|
67
70
|
type FrameSource = HostSource | SchemeSource | 'self' | 'none';
|
|
@@ -70,7 +73,16 @@ export namespace Csp {
|
|
|
70
73
|
type HostProtocolSchemes = `${string}://` | '';
|
|
71
74
|
type HttpDelineator = '/' | '?' | '#' | '\\';
|
|
72
75
|
type PortScheme = `:${number}` | '' | ':*';
|
|
73
|
-
type SchemeSource =
|
|
76
|
+
type SchemeSource =
|
|
77
|
+
| 'http:'
|
|
78
|
+
| 'https:'
|
|
79
|
+
| 'ws:'
|
|
80
|
+
| 'wss:'
|
|
81
|
+
| 'data:'
|
|
82
|
+
| 'mediastream:'
|
|
83
|
+
| 'blob:'
|
|
84
|
+
| 'filesystem:'
|
|
85
|
+
| (`${string}:` & {});
|
|
74
86
|
type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
|
|
75
87
|
type Sources = Source[];
|
|
76
88
|
}
|
|
@@ -252,3 +264,14 @@ export type DeepPartial<T> = T extends Record<PropertyKey, unknown> | unknown[]
|
|
|
252
264
|
: T | undefined;
|
|
253
265
|
|
|
254
266
|
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
267
|
+
|
|
268
|
+
export type HasNonOptionalBoolean<T> =
|
|
269
|
+
IsAny<T> extends true
|
|
270
|
+
? never
|
|
271
|
+
: [T] extends [boolean]
|
|
272
|
+
? true
|
|
273
|
+
: T extends Array<infer U>
|
|
274
|
+
? HasNonOptionalBoolean<U>
|
|
275
|
+
: T extends Record<string, any>
|
|
276
|
+
? { [K in keyof T]: HasNonOptionalBoolean<T[K]> }[keyof T]
|
|
277
|
+
: never;
|
|
@@ -52,6 +52,11 @@ export class SharedIterator {
|
|
|
52
52
|
/** @type {unknown} */
|
|
53
53
|
#terminal_error = undefined;
|
|
54
54
|
|
|
55
|
+
/** Whether `done()` or `fail()` has been broadcast. */
|
|
56
|
+
get closed() {
|
|
57
|
+
return this.#closed;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
/**
|
|
56
61
|
* @param {(instance: SharedIterator<T>) => (() => void)} [start]
|
|
57
62
|
*/
|
package/src/utils/url.js
CHANGED
|
@@ -119,6 +119,7 @@ export function make_trackable(url, callback, search_params_callback, allow_hash
|
|
|
119
119
|
/**
|
|
120
120
|
* URL properties that could change during the lifetime of the page,
|
|
121
121
|
* which excludes things like `origin`
|
|
122
|
+
* @type {(keyof URL)[]}
|
|
122
123
|
*/
|
|
123
124
|
const tracked_url_properties = ['href', 'pathname', 'search', 'toString', 'toJSON'];
|
|
124
125
|
if (allow_hash) tracked_url_properties.push('hash');
|
|
@@ -127,7 +128,6 @@ export function make_trackable(url, callback, search_params_callback, allow_hash
|
|
|
127
128
|
Object.defineProperty(tracked, property, {
|
|
128
129
|
get() {
|
|
129
130
|
callback();
|
|
130
|
-
// @ts-expect-error
|
|
131
131
|
return url[property];
|
|
132
132
|
},
|
|
133
133
|
|
package/src/version.js
CHANGED