Skip to content

Commit

Permalink
feat: implement shallow routing
Browse files Browse the repository at this point in the history
closes #2673
  • Loading branch information
dummdidumm committed Jan 25, 2023
1 parent 4df53c7 commit c8b99dd
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 15 deletions.
61 changes: 46 additions & 15 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export function create_client({ target, base }) {

/**
* @param {string | URL} url
* @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts
* @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean; skipLoad?: boolean }} opts
* @param {string[]} redirect_chain
* @param {{}} [nav_token]
*/
Expand All @@ -171,7 +171,8 @@ export function create_client({ target, base }) {
replaceState = false,
keepFocus = false,
state = {},
invalidateAll = false
invalidateAll = false,
skipLoad = false
},
redirect_chain,
nav_token
Expand All @@ -184,6 +185,7 @@ export function create_client({ target, base }) {
url,
scroll: noScroll ? scroll_state() : null,
keepfocus: keepFocus,
skip_load: skipLoad,
redirect_chain,
details: {
state,
Expand Down Expand Up @@ -238,13 +240,13 @@ export function create_client({ target, base }) {
* @param {import('./types').NavigationIntent | undefined} intent
* @param {URL} url
* @param {string[]} redirect_chain
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, skip_load: 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) {
token = nav_token;
let navigation_result = intent && (await load_route(intent));
let navigation_result = intent && (await load_route(intent, opts?.skip_load));

if (!navigation_result) {
navigation_result = await server_fallback(
Expand Down Expand Up @@ -474,6 +476,9 @@ export function create_client({ target, base }) {
p += 1;
}

// data_changed could be true if more nodes were added, but the data stayed the same
data_changed = Object.keys(result.props).some((key) => key.startsWith('data_'));

const page_changed =
!current.url ||
url.href !== current.url.href ||
Expand Down Expand Up @@ -510,10 +515,11 @@ export function create_client({ target, base }) {
* params: Record<string, string>;
* route: { id: string | null };
* server_data_node: import('./types').DataNode | null;
* skip_load?: boolean;
* }} options
* @returns {Promise<import('./types').BranchNode>}
*/
async function load_node({ loader, parent, url, params, route, server_data_node }) {
async function load_node({ loader, parent, url, params, route, server_data_node, skip_load }) {
/** @type {Record<string, any> | null} */
let data = null;

Expand All @@ -533,6 +539,11 @@ export function create_client({ target, base }) {
}

if (node.universal?.load) {
if (DEV && skip_load) {
// TODO warning instead?
throw new Error('Cannot skip load when some data needs to be loaded');
}

/** @param {string[]} deps */
function depends(...deps) {
for (const dep of deps) {
Expand Down Expand Up @@ -699,9 +710,10 @@ export function create_client({ target, base }) {

/**
* @param {import('./types').NavigationIntent} intent
* @param {boolean} [skip_load]
* @returns {Promise<import('./types').NavigationResult>}
*/
async function load_route({ id, invalidating, url, params, route }) {
async function load_route({ id, invalidating, url, params, route }, skip_load) {
if (load_cache?.id === id) {
return load_cache.promise;
}
Expand All @@ -728,19 +740,24 @@ export function create_client({ target, base }) {
const invalid =
!!loader?.[0] &&
(previous?.loader !== loader[1] ||
has_changed(
acc.some(Boolean),
route_changed,
url_changed,
previous.server?.uses,
params
));
(!skip_load &&
has_changed(
acc.some(Boolean),
route_changed,
url_changed,
previous.server?.uses,
params
)));

acc.push(invalid);
return acc;
}, /** @type {boolean[]} */ ([]));

if (invalid_server_nodes.some(Boolean)) {
if (DEV && skip_load) {
// TODO warning instead?
throw new Error('Cannot skip load when some data needs to be loaded');
}
try {
server_data = await load_data(url, invalid_server_nodes);
} catch (error) {
Expand Down Expand Up @@ -773,7 +790,14 @@ export function create_client({ target, base }) {
const valid =
(!server_data_node || server_data_node.type === 'skip') &&
loader[1] === previous?.loader &&
!has_changed(parent_changed, route_changed, url_changed, previous.universal?.uses, params);
(skip_load ||
!has_changed(
parent_changed,
route_changed,
url_changed,
previous.universal?.uses,
params
));
if (valid) return previous;

parent_changed = true;
Expand All @@ -800,7 +824,8 @@ export function create_client({ target, base }) {
// and if current loader uses server data, we want to reuse previous data.
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
previous?.server
)
),
skip_load
});
});

Expand Down Expand Up @@ -1049,6 +1074,7 @@ export function create_client({ target, base }) {
* url: URL;
* scroll: { x: number, y: number } | null;
* keepfocus: boolean;
* skip_load: boolean;
* redirect_chain: string[];
* details: {
* replaceState: boolean;
Expand All @@ -1065,6 +1091,7 @@ export function create_client({ target, base }) {
url,
scroll,
keepfocus,
skip_load,
redirect_chain,
details,
type,
Expand Down Expand Up @@ -1097,6 +1124,7 @@ export function create_client({ target, base }) {
redirect_chain,
{
scroll,
skip_load,
keepfocus,
details
},
Expand Down Expand Up @@ -1459,6 +1487,7 @@ export function create_client({ target, base }) {
url,
scroll: options.noscroll ? scroll_state() : null,
keepfocus: false,
skip_load: false, // TODO data-sveltekit-skipload?
redirect_chain: [],
details: {
state: {},
Expand Down Expand Up @@ -1513,6 +1542,7 @@ export function create_client({ target, base }) {
url,
scroll: noscroll ? scroll_state() : null,
keepfocus: false,
skip_load: false,
redirect_chain: [],
details: {
state: {},
Expand All @@ -1537,6 +1567,7 @@ export function create_client({ target, base }) {
url: new URL(location.href),
scroll: scroll_positions[event.state[INDEX_KEY]],
keepfocus: false,
skip_load: false, // TODO should popstate skip load if navigation to it was with skipLoad?
redirect_chain: [],
details: null,
accepted: () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('./$types').LayoutLoad} */
export async function load({ url }) {
url.pathname; // force rerun on every page change
return {
random: Math.random()
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import { page } from '$app/stores';
export let data;
</script>

<a href="/load/skip-load">/load/skip-load</a>
<a href="/load/skip-load/inner">/load/skip-load/inner</a>
<p class="skip-layout">{data.random}</p>
<pre>{JSON.stringify($page.data)}</pre>
<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('./$types').PageServerLoad} */
export async function load({ url }) {
url.search; // force rerun on every query change
return {
pageRandom: Math.random()
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
export let data;
</script>

<p class="skip-page">Skip load: {data.pageRandom}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p class="skip-page">Skip load inner</p>
26 changes: 26 additions & 0 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,32 @@ test.describe('Load', () => {
expect(did_request_data).toBe(false);
});

test('skipLoad skips loading data', async ({ page, app }) => {
await page.goto('/load/skip-load');

const page_content = await page.textContent('p.skip-page');
const layout_content = await page.textContent('p.skip-layout');
const store_content = await page.textContent('pre');

expect(page_content).toMatch(/Skip load: 0\.\d+/);
expect(layout_content).toMatch(/0\.\d+/);
expect(store_content).toMatch(
/{"foo":{"bar":"Custom layout"},"random":0\.\d+,"pageRandom":0\.\d+}/
);

await app.goto('/load/skip-load?foo', { skipLoad: true });
expect(await page.textContent('p.skip-page')).toBe(page_content);
expect(await page.textContent('p.skip-layout')).toBe(layout_content);
expect(await page.textContent('pre')).toBe(store_content);

await app.goto('/load/skip-load/inner', { skipLoad: true });
expect(await page.textContent('p.skip-page')).toBe('Skip load inner');
expect(await page.textContent('p.skip-layout')).toBe(layout_content);
expect(await page.textContent('pre')).toBe(
store_content.slice(0, store_content.indexOf(',"pageRandom"')) + '}'
);
});

if (process.env.DEV) {
test('using window.fetch causes a warning', async ({ page, baseURL }) => {
await Promise.all([
Expand Down
4 changes: 4 additions & 0 deletions packages/kit/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ declare module '$app/navigation' {
* If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#invalidation for more info on invalidation.
*/
invalidateAll?: boolean;
/**
* If `true`, will not run any `load` functions of the page. This only makes sense if you're navigating to a page where all `load` function results are already available.
*/
skipLoad?: boolean;
}
): Promise<void>;
/**
Expand Down

0 comments on commit c8b99dd

Please sign in to comment.