Skip to content

future-proof invalidate() #6493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ninety-tables-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[breaking] call `invalidate(fn)` predicates with a URL instead of a string
5 changes: 5 additions & 0 deletions .changeset/rude-nails-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[breaking] replace invalidate() with invalidateAll()
5 changes: 5 additions & 0 deletions .changeset/shiny-vans-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-svelte': patch
---

Use `invalidateAll()`
10 changes: 8 additions & 2 deletions documentation/docs/05-load.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,14 @@ An instance of [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), co

#### depends

This function declares that the `load` function has a _dependency_ on one or more URLs, which can subsequently be used with [`invalidate()`](/docs/modules#$app-navigation-invalidate) to cause `load` to rerun.
This function declares that the `load` function has a _dependency_ on one or more URLs or custom identifiers, which can subsequently be used with [`invalidate()`](/docs/modules#$app-navigation-invalidate) to cause `load` to rerun.

Most of the time you won't need this, as `fetch` calls `depends` on your behalf — it's only necessary if you're using a custom API client that bypasses `fetch`.

URLs can be absolute or relative to the page being loaded, and must be [encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding).

Custom identifiers have to be prefixed with one or more lowercase letters followed by a colon to conform to the [URI specification](https://www.rfc-editor.org/rfc/rfc3986.html)

```js
// @filename: ambient.d.ts
declare module '$lib/api' {
Expand All @@ -121,7 +123,11 @@ import * as api from '$lib/api';

/** @type {import('./$types').PageLoad} */
export async function load({ depends }) {
depends(`${api.base}/foo`, `${api.base}/bar`);
depends(
`${api.base}/foo`,
`${api.base}/bar`,
'my-stuff:foo'
);

return {
foo: api.client.get('/foo'),
Expand Down
4 changes: 2 additions & 2 deletions packages/create-svelte/templates/default/src/lib/form.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invalidate } from '$app/navigation';
import { invalidateAll } from '$app/navigation';

// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
Expand Down Expand Up @@ -83,7 +83,7 @@ export function enhance(

if (response.ok) {
if (result) result({ data, form, response });
invalidate();
invalidateAll();
} else if (error) {
error({ data, form, error: null, response });
} else {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const disableScrollHandling = ssr
: client.disable_scroll_handling;
export const goto = ssr ? guard('goto') : client.goto;
export const invalidate = ssr ? guard('invalidate') : client.invalidate;
export const invalidateAll = ssr ? guard('invalidateAll') : client.invalidateAll;
export const prefetch = ssr ? guard('prefetch') : client.prefetch;
export const prefetchRoutes = ssr ? guard('prefetchRoutes') : client.prefetch_routes;
export const beforeNavigate = ssr ? () => {} : client.before_navigate;
Expand Down
51 changes: 32 additions & 19 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function check_for_removed_attributes() {
* @returns {import('./types').Client}
*/
export function create_client({ target, base, trailing_slash }) {
/** @type {Array<((href: string) => boolean)>} */
/** @type {Array<((url: URL) => boolean)>} */
const invalidated = [];

/** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
Expand Down Expand Up @@ -103,6 +103,7 @@ export function create_client({ target, base, trailing_slash }) {

/** @type {Promise<void> | null} */
let invalidating = null;
let force_invalidation = false;

/** @type {import('svelte').SvelteComponent} */
let root;
Expand Down Expand Up @@ -139,6 +140,19 @@ export function create_client({ target, base, trailing_slash }) {
/** @type {{}} */
let token;

function invalidate() {
if (!invalidating) {
invalidating = Promise.resolve().then(async () => {
await update(new URL(location.href), []);

invalidating = null;
force_invalidation = false;
});
}

return invalidating;
}

/**
* @param {string | URL} url
* @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
Expand Down Expand Up @@ -639,6 +653,8 @@ export function create_client({ target, base, trailing_slash }) {
* @param {{ url: boolean, params: string[] }} changed
*/
function has_changed(changed, parent_changed, uses) {
if (force_invalidation) return true;

if (!uses) return false;

if (uses.parent && parent_changed) return true;
Expand All @@ -648,8 +664,8 @@ export function create_client({ target, base, trailing_slash }) {
if (uses.params.has(param)) return true;
}

for (const dep of uses.dependencies) {
if (invalidated.some((fn) => fn(dep))) return true;
for (const href of uses.dependencies) {
if (invalidated.some((fn) => fn(new URL(href)))) return true;
}

return false;
Expand Down Expand Up @@ -1057,28 +1073,25 @@ export function create_client({ target, base, trailing_slash }) {

invalidate: (resource) => {
if (resource === undefined) {
// Force rerun of all load functions, regardless of their dependencies
for (const node of current.branch) {
node?.server?.uses.dependencies.add('');
node?.shared?.uses.dependencies.add('');
}
invalidated.push(() => true);
} else if (typeof resource === 'function') {
// TODO remove for 1.0
throw new Error(
'`invalidate()` (with no arguments) has been replaced by `invalidateAll()`'
);
}

if (typeof resource === 'function') {
invalidated.push(resource);
} else {
const { href } = new URL(resource, location.href);
invalidated.push((dep) => dep === href);
invalidated.push((url) => url.href === href);
}

if (!invalidating) {
invalidating = Promise.resolve().then(async () => {
await update(new URL(location.href), []);

invalidating = null;
});
}
return invalidate();
},

return invalidating;
invalidateAll: () => {
force_invalidation = true;
return invalidate();
},

prefetch: async (href) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/src/runtime/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
beforeNavigate,
goto,
invalidate,
invalidateAll,
prefetch,
prefetchRoutes
} from '$app/navigation';
Expand All @@ -17,6 +18,7 @@ export interface Client {
disable_scroll_handling: () => void;
goto: typeof goto;
invalidate: typeof invalidate;
invalidateAll: typeof invalidateAll;
prefetch: typeof prefetch;
prefetch_routes: typeof prefetchRoutes;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { invalidate } from '$app/navigation';
import { invalidate, invalidateAll } from '$app/navigation';

/** @type {import('./$types').PageData} */
export let data;
Expand All @@ -10,15 +10,15 @@
<button
on:click={async () => {
window.invalidated = false;
await invalidate((dep) => dep.includes('change-detection/data.json'));
await invalidate((url) => url.pathname.includes('change-detection/data.json'));
window.invalidated = true;
}}>invalidate change-detection/data.json</button
>

<button
on:click={async () => {
window.invalidated = false;
await invalidate();
await invalidateAll();
window.invalidated = true;
}}>invalidate all</button
>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script>
import { invalidate } from '$app/navigation';
import { invalidateAll } from '$app/navigation';

/** @type {import('./$types').PageData} */
export let data;
</script>

<h1>a: {data.a}, b: {data.b}</h1>

<button on:click={() => invalidate()}>invalidate</button>
<button on:click={invalidateAll}>invalidate</button>
21 changes: 18 additions & 3 deletions packages/kit/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,25 @@ declare module '$app/navigation' {
opts?: { replaceState?: boolean; noscroll?: boolean; keepfocus?: boolean; state?: any }
): Promise<void>;
/**
* Causes any `load` functions belonging to the currently active page to re-run if they `fetch` the resource in question. If no argument is given, all resources will be invalidated. Returns a `Promise` that resolves when the page is subsequently updated.
* @param dependency The invalidated resource
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
*
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
*
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
*
* ```ts
* // Example: Match '/path' regardless of the query parameters
* invalidate((url) => url.pathname === '/path');
* ```
* @param url The invalidated URL
*/
export function invalidate(url: string | URL | ((url: URL) => boolean)): Promise<void>;
/**
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
*/
export function invalidate(dependency?: string | ((href: string) => boolean)): Promise<void>;
export function invalidateAll(): Promise<void>;
/**
* Programmatically prefetches the given page, which means
* 1. ensuring that the code for the page is loaded, and
Expand Down