From 16fc2dbe30c749a8e2b7b7ad1a84e22709daca33 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 19:11:33 -0500 Subject: [PATCH] fix #4038 --- packages/kit/src/core/dev/plugin.js | 1 + .../kit/src/core/generate_manifest/index.js | 1 + .../core/sync/create_manifest_data/index.js | 21 +++++++++---------- packages/kit/src/core/sync/write_manifest.js | 2 +- packages/kit/src/runtime/client/client.js | 11 +++++++--- packages/kit/src/runtime/server/index.js | 18 ++++++++++++---- packages/kit/types/internal.d.ts | 7 ++++--- 7 files changed, 39 insertions(+), 22 deletions(-) diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index c0fb8321aefb..1045292bc3d3 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -107,6 +107,7 @@ export async function create_plugin(config, cwd) { if (route.type === 'page') { return { type: 'page', + key: route.key, pattern: route.pattern, params: get_params(route.params), shadow: route.shadow diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 00c03e029926..c28cd3a1063c 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -72,6 +72,7 @@ export function generate_manifest( if (route.type === 'page') { return `{ type: 'page', + key: ${s(route.key)}, pattern: ${route.pattern}, params: ${get_params(route.params)}, path: ${route.path ? s(route.path) : null}, diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index 51e6cf3ffdd6..b8674c8d8445 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -261,20 +261,19 @@ export default function create_manifest_data({ walk(config.kit.files.routes, [], [], [], [layout], [error]); - // merge matching page/endpoint pairs into shadowed pages + const lookup = new Map(); + for (const route of routes) { + if (route.type === 'page') { + lookup.set(route.key, route); + } + } + let i = routes.length; while (i--) { const route = routes[i]; - const prev = routes[i - 1]; - - if (prev && prev.key === route.key) { - if (prev.type !== 'endpoint' || route.type !== 'page') { - const relative = path.relative(cwd, path.resolve(config.kit.files.routes, prev.key)); - throw new Error(`Duplicate route files: ${relative}`); - } - - route.shadow = prev.file; - routes.splice(--i, 1); + if (route.type === 'endpoint' && lookup.has(route.key)) { + lookup.get(route.key).shadow = route.file; + routes.splice(i, 1); } } diff --git a/packages/kit/src/core/sync/write_manifest.js b/packages/kit/src/core/sync/write_manifest.js index 66527c3b43de..15a85079132c 100644 --- a/packages/kit/src/core/sync/write_manifest.js +++ b/packages/kit/src/core/sync/write_manifest.js @@ -48,7 +48,7 @@ export function write_manifest(manifest_data, base, output) { // optional items if (params || route.shadow) tuple.push(params || 'null'); - if (route.shadow) tuple.push('1'); + if (route.shadow) tuple.push(`'${route.key}'`); return `// ${route.a[route.a.length - 1]}\n\t\t[${tuple.join(', ')}]`; } diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index af24e0c4d46c..6d7714ab4587 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -560,7 +560,7 @@ export function create_client({ target, session, base, trailing_slash }) { if (cached) return cached; } - const [pattern, a, b, get_params, has_shadow] = route; + const [pattern, a, b, get_params, shadow_key] = route; const params = get_params ? // the pattern is for the route which we've already matched to this path get_params(/** @type {RegExpExecArray} */ (pattern.exec(path))) @@ -611,18 +611,23 @@ export function create_client({ target, session, base, trailing_slash }) { /** @type {Record} */ let props = {}; - const is_shadow_page = has_shadow && i === a.length - 1; + const is_shadow_page = shadow_key !== undefined && i === a.length - 1; if (is_shadow_page) { const res = await fetch( `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, { headers: { - 'x-sveltekit-load': 'true' + 'x-sveltekit-load': /** @type {string} */ (shadow_key) } } ); + if (res.status === 204) { + // fallthrough + return; + } + if (res.ok) { const redirect = res.headers.get('x-sveltekit-location'); diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index a923a6364854..a8ffc66630d0 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -150,7 +150,17 @@ export async function respond(request, options, state = {}) { event.url = new URL(event.url.origin + normalized + event.url.search); } + // `key` will be set if this request came from a client-side navigation + // to a page with a matching endpoint + const key = request.headers.get('x-sveltekit-load'); + for (const route of options.manifest._.routes) { + if (key) { + // client is requesting data for a specific endpoint + if (route.type !== 'page') continue; + if (route.key !== key) continue; + } + const match = route.pattern.exec(decoded); if (!match) continue; @@ -163,7 +173,7 @@ export async function respond(request, options, state = {}) { response = await render_endpoint(event, await route.shadow()); // loading data for a client-side transition is a special case - if (request.headers.get('x-sveltekit-load') === 'true') { + if (key) { if (response) { // since redirects are opaque to the browser, we need to repackage // 3xx responses as 200s with a custom header @@ -180,9 +190,9 @@ export async function respond(request, options, state = {}) { } } } else { - // TODO ideally, the client wouldn't request this data - // in the first place (at least in production) - response = new Response('{}', { + // fallthrough + response = new Response(undefined, { + status: 204, headers: { 'content-type': 'application/json' } diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 139f0a950328..1b58ea9aaf97 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -71,7 +71,7 @@ export type CSRComponent = any; // TODO export type CSRComponentLoader = () => Promise; -export type CSRRoute = [RegExp, CSRComponentLoader[], CSRComponentLoader[], GetParams?, HasShadow?]; +export type CSRRoute = [RegExp, CSRComponentLoader[], CSRComponentLoader[], GetParams?, ShadowKey?]; export interface EndpointData { type: 'endpoint'; @@ -84,8 +84,6 @@ export interface EndpointData { export type GetParams = (match: RegExpExecArray) => Record; -type HasShadow = 1; - export interface Hooks { externalFetch: ExternalFetch; getSession: GetSession; @@ -179,6 +177,8 @@ export interface ShadowEndpointOutput { body?: Output; } +type ShadowKey = string; + export interface ShadowRequestHandler { (event: RequestEvent): MaybePromise, Fallthrough>>; } @@ -272,6 +272,7 @@ export interface SSROptions { export interface SSRPage { type: 'page'; + key: string; pattern: RegExp; params: GetParams; shadow: