diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 884145f783..46a673f9a0 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -76,7 +76,8 @@ }, "./store": { "types": "./types/index.d.ts", - "default": "./src/store/index.js" + "browser": "./src/store/index-client.js", + "default": "./src/store/index-server.js" }, "./transition": { "types": "./types/index.d.ts", diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index c3b4177579..c21ef42f99 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -1,7 +1,7 @@ /** @import { Task } from '#client' */ /** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */ /** @import { Spring } from './public.js' */ -import { writable } from '../store/index.js'; +import { writable } from '../store/shared/index.js'; import { loop } from '../internal/client/loop.js'; import { raf } from '../internal/client/timing.js'; import { is_date } from './utils.js'; diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index a1ec502fc8..a264cc9da5 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -1,7 +1,7 @@ /** @import { Task } from '../internal/client/types' */ /** @import { Tweened } from './public' */ /** @import { TweenedOptions } from './private' */ -import { writable } from '../store/index.js'; +import { writable } from '../store/shared/index.js'; import { raf } from '../internal/client/timing.js'; import { loop } from '../internal/client/loop.js'; import { linear } from '../easing/index.js'; diff --git a/packages/svelte/src/store/index-client.js b/packages/svelte/src/store/index-client.js new file mode 100644 index 0000000000..b612124e3c --- /dev/null +++ b/packages/svelte/src/store/index-client.js @@ -0,0 +1,165 @@ +/** @import { Readable, Writable } from './public.js' */ +import { noop } from '../internal/shared/utils.js'; +import { + effect_root, + effect_tracking, + render_effect +} from '../internal/client/reactivity/effects.js'; +import { source } from '../internal/client/reactivity/sources.js'; +import { get as get_source, tick } from '../internal/client/runtime.js'; +import { increment } from '../reactivity/utils.js'; +import { get, writable } from './shared/index.js'; + +export { derived, get, readable, readonly, writable } from './shared/index.js'; + +/** + * @template V + * @overload + * @param {() => V} get + * @param {(v: V) => void} set + * @returns {Writable} + */ +/** + * @template V + * @overload + * @param {() => V} get + * @returns {Readable} + */ +/** + * Create a store from a function that returns state, and (to make a writable store), an + * optional second function that sets state. + * + * ```ts + * import { toStore } from 'svelte/store'; + * + * let count = $state(0); + * + * const store = toStore(() => count, (v) => (count = v)); + * ``` + * @template V + * @param {() => V} get + * @param {(v: V) => void} [set] + * @returns {Writable | Readable} + */ +export function toStore(get, set) { + const store = writable(get(), (set) => { + let ran = false; + + // TODO do we need a different implementation on the server? + const teardown = effect_root(() => { + render_effect(() => { + const value = get(); + if (ran) set(value); + }); + }); + + ran = true; + + return teardown; + }); + + if (set) { + return { + set, + update: (fn) => set(fn(get())), + subscribe: store.subscribe + }; + } + + return { + subscribe: store.subscribe + }; +} + +/** + * @template V + * @overload + * @param {Writable} store + * @returns {{ current: V }} + */ +/** + * @template V + * @overload + * @param {Readable} store + * @returns {{ readonly current: V }} + */ +/** + * Convert a store to an object with a reactive `current` property. If `store` + * is a readable store, `current` will be a readonly property. + * + * ```ts + * import { fromStore, get, writable } from 'svelte/store'; + * + * const store = writable(0); + * + * const count = fromStore(store); + * + * count.current; // 0; + * store.set(1); + * count.current; // 1 + * + * count.current += 1; + * get(store); // 2 + * ``` + * @template V + * @param {Writable | Readable} store + */ +export function fromStore(store) { + let value = /** @type {V} */ (undefined); + let version = source(0); + let subscribers = 0; + + let unsubscribe = noop; + + function current() { + if (effect_tracking()) { + get_source(version); + + render_effect(() => { + if (subscribers === 0) { + let ran = false; + + unsubscribe = store.subscribe((v) => { + value = v; + if (ran) increment(version); + }); + + ran = true; + } + + subscribers += 1; + + return () => { + subscribers -= 1; + + tick().then(() => { + if (subscribers === 0) { + unsubscribe(); + } + }); + }; + }); + + return value; + } + + return get(store); + } + + if ('set' in store) { + return { + get current() { + return current(); + }, + set current(v) { + store.set(v); + } + }; + } + + return { + get current() { + return current(); + } + }; +} diff --git a/packages/svelte/src/store/index-server.js b/packages/svelte/src/store/index-server.js new file mode 100644 index 0000000000..3eddd781f2 --- /dev/null +++ b/packages/svelte/src/store/index-server.js @@ -0,0 +1,101 @@ +/** @import { Readable, Writable } from './public.js' */ +import { get, writable } from './shared/index.js'; + +export { derived, get, readable, readonly, writable } from './shared/index.js'; + +/** + * @template V + * @overload + * @param {() => V} get + * @param {(v: V) => void} set + * @returns {Writable} + */ +/** + * @template V + * @overload + * @param {() => V} get + * @returns {Readable} + */ +/** + * Create a store from a function that returns state, and (to make a writable store), an + * optional second function that sets state. + * + * ```ts + * import { toStore } from 'svelte/store'; + * + * let count = $state(0); + * + * const store = toStore(() => count, (v) => (count = v)); + * ``` + * @template V + * @param {() => V} get + * @param {(v: V) => void} [set] + * @returns {Writable | Readable} + */ +export function toStore(get, set) { + const store = writable(get()); + + if (set) { + return { + set, + update: (fn) => set(fn(get())), + subscribe: store.subscribe + }; + } + + return { + subscribe: store.subscribe + }; +} + +/** + * @template V + * @overload + * @param {Writable} store + * @returns {{ current: V }} + */ +/** + * @template V + * @overload + * @param {Readable} store + * @returns {{ readonly current: V }} + */ +/** + * Convert a store to an object with a reactive `current` property. If `store` + * is a readable store, `current` will be a readonly property. + * + * ```ts + * import { fromStore, get, writable } from 'svelte/store'; + * + * const store = writable(0); + * + * const count = fromStore(store); + * + * count.current; // 0; + * store.set(1); + * count.current; // 1 + * + * count.current += 1; + * get(store); // 2 + * ``` + * @template V + * @param {Writable | Readable} store + */ +export function fromStore(store) { + if ('set' in store) { + return { + get current() { + return get(store); + }, + set current(v) { + store.set(v); + } + }; + } + + return { + get current() { + return get(store); + } + }; +} diff --git a/packages/svelte/src/store/public.d.ts b/packages/svelte/src/store/public.d.ts index 63cb6bfc4c..ec262f7ebf 100644 --- a/packages/svelte/src/store/public.d.ts +++ b/packages/svelte/src/store/public.d.ts @@ -48,4 +48,4 @@ interface Writable extends Readable { export { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable }; -export * from './index.js'; +export * from './index-client.js'; diff --git a/packages/svelte/src/store/index.js b/packages/svelte/src/store/shared/index.js similarity index 62% rename from packages/svelte/src/store/index.js rename to packages/svelte/src/store/shared/index.js index e54e2f2e90..9bf08b4160 100644 --- a/packages/svelte/src/store/index.js +++ b/packages/svelte/src/store/shared/index.js @@ -1,16 +1,8 @@ -/** @import { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable } from './public' */ -/** @import { Stores, StoresValues, SubscribeInvalidateTuple } from './private' */ -import { noop, run_all } from '../internal/shared/utils.js'; -import { safe_not_equal } from '../internal/client/reactivity/equality.js'; -import { - effect_root, - effect_tracking, - render_effect -} from '../internal/client/reactivity/effects.js'; -import { subscribe_to_store } from './utils.js'; -import { source } from '../internal/client/reactivity/sources.js'; -import { get as get_source, tick } from '../internal/client/runtime.js'; -import { increment } from '../reactivity/utils.js'; +/** @import { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable } from '../public.js' */ +/** @import { Stores, StoresValues, SubscribeInvalidateTuple } from '../private.js' */ +import { noop, run_all } from '../../internal/shared/utils.js'; +import { safe_not_equal } from '../../internal/client/reactivity/equality.js'; +import { subscribe_to_store } from '../utils.js'; /** * @type {Array | any>} @@ -221,155 +213,3 @@ export function get(store) { // @ts-expect-error return value; } - -/** - * @template V - * @overload - * @param {() => V} get - * @param {(v: V) => void} set - * @returns {Writable} - */ -/** - * @template V - * @overload - * @param {() => V} get - * @returns {Readable} - */ -/** - * Create a store from a function that returns state, and (to make a writable store), an - * optional second function that sets state. - * - * ```ts - * import { toStore } from 'svelte/store'; - * - * let count = $state(0); - * - * const store = toStore(() => count, (v) => (count = v)); - * ``` - * @template V - * @param {() => V} get - * @param {(v: V) => void} [set] - * @returns {Writable | Readable} - */ -export function toStore(get, set) { - const store = writable(get(), (set) => { - let ran = false; - - // TODO do we need a different implementation on the server? - const teardown = effect_root(() => { - render_effect(() => { - const value = get(); - if (ran) set(value); - }); - }); - - ran = true; - - return teardown; - }); - - if (set) { - return { - set, - update: (fn) => set(fn(get())), - subscribe: store.subscribe - }; - } - - return { - subscribe: store.subscribe - }; -} - -/** - * @template V - * @overload - * @param {Writable} store - * @returns {{ current: V }} - */ -/** - * @template V - * @overload - * @param {Readable} store - * @returns {{ readonly current: V }} - */ -/** - * Convert a store to an object with a reactive `current` property. If `store` - * is a readable store, `current` will be a readonly property. - * - * ```ts - * import { fromStore, get, writable } from 'svelte/store'; - * - * const store = writable(0); - * - * const count = fromStore(store); - * - * count.current; // 0; - * store.set(1); - * count.current; // 1 - * - * count.current += 1; - * get(store); // 2 - * ``` - * @template V - * @param {Writable | Readable} store - */ -export function fromStore(store) { - let value = /** @type {V} */ (undefined); - let version = source(0); - let subscribers = 0; - - let unsubscribe = noop; - - function current() { - if (effect_tracking()) { - get_source(version); - - render_effect(() => { - if (subscribers === 0) { - let ran = false; - - unsubscribe = store.subscribe((v) => { - value = v; - if (ran) increment(version); - }); - - ran = true; - } - - subscribers += 1; - - return () => { - subscribers -= 1; - - tick().then(() => { - if (subscribers === 0) { - unsubscribe(); - } - }); - }; - }); - - return value; - } - - return get(store); - } - - if ('set' in store) { - return { - get current() { - return current(); - }, - set current(v) { - store.set(v); - } - }; - } - - return { - get current() { - return current(); - } - }; -} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index d3d4593b0a..8b7d3077e7 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2305,6 +2305,17 @@ declare module 'svelte/store' { */ update(this: void, updater: Updater): void; } + export function toStore(get: () => V, set: (v: V) => void): Writable; + + export function toStore(get: () => V): Readable; + + export function fromStore(store: Writable): { + current: V; + }; + + export function fromStore(store: Readable): { + readonly current: V; + }; /** * Creates a `Readable` store that allows reading by subscription. * @@ -2346,18 +2357,6 @@ declare module 'svelte/store' { * https://svelte.dev/docs/svelte-store#get * */ export function get(store: Readable): T; - - export function toStore(get: () => V, set: (v: V) => void): Writable; - - export function toStore(get: () => V): Readable; - - export function fromStore(store: Writable): { - current: V; - }; - - export function fromStore(store: Readable): { - readonly current: V; - }; /** One or more `Readable`s. */ type Stores = Readable | [Readable, ...Array>] | Array>;