Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/short-comics-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[fix] encode if root layout has server load
37 changes: 17 additions & 20 deletions packages/kit/src/core/sync/write_client_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(',')}]`);
Expand All @@ -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}`;
}
10 changes: 8 additions & 2 deletions packages/kit/src/runtime/client/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, [leaf: number, layouts?: number[], errors?: number[]]>;
export const dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;

export const matchers: Record<string, ParamMatcher>;
}
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
21 changes: 17 additions & 4 deletions packages/kit/src/runtime/client/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, (param: string) => 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);

Expand All @@ -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
Expand All @@ -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]];
}
}