Skip to content

Commit b9d709a

Browse files
authored
keep browser stores in always-accessible singleton (#6100)
* keep browser stores in always-accessible singleton * update docs * add test * add changeset * update getStores docs
1 parent 0842fb3 commit b9d709a

File tree

8 files changed

+56
-29
lines changed

8 files changed

+56
-29
lines changed

.changeset/gentle-years-leave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Allow `$app/stores` to be used from anywhere on the browser

packages/kit/src/core/sync/write_root.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function write_root(manifest_data, output) {
4343
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
4444
<script>
4545
import { setContext, afterUpdate, onMount } from 'svelte';
46+
import { browser } from '$app/env';
4647
4748
// stores
4849
export let stores;
@@ -52,7 +53,9 @@ export function write_root(manifest_data, output) {
5253
${levels.map((l) => `export let data_${l} = null;`).join('\n\t\t\t\t')}
5354
export let errors;
5455
55-
setContext('__svelte__', stores);
56+
if (!browser) {
57+
setContext('__svelte__', stores);
58+
}
5659
5760
$: stores.page.set(page);
5861
afterUpdate(stores.page.notify);

packages/kit/src/runtime/app/stores.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getContext } from 'svelte';
22
import { browser } from './env.js';
3+
import { stores as browser_stores } from '../client/singletons.js';
34

45
// TODO remove this (for 1.0? after 1.0?)
56
let warned = false;
@@ -15,7 +16,7 @@ export function stores() {
1516
* @type {import('$app/stores').getStores}
1617
*/
1718
export const getStores = () => {
18-
const stores = getContext('__svelte__');
19+
const stores = browser ? browser_stores : getContext('__svelte__');
1920

2021
const readonly_stores = {
2122
page: {

packages/kit/src/runtime/client/client.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import { onMount, tick } from 'svelte';
2-
import { writable } from 'svelte/store';
32
import { normalize_error } from '../../utils/error.js';
43
import { LoadURL, decode_params, normalize_path } from '../../utils/url.js';
5-
import {
6-
create_updated_store,
7-
find_anchor,
8-
get_base_uri,
9-
get_href,
10-
notifiable_store,
11-
scroll_state
12-
} from './utils.js';
4+
import { find_anchor, get_base_uri, get_href, scroll_state } from './utils.js';
135
import { lock_fetch, unlock_fetch, initial_fetch, native_fetch } from './fetcher.js';
146
import { parse } from './parse.js';
157
import { error } from '../../index/index.js';
168

179
import Root from '__GENERATED__/root.svelte';
1810
import { nodes, dictionary, matchers } from '__GENERATED__/client-manifest.js';
1911
import { HttpError, Redirect } from '../../index/private.js';
12+
import { stores } from './singletons.js';
2013

2114
const SCROLL_KEY = 'sveltekit:scroll';
2215
const INDEX_KEY = 'sveltekit:index';
@@ -59,13 +52,6 @@ export function create_client({ target, base, trailing_slash }) {
5952
/** @type {Array<((href: string) => boolean)>} */
6053
const invalidated = [];
6154

62-
const stores = {
63-
url: notifiable_store({}),
64-
page: notifiable_store({}),
65-
navigating: writable(/** @type {import('types').Navigation | null} */ (null)),
66-
updated: create_updated_store()
67-
};
68-
6955
/** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
7056
const load_cache = {
7157
id: null,

packages/kit/src/runtime/client/singletons.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { writable } from 'svelte/store';
2+
import { create_updated_store, notifiable_store } from './utils.js';
3+
14
/** @type {import('./types').Client} */
25
export let client;
36

@@ -9,3 +12,10 @@ export let client;
912
export function init(opts) {
1013
client = opts.client;
1114
}
15+
16+
export const stores = {
17+
url: notifiable_store({}),
18+
page: notifiable_store({}),
19+
navigating: writable(/** @type {import('types').Navigation | null} */ (null)),
20+
updated: create_updated_store()
21+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import { getStores } from '$app/stores';
3+
4+
let pathname;
5+
</script>
6+
7+
<h1>{pathname}</h1>
8+
9+
<button
10+
on:click={() => {
11+
getStores().page.subscribe(($page) => pathname = $page.url.pathname)();
12+
}}>click</button
13+
>

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,3 +585,10 @@ test('Can use browser-only global on client-only page', async ({ page, read_erro
585585
await expect(page.locator('p')).toHaveText('Works');
586586
expect(read_errors('/no-ssr/browser-only-global')).toBe(undefined);
587587
});
588+
589+
test('can use $app/stores from anywhere on client', async ({ page }) => {
590+
await page.goto('/store/client-access');
591+
await expect(page.locator('h1')).toHaveText('undefined');
592+
await page.click('button');
593+
await expect(page.locator('h1')).toHaveText('/store/client-access');
594+
});

packages/kit/types/ambient.d.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -216,24 +216,16 @@ declare module '$app/paths' {
216216
* import { getStores, navigating, page, updated } from '$app/stores';
217217
* ```
218218
*
219-
* Stores are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `page` is unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously.
219+
* Stores on the server are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `page` is unique to each request, rather than shared between multiple requests handled by the same server simultaneously.
220220
*
221221
* Because of that, you must subscribe to the stores during component initialization (which happens automatically if you reference the store value, e.g. as `$page`, in a component) before you can use them.
222+
*
223+
* In the browser, we don't need to worry about this, and stores can be accessed from anywhere. Code that will only ever run on the browser can refer to (or subscribe to) any of these stores at any time.
222224
*/
223225
declare module '$app/stores' {
224226
import { Readable } from 'svelte/store';
225227
import { Navigation, Page } from '@sveltejs/kit';
226228

227-
/**
228-
* A convenience function around `getContext`. Must be called during component initialization.
229-
* Only use this if you need to defer store subscription until after the component has mounted, for some reason.
230-
*/
231-
export function getStores(): {
232-
navigating: typeof navigating;
233-
page: typeof page;
234-
updated: typeof updated;
235-
};
236-
237229
/**
238230
* A readable store whose value contains page data.
239231
*/
@@ -248,6 +240,16 @@ declare module '$app/stores' {
248240
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
249241
*/
250242
export const updated: Readable<boolean> & { check: () => boolean };
243+
244+
/**
245+
* A function that returns all of the contextual stores. On the server, this must be called during component initialization.
246+
* Only use this if you need to defer store subscription until after the component has mounted, for some reason.
247+
*/
248+
export function getStores(): {
249+
navigating: typeof navigating;
250+
page: typeof page;
251+
updated: typeof updated;
252+
};
251253
}
252254

253255
/**

0 commit comments

Comments
 (0)