@@ -6,15 +6,78 @@ import { increment } from './utils.js';
66import { DEV } from 'esm-env' ;
77
88/**
9- * Returns a `subscribe` function that, if called in an effect (including expressions in the template),
10- * calls its `start` callback with an `update` function. Whenever `update` is called, the effect re-runs.
9+ * Returns a `subscribe` function that bridges external, non-reactive changes
10+ * to Svelte's reactivity system. It's ideal for integrating with browser APIs,
11+ * WebSockets, or any event-based source outside of Svelte's control.
1112 *
12- * If `start` returns a function, it will be called when the effect is destroyed.
13+ * Call the returned `subscribe()` function inside a getter to make that getter
14+ * reactive. When the external source changes, you call an `update` function,
15+ * which in turn causes any effects that depend on the getter to re-run.
1316 *
14- * If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
15- * are active, and the returned teardown function will only be called when all effects are destroyed.
17+ * @param {(update: () => void) => (() => void) | void } start
18+ * A callback that runs when the subscription is first activated by an effect.
19+ * It receives an `update` function, which you should call to signal that
20+ * the external data source has changed. The `start` callback can optionally
21+ * return a `cleanup` function, which will be called when the last effect
22+ * that depends on it is destroyed.
23+ * @returns {() => void }
24+ * A `subscribe` function that you call inside a getter to establish the
25+ * reactive connection.
26+ *
27+ * @example
28+ * ### The Generic Pattern
1629 *
17- * It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery):
30+ * This pattern shows how to create a reusable utility that encapsulates the
31+ * external state and subscription logic.
32+ *
33+ * ```js
34+ * import { createSubscriber } from 'svelte/reactivity';
35+ *
36+ * export function createReactiveExternalState() {
37+ * let state = someInitialValue;
38+ *
39+ * const subscribe = createSubscriber((update) => {
40+ * // Set up your external listener (DOM event, WebSocket, timer, etc.)
41+ * const cleanup = setupListener(() => {
42+ * state = newValue; // Update your state
43+ * update(); // Call this to trigger Svelte reactivity
44+ * });
45+ *
46+ * // Return cleanup function
47+ * return () => cleanup();
48+ * });
49+ *
50+ * return {
51+ * get current() {
52+ * subscribe(); // This "paints" the getter as reactive
53+ * return state;
54+ * }
55+ * };
56+ * }
57+ * ```
58+ *
59+ * ### Implementation Details
60+ *
61+ * Internally, `createSubscriber` creates a hidden reactive `$state` variable
62+ * that acts as a version number. Calling the `update` function increments this
63+ * version. When the `subscribe` function is called within an effect, it reads
64+ * this version number, creating a dependency. This mechanism ensures that
65+ * getters become reactive to the external changes you signal.
66+ *
67+ * This approach is highly efficient:
68+ * - **Lazy:** The `start` callback is only executed when the getter is first
69+ * used inside an active effect.
70+ * - **Automatic Cleanup:** The returned cleanup function is automatically
71+ * called when the last subscribing effect is destroyed.
72+ * - **Shared:** If multiple effects depend on the same getter, the `start`
73+ * callback is still only called once.
74+ *
75+ * It's best understood with more examples.
76+ *
77+ * @example
78+ * ### MediaQuery
79+ *
80+ * Here's a practical implementation of a reactive `MediaQuery` utility class.
1881 *
1982 * ```js
2083 * import { createSubscriber } from 'svelte/reactivity';
@@ -39,12 +102,58 @@ import { DEV } from 'esm-env';
39102 * get current() {
40103 * this.#subscribe();
41104 *
42- * // Return the current state of the query , whether or not we're in an effect
105+ * // Return the current state, whether or not we're in an effect
43106 * return this.#query.matches;
44107 * }
45108 * }
46109 * ```
47- * @param {(update: () => void) => (() => void) | void } start
110+ *
111+ * @example
112+ * ### Mouse Position
113+ *
114+ * This example creates a utility that reactively tracks mouse coordinates.
115+ *
116+ * ```js
117+ * import { createSubscriber } from 'svelte/reactivity';
118+ * import { on } from 'svelte/events';
119+ *
120+ * export function createMousePosition() {
121+ * let x = 0;
122+ * let y = 0;
123+ *
124+ * const subscribe = createSubscriber((update) => {
125+ * const handleMouseMove = (event) => {
126+ * x = event.clientX;
127+ * y = event.clientY;
128+ * update(); // Trigger reactivity
129+ * };
130+ *
131+ * const off = on(window, 'mousemove', handleMouseMove);
132+ * return () => off();
133+ * });
134+ *
135+ * return {
136+ * get x() {
137+ * subscribe(); // Makes x reactive
138+ * return x;
139+ * },
140+ * get y() {
141+ * subscribe(); // Makes y reactive
142+ * return y;
143+ * }
144+ * };
145+ * }
146+ * ```
147+ *
148+ * ### When to use `createSubscriber`
149+ *
150+ * - To synchronize Svelte's reactivity with external event sources like DOM
151+ * events, `postMessage`, or WebSockets.
152+ * - To create reactive wrappers around browser APIs (`matchMedia`,
153+ * `IntersectionObserver`, etc.).
154+ * - When you have a value that is read from an external source and you need
155+ * components to update when that value changes. It is a more direct
156+ * alternative to using `$state` and `$effect` for this specific purpose.
48157 * @since 5.7.0
49158 */
50159export function createSubscriber ( start ) {
@@ -72,7 +181,7 @@ export function createSubscriber(start) {
72181 tick ( ) . then ( ( ) => {
73182 // Only count down after timeout, else we would reach 0 before our own render effect reruns,
74183 // but reach 1 again when the tick callback of the prior teardown runs. That would mean we
75- // re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
184+ // re-subscribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
76185 subscribers -= 1 ;
77186
78187 if ( subscribers === 0 ) {
0 commit comments