From 246c82835ada3ecb2ecaede550b16a6ad87b1fe0 Mon Sep 17 00:00:00 2001 From: tom Date: Sat, 26 Feb 2022 23:34:43 +0800 Subject: [PATCH 1/9] fix #4038 --- packages/kit/src/core/create_app/index.js | 2 +- .../src/core/create_manifest_data/index.js | 31 +++++++++-------- packages/kit/src/core/dev/plugin.js | 1 + .../kit/src/core/generate_manifest/index.js | 1 + packages/kit/src/runtime/client/renderer.js | 7 ++-- packages/kit/src/runtime/server/index.js | 34 +++++++++++++++---- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/kit/src/core/create_app/index.js b/packages/kit/src/core/create_app/index.js index 74c565843098..0fd85b6d04ca 100644 --- a/packages/kit/src/core/create_app/index.js +++ b/packages/kit/src/core/create_app/index.js @@ -86,8 +86,8 @@ function generate_client_manifest(manifest_data, base) { // optional items if (params || route.shadow) tuple.push(params || 'null'); - if (route.shadow) tuple.push('1'); + if (route.shadow) tuple.push('1', `'${route.key}'`); return `// ${route.a[route.a.length - 1]}\n\t\t[${tuple.join(', ')}]`; } }) diff --git a/packages/kit/src/core/create_manifest_data/index.js b/packages/kit/src/core/create_manifest_data/index.js index de72e262c05b..c72110f62ec8 100644 --- a/packages/kit/src/core/create_manifest_data/index.js +++ b/packages/kit/src/core/create_manifest_data/index.js @@ -261,22 +261,25 @@ export default function create_manifest_data({ walk(config.kit.files.routes, [], [], [], [layout], [error]); - // merge matching page/endpoint pairs into shadowed pages - let i = routes.length; - while (i--) { + const pages = new Map(); + /**@type {number[]}**/ + const ends = []; + routes.forEach((route, i) => { + const { key, type } = route; + if (type === 'page') { + pages.set(key, route); + } else { + ends.unshift(i); + } + }); + ends.forEach((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); + const page = pages.get(route.key); + if (page) { + page.shadow = route.file; + routes.splice(i, 1); } - } + }); const assets = fs.existsSync(config.kit.files.assets) ? list_files({ config, dir: config.kit.files.assets, path: '' }) diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index 9c23b328007d..6593891f7ab8 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..6e841cc1c437 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -71,6 +71,7 @@ export function generate_manifest( ${routes.map(route => { if (route.type === 'page') { return `{ + key: '${route.key}', type: 'page', pattern: ${route.pattern}, params: ${get_params(route.params)}, diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 3c0db40a66eb..58464ba23192 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -718,7 +718,7 @@ export class Renderer { if (cached) return cached; } - const [pattern, a, b, get_params, has_shadow] = route; + const [pattern, a, b, get_params, has_shadow, routeKey] = 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))) @@ -776,11 +776,10 @@ export class Renderer { `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, { headers: { - 'x-sveltekit-load': 'true' + 'x-sveltekit-load': routeKey } } ); - if (res.ok) { const redirect = res.headers.get('x-sveltekit-location'); @@ -791,7 +790,7 @@ export class Renderer { state: this.current }; } - + if (res.status === 204) return; props = await res.json(); } else { status = res.status; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index eb26351dca46..0cdb9c14f6b4 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -151,6 +151,33 @@ export async function respond(request, options, state = {}) { event.url = new URL(event.url.origin + normalized + event.url.search); } + const requestRouteKey = request.headers.get('x-sveltekit-load'); + if (requestRouteKey !== null) { + const route = options.manifest._.routes.find((route) => route.key === requestRouteKey); + const shadow = route?.shadow; + if (shadow) { + const match = route.pattern.exec(decoded); + if (match) { + event.params = route.params ? decode_params(route.params(match)) : {}; + const response = await render_endpoint(event, await shadow()); + if (response) { + if (response.status >= 300 && response.status < 400) { + const location = response.headers.get('location'); + if (location) { + const headers = new Headers(response.headers); + headers.set('x-sveltekit-location', location); + return new Response(undefined, { + status: 204, + headers + }); + } + } + return response; + } + } + } + } + for (const route of options.manifest._.routes) { const match = route.pattern.exec(decoded); if (!match) continue; @@ -183,11 +210,7 @@ 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('{}', { - headers: { - 'content-type': 'application/json' - } - }); + return new Response(null, { status: 204 }); } } } else { @@ -261,7 +284,6 @@ export async function respond(request, options, state = {}) { throw new Error('request in handle has been replaced with event' + details); } }); - // TODO for 1.0, change the error message to point to docs rather than PR if (response && !(response instanceof Response)) { throw new Error('handle must return a Response object' + details); From 62439d833f34a0a84e5b5747ed020fb1d4fd9db8 Mon Sep 17 00:00:00 2001 From: tom Date: Sun, 27 Feb 2022 00:35:40 +0800 Subject: [PATCH 2/9] type check --- .../src/core/create_manifest_data/index.js | 7 ++-- packages/kit/src/core/dev/plugin.js | 5 ++- packages/kit/src/runtime/client/renderer.js | 2 +- packages/kit/src/runtime/server/index.js | 41 +++++++++++-------- packages/kit/types/internal.d.ts | 17 +++++++- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/packages/kit/src/core/create_manifest_data/index.js b/packages/kit/src/core/create_manifest_data/index.js index c72110f62ec8..dc020e9d620a 100644 --- a/packages/kit/src/core/create_manifest_data/index.js +++ b/packages/kit/src/core/create_manifest_data/index.js @@ -273,10 +273,11 @@ export default function create_manifest_data({ } }); ends.forEach((i) => { - const route = routes[i]; - const page = pages.get(route.key); + const endpoint = routes[i]; + const page = pages.get(endpoint.key); if (page) { - page.shadow = route.file; + // @ts-ignore + page.shadow = endpoint.file; routes.splice(i, 1); } }); diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index 6593891f7ab8..777c87c319a1 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -105,9 +105,10 @@ export async function create_plugin(config, cwd) { }), routes: manifest_data.routes.map((route) => { if (route.type === 'page') { + /**@type import('types').SSRPage**/ return { - type: 'page', key: route.key, + type: 'page', pattern: route.pattern, params: get_params(route.params), shadow: route.shadow @@ -120,7 +121,7 @@ export async function create_plugin(config, cwd) { b: route.b.map((id) => manifest_data.components.indexOf(id)) }; } - + /**@type import('types').SSREndpoint**/ return { type: 'endpoint', pattern: route.pattern, diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 58464ba23192..af5e45970182 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -718,7 +718,7 @@ export class Renderer { if (cached) return cached; } - const [pattern, a, b, get_params, has_shadow, routeKey] = route; + const [pattern, a, b, get_params, has_shadow, routeKey = ''] = 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))) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 0cdb9c14f6b4..62db332ac7ee 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -153,26 +153,31 @@ export async function respond(request, options, state = {}) { const requestRouteKey = request.headers.get('x-sveltekit-load'); if (requestRouteKey !== null) { - const route = options.manifest._.routes.find((route) => route.key === requestRouteKey); - const shadow = route?.shadow; - if (shadow) { - const match = route.pattern.exec(decoded); - if (match) { - event.params = route.params ? decode_params(route.params(match)) : {}; - const response = await render_endpoint(event, await shadow()); - if (response) { - if (response.status >= 300 && response.status < 400) { - const location = response.headers.get('location'); - if (location) { - const headers = new Headers(response.headers); - headers.set('x-sveltekit-location', location); - return new Response(undefined, { - status: 204, - headers - }); + const route = options.manifest._.routes.find( + (route) => route.type === 'page' && route.key === requestRouteKey + ); + if (route) { + // @ts-ignore + const shadow = route.shadow; + if (shadow) { + const match = route.pattern.exec(decoded); + if (match) { + event.params = route.params ? decode_params(route.params(match)) : {}; + const response = await render_endpoint(event, await shadow()); + if (response) { + if (response.status >= 300 && response.status < 400) { + const location = response.headers.get('location'); + if (location) { + const headers = new Headers(response.headers); + headers.set('x-sveltekit-location', location); + return new Response(undefined, { + status: 204, + headers + }); + } } + return response; } - return response; } } } diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index fff9f240fcd1..9e4196993f78 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -71,7 +71,14 @@ export type CSRComponent = any; // TODO export type CSRComponentLoader = () => Promise; -export type CSRRoute = [RegExp, CSRComponentLoader[], CSRComponentLoader[], GetParams?, HasShadow?]; +export type CSRRoute = [ + RegExp, + CSRComponentLoader[], + CSRComponentLoader[], + GetParams?, + HasShadow?, + string? +]; export interface EndpointData { type: 'endpoint'; @@ -211,6 +218,7 @@ export interface SSREndpoint { type: 'endpoint'; pattern: RegExp; params: GetParams; + load(): Promise<{ [method: string]: RequestHandler; }>; @@ -236,7 +244,9 @@ export interface SSROptions { dev: boolean; floc: boolean; get_stack: (error: Error) => string | undefined; + handle_error(error: Error & { frame?: string }, event: RequestEvent): void; + hooks: Hooks; hydrate: boolean; manifest: SSRManifest; @@ -247,10 +257,13 @@ export interface SSROptions { }; prefix: string; prerender: boolean; + read(file: string): Buffer; + root: SSRComponent['default']; router: boolean; service_worker?: string; + template({ head, body, @@ -262,11 +275,13 @@ export interface SSROptions { assets: string; nonce: string; }): string; + template_contains_nonce: boolean; trailing_slash: TrailingSlash; } export interface SSRPage { + key: string; type: 'page'; pattern: RegExp; params: GetParams; From 3c0fa46725a75a24824b7ccf181802a4f912bae3 Mon Sep 17 00:00:00 2001 From: tom Date: Sun, 27 Feb 2022 01:26:56 +0800 Subject: [PATCH 3/9] fix --- packages/kit/src/runtime/client/renderer.js | 3 ++- packages/kit/src/runtime/server/index.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index af5e45970182..a73471d6bd90 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -718,7 +718,7 @@ export class Renderer { if (cached) return cached; } - const [pattern, a, b, get_params, has_shadow, routeKey = ''] = route; + const [pattern, a, b, get_params, has_shadow, routeKey] = 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))) @@ -775,6 +775,7 @@ export class Renderer { const res = await fetch( `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, { + // @ts-ignore headers: { 'x-sveltekit-load': routeKey } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 62db332ac7ee..7667859b0f30 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -196,7 +196,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 (requestRouteKey !== null) { if (response) { // since redirects are opaque to the browser, we need to repackage // 3xx responses as 200s with a custom header From ed46e5ddd426e8f907398d2e8fa88411dcaa4244 Mon Sep 17 00:00:00 2001 From: Aolose <7620293+aolose@users.noreply.github.com> Date: Tue, 1 Mar 2022 03:06:25 +0800 Subject: [PATCH 4/9] Apply suggestions from code review Co-authored-by: Rich Harris --- packages/kit/src/core/create_app/index.js | 2 +- packages/kit/src/core/create_manifest_data/index.js | 13 ++++++------- packages/kit/src/core/dev/plugin.js | 2 -- packages/kit/src/runtime/client/renderer.js | 4 ++-- packages/kit/types/internal.d.ts | 8 -------- 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/kit/src/core/create_app/index.js b/packages/kit/src/core/create_app/index.js index 0fd85b6d04ca..359ec5826889 100644 --- a/packages/kit/src/core/create_app/index.js +++ b/packages/kit/src/core/create_app/index.js @@ -87,7 +87,7 @@ function generate_client_manifest(manifest_data, base) { // optional items if (params || route.shadow) tuple.push(params || 'null'); - if (route.shadow) tuple.push('1', `'${route.key}'`); + 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/core/create_manifest_data/index.js b/packages/kit/src/core/create_manifest_data/index.js index dc020e9d620a..4322e2738717 100644 --- a/packages/kit/src/core/create_manifest_data/index.js +++ b/packages/kit/src/core/create_manifest_data/index.js @@ -262,17 +262,16 @@ export default function create_manifest_data({ walk(config.kit.files.routes, [], [], [], [layout], [error]); const pages = new Map(); - /**@type {number[]}**/ - const ends = []; + /** @type {number[]} */ + const endpoints = []; routes.forEach((route, i) => { - const { key, type } = route; - if (type === 'page') { - pages.set(key, route); + if (route.type === 'page') { + pages.set(route.key, route); } else { - ends.unshift(i); + endpoints.unshift(i); } }); - ends.forEach((i) => { + endpoints.forEach((i) => { const endpoint = routes[i]; const page = pages.get(endpoint.key); if (page) { diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index 777c87c319a1..ba0fd4293163 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -105,7 +105,6 @@ export async function create_plugin(config, cwd) { }), routes: manifest_data.routes.map((route) => { if (route.type === 'page') { - /**@type import('types').SSRPage**/ return { key: route.key, type: 'page', @@ -121,7 +120,6 @@ export async function create_plugin(config, cwd) { b: route.b.map((id) => manifest_data.components.indexOf(id)) }; } - /**@type import('types').SSREndpoint**/ return { type: 'endpoint', pattern: route.pattern, diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index a73471d6bd90..9277ab7e32c9 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -718,7 +718,7 @@ export class Renderer { if (cached) return cached; } - const [pattern, a, b, get_params, has_shadow, routeKey] = 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))) @@ -777,7 +777,7 @@ export class Renderer { { // @ts-ignore headers: { - 'x-sveltekit-load': routeKey + 'x-sveltekit-load': shadow_key } } ); diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 9e4196993f78..52c7535204f3 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -76,7 +76,6 @@ export type CSRRoute = [ CSRComponentLoader[], CSRComponentLoader[], GetParams?, - HasShadow?, string? ]; @@ -218,7 +217,6 @@ export interface SSREndpoint { type: 'endpoint'; pattern: RegExp; params: GetParams; - load(): Promise<{ [method: string]: RequestHandler; }>; @@ -244,9 +242,7 @@ export interface SSROptions { dev: boolean; floc: boolean; get_stack: (error: Error) => string | undefined; - handle_error(error: Error & { frame?: string }, event: RequestEvent): void; - hooks: Hooks; hydrate: boolean; manifest: SSRManifest; @@ -257,13 +253,10 @@ export interface SSROptions { }; prefix: string; prerender: boolean; - read(file: string): Buffer; - root: SSRComponent['default']; router: boolean; service_worker?: string; - template({ head, body, @@ -275,7 +268,6 @@ export interface SSROptions { assets: string; nonce: string; }): string; - template_contains_nonce: boolean; trailing_slash: TrailingSlash; } From fe06c7fba19b68b3e6d0622c575bf0de51423175 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 1 Mar 2022 03:31:26 +0800 Subject: [PATCH 5/9] apply suggestion --- packages/kit/src/runtime/client/renderer.js | 2 +- packages/kit/src/runtime/server/index.js | 35 ++------------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 9277ab7e32c9..126275a4f758 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -769,7 +769,7 @@ export class Renderer { /** @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( diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 7667859b0f30..37bfbc392918 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -151,39 +151,10 @@ export async function respond(request, options, state = {}) { event.url = new URL(event.url.origin + normalized + event.url.search); } - const requestRouteKey = request.headers.get('x-sveltekit-load'); - if (requestRouteKey !== null) { - const route = options.manifest._.routes.find( - (route) => route.type === 'page' && route.key === requestRouteKey - ); - if (route) { - // @ts-ignore - const shadow = route.shadow; - if (shadow) { - const match = route.pattern.exec(decoded); - if (match) { - event.params = route.params ? decode_params(route.params(match)) : {}; - const response = await render_endpoint(event, await shadow()); - if (response) { - if (response.status >= 300 && response.status < 400) { - const location = response.headers.get('location'); - if (location) { - const headers = new Headers(response.headers); - headers.set('x-sveltekit-location', location); - return new Response(undefined, { - status: 204, - headers - }); - } - } - return response; - } - } - } - } - } + const shadow_key = request.headers.get('x-sveltekit-load'); for (const route of options.manifest._.routes) { + if (shadow_key && route.type === 'page' && shadow_key !== route.key) continue; const match = route.pattern.exec(decoded); if (!match) continue; @@ -196,7 +167,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 (requestRouteKey !== null) { + if (shadow_key !== null) { if (response) { // since redirects are opaque to the browser, we need to repackage // 3xx responses as 200s with a custom header From 4eb452c031f742a2eaeac3da232eda586ce2d181 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 1 Mar 2022 03:36:45 +0800 Subject: [PATCH 6/9] apply suggestion --- packages/kit/src/runtime/client/renderer.js | 2 +- packages/kit/types/internal.d.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 126275a4f758..74bdc2430a14 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -769,7 +769,7 @@ export class Renderer { /** @type {Record} */ let props = {}; - const is_shadow_page = shadow_key !== undefined && i === a.length - 1; + const is_shadow_page = shadow_key !== undefined && i === a.length - 1; if (is_shadow_page) { const res = await fetch( diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 52c7535204f3..46eb42a236db 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -71,13 +71,7 @@ export type CSRComponent = any; // TODO export type CSRComponentLoader = () => Promise; -export type CSRRoute = [ - RegExp, - CSRComponentLoader[], - CSRComponentLoader[], - GetParams?, - string? -]; +export type CSRRoute = [RegExp, CSRComponentLoader[], CSRComponentLoader[], GetParams?, string?]; export interface EndpointData { type: 'endpoint'; From 0e123e63623f089c7813b45c6a5c03c6437a640c Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 1 Mar 2022 22:20:52 +0800 Subject: [PATCH 7/9] Merge multiple fallthrough --- packages/kit/src/runtime/client/renderer.js | 64 +++++++++++++-------- packages/kit/src/runtime/server/index.js | 60 +++++++++++++------ 2 files changed, 83 insertions(+), 41 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 74bdc2430a14..51ea5bd74a9c 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -708,11 +708,13 @@ export class Renderer { /** * @param {import('./types').NavigationCandidate} selected * @param {boolean} no_cache + * @param {string} [next_route_key] + * @param {Record} [next_route_props] * @returns {Promise} undefined if fallthrough */ - async _load({ route, info: { url, path } }, no_cache) { + async _load({ route, info }, no_cache, next_route_key, next_route_props) { + const { url, path, routes } = info; const key = url.pathname + url.search; - if (!no_cache) { const cached = this.cache.get(key); if (cached) return cached; @@ -772,30 +774,44 @@ export class Renderer { 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}`, - { - // @ts-ignore - headers: { - 'x-sveltekit-load': shadow_key + if (next_route_key !== undefined && shadow_key === next_route_key) { + if (next_route_props) props = next_route_props; + } else { + const res = await fetch( + `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, + { + // @ts-ignore + headers: { + 'x-sveltekit-load': shadow_key + } } + ); + if (res.ok) { + const redirect = res.headers.get('x-sveltekit-location'); + const route_key = res.headers.get('x-sveltekit-load'); + if (redirect) { + return { + redirect, + props: {}, + state: this.current + }; + } + props = await res.json(); + if (route_key) { + const next_route = routes.find((r) => r[4] === route_key); + if (next_route) { + return await this._load( + { route: next_route, info }, + no_cache, + route_key, + props + ); + } + } + } else { + status = res.status; + error = new Error('Failed to load data'); } - ); - if (res.ok) { - const redirect = res.headers.get('x-sveltekit-location'); - - if (redirect) { - return { - redirect, - props: {}, - state: this.current - }; - } - if (res.status === 204) return; - props = await res.json(); - } else { - status = res.status; - error = new Error('Failed to load data'); } } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 37bfbc392918..2ac381a39919 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -152,28 +152,26 @@ export async function respond(request, options, state = {}) { } const shadow_key = request.headers.get('x-sveltekit-load'); + let from_fallthrough = false; for (const route of options.manifest._.routes) { - if (shadow_key && route.type === 'page' && shadow_key !== route.key) continue; const match = route.pattern.exec(decoded); if (!match) continue; - event.params = route.params ? decode_params(route.params(match)) : {}; /** @type {Response | undefined} */ let response; - - if (is_data_request && route.type === 'page' && route.shadow) { - response = await render_endpoint(event, await route.shadow()); - - // loading data for a client-side transition is a special case - if (shadow_key !== null) { + const is_page = route.type === 'page'; + if (is_data_request && is_page && route.shadow) { + if (!from_fallthrough && shadow_key && shadow_key !== route.key) continue; + if (is_data_request && route.shadow) { + response = await render_endpoint(event, await route.shadow()); + // loading data for a client-side transition is a special case if (response) { // since redirects are opaque to the browser, we need to repackage // 3xx responses as 200s with a custom header if (response.status >= 300 && response.status < 400) { const location = response.headers.get('location'); - if (location) { const headers = new Headers(response.headers); headers.set('x-sveltekit-location', location); @@ -183,17 +181,39 @@ export async function respond(request, options, state = {}) { }); } } + if (from_fallthrough && response.ok) { + const headers = new Headers(response.headers); + // client-site will fallthrough to the route + headers.set('x-sveltekit-load', route.key); + response = new Response(response.body, { + status: response.status, + headers + }); + } } else { - // TODO ideally, the client wouldn't request this data - // in the first place (at least in production) - return new Response(null, { status: 204 }); + // continue to next match page router + // if next page has shadow , fallthrough at serve-site + from_fallthrough = true; + continue; } } } else { - response = - route.type === 'endpoint' - ? await render_endpoint(event, await route.load()) - : await render_page(event, route, options, state, resolve_opts); + if (is_page) { + if (from_fallthrough) { + const headers = new Headers({ + 'x-sveltekit-load': route.key + }); + // next match not a shadow so fallthrough at client-side + return new Response(undefined, { + headers, + status: 204 + }); + } + response = await render_page(event, route, options, state, resolve_opts); + } else { + if (shadow_key || from_fallthrough) continue; + response = await render_endpoint(event, await route.load()); + } } if (response) { @@ -229,11 +249,17 @@ export async function respond(request, options, state = {}) { }); } } - return response; } } + // shadow point fallthrough but not match any page + if (from_fallthrough) { + return new Response(undefined, { + status: 404 + }); + } + // if this request came direct from the user, rather than // via a `fetch` in a `load`, render a 404 page if (!state.initiator) { From 26d6bd3ee016e0a76057de8c91bbe5ff380944bb Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 1 Mar 2022 23:36:39 +0800 Subject: [PATCH 8/9] fix bug --- packages/kit/src/runtime/client/renderer.js | 2 ++ packages/kit/src/runtime/server/index.js | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 51ea5bd74a9c..e1c25b7a66d1 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -796,6 +796,8 @@ export class Renderer { state: this.current }; } + // '.' means fallthrough from server-side not match any router + if (route_key === '.') return; props = await res.json(); if (route_key) { const next_route = routes.find((r) => r[4] === route_key); diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 2ac381a39919..5bc1bc141e27 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -164,7 +164,7 @@ export async function respond(request, options, state = {}) { const is_page = route.type === 'page'; if (is_data_request && is_page && route.shadow) { if (!from_fallthrough && shadow_key && shadow_key !== route.key) continue; - if (is_data_request && route.shadow) { + if (route.shadow) { response = await render_endpoint(event, await route.shadow()); // loading data for a client-side transition is a special case if (response) { @@ -253,10 +253,15 @@ export async function respond(request, options, state = {}) { } } - // shadow point fallthrough but not match any page + // no match route if (from_fallthrough) { + const headers = new Headers({ + 'x-sveltekit-load': '.' + }); + // next match not a shadow so fallthrough at client-side return new Response(undefined, { - status: 404 + headers, + status: 204 }); } From 7397a202c46566d1dfbfac84df0b0d437ab853fd Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 3 Mar 2022 03:12:37 +0800 Subject: [PATCH 9/9] fix bug and add test --- packages/kit/src/runtime/client/renderer.js | 22 +++++++++---------- packages/kit/src/runtime/server/index.js | 8 ++++--- .../src/routes/shadowed/fallthrough/[a].js | 14 ++++++++++++ .../routes/shadowed/fallthrough/[a].svelte | 5 +++++ .../src/routes/shadowed/fallthrough/[b].js | 14 ++++++++++++ .../routes/shadowed/fallthrough/[b].svelte | 5 +++++ .../routes/shadowed/fallthrough/[c].svelte | 1 + .../routes/shadowed/fallthrough/index.svelte | 3 +++ packages/kit/test/apps/basics/test/test.js | 12 ++++++++++ 9 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].js create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].svelte create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].js create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].svelte create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[c].svelte create mode 100644 packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/index.svelte diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 6386f56f1339..0708a2b8f8c6 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -724,11 +724,11 @@ export class Renderer { /** * @param {import('./types').NavigationCandidate} selected * @param {boolean} no_cache - * @param {string} [next_route_key] - * @param {Record} [next_route_props] + * @param {string} [fallthrough_target] + * @param {Record} [fallthrough_props] * @returns {Promise} undefined if fallthrough */ - async _load({ route, info }, no_cache, next_route_key, next_route_props) { + async _load({ route, info }, no_cache, fallthrough_target, fallthrough_props) { const { url, path, routes } = info; const key = url.pathname + url.search; if (!no_cache) { @@ -790,8 +790,8 @@ export class Renderer { const is_shadow_page = shadow_key !== undefined && i === a.length - 1; if (is_shadow_page) { - if (next_route_key !== undefined && shadow_key === next_route_key) { - if (next_route_props) props = next_route_props; + if (fallthrough_target !== undefined && shadow_key === fallthrough_target) { + if (fallthrough_props) props = fallthrough_props; } else { const res = await fetch( `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, @@ -804,7 +804,7 @@ export class Renderer { ); if (res.ok) { const redirect = res.headers.get('x-sveltekit-location'); - const route_key = res.headers.get('x-sveltekit-load'); + const route_index = res.headers.get('x-sveltekit-load'); if (redirect) { return { redirect, @@ -813,15 +813,15 @@ export class Renderer { }; } // '.' means fallthrough from server-side not match any router - if (route_key === '.') return; - props = await res.json(); - if (route_key) { - const next_route = routes.find((r) => r[4] === route_key); + if (route_index === '-1') return; + props = res.status === 204 ? {} : await res.json(); + if (route_index) { + const next_route = routes[+route_index]; if (next_route) { return await this._load( { route: next_route, info }, no_cache, - route_key, + next_route[4], props ); } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 5bc1bc141e27..ba897dc94861 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -153,6 +153,7 @@ export async function respond(request, options, state = {}) { const shadow_key = request.headers.get('x-sveltekit-load'); let from_fallthrough = false; + let route_index = -1; for (const route of options.manifest._.routes) { const match = route.pattern.exec(decoded); @@ -162,6 +163,7 @@ export async function respond(request, options, state = {}) { /** @type {Response | undefined} */ let response; const is_page = route.type === 'page'; + if (is_page) route_index++; if (is_data_request && is_page && route.shadow) { if (!from_fallthrough && shadow_key && shadow_key !== route.key) continue; if (route.shadow) { @@ -184,7 +186,7 @@ export async function respond(request, options, state = {}) { if (from_fallthrough && response.ok) { const headers = new Headers(response.headers); // client-site will fallthrough to the route - headers.set('x-sveltekit-load', route.key); + headers.set('x-sveltekit-load', `${route_index}`); response = new Response(response.body, { status: response.status, headers @@ -201,7 +203,7 @@ export async function respond(request, options, state = {}) { if (is_page) { if (from_fallthrough) { const headers = new Headers({ - 'x-sveltekit-load': route.key + 'x-sveltekit-load': `${route_index}` }); // next match not a shadow so fallthrough at client-side return new Response(undefined, { @@ -256,7 +258,7 @@ export async function respond(request, options, state = {}) { // no match route if (from_fallthrough) { const headers = new Headers({ - 'x-sveltekit-load': '.' + 'x-sveltekit-load': '-1' }); // next match not a shadow so fallthrough at client-side return new Response(undefined, { diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].js b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].js new file mode 100644 index 000000000000..7411460dc0e1 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].js @@ -0,0 +1,14 @@ +/** @type {import('@sveltejs/kit').RequestHandler} */ +export async function get({ params }) { + const param = params.a; + if (param !== 'a') { + return { + fallthrough: true + }; + } + + return { + status: 200, + body: { param } + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].svelte b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].svelte new file mode 100644 index 000000000000..927c921aea92 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[a].svelte @@ -0,0 +1,5 @@ + +

a-{param}

diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].js b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].js new file mode 100644 index 000000000000..c49b369ea58c --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].js @@ -0,0 +1,14 @@ +/** @type {import('@sveltejs/kit').RequestHandler} */ +export async function get({ params }) { + const param = params.b; + if (param !== 'b') { + return { + fallthrough: true + }; + } + + return { + status: 200, + body: { param } + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].svelte b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].svelte new file mode 100644 index 000000000000..c26ad86352bd --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[b].svelte @@ -0,0 +1,5 @@ + +

b-{param}

diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[c].svelte b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[c].svelte new file mode 100644 index 000000000000..53379894746e --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/[c].svelte @@ -0,0 +1 @@ +

c

diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/index.svelte b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/index.svelte new file mode 100644 index 000000000000..8e3b7ad45318 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shadowed/fallthrough/index.svelte @@ -0,0 +1,3 @@ +fallthrough to shadow a +fallthrough to shadow b +fallthrough to no shadow c diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index bcf6b02546ad..be040856e25e 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -504,6 +504,18 @@ test.describe.parallel('Shadowed pages', () => { await clicknav('[href="/shadowed/dynamic/bar"]'); expect(await page.textContent('h1')).toBe('slug: bar'); }); + + test('Shadow fallthrough shadow', async ({ page, clicknav }) => { + await page.goto('/shadowed/fallthrough'); + await clicknav('[href="/shadowed/fallthrough/b"]'); + expect(await page.textContent('h2')).toBe('b-b'); + }); + + test('Shadow fallthrough to no_shadow', async ({ page, clicknav }) => { + await page.goto('/shadowed/fallthrough'); + await clicknav('[href="/shadowed/fallthrough/c"]'); + expect(await page.textContent('h2')).toBe('c'); + }); }); test.describe.parallel('Endpoints', () => {