From f303d8204304772c2305b6a49bd02c777845cd79 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Mar 2024 17:21:05 -0400 Subject: [PATCH] chore: tidy up server exports (#10972) * tidy up server exports * tidy up server exports * docs are unnecessary here * eliminate client dependencies from server code * lint --- packages/svelte/src/constants.js | 2 + packages/svelte/src/index-server.js | 54 ++++++------ .../svelte/src/internal/client/constants.js | 1 - .../src/internal/client/dom/blocks/await.js | 2 +- .../src/internal/client/dom/blocks/key.js | 2 +- .../client/dom/elements/transitions.js | 2 +- .../internal/client/dom/legacy/lifecycle.js | 7 +- .../svelte/src/internal/client/dom/task.js | 2 +- packages/svelte/src/internal/client/index.js | 16 ++-- packages/svelte/src/internal/client/proxy.js | 3 +- .../src/internal/client/reactivity/effects.js | 2 +- .../src/internal/client/reactivity/sources.js | 4 +- .../src/internal/client/reactivity/store.js | 18 ++-- .../svelte/src/internal/client/runtime.js | 3 +- packages/svelte/src/internal/client/timing.js | 2 +- .../svelte/src/internal/client/types.d.ts | 8 +- .../svelte/src/internal/client/validate.js | 56 ------------- .../svelte/src/internal/server/context.js | 84 +++++++++++++++++++ packages/svelte/src/internal/server/index.js | 56 ++++++------- .../svelte/src/internal/server/types.d.ts | 8 ++ .../svelte/src/internal/shared/types.d.ts | 4 + .../internal/{common.js => shared/utils.js} | 5 ++ .../svelte/src/internal/shared/validate.js | 57 +++++++++++++ packages/svelte/src/reactivity/map.js | 2 +- packages/svelte/src/store/index.js | 2 +- packages/svelte/src/store/utils.js | 2 +- packages/svelte/tsconfig.json | 4 +- 27 files changed, 251 insertions(+), 157 deletions(-) create mode 100644 packages/svelte/src/internal/server/context.js create mode 100644 packages/svelte/src/internal/server/types.d.ts create mode 100644 packages/svelte/src/internal/shared/types.d.ts rename packages/svelte/src/internal/{common.js => shared/utils.js} (88%) create mode 100644 packages/svelte/src/internal/shared/validate.js diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 736c9affb5..28e6d8d117 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -19,6 +19,8 @@ export const TRANSITION_GLOBAL = 1 << 2; export const TEMPLATE_FRAGMENT = 1; export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; +export const UNINITIALIZED = Symbol(); + /** List of Element events that will be delegated */ export const DelegatedEvents = [ 'beforeinput', diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index 1baf3207ce..aacf4dd711 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -1,33 +1,37 @@ -import { current_component_context } from './internal/client/runtime.js'; - -export { - createEventDispatcher, - flushSync, - getAllContexts, - getContext, - hasContext, - mount, - hydrate, - setContext, - tick, - unmount, - untrack -} from './index-client.js'; - -/** @returns {void} */ -export function onMount() {} +import { current_component } from './internal/server/context.js'; +import { noop } from './internal/shared/utils.js'; /** @param {() => void} fn */ export function onDestroy(fn) { - const context = /** @type {import('#client').ComponentContext} */ (current_component_context); - (context.ondestroy ??= []).push(fn); + var context = /** @type {import('#server').Component} */ (current_component); + (context.d ??= []).push(fn); +} + +export { + noop as beforeUpdate, + noop as afterUpdate, + noop as onMount, + noop as flushSync, + run as untrack +} from './internal/shared/utils.js'; + +export function createEventDispatcher() { + return noop; } -/** @returns {void} */ -export function beforeUpdate() {} +export function mount() { + throw new Error('mount(...) is not available on the server'); +} + +export function hydrate() { + throw new Error('hydrate(...) is not available on the server'); +} -/** @returns {void} */ -export function afterUpdate() {} +export function unmount() { + throw new Error('unmount(...) is not available on the server'); +} + +export async function tick() {} /** * @template T @@ -38,3 +42,5 @@ export function unstate(value) { // There's no signals/proxies on the server, so just return the value return value; } + +export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js'; diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 77b5f399d5..563e10384a 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -13,5 +13,4 @@ export const DESTROYED = 1 << 12; export const IS_ELSEIF = 1 << 13; export const EFFECT_RAN = 1 << 14; -export const UNINITIALIZED = Symbol(); export const STATE_SYMBOL = Symbol('$state'); diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 2aa5eb401a..6d8d3e55bc 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -1,4 +1,4 @@ -import { is_promise } from '../../../common.js'; +import { is_promise } from '../../../shared/utils.js'; import { current_component_context, flush_sync, diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index 07d138b15f..eb68978ffe 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -1,4 +1,4 @@ -import { UNINITIALIZED } from '../../constants.js'; +import { UNINITIALIZED } from '../../../../constants.js'; import { block, branch, pause_effect } from '../../reactivity/effects.js'; import { safe_not_equal } from '../../reactivity/equality.js'; diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index 5a12566a8e..a5d89b2595 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -1,4 +1,4 @@ -import { noop } from '../../../common.js'; +import { noop } from '../../../shared/utils.js'; import { effect } from '../../reactivity/effects.js'; import { current_effect, untrack } from '../../runtime.js'; import { raf } from '../../timing.js'; diff --git a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js index 0c0090e1f6..fc7efbb006 100644 --- a/packages/svelte/src/internal/client/dom/legacy/lifecycle.js +++ b/packages/svelte/src/internal/client/dom/legacy/lifecycle.js @@ -1,4 +1,4 @@ -import { run_all } from '../../../common.js'; +import { run, run_all } from '../../../shared/utils.js'; import { user_pre_effect, user_effect } from '../../reactivity/effects.js'; import { current_component_context, @@ -9,11 +9,6 @@ import { untrack } from '../../runtime.js'; -/** @param {Function} fn */ -function run(fn) { - return fn(); -} - /** * Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects */ diff --git a/packages/svelte/src/internal/client/dom/task.js b/packages/svelte/src/internal/client/dom/task.js index e2f3082e90..89307a4e56 100644 --- a/packages/svelte/src/internal/client/dom/task.js +++ b/packages/svelte/src/internal/client/dom/task.js @@ -1,4 +1,4 @@ -import { run_all } from '../../common.js'; +import { run_all } from '../../shared/utils.js'; let is_task_queued = false; let is_raf_queued = false; diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index d03b719ca2..0a2eeaa19a 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -120,15 +120,10 @@ export { hasContext } from './runtime.js'; export { - add_snippet_symbol, - validate_component, validate_dynamic_component, - validate_dynamic_element_tag, validate_each_keys, validate_prop_bindings, - validate_snippet, - validate_store, - validate_void_dynamic_element + validate_store } from './validate.js'; export { raf } from './timing.js'; export { proxy, unstate } from './proxy.js'; @@ -140,4 +135,11 @@ export { $window as window, $document as document } from './dom/operations.js'; -export { noop } from '../common.js'; +export { noop } from '../shared/utils.js'; +export { + add_snippet_symbol, + validate_component, + validate_dynamic_element_tag, + validate_snippet, + validate_void_dynamic_element +} from '../shared/validate.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 198e16f939..e9c3bc8fd1 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -13,8 +13,9 @@ import { } from './utils.js'; import { add_owner, check_ownership, strip_owner } from './dev/ownership.js'; import { mutable_source, source, set } from './reactivity/sources.js'; -import { STATE_SYMBOL, UNINITIALIZED } from './constants.js'; +import { STATE_SYMBOL } from './constants.js'; import { updating_derived } from './reactivity/deriveds.js'; +import { UNINITIALIZED } from '../../constants.js'; /** * @template T diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 0cd66c8cb7..f4f52fff11 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -27,7 +27,7 @@ import { ROOT_EFFECT } from '../constants.js'; import { set } from './sources.js'; -import { noop } from '../../common.js'; +import { noop } from '../../shared/utils.js'; import { remove } from '../dom/reconciler.js'; /** diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index db82edd740..6ad3240d5e 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -6,7 +6,6 @@ import { current_effect, current_untracked_writes, current_untracking, - flush_sync, get, is_batching_effect, is_runes, @@ -18,7 +17,8 @@ import { untrack } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; -import { CLEAN, DERIVED, DIRTY, BRANCH_EFFECT, UNINITIALIZED } from '../constants.js'; +import { CLEAN, DERIVED, DIRTY, BRANCH_EFFECT } from '../constants.js'; +import { UNINITIALIZED } from '../../../constants.js'; /** * @template V diff --git a/packages/svelte/src/internal/client/reactivity/store.js b/packages/svelte/src/internal/client/reactivity/store.js index d17c8e5d44..0f256b04f0 100644 --- a/packages/svelte/src/internal/client/reactivity/store.js +++ b/packages/svelte/src/internal/client/reactivity/store.js @@ -1,6 +1,6 @@ import { subscribe_to_store } from '../../../store/utils.js'; -import { noop } from '../../common.js'; -import { UNINITIALIZED } from '../constants.js'; +import { noop } from '../../shared/utils.js'; +import { UNINITIALIZED } from '../../../constants.js'; import { get, untrack } from '../runtime.js'; import { effect } from './effects.js'; import { mutable_source, set } from './sources.js'; @@ -10,7 +10,7 @@ import { mutable_source, set } from './sources.js'; * signal that will be updated when the store is. The store references container is needed to * track reassignments to stores and to track the correct component context. * @template V - * @param {import('#client').Store | null | undefined} store + * @param {import('#shared').Store | null | undefined} store * @param {string} store_name * @param {import('#client').StoreReferencesContainer} stores * @returns {V} @@ -46,7 +46,7 @@ export function store_get(store, store_name, stores) { * Unsubscribe from a store if it's not the same as the one in the store references container. * We need this in addition to `store_get` because someone could unsubscribe from a store but * then never subscribe to the new one (if any), causing the subscription to stay open wrongfully. - * @param {import('#client').Store | null | undefined} store + * @param {import('#shared').Store | null | undefined} store * @param {string} store_name * @param {import('#client').StoreReferencesContainer} stores */ @@ -65,7 +65,7 @@ export function store_unsub(store, store_name, stores) { /** * @template V - * @param {import('#client').Store | null | undefined} store + * @param {import('#shared').Store | null | undefined} store * @param {import('#client').Source} source */ function connect_store_to_signal(store, source) { @@ -80,7 +80,7 @@ function connect_store_to_signal(store, source) { /** * Sets the new value of a store and returns that value. * @template V - * @param {import('#client').Store} store + * @param {import('#shared').Store} store * @param {V} value * @returns {V} */ @@ -116,7 +116,7 @@ export function unsubscribe_on_destroy(stores) { /** * Updates a store with a new value. - * @param {import('#client').Store} store the store to update + * @param {import('#shared').Store} store the store to update * @param {any} expression the expression that mutates the store * @param {V} new_value the new store value * @template V @@ -127,7 +127,7 @@ export function mutate_store(store, expression, new_value) { } /** - * @param {import('#client').Store} store + * @param {import('#shared').Store} store * @param {number} store_value * @param {1 | -1} [d] * @returns {number} @@ -138,7 +138,7 @@ export function update_store(store, store_value, d = 1) { } /** - * @param {import('#client').Store} store + * @param {import('#shared').Store} store * @param {number} store_value * @param {1 | -1} [d] * @returns {number} diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8f8a57c676..63d25ebe3a 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1055,8 +1055,7 @@ export function push(props, runes = false, fn) { l1: [], l2: source(false), // update_callbacks - u: null, - ondestroy: null + u: null }; if (DEV) { diff --git a/packages/svelte/src/internal/client/timing.js b/packages/svelte/src/internal/client/timing.js index 241fdcec73..88e3d9dcb2 100644 --- a/packages/svelte/src/internal/client/timing.js +++ b/packages/svelte/src/internal/client/timing.js @@ -1,4 +1,4 @@ -import { noop } from '../common.js'; +import { noop } from '../shared/utils.js'; const is_client = typeof window !== 'undefined'; diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index db89576e7c..1991769eed 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -1,14 +1,10 @@ +import type { Store } from '#shared'; import { STATE_SYMBOL } from './constants.js'; import type { Effect, Source, Value } from './reactivity/types.js'; type EventCallback = (event: Event) => boolean; export type EventCallbackMap = Record; -export type Store = { - subscribe: (run: (value: V) => void) => () => void; - set(value: V): void; -}; - // For all the core internal objects, we use single-character property strings. // This not only reduces code-size and parsing, but it also improves the performance // when the JS VM JITs the code. @@ -43,8 +39,6 @@ export type ComponentContext = { /** onMount callbacks */ m: Array<() => any>; }; - // TODO move this to a separate server component context object - ondestroy: null | Array<() => void>; }; export type Equals = (this: Value, value: unknown) => boolean; diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index befd68b027..add59ebe82 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -42,27 +42,6 @@ export function validate_dynamic_component(component_fn) { } } -/** - * @param {() => string} tag_fn - * @returns {void} - */ -export function validate_void_dynamic_element(tag_fn) { - const tag = tag_fn(); - if (tag && is_void(tag)) { - // eslint-disable-next-line no-console - console.warn(` is self-closing and cannot have content.`); - } -} - -/** @param {() => unknown} tag_fn */ -export function validate_dynamic_element_tag(tag_fn) { - const tag = tag_fn(); - const is_string = typeof tag === 'string'; - if (tag && !is_string) { - throw new Error(' expects "this" attribute to be a string.'); - } -} - /** * @param {() => any} collection * @param {(item: any, index: number) => string} key_fn @@ -103,41 +82,6 @@ export function loop_guard(timeout) { }; } -const snippet_symbol = Symbol.for('svelte.snippet'); - -/** - * @param {any} fn - */ -export function add_snippet_symbol(fn) { - fn[snippet_symbol] = true; - return fn; -} - -/** - * Validate that the function handed to `{@render ...}` is a snippet function, and not some other kind of function. - * @param {any} snippet_fn - */ -export function validate_snippet(snippet_fn) { - if (snippet_fn && snippet_fn[snippet_symbol] !== true) { - throw new Error( - 'The argument to `{@render ...}` must be a snippet function, not a component or some other kind of function. ' + - 'If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`.' - ); - } - return snippet_fn; -} - -/** - * Validate that the function behind `` isn't a snippet. - * @param {any} component_fn - */ -export function validate_component(component_fn) { - if (component_fn?.[snippet_symbol] === true) { - throw new Error('A snippet must be rendered with `{@render ...}`'); - } - return component_fn; -} - /** * @param {Record} $$props * @param {string[]} bindable diff --git a/packages/svelte/src/internal/server/context.js b/packages/svelte/src/internal/server/context.js new file mode 100644 index 0000000000..da9206bab6 --- /dev/null +++ b/packages/svelte/src/internal/server/context.js @@ -0,0 +1,84 @@ +import { DEV } from 'esm-env'; +import { on_destroy } from './index.js'; + +/** @type {import('#server').Component | null} */ +export var current_component = null; + +/** + * @template T + * @param {any} key + * @returns {T} + */ +export function getContext(key) { + const context_map = getAllContexts(); + const result = /** @type {T} */ (context_map.get(key)); + + return result; +} + +/** + * @template T + * @param {any} key + * @param {T} context + * @returns {T} + */ +export function setContext(key, context) { + getAllContexts().set(key, context); + return context; +} + +/** + * @param {any} key + * @returns {boolean} + */ +export function hasContext(key) { + return getAllContexts().has(key); +} + +/** @returns {Map} */ +export function getAllContexts() { + const context = current_component; + + if (context === null) { + throw new Error( + 'ERR_SVELTE_ORPHAN_CONTEXT' + + (DEV ? 'Context can only be used during component initialisation.' : '') + ); + } + + return (context.c ??= new Map(get_parent_context(context) || undefined)); +} + +export function push() { + current_component = { p: current_component, c: null, d: null }; +} + +export function pop() { + var component = /** @type {import('#server').Component} */ (current_component); + + var ondestroy = component.d; + + if (ondestroy) { + on_destroy.push(...ondestroy); + } + + current_component = component.p; +} + +/** + * @param {import('#server').Component} component_context + * @returns {Map | null} + */ +function get_parent_context(component_context) { + let parent = component_context.p; + + while (parent !== null) { + const context_map = parent.c; + if (context_map !== null) { + return context_map; + } + parent = parent.p; + } + + return null; +} diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index a1b7d103ab..cd216783a9 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -1,16 +1,14 @@ -import * as $ from '../client/runtime.js'; -import { is_promise, noop } from '../common.js'; +import { is_promise, noop } from '../shared/utils.js'; import { subscribe_to_store } from '../../store/utils.js'; import { + UNINITIALIZED, DOMBooleanAttributes, disallowed_paragraph_contents, interactive_elements, is_tag_valid_with_parent } from '../../constants.js'; import { DEV } from 'esm-env'; -import { UNINITIALIZED } from '../client/constants.js'; - -export * from '../client/validate.js'; +import { current_component, pop, push } from './context.js'; /** * @typedef {{ @@ -192,14 +190,16 @@ export function render(component, options) { payload.out += ''; if (options.context) { - $.push({}); - /** @type {import('../client/types.js').ComponentContext} */ ($.current_component_context).c = - options.context; + push(); + /** @type {import('#server').Component} */ (current_component).c = options.context; } + component(payload, options.props, {}, {}); + if (options.context) { - $.pop(); + pop(); } + payload.out += ''; for (const cleanup of on_destroy) cleanup(); on_destroy = prev_on_destroy; @@ -213,24 +213,6 @@ export function render(component, options) { }; } -/** - * @param {boolean} runes - */ -export function push(runes) { - $.push({}, runes); -} - -export function pop() { - var context = /** @type {import('#client').ComponentContext} */ ($.current_component_context); - var ondestroy = context.ondestroy; - - if (ondestroy) { - on_destroy.push(...ondestroy); - } - - $.pop(); -} - /** * @template V * @param {V} value @@ -435,7 +417,7 @@ export function merge_styles(style_attribute, style_directive) { * @template V * @param {Record} store_values * @param {string} store_name - * @param {import('../client/types.js').Store | null | undefined} store + * @param {import('#shared').Store | null | undefined} store * @returns {V} */ export function store_get(store_values, store_name, store) { @@ -472,7 +454,7 @@ export function validate_store(store, name) { /** * Sets the new value of a store and returns that value. * @template V - * @param {import('../client/types.js').Store} store + * @param {import('#shared').Store} store * @param {V} value * @returns {V} */ @@ -486,7 +468,7 @@ export function store_set(store, value) { * @template V * @param {Record} store_values * @param {string} store_name - * @param {import('../client/types.js').Store} store + * @param {import('#shared').Store} store * @param {any} expression */ export function mutate_store(store_values, store_name, store, expression) { @@ -497,7 +479,7 @@ export function mutate_store(store_values, store_name, store, expression) { /** * @param {Record} store_values * @param {string} store_name - * @param {import('../client/types.js').Store} store + * @param {import('#shared').Store} store * @param {1 | -1} [d] * @returns {number} */ @@ -510,7 +492,7 @@ export function update_store(store_values, store_name, store, d = 1) { /** * @param {Record} store_values * @param {string} store_name - * @param {import('../client/types.js').Store} store + * @param {import('#shared').Store} store * @param {1 | -1} [d] * @returns {number} */ @@ -672,3 +654,13 @@ export function once(get_value) { return value; }; } + +export { push, pop } from './context.js'; + +export { + add_snippet_symbol, + validate_component, + validate_dynamic_element_tag, + validate_snippet, + validate_void_dynamic_element +} from '../shared/validate.js'; diff --git a/packages/svelte/src/internal/server/types.d.ts b/packages/svelte/src/internal/server/types.d.ts new file mode 100644 index 0000000000..9e289bfaa0 --- /dev/null +++ b/packages/svelte/src/internal/server/types.d.ts @@ -0,0 +1,8 @@ +export interface Component { + /** parent */ + p: null | Component; + /** context */ + c: null | Map; + /** ondestroy */ + d: null | Array<() => void>; +} diff --git a/packages/svelte/src/internal/shared/types.d.ts b/packages/svelte/src/internal/shared/types.d.ts new file mode 100644 index 0000000000..ffde04249d --- /dev/null +++ b/packages/svelte/src/internal/shared/types.d.ts @@ -0,0 +1,4 @@ +export type Store = { + subscribe: (run: (value: V) => void) => () => void; + set(value: V): void; +}; diff --git a/packages/svelte/src/internal/common.js b/packages/svelte/src/internal/shared/utils.js similarity index 88% rename from packages/svelte/src/internal/common.js rename to packages/svelte/src/internal/shared/utils.js index 2a4295f276..2b81e42253 100644 --- a/packages/svelte/src/internal/common.js +++ b/packages/svelte/src/internal/shared/utils.js @@ -13,6 +13,11 @@ export function is_promise(value) { return typeof value?.then === 'function'; } +/** @param {Function} fn */ +export function run(fn) { + return fn(); +} + /** @param {Array<() => void>} arr */ export function run_all(arr) { for (var i = 0; i < arr.length; i++) { diff --git a/packages/svelte/src/internal/shared/validate.js b/packages/svelte/src/internal/shared/validate.js new file mode 100644 index 0000000000..692a62fa07 --- /dev/null +++ b/packages/svelte/src/internal/shared/validate.js @@ -0,0 +1,57 @@ +import { is_void } from '../../compiler/phases/1-parse/utils/names.js'; + +const snippet_symbol = Symbol.for('svelte.snippet'); + +/** + * @param {any} fn + */ +export function add_snippet_symbol(fn) { + fn[snippet_symbol] = true; + return fn; +} + +/** + * Validate that the function handed to `{@render ...}` is a snippet function, and not some other kind of function. + * @param {any} snippet_fn + */ +export function validate_snippet(snippet_fn) { + if (snippet_fn && snippet_fn[snippet_symbol] !== true) { + throw new Error( + 'The argument to `{@render ...}` must be a snippet function, not a component or some other kind of function. ' + + 'If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`.' + ); + } + return snippet_fn; +} + +/** + * Validate that the function behind `` isn't a snippet. + * @param {any} component_fn + */ +export function validate_component(component_fn) { + if (component_fn?.[snippet_symbol] === true) { + throw new Error('A snippet must be rendered with `{@render ...}`'); + } + return component_fn; +} + +/** + * @param {() => string} tag_fn + * @returns {void} + */ +export function validate_void_dynamic_element(tag_fn) { + const tag = tag_fn(); + if (tag && is_void(tag)) { + // eslint-disable-next-line no-console + console.warn(` is self-closing and cannot have content.`); + } +} + +/** @param {() => unknown} tag_fn */ +export function validate_dynamic_element_tag(tag_fn) { + const tag = tag_fn(); + const is_string = typeof tag === 'string'; + if (tag && !is_string) { + throw new Error(' expects "this" attribute to be a string.'); + } +} diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index f57f52e7b7..a077ad1c5a 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -1,7 +1,7 @@ import { DEV } from 'esm-env'; import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; -import { UNINITIALIZED } from '../internal/client/constants.js'; +import { UNINITIALIZED } from '../constants.js'; import { map } from './utils.js'; /** diff --git a/packages/svelte/src/store/index.js b/packages/svelte/src/store/index.js index 4cfa1624d6..44af332d2a 100644 --- a/packages/svelte/src/store/index.js +++ b/packages/svelte/src/store/index.js @@ -1,4 +1,4 @@ -import { noop, run_all } from '../internal/common.js'; +import { noop, run_all } from '../internal/shared/utils.js'; import { subscribe_to_store } from './utils.js'; /** diff --git a/packages/svelte/src/store/utils.js b/packages/svelte/src/store/utils.js index 9bcc914fd1..659683ae13 100644 --- a/packages/svelte/src/store/utils.js +++ b/packages/svelte/src/store/utils.js @@ -1,4 +1,4 @@ -import { noop } from '../internal/common.js'; +import { noop } from '../internal/shared/utils.js'; /** * @template T diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index e1bc981ec8..8c27517244 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -25,7 +25,9 @@ "svelte/server": ["./src/server/index.js"], "svelte/store": ["./src/store/public.d.ts"], "#compiler": ["./src/compiler/types/index.d.ts"], - "#client": ["./src/internal/client/types.d.ts"] + "#client": ["./src/internal/client/types.d.ts"], + "#server": ["./src/internal/server/types.d.ts"], + "#shared": ["./src/internal/shared/types.d.ts"] } }, "include": [