diff --git a/packages/kit/package.json b/packages/kit/package.json index 2b2bf749dbbc..989cb5792750 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -47,7 +47,7 @@ "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", - "svelte": "^5.48.0", + "svelte": "^5.56.4", "typescript": "^6.0.0", "vite": "^8.0.12" }, diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index a53b6832c370..0adf76e3c5ac 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -2,7 +2,6 @@ import path from 'node:path'; import process from 'node:process'; import create_manifest_data from './create_manifest_data/index.js'; import { write_client_manifest } from './write_client_manifest.js'; -import { write_root } from './write_root.js'; import { write_tsconfig } from './write_tsconfig.js'; import { write_types, write_all_types } from './write_types/index.js'; import { write_ambient } from './write_ambient.js'; @@ -37,7 +36,6 @@ export function create(config, root) { write_client_manifest(config.kit, manifest_data, `${output}/client`); write_server(config, output, root); - write_root(manifest_data, output); write_all_types(config, manifest_data, root); write_non_ambient(config.kit, manifest_data); diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js index 696f72c72306..42b3e940282d 100644 --- a/packages/kit/src/core/sync/write_client_manifest.js +++ b/packages/kit/src/core/sync/write_client_manifest.js @@ -181,8 +181,6 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { export const decode = (type, value) => decoders[type](value); - export { default as root } from '../root.js'; - export const get_error_template = () => import('../shared/error-template.js').then(m => m.default); ` ); diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js deleted file mode 100644 index 082b4c8b740d..000000000000 --- a/packages/kit/src/core/sync/write_root.js +++ /dev/null @@ -1,90 +0,0 @@ -import { dedent, write_if_changed } from './utils.js'; - -/** - * @param {import('types').ManifestData} manifest_data - * @param {string} output - */ -export function write_root(manifest_data, output) { - // TODO remove default layout altogether - - const max_depth = Math.max( - ...manifest_data.routes.map((route) => - route.page ? route.page.layouts.filter(Boolean).length + 1 : 0 - ), - 1 - ); - - const levels = []; - for (let i = 0; i <= max_depth; i += 1) { - levels.push(i); - } - - write_if_changed( - `${output}/root.svelte`, - dedent` - - - - - {#snippet pyramid(depth)} - {@const Pyramid = constructors[depth]} - {#snippet failed(error)} - {@const ErrorPage = errors[depth]} - - {/snippet} - (resetters[depth] = reset) : undefined}> - {#if constructors[depth + 1]} - {@const d = data[depth]} - - - {@render pyramid(depth + 1)} - - {:else} - {@const d = data[depth]} - - - {/if} - - {/snippet} - - {@render pyramid(0)} - - {#if mounted} -
- {#if navigated} - {title} - {/if} -
- {/if} - ` - ); - - write_if_changed( - `${output}/root.js`, - dedent` - import { asClassComponent } from 'svelte/legacy'; - import Root from './root.svelte'; - export default asClassComponent(Root); - ` - ); -} diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 5f0ccf0519c2..2609925a443d 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -24,7 +24,6 @@ const server_template = ({ has_service_worker, template }) => ` -import root from '../root.js'; import { set_building, set_prerendering } from '$app/env/internal'; import { set_assets } from '$app/paths/internal/server'; import { set_manifest, set_read_implementation } from '__sveltekit/server'; @@ -40,7 +39,6 @@ export const options = { hooks: null, // added lazily, via \`get_hooks\` link_header_preload: ${s(config.kit.output.linkHeaderPreload)}, paths_origin: ${s(config.kit.paths.origin)}, - root, service_worker: ${has_service_worker}, service_worker_options: ${config.kit.serviceWorker.register ? s(config.kit.serviceWorker.options) : 'null'}, templates: { diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index dcc3c53aee61..cd55a9c83207 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1,5 +1,6 @@ /** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */ /** @import { NavigationIntent } from './types.js' */ +/** @import { RenderNode } from '../types.js' */ /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */ /** @import { Query } from './remote-functions/query/instance.svelte.js' */ /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */ @@ -33,7 +34,6 @@ import { } from './constants.js'; import { validate_page_exports } from '../../utils/exports.js'; import { noop } from '../../utils/functions.js'; -import { compact } from '../../utils/array.js'; import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, @@ -46,6 +46,10 @@ import { page, update, navigating, updated } from './state.svelte.js'; import { add_data_suffix, add_resolution_suffix } from '../pathname.js'; import { noop_span } from '../telemetry/noop.js'; import { read_ndjson } from './ndjson.js'; +import RootModern from '../components/root.svelte'; +import { asClassComponent } from 'svelte/legacy'; + +const Root = asClassComponent(RootModern); export { load_css }; const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']); @@ -87,6 +91,11 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {}; */ const snapshots = storage.get(SNAPSHOT_KEY) ?? {}; +/** + * @deprecated this is a temporary measure to avoid a regression, replace with nested `RenderNode` classes + */ +let current_tree = /** @type {RenderNode} */ ({}); + if (DEV && BROWSER) { let warned = false; @@ -479,6 +488,7 @@ async function _invalidate(reset_page_state = true) { navigation_result.props.page.state = prev_state; } update(navigation_result.props.page); + current_tree = navigation_result.props.tree; current = { ...navigation_result.state, nav: current.nav }; reset_invalidation(); root.$set(navigation_result.props); @@ -628,17 +638,14 @@ async function _preload_data(intent) { load_cache = { id: intent.id, token: preload, - promise: load_route({ ...intent, preload }).then((result) => { + promise: load_route({ ...intent, preload }).finally(() => { preload_tokens.delete(preload); - if (result.type === 'loaded' && result.state.error) { - // Don't cache errors, because they might be transient - discard_load_cache(); - } - return result; }), fork: null }; + load_cache.promise.catch(discard_load_cache); + if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) { const lc = load_cache; @@ -650,6 +657,7 @@ async function _preload_data(intent) { return svelte.fork(() => { root.$set(result.props); update(result.props.page); + current_tree = result.props.tree; }); } catch { // if it errors, it's because the experimental flag isn't enabled in Svelte @@ -708,9 +716,10 @@ async function initialize(result, target, hydrate) { } update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page)); + current_tree = result.props.tree; // TODO: use mount() - root = new app.root({ + root = new Root({ target, props: { ...result.props, components, resetters }, hydrate, @@ -801,33 +810,11 @@ async function get_navigation_result_from_branch({ route }, props: { - constructors: compact(branch).map((branch_node) => branch_node.node.component), - page + page, + tree: /** @type {RenderNode} */ ({}) } }; - if (errors) { - let last_idx = -1; - result.props.errors = await Promise.all( - // eslint-disable-next-line @typescript-eslint/await-thenable - branch - .map((b, i) => { - if (i === 0) return undefined; // root layout wraps root error component, not the other way around - if (!b) return null; - - i--; - // Find the closest error component up to the previous branch - while (i > last_idx + 1 && !errors[i]) i -= 1; - last_idx = i; - return errors[i]?.() - .then((e) => e.component) - .catch(() => undefined); - }) - // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component - .filter((e) => e !== null) - ); - } - if (error) { result.props.error = error; } @@ -839,23 +826,44 @@ async function get_navigation_result_from_branch({ let data = {}; let data_changed = !page; - let p = 0; + let current_node = result.props.tree; - for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) { + /** @type {RenderNode | undefined} */ + let previous_node = current_tree; + + for (let i = 0; i < branch.length; i += 1) { const node = branch[i]; const prev = current.branch[i]; - if (node?.data !== prev?.data) data_changed = true; if (!node) continue; - data = { ...data, ...node.data }; + const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader; + + current_node.error = (await error_loader()).component; + current_node.component = node.node.component; - // Only set props if the node actually updated. This prevents needless rerenders. - if (data_changed) { - result.props[`data_${p}`] = data; + if ( + // if an ancestor node in this path changed, `data_changed` is already true and the + // accumulated `data` differs from the previous render, so we must re-merge + data_changed || + !previous_node || + node?.data !== prev?.data + ) { + current_node.data = { ...data, ...node.data }; + data_changed = true; + } else { + // use existing object — prevents effects re-running unnecessarily + current_node.data = previous_node.data; } - p += 1; + data = current_node.data; + + if (i < branch.length - 1) { + current_node.child = /** @type {import('../types.js').RenderNode} */ ({}); + current_node = current_node.child; + + previous_node = previous_node?.child; + } } const page_changed = @@ -876,8 +884,7 @@ async function get_navigation_result_from_branch({ status: status ?? error?.status ?? 200, url: new URL(url), form: form ?? null, - // The whole page store is updated, but this way the object reference stays the same - data: data_changed ? data : page.data + data }; } @@ -1155,32 +1162,6 @@ function diff_search_params(old_url, new_url) { return changed; } -/** - * @param {Omit & { error: App.Error }} opts - * @returns {import('./types.js').NavigationFinished} - */ -function preload_error({ error, url, route, params }) { - return { - type: 'loaded', - state: { - error, - url, - route, - params, - branch: [] - }, - props: { - page: { - // we skipped loading the error page, so we have to use the current page - // store, but update the status received while preloading - ...page, - status: error.status - }, - constructors: [] - } - }; -} - /** * @overload * @param {import('./types.js').NavigationIntent} intent @@ -1251,7 +1232,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) { const handled_error = await handle_error(error, { url, params, route: { id } }); if (preload && preload_tokens.has(preload)) { - return preload_error({ error: handled_error, url, params, route }); + throw handled_error; } return load_root_error_page({ @@ -1341,13 +1322,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) { } if (preload && preload_tokens.has(preload)) { - const error = await handle_error(err, { params, url, route: { id: route.id } }); - return preload_error({ - error, - url, - params, - route - }); + throw await handle_error(err, { params, url, route: { id: route.id } }); } /** @type {App.Error} */ @@ -1972,6 +1947,7 @@ async function navigate({ } else { rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5 root.$set(navigation_result.props); + current_tree = navigation_result.props.tree; // Reset any boundaries that failed on a previous navigation now that the // new props are applied, otherwise the stale `+error.svelte` stays // mounted above the new route's content. See sveltejs/kit#15694. @@ -2175,15 +2151,13 @@ function setup_preload() { if (!intent) return; if (DEV) { - void _preload_data(intent).then((result) => { - if (result.type === 'loaded' && result.state.error) { - console.warn( - `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` + - 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' + - 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' + - 'See https://svelte.dev/docs/kit/link-options for more info' - ); - } + void _preload_data(intent).catch((error) => { + console.warn( + `Preloading data for ${intent.url.pathname} failed with the following error: ${error.message}\n` + + 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' + + 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' + + 'See https://svelte.dev/docs/kit/link-options for more info' + ); }); } else { void _preload_data(intent); @@ -2471,7 +2445,22 @@ export async function preloadData(href) { throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`); } - const result = await _preload_data(intent); + /** @type {Awaited>} */ + let result; + + try { + result = await _preload_data(intent); + } catch (error) { + // `load_route` throws the handled error (an `App.Error` with a `status`) + // when a preload fails, so surface it in the documented `{ type: 'error' }` shape + const handled = /** @type {App.Error & { status?: number }} */ (error); + return { + type: 'error', + status: handled?.status ?? 500, + error: handled + }; + } + if (result.type === 'redirect') { return { type: result.type, @@ -2482,14 +2471,6 @@ export async function preloadData(href) { const { status, data } = result.props.page ?? page; - if (result.type === 'loaded' && result.state.error) { - return { - type: 'error', - status, - error: result.state.error - }; - } - return { type: result.type, status, data }; } @@ -2680,6 +2661,7 @@ export async function set_nearest_error_page(error) { current = { ...navigation_result.state, nav: current.nav }; root.$set(navigation_result.props); + current_tree = navigation_result.props.tree; update(navigation_result.props.page); void svelte.tick().then(() => reset_focus(current.url)); diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index 9075d7b7054a..33595faab083 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -10,6 +10,7 @@ import { Uses } from 'types'; import { Page, ParamMatcher } from '@sveltejs/kit'; +import { RenderNode } from '../types.js'; export interface SvelteKitApp { /** @@ -57,8 +58,6 @@ export interface SvelteKitApp { */ hash: boolean; - root: typeof SvelteComponent; - /** * Lazily loads the contents of src/error.html, used as a last-resort * error page when the root layout's load function throws during client-side rendering. @@ -91,13 +90,10 @@ export type NavigationFinished = { type: 'loaded'; state: NavigationState; props: { - constructors: Array; - errors?: Array; - components?: SvelteComponent[]; page: Page; form?: Record | null; error?: App.Error; - [key: `data_${number}`]: Record; + tree: RenderNode; }; }; diff --git a/packages/kit/src/runtime/components/root.svelte b/packages/kit/src/runtime/components/root.svelte new file mode 100644 index 000000000000..4b230208ba66 --- /dev/null +++ b/packages/kit/src/runtime/components/root.svelte @@ -0,0 +1,66 @@ + + +{#snippet node(n: RenderNode, depth: number)} + {const Component = $derived(n.component)} + {const Error = $derived(n.error)} + {const data = $derived(n.data)} + + (resetters[depth] = reset)}> + {#if n.child} + + + {@render node(n.child, depth + 1)} + + {:else} + + + {/if} + + {#snippet failed(error)} + + {/snippet} + +{/snippet} + +{@render node(tree, 0)} + +{#if mounted} +
+ {#if navigated} + {title} + {/if} +
+{/if} diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 3e6d5a8c408a..a091d9c4a169 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -1,5 +1,6 @@ +/** @import { Component } from 'svelte' */ /** @import { ActionResult, RequestEvent, SSRManifest } from '@sveltejs/kit' */ -/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRComponent, SSRNode, SSROptions, SSRState } from 'types' */ +/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode, SSROptions, SSRState } from 'types' */ import { text } from '@sveltejs/kit'; import { HttpError, Redirect } from '@sveltejs/kit/internal'; import { compact } from '../../../utils/array.js'; @@ -398,7 +399,7 @@ export async function render_page( * @param {SSRManifest} manifest */ async function load_error_components(ssr, branch, page, manifest) { - /** @type {Array | undefined} */ + /** @type {Array | undefined} */ let error_components; if (ssr) { diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 583d4584324f..d945f01b7489 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,5 +1,5 @@ +/** @import { RenderNode } from '../../types.js' */ import * as devalue from 'devalue'; -import { readable, writable } from 'svelte/store'; import { DEV } from 'esm-env'; import { isRedirect, text } from '@sveltejs/kit'; import * as paths from '$app/paths/internal/server'; @@ -22,14 +22,11 @@ import { } from '../utils.js'; import * as env from '__sveltekit/env'; import { collect_remote_data } from '../remote.js'; +import Root from '../../components/root.svelte'; +import { render } from 'svelte/server'; // TODO rename this function/module -const updated = { - ...readable(false), - check: () => false -}; - /** * Creates the HTML response. * @param {{ @@ -46,7 +43,7 @@ const updated = { * resolve_opts: import('types').RequiredResolveOptions; * action_result?: import('@sveltejs/kit').ActionResult; * data_serializer: import('./types.js').ServerDataSerializer; - * error_components?: Array + * error_components?: Array * }} opts */ export async function render_response({ @@ -91,7 +88,8 @@ export async function render_response({ // TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold const inline_styles = new Map(); - /** @type {Awaited>} */ + // TODO `svelte/server` should expose `RenderOutput` + /** @type {{ head: string, body: string, hashes: { script: string[] } }} */ let rendered; const form_value = @@ -142,49 +140,45 @@ export async function render_response({ if (page_config.ssr) { /** @type {Record} */ const props = { - stores: { - page: writable(null), - navigating: writable(null), - updated - }, - constructors: await Promise.all( - branch.map(({ node }) => { - if (!node.component) { - // Can only be the leaf, layouts have a fallback component generated - throw new Error(`Missing +page.svelte component for route ${event.route.id}`); - } - return node.component(); - }) - ), - form: form_value - }; - - if (error_components) { - if (error) { - props.error = error; + components: [], + resetters: [], + form: form_value, + tree: /** @type {RenderNode} */ ({}), + error, + page: { + error, + params: /** @type {Record} */ (event.params), + route: event.route, + status, + url: event.url, + data: {}, + form: form_value, + state: {} } - props.errors = error_components; - } + }; - let data = {}; + let current_node = props.tree; + let data = props.page.data; - // props_n (instead of props[n]) makes it easy to avoid - // unnecessary updates for layout components for (let i = 0; i < branch.length; i += 1) { - data = { ...data, ...branch[i].data }; - props[`data_${i}`] = data; + const node = branch[i]; + + data = { ...data, ...node.data }; + + // TODO this is undefined sometimes... where does the default error component come from? + const error = error_components?.slice(0, i + 1).findLast((x) => x); + + current_node.error = error; + current_node.component = await node.node.component?.(); + current_node.data = data; + + if (i < branch.length - 1) { + current_node.child = /** @type {import('../../types.js').RenderNode} */ ({}); + current_node = current_node.child; + } } - props.page = { - error, - params: /** @type {Record} */ (event.params), - route: event.route, - status, - url: event.url, - data, - form: form_value, - state: {} - }; + props.page.data = data; const render_state = { ...event_state, is_in_render: true }; @@ -250,17 +244,15 @@ export async function render_response({ // We have to invoke .then eagerly here in order to kick off rendering: it's only starting on access, // and `await maybe_promise` would eagerly access the .then property but call its function only after a tick, which is too late // for the paths.reset() below and for any eager getRequestEvent() calls during rendering without AsyncLocalStorage available. - // TODO use render from 'svelte/server' here - const rendered = options.root.render(props, render_opts).then((r) => r); + const rendered = render(Root, { ...render_opts, props }); - // @ts-expect-error the legacy `render` API only returns html still, but the new API uses body - const { head, html: body, css, hashes } = await rendered; + const { head, body, hashes } = await rendered; if (hashes) { csp.add_script_hashes(hashes.script); } - return { head, body, css, hashes }; + return { head, body, hashes }; }); } finally { if (DEV) { @@ -268,7 +260,7 @@ export async function render_response({ } } } else { - rendered = { head: '', body: '', css: { code: '', map: null }, hashes: { script: [] } }; + rendered = { head: '', body: '', hashes: { script: [] } }; } for (const { node } of branch) { diff --git a/packages/kit/src/runtime/types.d.ts b/packages/kit/src/runtime/types.d.ts new file mode 100644 index 000000000000..71afa916565e --- /dev/null +++ b/packages/kit/src/runtime/types.d.ts @@ -0,0 +1,8 @@ +import { Component } from 'svelte'; + +export interface RenderNode { + component: Component; + error: Component; + data: Record; + child?: RenderNode; +} diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 096593aede22..b71a05673375 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -1,4 +1,4 @@ -import { SvelteComponent } from 'svelte'; +import { Component } from 'svelte'; import { Config, ServerLoad, @@ -113,7 +113,7 @@ export interface BuildData { } export interface CSRPageNode { - component: typeof SvelteComponent; + component: Component; universal: { load?: Load; trailingSlash?: TrailingSlash; @@ -417,27 +417,7 @@ export interface ServerMetadata { remotes: Map>; } -// TODO get rid of this in favor us using just import('svelte').Component -export interface SSRComponent { - default: { - render( - props: Record, - opts: { context: Map; csp?: { nonce?: string; hash?: boolean } } - ): Promise<{ - body: string; - head: string; - css: { - code: string; - map: any; // TODO - }; - hashes: { - script: Array<`sha256-${string}`>; - }; - }>; - }; -} - -export type SSRComponentLoader = () => Promise; +export type SSRComponentLoader = () => Promise; export interface UniversalNode { /** Is `null` in case static analysis succeeds but the node is ssr=false */ @@ -501,7 +481,6 @@ export interface SSROptions { hooks: ServerHooks; link_header_preload: ValidatedConfig['kit']['output']['linkHeaderPreload']; paths_origin: string | undefined; - root: SSRComponent['default']; service_worker: boolean; service_worker_options: RegistrationOptions; templates: { diff --git a/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte b/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte index 5e83135ec2a4..7c4d3062880c 100644 --- a/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte +++ b/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte @@ -15,7 +15,7 @@

page.data was updated {count} time(s)

-a -b +a +b {@render children()} diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index af5f78f382f3..af6ab67afee7 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -6,6 +6,7 @@ declare module '@sveltejs/kit' { import type { StandardSchemaV1 } from '@standard-schema/spec'; import type { Plugin } from 'vite'; import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, ResolvedPathname } from '$app/types'; + import type { Component } from 'svelte'; // @ts-ignore this is an optional peer dependency so could be missing. Written like this so dts-buddy preserves the ts-ignore type Span = import('@opentelemetry/api').Span; @@ -2836,27 +2837,7 @@ declare module '@sveltejs/kit' { } | null; } - // TODO get rid of this in favor us using just import('svelte').Component - interface SSRComponent { - default: { - render( - props: Record, - opts: { context: Map; csp?: { nonce?: string; hash?: boolean } } - ): Promise<{ - body: string; - head: string; - css: { - code: string; - map: any; // TODO - }; - hashes: { - script: Array<`sha256-${string}`>; - }; - }>; - }; - } - - type SSRComponentLoader = () => Promise; + type SSRComponentLoader = () => Promise; interface UniversalNode { /** Is `null` in case static analysis succeeds but the node is ssr=false */