diff --git a/.changeset/short-comics-unite.md b/.changeset/short-comics-unite.md new file mode 100644 index 000000000000..4459af1d8031 --- /dev/null +++ b/.changeset/short-comics-unite.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[fix] encode if root layout has server load diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js index ff200ae13fab..2fc49b937ce0 100644 --- a/packages/kit/src/core/sync/write_client_manifest.js +++ b/packages/kit/src/core/sync/write_client_manifest.js @@ -43,22 +43,29 @@ export function write_client_manifest(manifest_data, output) { }) .join(',\n\t'); + const layouts_with_server_load = new Set(); + const dictionary = `{ ${manifest_data.routes .map((route) => { if (route.page) { const errors = route.page.errors.slice(1).map((n) => n ?? ''); - const layouts = route.page.layouts.slice(1).map((n) => { - if (n == undefined) { - return ''; - } - return get_node_id(manifest_data.nodes, n); - }); + const layouts = route.page.layouts.slice(1).map((n) => n ?? ''); while (layouts.at(-1) === '') layouts.pop(); while (errors.at(-1) === '') errors.pop(); - const array = [get_node_id(manifest_data.nodes, route.page.leaf)]; + // Encode whether or not the route uses server data + // using the ones' complement, to save space + const array = [`${route.leaf?.server ? '~' : ''}${route.page.leaf}`]; + // Encode whether or not the layout uses server data. + // It's a different method compared to pages because layouts + // are reused across pages, so we safe space by doing it this way. + route.page.layouts.forEach((layout) => { + if (layout != undefined && manifest_data.nodes[layout].server) { + layouts_with_server_load.add(layout); + } + }); // only include non-root layout/error nodes if they exist if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`); @@ -77,21 +84,11 @@ export function write_client_manifest(manifest_data, output) { trim(` export { matchers } from './client-matchers.js'; - export const nodes = [ - ${nodes} - ]; + export const nodes = [${nodes}]; + + export const server_loads = [${[...layouts_with_server_load].join(',')}]; export const dictionary = ${dictionary}; `) ); } - -/** - * Encode whether or not the route uses the server data - * using the ones' complement, to save space - * @param {import('types').PageNode[]} nodes - * @param {number} id - */ -function get_node_id(nodes, id) { - return `${nodes[id].server ? '~' : ''}${id}`; -} diff --git a/packages/kit/src/runtime/client/ambient.d.ts b/packages/kit/src/runtime/client/ambient.d.ts index ac822de8ac03..4cf3c8f9aa40 100644 --- a/packages/kit/src/runtime/client/ambient.d.ts +++ b/packages/kit/src/runtime/client/ambient.d.ts @@ -6,13 +6,19 @@ declare module '__GENERATED__/client-manifest.js' { */ export const nodes: CSRPageNodeLoader[]; + /** + * A list of all layout node ids that have a server load function. + * Pages are not present because it's shorter to encode it on the leaf itself. + */ + export const server_loads: number[]; + /** * A map of `[routeId: string]: [leaf, layouts, errors]` tuples, which * is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`. - * If the number is negative, it means it does use a server load function and the complement is the node index. + * If the leaf number is negative, it means it does use a server load function and the complement is the node index. * The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply. */ - export const dictionary: Record; + export const dictionary: Record; export const matchers: Record; } diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 6849a433f03a..4bf1994d710a 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -7,14 +7,14 @@ import { parse } from './parse.js'; import { error } from '../../exports/index.js'; import Root from '__GENERATED__/root.svelte'; -import { nodes, dictionary, matchers } from '__GENERATED__/client-manifest.js'; +import { nodes, server_loads, dictionary, matchers } from '__GENERATED__/client-manifest.js'; import { HttpError, Redirect } from '../control.js'; import { stores } from './singletons.js'; const SCROLL_KEY = 'sveltekit:scroll'; const INDEX_KEY = 'sveltekit:index'; -const routes = parse(nodes, dictionary, matchers); +const routes = parse(nodes, server_loads, dictionary, matchers); const default_layout_loader = nodes[0]; const default_error_loader = nodes[1]; diff --git a/packages/kit/src/runtime/client/parse.js b/packages/kit/src/runtime/client/parse.js index 16e0c65ff84d..167cc36e06f7 100644 --- a/packages/kit/src/runtime/client/parse.js +++ b/packages/kit/src/runtime/client/parse.js @@ -2,11 +2,14 @@ import { exec, parse_route_id } from '../../utils/routing.js'; /** * @param {import('types').CSRPageNodeLoader[]} nodes + * @param {number[]} server_loads * @param {typeof import('__GENERATED__/client-manifest.js').dictionary} dictionary * @param {Record boolean>} matchers * @returns {import('types').CSRRoute[]} */ -export function parse(nodes, dictionary, matchers) { +export function parse(nodes, server_loads, dictionary, matchers) { + const layouts_with_server_load = new Set(server_loads); + return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => { const { pattern, names, types } = parse_route_id(id); @@ -18,8 +21,8 @@ export function parse(nodes, dictionary, matchers) { if (match) return exec(match, names, types, matchers); }, errors: [1, ...(errors || [])].map((n) => nodes[n]), - layouts: [0, ...(layouts || [])].map(create_loader), - leaf: create_loader(leaf) + layouts: [0, ...(layouts || [])].map(create_layout_loader), + leaf: create_leaf_loader(leaf) }; // bit of a hack, but ensures that layout/error node lists are the same @@ -37,11 +40,21 @@ export function parse(nodes, dictionary, matchers) { * @param {number} id * @returns {[boolean, import('types').CSRPageNodeLoader]} */ - function create_loader(id) { + function create_leaf_loader(id) { // whether or not the route uses the server data is // encoded using the ones' complement, to save space const uses_server_data = id < 0; if (uses_server_data) id = ~id; return [uses_server_data, nodes[id]]; } + + /** + * @param {number | undefined} id + * @returns {[boolean, import('types').CSRPageNodeLoader] | undefined} + */ + function create_layout_loader(id) { + // whether or not the layout uses the server data is + // encoded in the layouts array, to save space + return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]]; + } }