From 5b9f0df8ee97ba43a3d3af18f99f2dd44bd86965 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 18 Mar 2025 11:51:26 +0100 Subject: [PATCH 1/6] fix: don't hoist listeners that access non hoistable snippets (#15534) * fix: don't hoist listeners that access non hoistable snippets * chore: add comment * chore: fix auto import fumble --- .changeset/thick-pans-fold.md | 5 +++++ .../compiler/phases/2-analyze/visitors/Attribute.js | 9 +++++++++ .../unhoist-function-accessing-snippet/_config.js | 12 ++++++++++++ .../unhoist-function-accessing-snippet/main.svelte | 12 ++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 .changeset/thick-pans-fold.md create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte diff --git a/.changeset/thick-pans-fold.md b/.changeset/thick-pans-fold.md new file mode 100644 index 0000000000..b5b5cee53e --- /dev/null +++ b/.changeset/thick-pans-fold.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't hoist listeners that access non hoistable snippets diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index 9124a8822f..3ba81767cc 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -183,6 +183,15 @@ function get_delegated_event(event_name, handler, context) { const binding = scope.get(reference); const local_binding = context.state.scope.get(reference); + // if the function access a snippet that can't be hoisted we bail out + if ( + local_binding !== null && + local_binding.initial?.type === 'SnippetBlock' && + !local_binding.initial.metadata.can_hoist + ) { + return unhoisted; + } + // If we are referencing a binding that is shadowed in another scope then bail out. if (local_binding !== null && binding !== null && local_binding.node !== binding.node) { return unhoisted; diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js new file mode 100644 index 0000000000..b1229f5a8a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, errors }) { + const button = target.querySelector('button'); + flushSync(() => { + button?.click(); + }); + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte new file mode 100644 index 0000000000..e909d77fd6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte @@ -0,0 +1,12 @@ + + + + +{#snippet snip()} + snippet {x} +{/snippet} \ No newline at end of file From 0af6f20c77c209a5ea5691f2d1c15e0e359fbed6 Mon Sep 17 00:00:00 2001 From: henrykrinkle01 <162001892+henrykrinkle01@users.noreply.github.com> Date: Tue, 18 Mar 2025 17:54:32 +0700 Subject: [PATCH 2/6] Fix grammar (#15533) --- documentation/docs/06-runtime/02-context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index 30799215b6..87b93a92b2 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -30,7 +30,7 @@ export const myGlobalState = $state({ This has a few drawbacks though: - it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs -- it may give the false impression that certain state is global when in reality it should only used in a certain part of your app +- it may give the false impression that certain state is global when in reality it should only be used in a certain part of your app To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems. From 190c0c7653435fd983d13a9594f5d70bdb4dd26f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 13:28:29 -0400 Subject: [PATCH 3/6] Version Packages (#15536) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/thick-pans-fold.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/thick-pans-fold.md diff --git a/.changeset/thick-pans-fold.md b/.changeset/thick-pans-fold.md deleted file mode 100644 index b5b5cee53e..0000000000 --- a/.changeset/thick-pans-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't hoist listeners that access non hoistable snippets diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index e10a606fe4..6461df1d25 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.23.2 + +### Patch Changes + +- fix: don't hoist listeners that access non hoistable snippets ([#15534](https://github.com/sveltejs/svelte/pull/15534)) + ## 5.23.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6f10b2a9ea..d005eca0b9 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.23.1", + "version": "5.23.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 32a50f3bce..191b52ecef 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.23.1'; +export const VERSION = '5.23.2'; export const PUBLIC_VERSION = '5'; From 8f940ee0ff12be2ae6b393b4e021507d3f3e2068 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Mar 2025 10:41:13 -0400 Subject: [PATCH 4/6] docs: use function bindings in "when not to use effect" (#15544) --- documentation/docs/02-runes/04-$effect.md | 42 ++++------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index e346bceba8..6a2b565aea 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -280,7 +280,7 @@ You might be tempted to do something convoluted with effects to link one value t ``` -Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)): +Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)): ```svelte ``` -If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)): - -```svelte - - - - - -``` - If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack). From 701f085c82d11e6064433731d36b33d4894c706a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Mar 2025 10:43:45 -0400 Subject: [PATCH 5/6] docs: rewrite context docs (#15541) --- documentation/docs/06-runtime/02-context.md | 164 ++++++++++---------- 1 file changed, 86 insertions(+), 78 deletions(-) diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index 87b93a92b2..b698323a04 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -2,129 +2,137 @@ title: Context --- - +Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`... -Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow. - -The easiest way to do that is to create global state and just import that. +```svelte + + ``` +...and the child retrieves it with `getContext`: + ```svelte - + + +

{message}, inside Child.svelte

``` -This has a few drawbacks though: +This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)): + +```svelte + + + +``` -- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs -- it may give the false impression that certain state is global when in reality it should only be used in a certain part of your app +The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value. -To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems. +In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions. -## Setting and getting context +## Using context with state -To associate an arbitrary object with the current component, use `setContext`. +You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))... ```svelte + + + + + + ``` -The context is then available to children of the component (including slotted content) with `getContext`. +...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this... ```svelte - + ``` -`setContext` and `getContext` solve the above problems: +...you must do this: -- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state -- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app +```svelte + +``` -> [!NOTE] `setContext`/`getContext` must be called during component initialisation. +Svelte will warn you if you get it wrong. -Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive. +## Type-safe context -```svelte - - +```js +/// file: context.js +// @filename: ambient.d.ts +interface User {} - -``` +// @filename: index.js +// ---cut--- +import { getContext, setContext } from 'svelte'; -```svelte - - +/** @param {User} user */ +export function setUserContext(user) { + setContext(key, user); +} -

Count is {value.count}

+export function getUserContext() { + return /** @type {User} */ (getContext(key)); +} ``` -To check whether a given `key` has been set in the context of a parent component, use `hasContext`. +## Replacing global state -```svelte - + // ... +}); ``` -You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it. +In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)... ```svelte + ``` -## Encapsulating context interactions - -The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them. - -```ts -// @errors: 2304 -import { getContext, setContext } from 'svelte'; - -let userKey = Symbol('user'); - -export function setUserContext(user: User) { - setContext(userKey, user); -} - -export function getUserContext(): User { - return getContext(userKey) as User; -} -``` +...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests. From 99ca7a4d7f5948f94c2fa0137a481b57c4a6c17b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Mar 2025 14:06:20 -0400 Subject: [PATCH 6/6] chore: create stack lazily when proxying value (#15547) --- packages/svelte/src/internal/client/proxy.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 4c262880f1..29828a7c99 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -25,11 +25,6 @@ import { tracing_mode_flag } from '../flags/index.js'; * @returns {T} */ export function proxy(value, parent = null, prev) { - /** @type {Error | null} */ - var stack = null; - if (DEV && tracing_mode_flag) { - stack = get_stack('CreatedAt'); - } // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -46,6 +41,8 @@ export function proxy(value, parent = null, prev) { var is_proxied_array = is_array(value); var version = source(0); + var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null; + if (is_proxied_array) { // We need to create the length source eagerly to ensure that // mutations to the array are properly synced with our proxy