Skip to content

Commit

Permalink
do this via ssrmanifest instead (previous approach does not work beca…
Browse files Browse the repository at this point in the history
…use in prod the imports are different / not resolved through vite / wrong; also it's better this way); better hydration without an unnecessary roundtrip
  • Loading branch information
dummdidumm committed Jan 24, 2025
1 parent b0ecbeb commit 72782d8
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 132 deletions.
2 changes: 1 addition & 1 deletion packages/kit/src/core/sync/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function create(config) {

const output = path.join(config.kit.outDir, 'generated');

write_client_manifest(config.kit, manifest_data, `${output}/client`, `${output}/server`);
write_client_manifest(config.kit, manifest_data, `${output}/client`);
write_server(config, output);
write_root(manifest_data, output);
write_all_types(config, manifest_data);
Expand Down
82 changes: 10 additions & 72 deletions packages/kit/src/core/sync/write_client_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import colors from 'kleur';
* list of routes and corresponding Svelte components (i.e. pages and layouts).
* @param {import('types').ValidatedKitConfig} kit
* @param {import('types').ManifestData} manifest_data
* @param {string} output_client
* @param {string} output_server
* @param {string} output
* @param {Array<{ has_server_load: boolean }>} [metadata] If this is omitted, we have to assume that all routes with a `+layout/page.server.js` file have a server load function
*/
export function write_client_manifest(kit, manifest_data, output_client, output_server, metadata) {
export function write_client_manifest(kit, manifest_data, output, metadata) {
/**
* Creates a module that exports a `CSRPageNode`
* @param {import('types').PageNode} node
Expand All @@ -23,15 +22,15 @@ export function write_client_manifest(kit, manifest_data, output_client, output_

if (node.universal) {
declarations.push(
`import * as universal from ${s(relative_path(`${output_client}/nodes`, node.universal))};`,
`import * as universal from ${s(relative_path(`${output}/nodes`, node.universal))};`,
'export { universal };'
);
}

if (node.component) {
declarations.push(
`export { default as component } from ${s(
relative_path(`${output_client}/nodes`, node.component)
relative_path(`${output}/nodes`, node.component)
)};`
);
}
Expand All @@ -45,7 +44,7 @@ export function write_client_manifest(kit, manifest_data, output_client, output_
.map((node, i) => {
indices.set(node, i);

write_if_changed(`${output_client}/nodes/${i}.js`, generate_node(node));
write_if_changed(`${output}/nodes/${i}.js`, generate_node(node));
return `() => import('./nodes/${i}')`;
})
.join(',\n');
Expand Down Expand Up @@ -125,16 +124,16 @@ export function write_client_manifest(kit, manifest_data, output_client, output_
}

write_if_changed(
`${output_client}/app.js`,
`${output}/app.js`,
dedent`
${
client_hooks_file
? `import * as client_hooks from '${relative_path(output_client, client_hooks_file)}';`
? `import * as client_hooks from '${relative_path(output, client_hooks_file)}';`
: ''
}
${
universal_hooks_file
? `import * as universal_hooks from '${relative_path(output_client, universal_hooks_file)}';`
? `import * as universal_hooks from '${relative_path(output, universal_hooks_file)}';`
: ''
}
Expand Down Expand Up @@ -175,74 +174,13 @@ export function write_client_manifest(kit, manifest_data, output_client, output_
for (const key in manifest_data.matchers) {
const src = manifest_data.matchers[key];

imports.push(`import { match as ${key} } from ${s(relative_path(output_client, src))};`);
imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
matchers.push(key);
}

const module = imports.length
? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
: 'export const matchers = {};';

write_if_changed(`${output_client}/matchers.js`, module);

// Write out the server version of the routing manifest for use of _app/routes/... requests
// We do that by creating a dictionary of route_id -> CSRRoute. The imports will be transformed
// by Vite to point to the correct client nodes, and at runtime, the server will stringify the object.
const server_nodes = manifest_data.nodes.map((_, i) => {
return `() => import('${relative_path(`${output_server}`, `${output_client}/nodes`)}/${i}')`;
});
const server_dictionary = dedent`
{
${manifest_data.routes
.map((route) => {
if (route.page && route.leaf) {
let leaf_has_server_load = false;
if (metadata) {
leaf_has_server_load = metadata[route.page.leaf].has_server_load;
} else if (route.leaf.server) {
leaf_has_server_load = true;
}
const id = s(route.id);
const leaf = `[${leaf_has_server_load}, ${server_nodes[route.page.leaf]}]`;
// Setting undefined explictly instead of the empty string in case of no loader prevents truncation of the array
// when there's trailing undefined values, which is important because errors/layouts need to be the same length.
const errors = `[${route.page.errors.map((n) => (n != null ? server_nodes[n] : 'undefined')).join(',')}]`;
const layouts = `[${route.page.layouts.map((n) => (n != null ? `[${layouts_with_server_load.has(n)}, ${server_nodes[n]}]` : 'undefined')).join(',')}]`;
const nodes = [`${route.page.leaf}: ${server_nodes[route.page.leaf]}`];
for (const node of route.page.layouts.concat(route.page.errors)) {
if (node != null) {
nodes.push(`${node}: ${server_nodes[node]}`);
}
}
// String representation of
/** @type {import('types').CSRRoute} */
// without exec because that's not needed when doing server side routing resolution
return (
s(route.id) +
': {' +
dedent`
id: ${id},
leaf: ${leaf},
errors: ${errors},
layouts: ${layouts},
nodes: {${nodes.join(',\n')}},
` +
'}'
);
}
})
.filter(Boolean)
.join(',\n')}
}
`;

write_if_changed(
`${output_server}/route_resolution_dictionary.js`,
dedent`
export const dictionary = ${server_dictionary};
`
);
write_if_changed(`${output}/matchers.js`, module);
}
7 changes: 5 additions & 2 deletions packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,11 @@ export async function dev(vite, vite_config, svelte_config) {

result.index = index;

// these are unused in dev, it's easier to include them
result.imports = [];
// these are unused in dev (except imports[0], which is the entry point for the given node and used for server side routing)
result.imports = [
// remove leading slash because it's not present in prod, so we need to prepend it in the runtime
`${to_fs(svelte_config.kit.outDir)}/generated/client/nodes/${index}.js`.slice(1)
];
result.stylesheets = [];
result.fonts = [];

Expand Down
1 change: 0 additions & 1 deletion packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,6 @@ Tips:
kit,
manifest_data,
`${kit.outDir}/generated/client-optimized`,
`${out}/server`,
metadata.nodes
);

Expand Down
79 changes: 41 additions & 38 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
normalize_path
} from '../../utils/url.js';
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
import { parse } from './parse.js';
import { parse, parse_server_route } from './parse.js';
import * as storage from './session-storage.js';
import {
find_anchor,
Expand Down Expand Up @@ -1247,13 +1247,15 @@ async function get_navigation_intent(url, invalidating) {

// TODO app.server_routing or whatever
if (true) {

Check failure on line 1249 in packages/kit/src/runtime/client/client.js

View workflow job for this annotation

GitHub Actions / lint-all

Unexpected constant condition
/** @type {{ route: import('types').CSRRouteServer, params: Record<string, string>} | {}} */
const { route, params } = await import(
// TODO .js ?
new URL(
base + '/_app/routes' + (url.pathname === '/' ? '.js' : url.pathname + '.js'),
location.href
).href
);

if (!route) return;

const id = get_page_key(url);
Expand All @@ -1263,34 +1265,34 @@ async function get_navigation_intent(url, invalidating) {
invalidating,
// route.exec is missing but it's not used anywhere except route matching,
// which happened on the server in this case, so it's fine
route,
route: parse_server_route(route, app.nodes),
params,
url
};
{
const x = intent;
const rerouted = get_rerouted_url(url);
if (!rerouted) return;

const path = get_url_path(rerouted);
for (const route of routes) {
const params = route.exec(path);

if (params) {
const id = get_page_key(url);
/** @type {import('./types.js').NavigationIntent} */
const intent = {
id,
invalidating,
route,
params: decode_params(params),
url
};
console.log('original', intent, 'vs', x);
break;
}
}
}
// {
// const x = intent;
// const rerouted = get_rerouted_url(url);
// if (!rerouted) return;

// const path = get_url_path(rerouted);
// for (const route of routes) {
// const params = route.exec(path);

// if (params) {
// const id = get_page_key(url);
// /** @type {import('./types.js').NavigationIntent} */
// const intent = {
// id,
// invalidating,
// route,
// params: decode_params(params),
// url
// };
// console.log('original', intent, 'vs', x);
// break;
// }
// }
// }
return intent;
}

Expand Down Expand Up @@ -2534,8 +2536,17 @@ async function _hydrate(

const url = new URL(location.href);

// TODO `|| server side routing`
if (!__SVELTEKIT_EMBEDDED__) {
/** @type {import('types').CSRRoute | undefined} */
let parsed_route;

// TODO optionize
if (true) {

Check failure on line 2543 in packages/kit/src/runtime/client/client.js

View workflow job for this annotation

GitHub Actions / lint-all

Unexpected constant condition
// undefined in case of 404
if (route) {
// @ts-expect-error route is the full object in case of server routing
parsed_route = parse_server_route(route, app.nodes);
}
} else if (!__SVELTEKIT_EMBEDDED__) {
// See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation
// of determining the params on the client side.
({ params = {}, route = { id: null } } = (await get_navigation_intent(url, false)) || {});
Expand All @@ -2554,9 +2565,7 @@ async function _hydrate(
}

return load_node({
// TODO branch on routing style
// TODO avoid route request on initial load when server routing; put app.nodes into render.js instead
loader: /** @type {import('types').CSRRoute} */ (route).nodes?.[n] ?? app.nodes[n],
loader: app.nodes[n],
url,
params,
route,
Expand All @@ -2574,14 +2583,8 @@ async function _hydrate(
/** @type {Array<import('./types.js').BranchNode | undefined>} */
const branch = await Promise.all(branch_promises);

/** @type {import('types').CSRRoute | undefined} */
let parsed_route;

// TODO optionize
if (true) {
// @ts-expect-error get_navigation_intent called above returns a full route
parsed_route = route.id !== null ? route : undefined;
} else {
if (false) {

Check failure on line 2587 in packages/kit/src/runtime/client/client.js

View workflow job for this annotation

GitHub Actions / lint-all

Unexpected constant condition
parsed_route = routes.find(({ id }) => id === route.id);

// server-side will have compacted the branch, reinstate empty slots
Expand Down
19 changes: 19 additions & 0 deletions packages/kit/src/runtime/client/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,22 @@ export function parse({ nodes, server_loads, dictionary, matchers }) {
return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]];
}
}

/**
* @param {import('types').CSRRouteServer} input
* @param {import('types').CSRPageNodeLoader[]} app_nodes Will be modified if a new node is loaded that's not already in the array
* @returns {import('types').CSRRoute}
*/
export function parse_server_route({ nodes, id, leaf, layouts, errors }, app_nodes) {
return {
id,
exec: () => {
throw new Error('shouldnt be called');
},
// By writing to app_nodes only when a loader at that index is not already defined,
// we ensure that loaders have referential equality when they load the same node.
errors: errors.map((n) => (n ? (app_nodes[n] ||= nodes[n]) : undefined)),
layouts: layouts.map((n) => (n ? [n[0], (app_nodes[n[1]] ||= nodes[n[1]])] : undefined)),
leaf: [leaf[0], (app_nodes[leaf[1]] ||= nodes[leaf[1]])]
};
}
4 changes: 4 additions & 0 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export async function render_page(event, page, options, manifest, state, resolve
return await render_response({
branch: [],
fetched,
page,
page_config: {
ssr: false,
csr: get_option(nodes, 'csr') ?? true
Expand Down Expand Up @@ -256,6 +257,7 @@ export async function render_page(event, page, options, manifest, state, resolve
manifest,
state,
resolve_opts,
page,
page_config: { ssr: true, csr: true },
status,
error,
Expand Down Expand Up @@ -308,6 +310,7 @@ export async function render_page(event, page, options, manifest, state, resolve
manifest,
state,
resolve_opts,
page,
page_config: {
csr: get_option(nodes, 'csr') ?? true,
ssr
Expand All @@ -325,6 +328,7 @@ export async function render_page(event, page, options, manifest, state, resolve
event,
options,
manifest,
page,
state,
status: 500,
error: e,
Expand Down
23 changes: 23 additions & 0 deletions packages/kit/src/runtime/server/page/load_page_nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,26 @@ export function load_page_nodes(page, manifest) {
manifest._.nodes[page.leaf]()
]);
}

/**
* @param {import('types').PageNodeIndexes} page
* @param {import('@sveltejs/kit').SSRManifest} manifest
*/
export async function load_page_and_error_nodes(page, manifest) {
// we use == here rather than === because [undefined] serializes as "[null]"
const pending_layouts = page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]()));
const pending_errors = page.errors.map((n) => (n == undefined ? n : manifest._.nodes[n]()));
const pending_leaf = manifest._.nodes[page.leaf]();

const [layouts, errors, leaf] = await Promise.all([
Promise.all(pending_layouts),
Promise.all(pending_errors),
pending_leaf
]);

return {
layouts,
errors,
leaf
};
}
Loading

0 comments on commit 72782d8

Please sign in to comment.