Skip to content

[feat] pass ComponentLoader to handle #2663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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/three-steaks-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[feat] experimental: pass ComponentLoader to handle
17 changes: 10 additions & 7 deletions packages/kit/src/core/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ async function build_server(
.replace('%svelte.head%', '" + head + "')
.replace('%svelte.body%', '" + body + "')};

const loader = {
fixStackTrace: ({ error }) => {},
loadComponent
};

let options = null;

const default_settings = { paths: ${s(config.kit.paths)} };
Expand All @@ -324,15 +329,13 @@ async function build_server(
fetched: undefined,
floc: ${config.kit.floc},
get_component_path: id => assets + ${s(prefix)} + entry_lookup[id],
get_stack: error => String(error), // for security
handle_error: (error, request) => {
loader.fixStackTrace({ error });
hooks.handleError({ error, request });
error.stack = options.get_stack(error);
},
hooks,
hydrate: ${s(config.kit.hydrate)},
initiator: undefined,
load_component,
manifest,
paths: settings.paths,
prerender: ${config.kit.prerender.enabled},
Expand Down Expand Up @@ -412,10 +415,10 @@ async function build_server(

const metadata_lookup = ${s(metadata_lookup)};

async function load_component(file) {
const { entry, css, js, styles } = metadata_lookup[file];
async function loadComponent({ id }) {
const { entry, css, js, styles } = metadata_lookup[id];
return {
module: await module_lookup[file](),
module: await module_lookup[id](),
entry: assets + ${s(prefix)} + entry,
css: css.map(dep => assets + ${s(prefix)} + dep),
js: js.map(dep => assets + ${s(prefix)} + dep),
Expand All @@ -427,7 +430,7 @@ async function build_server(
prerender
} = {}) {
const host = ${config.kit.host ? s(config.kit.host) : `request.headers[${s(config.kit.hostHeader || 'host')}]`};
return respond({ ...request, host }, options, { prerender });
return respond({ ...request, host }, loader, options, { prerender });
}
`
.replace(/^\t{3}/gm, '')
Expand Down
95 changes: 49 additions & 46 deletions packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,53 @@ async function create_plugin(config, dir, cwd, get_manifest) {
? await vite.ssrLoadModule(`/${config.kit.files.hooks}`)
: {};

/** @type {import('types/internal').ComponentLoader} */
const loader = {
fixStackTrace: ({ error }) => {
vite.ssrFixStacktrace(error);
},
loadComponent: async ({ id }) => {
const url = `/${id}`;

const module = /** @type {SSRComponent} */ (await vite.ssrLoadModule(url));
const node = await vite.moduleGraph.getModuleByUrl(url);

if (!node) throw new Error(`Could not find node for ${url}`);

const deps = new Set();
find_deps(node, deps);

const styles = new Set();

for (const dep of deps) {
const query = new URL(dep.url, 'http://localhost/').searchParams;

// TODO what about .scss files, etc?
if (
dep.file.endsWith('.css') ||
(query.has('svelte') && query.get('type') === 'style')
) {
try {
const mod = await vite.ssrLoadModule(dep.url);
styles.add(mod.default);
} catch {
// this can happen with dynamically imported modules, I think
// because the Vite module graph doesn't distinguish between
// static and dynamic imports? TODO investigate, submit fix
}
}
}

return {
module,
entry: url.endsWith('.svelte') ? url : url + '?import',
css: [],
js: [],
styles: Array.from(styles)
};
}
};

/** @type {import('types/internal').Hooks} */
const hooks = {
getSession: user_hooks.getSession || (() => ({})),
Expand Down Expand Up @@ -387,6 +434,7 @@ async function create_plugin(config, dir, cwd, get_manifest) {
query: parsed.searchParams,
rawBody: body
},
loader,
{
amp: config.kit.amp,
dev: true,
Expand All @@ -396,12 +444,8 @@ async function create_plugin(config, dir, cwd, get_manifest) {
js: []
},
floc: config.kit.floc,
get_stack: (error) => {
vite.ssrFixStacktrace(error);
return error.stack;
},
handle_error: (error, request) => {
vite.ssrFixStacktrace(error);
loader.fixStackTrace({ error });
hooks.handleError({ error, request });
},
hooks,
Expand All @@ -410,47 +454,6 @@ async function create_plugin(config, dir, cwd, get_manifest) {
base: config.kit.paths.base,
assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base
},
load_component: async (id) => {
const url = `/${id}`;

const module = /** @type {SSRComponent} */ (await vite.ssrLoadModule(url));
const node = await vite.moduleGraph.getModuleByUrl(url);

if (!node) throw new Error(`Could not find node for ${url}`);

const deps = new Set();
find_deps(node, deps);

const styles = new Set();

for (const dep of deps) {
const parsed = new URL(dep.url, 'http://localhost/');
const query = parsed.searchParams;

// TODO what about .scss files, etc?
if (
dep.file.endsWith('.css') ||
(query.has('svelte') && query.get('type') === 'style')
) {
try {
const mod = await vite.ssrLoadModule(dep.url);
styles.add(mod.default);
} catch {
// this can happen with dynamically imported modules, I think
// because the Vite module graph doesn't distinguish between
// static and dynamic imports? TODO investigate, submit fix
}
}
}

return {
module,
entry: url.endsWith('.svelte') ? url : url + '?import',
css: [],
js: [],
styles: Array.from(styles)
};
},
manifest: get_manifest(),
prerender: config.kit.prerender.enabled,
read: (file) => fs.readFileSync(path.join(config.kit.files.assets, file)),
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function sequence(...handlers) {
const length = handlers.length;
if (!length) return ({ request, resolve }) => resolve(request);

return ({ request, resolve }) => {
return ({ request, loader, resolve }) => {
return apply_handle(0, request);

/**
Expand All @@ -19,6 +19,7 @@ export function sequence(...handlers) {

return handle({
request,
loader,
resolve: i < length - 1 ? (request) => apply_handle(i + 1, request) : resolve
});
}
Expand Down
7 changes: 5 additions & 2 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { get_single_valued_header } from '../../utils/http.js';
import { coalesce_to_error } from '../../utils/error.js';

/** @type {import('@sveltejs/kit/ssr').Respond} */
export async function respond(incoming, options, state = {}) {
export async function respond(incoming, loader, options, state = {}) {
if (incoming.path !== '/' && options.trailing_slash !== 'ignore') {
const has_trailing_slash = incoming.path.endsWith('/');

Expand Down Expand Up @@ -43,9 +43,11 @@ export async function respond(incoming, options, state = {}) {
try {
return await options.hooks.handle({
request,
loader,
resolve: async (request) => {
if (state.prerender && state.prerender.fallback) {
return await render_response({
loader,
options,
$session: await options.hooks.getSession(request),
page_config: { ssr: false, router: true, hydrate: true },
Expand All @@ -62,7 +64,7 @@ export async function respond(incoming, options, state = {}) {
const response =
route.type === 'endpoint'
? await render_endpoint(request, route, match)
: await render_page(request, route, match, options, state);
: await render_page(request, route, match, loader, options, state);

if (response) {
// inject ETags for 200 responses
Expand All @@ -89,6 +91,7 @@ export async function respond(incoming, options, state = {}) {
const $session = await options.hooks.getSession(request);
return await respond_with_error({
request,
loader,
options,
state,
$session,
Expand Down
4 changes: 3 additions & 1 deletion packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { respond } from './respond.js';
* @param {import('types/hooks').ServerRequest} request
* @param {import('types/internal').SSRPage} route
* @param {RegExpExecArray} match
* @param {import('types/internal').ComponentLoader} loader
* @param {import('types/internal').SSRRenderOptions} options
* @param {import('types/internal').SSRRenderState} state
* @returns {Promise<import('types/hooks').ServerResponse | undefined>}
*/
export async function render_page(request, route, match, options, state) {
export async function render_page(request, route, match, loader, options, state) {
if (state.initiator === route) {
// infinite request cycle detected
return {
Expand All @@ -31,6 +32,7 @@ export async function render_page(request, route, match, options, state) {

const response = await respond({
request,
loader,
options,
state,
$session,
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const s = JSON.stringify;
/**
* @param {{
* request: import('types/hooks').ServerRequest;
* loader: import('types/internal').ComponentLoader;
* options: import('types/internal').SSRRenderOptions;
* state: import('types/internal').SSRRenderState;
* route: import('types/internal').SSRPage | null;
Expand All @@ -24,6 +25,7 @@ const s = JSON.stringify;
*/
export async function load_node({
request,
loader,
options,
state,
route,
Expand All @@ -37,7 +39,7 @@ export async function load_node({
status,
error
}) {
const { module } = node;
const module = /** @type import('types/internal').SSRComponent */ (node.module);

let uses_credentials = false;

Expand Down Expand Up @@ -163,6 +165,7 @@ export async function load_node({
rawBody: opts.body == null ? null : new TextEncoder().encode(opts.body),
query: new URLSearchParams(search)
},
loader,
options,
{
fetched: url,
Expand Down
4 changes: 3 additions & 1 deletion packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const s = JSON.stringify;
/**
* @param {{
* branch: Array<import('./types').Loaded>;
* loader: import('types/internal').ComponentLoader;
* options: import('types/internal').SSRRenderOptions;
* $session: any;
* page_config: { hydrate: boolean, router: boolean, ssr: boolean };
Expand All @@ -21,6 +22,7 @@ const s = JSON.stringify;
*/
export async function render_response({
branch,
loader,
options,
$session,
page_config,
Expand All @@ -41,7 +43,7 @@ export async function render_response({
let maxage;

if (error) {
error.stack = options.get_stack(error);
loader.fixStackTrace({ error });
}

if (page_config.ssr) {
Expand Down
23 changes: 14 additions & 9 deletions packages/kit/src/runtime/server/page/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { coalesce_to_error } from '../../../utils/error.js';
/**
* @param {{
* request: import('types/hooks').ServerRequest;
* loader: import('types/internal').ComponentLoader;
* options: SSRRenderOptions;
* state: SSRRenderState;
* $session: any;
Expand All @@ -23,20 +24,21 @@ import { coalesce_to_error } from '../../../utils/error.js';
* @returns {Promise<ServerResponse | undefined>}
*/
export async function respond(opts) {
const { request, options, state, $session, route } = opts;
const { request, loader, options, state, $session, route } = opts;

/** @type {Array<SSRNode | undefined>} */
let nodes;

try {
nodes = await Promise.all(route.a.map((id) => (id ? options.load_component(id) : undefined)));
nodes = await Promise.all(route.a.map((id) => (id ? loader.loadComponent({ id }) : undefined)));
} catch (err) {
const error = coalesce_to_error(err);

options.handle_error(error, request);

return await respond_with_error({
request,
loader,
options,
state,
$session,
Expand All @@ -46,7 +48,9 @@ export async function respond(opts) {
}

// the leaf node will be present. only layouts may be undefined
const leaf = /** @type {SSRNode} */ (nodes[nodes.length - 1]).module;
const leaf_node = /** @type {SSRNode} */ (nodes[nodes.length - 1]);
// cast to instance of module specific to the SvelteKit router
const leaf = /** @type {import('types/internal').SSRComponent} */ (leaf_node.module);

let page_config = get_page_config(leaf, options);

Expand Down Expand Up @@ -126,7 +130,7 @@ export async function respond(opts) {
if (error) {
while (i--) {
if (route.b[i]) {
const error_node = await options.load_component(route.b[i]);
const error_node = await loader.loadComponent({ id: route.b[i] });

/** @type {Loaded} */
let node_loaded;
Expand Down Expand Up @@ -154,14 +158,14 @@ export async function respond(opts) {
continue;
}

page_config = get_page_config(error_node.module, options);
const error_module = /** @type import('types/internal').SSRComponent */ (
error_node.module
);
page_config = get_page_config(error_module, options);
branch = branch.slice(0, j + 1).concat(error_loaded);
break ssr;
} catch (err) {
const e = coalesce_to_error(err);

options.handle_error(e, request);

options.handle_error(coalesce_to_error(err), request);
continue;
}
}
Expand All @@ -173,6 +177,7 @@ export async function respond(opts) {
return with_cookies(
await respond_with_error({
request,
loader,
options,
state,
$session,
Expand Down
Loading