mirror of https://github.com/sveltejs/svelte
feat: add compiler error when encountering a $-prefixed store value outside a `.svelte` file (#12799)
* feat: add compiler error when encountering a $-prefixed store value outside a .svelte file * add fromState/toState APIs * another test, update types * rename fromState to toStore, and toState to fromStore * docs * add docs * separate client/server entry points for svelte/storepull/12816/head
parent
31a4449012
commit
dfb6755514
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: add compiler error when encountering a $-prefixed store value outside a .svelte file
|
@ -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<V>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @overload
|
||||||
|
* @param {() => V} get
|
||||||
|
* @returns {Readable<V>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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<V> | Readable<V>}
|
||||||
|
*/
|
||||||
|
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<V>} store
|
||||||
|
* @returns {{ current: V }}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @overload
|
||||||
|
* @param {Readable<V>} 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<V> | Readable<V>} 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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<V>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @overload
|
||||||
|
* @param {() => V} get
|
||||||
|
* @returns {Readable<V>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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<V> | Readable<V>}
|
||||||
|
*/
|
||||||
|
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<V>} store
|
||||||
|
* @returns {{ current: V }}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @overload
|
||||||
|
* @param {Readable<V>} 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<V> | Readable<V>} 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
/** @import { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable } from './public' */
|
/** @import { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable } from '../public.js' */
|
||||||
/** @import { Stores, StoresValues, SubscribeInvalidateTuple } from './private' */
|
/** @import { Stores, StoresValues, SubscribeInvalidateTuple } from '../private.js' */
|
||||||
import { noop, run_all } from '../internal/shared/utils.js';
|
import { noop, run_all } from '../../internal/shared/utils.js';
|
||||||
import { safe_not_equal } from '../internal/client/reactivity/equality.js';
|
import { safe_not_equal } from '../../internal/client/reactivity/equality.js';
|
||||||
import { subscribe_to_store } from './utils.js';
|
import { subscribe_to_store } from '../utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<SubscribeInvalidateTuple<any> | any>}
|
* @type {Array<SubscribeInvalidateTuple<any> | any>}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'store_invalid_subscription_module',
|
||||||
|
message: 'Cannot reference store value outside a `.svelte` file'
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
import { store } from 'somewhere';
|
||||||
|
|
||||||
|
console.log($store);
|
Loading…
Reference in new issue