From f4a5e056173901e05af1bb131954be7dd0ca13c1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 16:10:26 -0500 Subject: [PATCH 01/25] add failing test --- .../basics/src/routes/snapshot/+layout.svelte | 5 +++++ .../basics/src/routes/snapshot/a/+page.svelte | 10 ++++++++++ .../kit/test/apps/basics/test/client.test.js | 17 +++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 packages/kit/test/apps/basics/src/routes/snapshot/+layout.svelte create mode 100644 packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/+layout.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/+layout.svelte new file mode 100644 index 000000000000..84cdf156497f --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/snapshot/+layout.svelte @@ -0,0 +1,5 @@ +a +b +c + + diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte new file mode 100644 index 000000000000..c83685a9e35c --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 91b4beec802a..320df211e830 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -607,3 +607,20 @@ test.describe('env in app.html', () => { expect(await page.locator('body').getAttribute('class')).toContain('groovy'); }); }); + +test.describe('Snapshots', () => { + test.only('recovers snapshotted data', async ({ page, clicknav }) => { + await page.goto('/snapshot/a'); + await page.locator('input').type('hello'); + + await clicknav('[href="/snapshot/b"]'); + await page.goBack(); + expect(await page.locator('input').inputValue()).toBe('hello'); + + await page.locator('input').type('works for cross-document navigations'); + + await clicknav('[href="/snapshot/c"]'); + await page.goBack(); + expect(await page.locator('input').inputValue()).toBe('works for cross-document navigations'); + }); +}); From 8e2dab729e1631e0a0bbefd711d8c835d7613830 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 16:57:14 -0500 Subject: [PATCH 02/25] make it possible to read snapshot props --- packages/kit/src/core/sync/write_root.js | 11 ++++++----- packages/kit/src/exports/vite/index.js | 17 +++++++++++++++++ packages/kit/src/runtime/client/client.js | 7 +++++-- packages/kit/src/runtime/server/page/render.js | 2 +- .../basics/src/routes/snapshot/b/+page.svelte | 1 + .../basics/src/routes/snapshot/c/+page.svelte | 1 + 6 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 packages/kit/test/apps/basics/src/routes/snapshot/b/+page.svelte create mode 100644 packages/kit/test/apps/basics/src/routes/snapshot/c/+page.svelte diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js index e40fe3021870..a82e2d00770f 100644 --- a/packages/kit/src/core/sync/write_root.js +++ b/packages/kit/src/core/sync/write_root.js @@ -21,16 +21,16 @@ export function write_root(manifest_data, output) { let l = max_depth; - let pyramid = ``; + let pyramid = ``; while (l--) { pyramid = ` - {#if components[${l + 1}]} - + {#if constructors[${l + 1}]} + ${pyramid.replace(/\n/g, '\n\t\t\t\t\t')} {:else} - + {/if} ` .replace(/^\t\t\t/gm, '') @@ -49,7 +49,8 @@ export function write_root(manifest_data, output) { export let stores; export let page; - export let components; + export let constructors; + export let components = []; export let form; ${levels.map((l) => `export let data_${l} = null;`).join('\n\t\t\t\t')} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 409f2399b6a8..5883e4841ac6 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -132,6 +132,23 @@ export async function sveltekit() { hydratable: true, ...svelte_config.compilerOptions }, + experimental: { + dynamicCompileOptions: ({ filename, code, compileOptions }) => { + const options = + svelte_config.vitePlugin?.experimental?.dynamicCompileOptions?.({ + filename, + code, + compileOptions + }) ?? {}; + + const basename = path.basename(filename); + if (basename.startsWith('+layout') || basename.startsWith('+page')) { + options.accessors = true; + } + + return options; + } + }, ...svelte_config.vitePlugin }; diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index c3fb0bbc22b2..c1da9b49b3e3 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -75,6 +75,9 @@ export function create_client({ target, base }) { /** @type {Array<((url: URL) => boolean)>} */ const invalidated = []; + /** @type {import('svelte').SvelteComponent[]} */ + const components = []; + /** @type {{id: string, promise: Promise} | null} */ let load_cache = null; @@ -383,7 +386,7 @@ export function create_client({ target, base }) { root = new Root({ target, - props: { ...result.props, stores }, + props: { ...result.props, stores, components }, hydrate: true }); @@ -444,7 +447,7 @@ export function create_client({ target, base }) { }, props: { // @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up - components: compact(branch).map((branch_node) => branch_node.node.component) + constructors: compact(branch).map((branch_node) => branch_node.node.component) } }; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 215ca2b3fef6..8ace4af9c636 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -89,7 +89,7 @@ export async function render_response({ navigating: writable(null), updated }, - components: await Promise.all(branch.map(({ node }) => node.component())), + constructors: await Promise.all(branch.map(({ node }) => node.component())), form: form_value }; diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/b/+page.svelte new file mode 100644 index 000000000000..e96ed3db8987 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/snapshot/b/+page.svelte @@ -0,0 +1 @@ +

b

diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/c/+page.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/c/+page.svelte new file mode 100644 index 000000000000..232c276f86cb --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/snapshot/c/+page.svelte @@ -0,0 +1 @@ +

c

From c43ddf2107e3d04ffb19371ca201138bba7bd0d0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:03:08 -0500 Subject: [PATCH 03/25] refactor sessionStorage stuff a bit --- packages/kit/src/runtime/client/client.js | 15 +++--------- packages/kit/src/runtime/client/constants.js | 1 + .../kit/src/runtime/client/session-storage.js | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 packages/kit/src/runtime/client/session-storage.js diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index c1da9b49b3e3..263d178bd31f 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -15,6 +15,7 @@ import { is_external_url, scroll_state } from './utils.js'; +import * as storage from './session-storage.js'; import { lock_fetch, unlock_fetch, @@ -51,12 +52,7 @@ default_error_loader(); /** @typedef {{ x: number, y: number }} ScrollPosition */ /** @type {Record} */ -let scroll_positions = {}; -try { - scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]); -} catch { - // do nothing -} +const scroll_positions = storage.get(SCROLL_KEY); /** @param {number} index */ function update_scroll_positions(index) { @@ -1376,12 +1372,7 @@ export function create_client({ target, base }) { addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { update_scroll_positions(current_history_index); - - try { - sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions); - } catch { - // do nothing - } + storage.set(SCROLL_KEY, scroll_positions); } }); diff --git a/packages/kit/src/runtime/client/constants.js b/packages/kit/src/runtime/client/constants.js index b7a8d9d61700..cbb2353ed2be 100644 --- a/packages/kit/src/runtime/client/constants.js +++ b/packages/kit/src/runtime/client/constants.js @@ -1,3 +1,4 @@ +export const SNAPSHOT_KEY = 'sveltekit:snapshot'; export const SCROLL_KEY = 'sveltekit:scroll'; export const INDEX_KEY = 'sveltekit:index'; diff --git a/packages/kit/src/runtime/client/session-storage.js b/packages/kit/src/runtime/client/session-storage.js new file mode 100644 index 000000000000..4b5db6eb09cb --- /dev/null +++ b/packages/kit/src/runtime/client/session-storage.js @@ -0,0 +1,24 @@ +/** + * Read a value from `sessionStorage` + * @param {string} key + */ +export function get(key) { + try { + return JSON.parse(sessionStorage[key]); + } catch { + // do nothing + } +} + +/** + * Write a value to `sessionStorage` + * @param {any} value + */ +export function set(key, value) { + const json = JSON.stringify(value); + try { + sessionStorage[key] = json; + } catch { + // do nothing + } +} From c25970ffc80e96359082336a42cc5fa9f3b14d78 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:12:18 -0500 Subject: [PATCH 04/25] snapshots --- packages/kit/src/runtime/client/client.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 263d178bd31f..e6cb81904b2a 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -31,7 +31,7 @@ import { HttpError, Redirect } from '../control.js'; import { stores } from './singletons.js'; import { unwrap_promises } from '../../utils/promises.js'; import * as devalue from 'devalue'; -import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY } from './constants.js'; +import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js'; import { validate_common_exports } from '../../utils/exports.js'; import { compact } from '../../utils/array.js'; @@ -52,7 +52,10 @@ default_error_loader(); /** @typedef {{ x: number, y: number }} ScrollPosition */ /** @type {Record} */ -const scroll_positions = storage.get(SCROLL_KEY); +const scroll_positions = storage.get(SCROLL_KEY) ?? {}; + +/** @type {Record} */ +const snapshots = storage.get(SNAPSHOT_KEY) ?? {}; /** @param {number} index */ function update_scroll_positions(index) { From 201592a5c8f876aed851e509fed7eb6ed33ba3ef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:44:26 -0500 Subject: [PATCH 05/25] working --- packages/kit/src/runtime/client/client.js | 50 ++++++++++++++++--- .../basics/src/routes/snapshot/a/+page.svelte | 2 +- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index e6cb81904b2a..209784a13bb2 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -54,7 +54,7 @@ default_error_loader(); /** @type {Record} */ const scroll_positions = storage.get(SCROLL_KEY) ?? {}; -/** @type {Record} */ +/** @type {Record} */ const snapshots = storage.get(SNAPSHOT_KEY) ?? {}; /** @param {number} index */ @@ -125,6 +125,8 @@ export function create_client({ target, base }) { ); } + const initial_history_index = current_history_index; + // if we reload the page, or Cmd-Shift-T back to it, // recover scroll position const scroll = scroll_positions[current_history_index]; @@ -198,7 +200,8 @@ export function create_client({ target, base }) { } }, blocked: () => {}, - type: 'goto' + type: 'goto', + previous_history_index: current_history_index }); } @@ -240,11 +243,20 @@ export function create_client({ target, base }) { * @param {import('./types').NavigationIntent | undefined} intent * @param {URL} url * @param {string[]} redirect_chain + * @param {number} [previous_history_index] * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts] * @param {{}} [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token * @param {() => void} [callback] */ - async function update(intent, url, redirect_chain, opts, nav_token = {}, callback) { + async function update( + intent, + url, + redirect_chain, + previous_history_index, + opts, + nav_token = {}, + callback + ) { token = nav_token; let navigation_result = intent && (await load_route(intent)); @@ -303,6 +315,11 @@ export function create_client({ target, base }) { updating = true; + // `previous_history_index` will be undefined for invalidation + if (previous_history_index) { + snapshots[previous_history_index] = components.map((c) => c.snapshot?.capture()); + } + if (opts && opts.details) { const { details } = opts; const change = details.replaceState ? 0 : 1; @@ -389,6 +406,10 @@ export function create_client({ target, base }) { hydrate: true }); + snapshots[current_history_index]?.forEach((value, i) => { + components[i].snapshot?.restore(value); + }); + /** @type {import('types').AfterNavigate} */ const navigation = { from: null, @@ -1059,6 +1080,7 @@ export function create_client({ target, base }) { * type: import('types').NavigationType; * delta?: number; * nav_token?: {}; + * previous_history_index: number; * accepted: () => void; * blocked: () => void; * }} opts @@ -1072,6 +1094,7 @@ export function create_client({ target, base }) { type, delta, nav_token, + previous_history_index, accepted, blocked }) { @@ -1097,6 +1120,7 @@ export function create_client({ target, base }) { intent, url, redirect_chain, + previous_history_index, { scroll, keepfocus, @@ -1376,6 +1400,9 @@ export function create_client({ target, base }) { if (document.visibilityState === 'hidden') { update_scroll_positions(current_history_index); storage.set(SCROLL_KEY, scroll_positions); + + snapshots[current_history_index] = components.map((c) => c.snapshot?.capture()); + storage.set(SNAPSHOT_KEY, snapshots); } }); @@ -1463,7 +1490,8 @@ export function create_client({ target, base }) { }, accepted: () => event.preventDefault(), blocked: () => event.preventDefault(), - type: 'link' + type: 'link', + previous_history_index: current_history_index }); }); @@ -1518,11 +1546,12 @@ export function create_client({ target, base }) { nav_token: {}, accepted: () => {}, blocked: () => {}, - type: 'form' + type: 'form', + previous_history_index: current_history_index }); }); - addEventListener('popstate', (event) => { + addEventListener('popstate', async (event) => { if (event.state?.[INDEX_KEY]) { // if a popstate-driven navigation is cancelled, we need to counteract it // with history.go, which means we end up back here, hence this check @@ -1530,7 +1559,7 @@ export function create_client({ target, base }) { const delta = event.state[INDEX_KEY] - current_history_index; - navigate({ + await navigate({ url: new URL(location.href), scroll: scroll_positions[event.state[INDEX_KEY]], keepfocus: false, @@ -1543,7 +1572,12 @@ export function create_client({ target, base }) { history.go(-delta); }, type: 'popstate', - delta + delta, + previous_history_index: current_history_index + }); + + snapshots[current_history_index].forEach((value, i) => { + components[i].snapshot?.restore(value); }); } }); diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte index c83685a9e35c..474a855c1e4b 100644 --- a/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte @@ -3,7 +3,7 @@ export const snapshot = { capture: () => message, - apply: (snapshot) => (message = snapshot) + restore: (snapshot) => (message = snapshot) }; From 1c9760a87f44ad898e9628fd3bc4da54e9ea10fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:52:44 -0500 Subject: [PATCH 06/25] beef up test --- .../kit/test/apps/basics/test/client.test.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 320df211e830..48bd82839115 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -611,16 +611,28 @@ test.describe('env in app.html', () => { test.describe('Snapshots', () => { test.only('recovers snapshotted data', async ({ page, clicknav }) => { await page.goto('/snapshot/a'); - await page.locator('input').type('hello'); + + let input = page.locator('input'); + await input.type('hello'); await clicknav('[href="/snapshot/b"]'); await page.goBack(); - expect(await page.locator('input').inputValue()).toBe('hello'); - await page.locator('input').type('works for cross-document navigations'); + input = page.locator('input'); + expect(await input.inputValue()).toBe('hello'); + + await input.clear(); + await input.type('works for cross-document navigations'); await clicknav('[href="/snapshot/c"]'); await page.goBack(); expect(await page.locator('input').inputValue()).toBe('works for cross-document navigations'); + + input = page.locator('input'); + await input.clear(); + await input.type('works for reloads'); + + await page.reload(); + expect(await page.locator('input').inputValue()).toBe('works for reloads'); }); }); From 0ce333fb70301c9303643e81823b84865d57dd16 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:56:04 -0500 Subject: [PATCH 07/25] tidy up --- packages/kit/src/runtime/client/client.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 209784a13bb2..bbdfe369db91 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -200,8 +200,7 @@ export function create_client({ target, base }) { } }, blocked: () => {}, - type: 'goto', - previous_history_index: current_history_index + type: 'goto' }); } @@ -1080,7 +1079,6 @@ export function create_client({ target, base }) { * type: import('types').NavigationType; * delta?: number; * nav_token?: {}; - * previous_history_index: number; * accepted: () => void; * blocked: () => void; * }} opts @@ -1094,7 +1092,6 @@ export function create_client({ target, base }) { type, delta, nav_token, - previous_history_index, accepted, blocked }) { @@ -1106,8 +1103,11 @@ export function create_client({ target, base }) { return; } + // TODO update scroll positions at same time as snapshot? update_scroll_positions(current_history_index); + const previous_history_index = current_history_index; + accepted(); navigating = true; @@ -1490,8 +1490,7 @@ export function create_client({ target, base }) { }, accepted: () => event.preventDefault(), blocked: () => event.preventDefault(), - type: 'link', - previous_history_index: current_history_index + type: 'link' }); }); @@ -1546,8 +1545,7 @@ export function create_client({ target, base }) { nav_token: {}, accepted: () => {}, blocked: () => {}, - type: 'form', - previous_history_index: current_history_index + type: 'form' }); }); @@ -1572,8 +1570,7 @@ export function create_client({ target, base }) { history.go(-delta); }, type: 'popstate', - delta, - previous_history_index: current_history_index + delta }); snapshots[current_history_index].forEach((value, i) => { From 58a8ae6e9da51b3ae9111ca3907ae1653e4169ff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:56:28 -0500 Subject: [PATCH 08/25] fix type --- packages/kit/src/runtime/client/session-storage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/runtime/client/session-storage.js b/packages/kit/src/runtime/client/session-storage.js index 4b5db6eb09cb..dc2639ef1b5e 100644 --- a/packages/kit/src/runtime/client/session-storage.js +++ b/packages/kit/src/runtime/client/session-storage.js @@ -12,6 +12,7 @@ export function get(key) { /** * Write a value to `sessionStorage` + * @param {string} key * @param {any} value */ export function set(key, value) { From 808b821c2ca8e41ebad59bb78bc1bce3fde6faa6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 17:56:50 -0500 Subject: [PATCH 09/25] run all tests --- packages/kit/test/apps/basics/test/client.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 48bd82839115..0d6b6ea46a77 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -609,7 +609,7 @@ test.describe('env in app.html', () => { }); test.describe('Snapshots', () => { - test.only('recovers snapshotted data', async ({ page, clicknav }) => { + test('recovers snapshotted data', async ({ page, clicknav }) => { await page.goto('/snapshot/a'); let input = page.locator('input'); From 4821267b221536460adc98622164bbf78fb82a67 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 18:18:38 -0500 Subject: [PATCH 10/25] err.... --- turbo.json | 1 - 1 file changed, 1 deletion(-) diff --git a/turbo.json b/turbo.json index 7a422b757850..bb68b16b18b7 100644 --- a/turbo.json +++ b/turbo.json @@ -43,7 +43,6 @@ "outputMode": "new-only" }, "test": { - "dependsOn": ["^build"], "inputs": ["src/**", "scripts/**", "shared/**", "templates/**", "test/**"], "outputs": ["coverage/", "test-results/**"], "outputMode": "new-only", From 764480de869edcd1dd79c007a8f449f616ef2512 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 19:14:11 -0500 Subject: [PATCH 11/25] lint --- packages/kit/src/runtime/client/client.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index bbdfe369db91..721cb44e58fd 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -125,8 +125,6 @@ export function create_client({ target, base }) { ); } - const initial_history_index = current_history_index; - // if we reload the page, or Cmd-Shift-T back to it, // recover scroll position const scroll = scroll_positions[current_history_index]; From 0b25fefdc7fdc691ba5690940045ddf556d0be1c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 24 Jan 2023 19:49:54 -0500 Subject: [PATCH 12/25] account for missing layouts etc --- packages/kit/src/runtime/client/client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 721cb44e58fd..09082622ba1e 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -314,7 +314,7 @@ export function create_client({ target, base }) { // `previous_history_index` will be undefined for invalidation if (previous_history_index) { - snapshots[previous_history_index] = components.map((c) => c.snapshot?.capture()); + snapshots[previous_history_index] = components.map((c) => c?.snapshot?.capture()); } if (opts && opts.details) { @@ -404,7 +404,7 @@ export function create_client({ target, base }) { }); snapshots[current_history_index]?.forEach((value, i) => { - components[i].snapshot?.restore(value); + components[i]?.snapshot?.restore(value); }); /** @type {import('types').AfterNavigate} */ @@ -1399,7 +1399,7 @@ export function create_client({ target, base }) { update_scroll_positions(current_history_index); storage.set(SCROLL_KEY, scroll_positions); - snapshots[current_history_index] = components.map((c) => c.snapshot?.capture()); + snapshots[current_history_index] = components.map((c) => c?.snapshot?.capture()); storage.set(SNAPSHOT_KEY, snapshots); } }); @@ -1572,7 +1572,7 @@ export function create_client({ target, base }) { }); snapshots[current_history_index].forEach((value, i) => { - components[i].snapshot?.restore(value); + components[i]?.snapshot?.restore(value); }); } }); From 665fcb3cc0ec63e2949a920b5d8b51ed23018574 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 25 Jan 2023 09:30:26 -0500 Subject: [PATCH 13/25] thank you, past us, for creating accessors for export const automatically --- packages/kit/src/exports/vite/index.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5883e4841ac6..409f2399b6a8 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -132,23 +132,6 @@ export async function sveltekit() { hydratable: true, ...svelte_config.compilerOptions }, - experimental: { - dynamicCompileOptions: ({ filename, code, compileOptions }) => { - const options = - svelte_config.vitePlugin?.experimental?.dynamicCompileOptions?.({ - filename, - code, - compileOptions - }) ?? {}; - - const basename = path.basename(filename); - if (basename.startsWith('+layout') || basename.startsWith('+page')) { - options.accessors = true; - } - - return options; - } - }, ...svelte_config.vitePlugin }; From dea4d35a211663f274751dd059bd4a3a4e9b8e95 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 25 Jan 2023 10:43:52 -0500 Subject: [PATCH 14/25] don't restore if navigation was blocked --- packages/kit/src/runtime/client/client.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 09082622ba1e..5da79c64e9fa 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1554,6 +1554,7 @@ export function create_client({ target, base }) { if (event.state[INDEX_KEY] === current_history_index) return; const delta = event.state[INDEX_KEY] - current_history_index; + let blocked = false; await navigate({ url: new URL(location.href), @@ -1566,14 +1567,17 @@ export function create_client({ target, base }) { }, blocked: () => { history.go(-delta); + blocked = true; }, type: 'popstate', delta }); - snapshots[current_history_index].forEach((value, i) => { - components[i]?.snapshot?.restore(value); - }); + if (!blocked) { + snapshots[current_history_index].forEach((value, i) => { + components[i]?.snapshot?.restore(value); + }); + } } }); From 25994a80a2b4d4b8885e404ac40f3e855459f269 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 25 Jan 2023 12:11:21 -0500 Subject: [PATCH 15/25] update scroll position at same time as snapshot --- packages/kit/src/runtime/client/client.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 5da79c64e9fa..d189e086267c 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -314,6 +314,7 @@ export function create_client({ target, base }) { // `previous_history_index` will be undefined for invalidation if (previous_history_index) { + update_scroll_positions(previous_history_index); snapshots[previous_history_index] = components.map((c) => c?.snapshot?.capture()); } @@ -1101,9 +1102,7 @@ export function create_client({ target, base }) { return; } - // TODO update scroll positions at same time as snapshot? - update_scroll_positions(current_history_index); - + // store this before calling `accepted()`, which may change the index const previous_history_index = current_history_index; accepted(); From 75cfbcf59e9335382830e38a42ffa0bcb91dab05 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 26 Jan 2023 15:09:22 -0500 Subject: [PATCH 16/25] DRY --- packages/kit/src/runtime/client/client.js | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index d91c6db3cfc5..0ade212aa544 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -160,6 +160,18 @@ export function create_client({ target, base }) { await update(intent, url, []); } + /** @param {number} index */ + function capture_snapshot(index) { + snapshots[index] = components.map((c) => c?.snapshot?.capture()); + } + + /** @param {number} index */ + function restore_snapshot(index) { + snapshots[index]?.forEach((value, i) => { + components[i]?.snapshot?.restore(value); + }); + } + /** * @param {string | URL} url * @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts @@ -315,7 +327,7 @@ export function create_client({ target, base }) { // `previous_history_index` will be undefined for invalidation if (previous_history_index) { update_scroll_positions(previous_history_index); - snapshots[previous_history_index] = components.map((c) => c?.snapshot?.capture()); + capture_snapshot(previous_history_index); } if (opts && opts.details) { @@ -404,9 +416,7 @@ export function create_client({ target, base }) { hydrate: true }); - snapshots[current_history_index]?.forEach((value, i) => { - components[i]?.snapshot?.restore(value); - }); + restore_snapshot(current_history_index); /** @type {import('types').AfterNavigate} */ const navigation = { @@ -1398,7 +1408,7 @@ export function create_client({ target, base }) { update_scroll_positions(current_history_index); storage.set(SCROLL_KEY, scroll_positions); - snapshots[current_history_index] = components.map((c) => c?.snapshot?.capture()); + capture_snapshot(current_history_index); storage.set(SNAPSHOT_KEY, snapshots); } }); @@ -1584,9 +1594,7 @@ export function create_client({ target, base }) { }); if (!blocked) { - snapshots[current_history_index].forEach((value, i) => { - components[i]?.snapshot?.restore(value); - }); + restore_snapshot(current_history_index); } } }); From fe62d2ba2a193cd27f4d00b1f8c9408be028944a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 26 Jan 2023 15:31:49 -0500 Subject: [PATCH 17/25] types --- .../kit/src/core/sync/write_types/index.js | 35 ++++++++++--------- .../basics/src/routes/snapshot/a/+page.svelte | 1 + packages/kit/types/index.d.ts | 8 +++++ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 2d90123ddbb6..8f58ef4d1cf9 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -198,23 +198,26 @@ function update_types(config, routes, route, to_delete = new Set()) { // These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future if (route.layout || route.leaf) { - // If T extends the empty object, void is also allowed as a return type - declarations.push(`type MaybeWithVoid = {} extends T ? T | void : T;`); - // Returns the key of the object whose values are required. declarations.push( - `export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];` - ); - // Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required. - // If none, void is also allowed as a return type. - declarations.push( - `type OutputDataShape = MaybeWithVoid> & Partial> & Record>` - ); - // null & {} == null, we need to prevent that in some situations - declarations.push(`type EnsureDefined = T extends null | undefined ? {} : T;`); - // Takes a union type and returns a union type where each type also has all properties - // of all possible types (typed as undefined), making accessing them more ergonomic - declarations.push( - `type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never;` + // If T extends the empty object, void is also allowed as a return type + `type MaybeWithVoid = {} extends T ? T | void : T;`, + + // Returns the key of the object whose values are required. + `export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];`, + + // Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required. + // If none, void is also allowed as a return type. + `type OutputDataShape = MaybeWithVoid> & Partial> & Record>`, + + // null & {} == null, we need to prevent that in some situations + `type EnsureDefined = T extends null | undefined ? {} : T;`, + + // Takes a union type and returns a union type where each type also has all properties + // of all possible types (typed as undefined), making accessing them more ergonomic + `type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never;`, + + // Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer from the return type of `snapshot.capture` + `export type Snapshot = Kit.Snapshot;` ); } diff --git a/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte index 474a855c1e4b..647734468d40 100644 --- a/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/snapshot/a/+page.svelte @@ -1,6 +1,7 @@ + +
+