diff --git a/.changeset/quiet-mangos-shop.md b/.changeset/quiet-mangos-shop.md new file mode 100644 index 000000000000..c4dc53407a8c --- /dev/null +++ b/.changeset/quiet-mangos-shop.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Always return a response from render function in handle diff --git a/documentation/docs/04-hooks.md b/documentation/docs/04-hooks.md index feefa02f3b05..234cfd3521a4 100644 --- a/documentation/docs/04-hooks.md +++ b/documentation/docs/04-hooks.md @@ -97,7 +97,7 @@ type Response = { type Handle = ( request: Request, - render: (request: Request) => Response | Promise + render: (request: Request) => Promise ) => Response | Promise; ``` diff --git a/packages/kit/src/core/adapt/prerender.js b/packages/kit/src/core/adapt/prerender.js index 66a33fcf398f..9f73a72fedf5 100644 --- a/packages/kit/src/core/adapt/prerender.js +++ b/packages/kit/src/core/adapt/prerender.js @@ -84,6 +84,9 @@ export async function prerender({ cwd, out, log, config, force }) { if (seen.has(path)) return; seen.add(path); + /** @type {Map} */ + const dependencies = new Map(); + const rendered = await app.render( { host: config.kit.host, @@ -95,6 +98,7 @@ export async function prerender({ cwd, out, log, config, force }) { }, { local: true, + dependencies, only_render_prerenderable_pages: !force, get_static_file: (file) => readFileSync(join(config.kit.files.assets, file)) } @@ -123,39 +127,34 @@ export async function prerender({ cwd, out, log, config, force }) { return; } - if (response_type === OK) { + if (rendered.status === 200) { log.info(`${rendered.status} ${path}`); writeFileSync(file, rendered.body); // TODO minify where possible? - } else { + } else if (response_type !== OK) { error(rendered.status, path); } - const { dependencies } = rendered; - - if (dependencies) { - for (const path in dependencies) { - const result = dependencies[path]; - const response_type = Math.floor(result.status / 100); + dependencies.forEach((result, path) => { + const response_type = Math.floor(result.status / 100); - const is_html = result.headers['content-type'] === 'text/html'; + const is_html = result.headers['content-type'] === 'text/html'; - const parts = path.split('/'); - if (is_html && parts[parts.length - 1] !== 'index.html') { - parts.push('index.html'); - } + const parts = path.split('/'); + if (is_html && parts[parts.length - 1] !== 'index.html') { + parts.push('index.html'); + } - const file = `${out}${parts.join('/')}`; - mkdirp(dirname(file)); + const file = `${out}${parts.join('/')}`; + mkdirp(dirname(file)); - writeFileSync(file, result.body); + writeFileSync(file, result.body); - if (response_type === OK) { - log.info(`${result.status} ${path}`); - } else { - error(result.status, path); - } + if (response_type === OK) { + log.info(`${result.status} ${path}`); + } else { + error(result.status, path); } - } + }); if (is_html && config.kit.prerender.crawl) { const cleaned = clean_html(rendered.body); diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js index 1bd59c19bc3e..77106f9496b5 100644 --- a/packages/kit/src/core/build/index.js +++ b/packages/kit/src/core/build/index.js @@ -354,6 +354,7 @@ async function build_server( export function render(request, { paths = ${s(config.kit.paths)}, local = false, + dependencies, only_render_prerenderable_pages = false, get_static_file } = {}) { @@ -371,6 +372,7 @@ async function build_server( hooks, dev: false, amp: ${config.kit.amp}, + dependencies, only_render_prerenderable_pages, app_dir: ${s(config.kit.appDir)}, get_component_path: id => ${s(`${config.kit.paths.assets}/${config.kit.appDir}/`)} + client_component_lookup[id], diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js index 154256e85d28..09058055e525 100644 --- a/packages/kit/src/runtime/server/endpoint.js +++ b/packages/kit/src/runtime/server/endpoint.js @@ -1,7 +1,7 @@ /** * @param {import('types').Request} request * @param {import('types.internal').SSREndpoint} route - * @returns {Promise} + * @returns {Promise} */ export default async function render_route(request, route) { const mod = await route.load(); diff --git a/packages/kit/src/runtime/server/page.js b/packages/kit/src/runtime/server/page.js index 60f3be7bee69..d4cef5bffdad 100644 --- a/packages/kit/src/runtime/server/page.js +++ b/packages/kit/src/runtime/server/page.js @@ -16,12 +16,9 @@ const s = JSON.stringify; * status: number; * error: Error * }} opts - * @returns {Promise} + * @returns {Promise} */ async function get_response({ request, options, $session, route, status = 200, error }) { - /** @type {Record} */ - const dependencies = {}; - const serialized_session = try_serialize($session, (error) => { throw new Error(`Failed to serialize session data: ${error.message}`); }); @@ -146,9 +143,9 @@ async function get_response({ request, options, $session, route, status = 200, e ); if (rendered) { - // TODO this is primarily for the benefit of the static case, - // but could it be used elsewhere? - dependencies[resolved] = rendered; + if (options.dependencies) { + options.dependencies.set(resolved, rendered); + } response = new Response(rendered.body, { status: rendered.status, @@ -240,11 +237,23 @@ async function get_response({ request, options, $session, route, status = 200, e }; if (options.only_render_prerenderable_pages) { - if (error) return; // don't prerender an error page + if (error) { + return { + status, + headers: {}, + body: error.message + }; + } // if the page has `export const prerender = true`, continue, // otherwise bail out at this point - if (!page_component.prerender) return; + if (!page_component.prerender) { + return { + status: 204, + headers: {}, + body: null + }; + } } /** @type {{ head: string, html: string, css: string }} */ @@ -468,8 +477,7 @@ async function get_response({ request, options, $session, route, status = 200, e return { status, headers, - body: options.template({ head, body }), - dependencies + body: options.template({ head, body }) }; } @@ -477,7 +485,7 @@ async function get_response({ request, options, $session, route, status = 200, e * @param {import('types').Request} request * @param {import('types.internal').SSRPage} route * @param {import('types.internal').SSRRenderOptions} options - * @returns {Promise} + * @returns {Promise} */ export default async function render_page(request, route, options) { if (options.initiator === route) { diff --git a/packages/kit/types.internal.d.ts b/packages/kit/types.internal.d.ts index 94f71ec9c201..a72bc98f90b1 100644 --- a/packages/kit/types.internal.d.ts +++ b/packages/kit/types.internal.d.ts @@ -65,7 +65,7 @@ export type App = { }; prerendering: boolean; }) => void; - render: (incoming: Incoming, options: SSRRenderOptions) => ResponseWithDependencies; + render: (incoming: Incoming, options: SSRRenderOptions) => Response; }; // TODO we want to differentiate between request headers, which @@ -74,10 +74,6 @@ export type App = { // but this can't happen until TypeScript 4.3 export type Headers = Record; -export type ResponseWithDependencies = Response & { - dependencies?: Record; -}; - export type Page = { host: string; path: string; @@ -185,6 +181,7 @@ export type SSRRenderOptions = { hooks?: Hooks; dev?: boolean; amp?: boolean; + dependencies?: Map; only_render_prerenderable_pages?: boolean; app_dir?: string; get_component_path?: (id: string) => string;