From cb529fc666a3fb5fd35b27b91c61494a615a8106 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 8 Dec 2023 01:26:19 +0000 Subject: [PATCH 01/24] chore: improve each block fast-path heuristic (#9855) * chore: improve each block fast-path heuristic * chore: improve each block fast-path heuristic --- .changeset/slow-chefs-dream.md | 5 ++++ .../3-transform/client/visitors/template.js | 24 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 .changeset/slow-chefs-dream.md diff --git a/.changeset/slow-chefs-dream.md b/.changeset/slow-chefs-dream.md new file mode 100644 index 000000000000..9cd0de887b31 --- /dev/null +++ b/.changeset/slow-chefs-dream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: improve each block fast-path heuristic diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 1330141a8e43..dc420fc805cc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2137,11 +2137,10 @@ export const template_visitors = { } // The runtime needs to know what kind of each block this is in order to optimize for the - // immutable + key==entry case. In that case, the item doesn't need to be reactive, because - // the array as a whole is immutable, so if something changes, it either has to recreate the - // array or use nested reactivity through runes. - // TODO this feels a bit "hidden performance boost"-style, investigate if there's a way - // to make this apply in more cases + // key === item (we avoid extra allocations). In that case, the item doesn't need to be reactive. + // We can guarantee this by knowing that in order for the item of the each block to change, they + // would need to mutate the key/item directly in the array. Given that in runes mode we use === + // equality, we can apply a fast-path (as long as the index isn't reactive). let each_type = 0; if ( @@ -2149,21 +2148,22 @@ export const template_visitors = { (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index) ) { each_type |= EACH_KEYED; + // If there's a destructuring, then we likely need the generated $$index + if (node.index || node.context.type !== 'Identifier') { + each_type |= EACH_INDEX_REACTIVE; + } if ( + context.state.analysis.runes && node.key.type === 'Identifier' && node.context.type === 'Identifier' && node.context.name === node.key.name && - context.state.options.immutable + (each_type & EACH_INDEX_REACTIVE) === 0 ) { - // Fast-path + // Fast-path for when the key === item each_item_is_reactive = false; } else { each_type |= EACH_ITEM_REACTIVE; } - // If there's a destructuring, then we likely need the generated $$index - if (node.index || node.context.type !== 'Identifier') { - each_type |= EACH_INDEX_REACTIVE; - } } else { each_type |= EACH_ITEM_REACTIVE; } @@ -2289,7 +2289,7 @@ export const template_visitors = { ) : b.literal(null); const key_function = - node.key && (each_type & 1) /* EACH_ITEM_REACTIVE */ !== 0 + node.key && ((each_type & EACH_ITEM_REACTIVE) !== 0 || context.state.options.dev) ? b.arrow( [node.context.type === 'Identifier' ? node.context : b.id('$$item')], b.block( From acf73104b5aad66821fcdf1441a0a82b76b13395 Mon Sep 17 00:00:00 2001 From: Jirawat Boonkumnerd Date: Fri, 8 Dec 2023 22:34:36 +0700 Subject: [PATCH 02/24] feat: add GamepadEventHandler type (#9861) Add GamepadEventHandler type for window.addEventListener `gamepadconnected` and `gamepaddisconnected` --- .changeset/late-crabs-lay.md | 5 +++++ packages/svelte/elements.d.ts | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/late-crabs-lay.md diff --git a/.changeset/late-crabs-lay.md b/.changeset/late-crabs-lay.md new file mode 100644 index 000000000000..26a2504a3768 --- /dev/null +++ b/.changeset/late-crabs-lay.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add `gamepadconnected` and `gamepaddisconnected` events diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index a45634028309..09df6ddbc8ef 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -53,6 +53,7 @@ export type KeyboardEventHandler = EventHandler = EventHandler; export type TouchEventHandler = EventHandler; export type PointerEventHandler = EventHandler; +export type GamepadEventHandler = EventHandler; export type UIEventHandler = EventHandler; export type WheelEventHandler = EventHandler; export type AnimationEventHandler = EventHandler; @@ -336,6 +337,12 @@ export interface DOMAttributes { onlostpointercapture?: PointerEventHandler | undefined | null; onlostpointercapturecapture?: PointerEventHandler | undefined | null; + // Gamepad Events + 'on:gamepadconnected'?: GamepadEventHandler | undefined | null; + ongamepadconnected?: GamepadEventHandler | undefined | null; + 'on:gamepaddisconnected'?: GamepadEventHandler | undefined | null; + ongamepaddisconnected?: GamepadEventHandler | undefined | null; + // UI Events 'on:scroll'?: UIEventHandler | undefined | null; onscroll?: UIEventHandler | undefined | null; From 6a89a8f4823433985ea5944cd6bb8e9902e41f4a Mon Sep 17 00:00:00 2001 From: Mike Tunik <57989636+Link-the-elf@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:17:35 +0300 Subject: [PATCH 03/24] chore: optimize `get_amount_class_specificity_increased` (#9853) * Optimaze * pnpm format --------- Co-authored-by: Mike --- .../svelte/src/compiler/phases/2-analyze/css/Selector.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js index b0942d6572c3..2a8e4eff4037 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js @@ -193,13 +193,7 @@ export default class Selector { } get_amount_class_specificity_increased() { - let count = 0; - for (const block of this.blocks) { - if (block.should_encapsulate) { - count++; - } - } - return count; + return this.blocks.filter((block) => block.should_encapsulate).length; } } From 646c0c432b0e22d229e97311099713941926b39b Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 10 Dec 2023 15:50:22 -0800 Subject: [PATCH 04/24] docs: update FAQ regarding Svelte 5 release date (#9882) --- .../src/routes/docs/content/03-appendix/01-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md index af431b850ec8..0acacd3522df 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md @@ -95,7 +95,7 @@ All other features, including stores, are still fully supported in runes mode. ### When is it coming out? -When it's done. The goal is 'sometime later this year'. +When it's done. The goal is sometime in early 2024. ### Should I prepare my code for Svelte 5? From bdd63c81872ec10d2c9a1e9aac06a74c6dfe1353 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 11 Dec 2023 12:42:23 +0000 Subject: [PATCH 05/24] fix: ensure class constructor values are proxied (#9888) * fix: ensure class constructor values are proxied * debugger --- .changeset/tall-books-grin.md | 5 +++ .../phases/3-transform/client/utils.js | 35 +++++++++++++++++-- .../class-private-state-proxy/_config.js | 15 ++++++++ .../class-private-state-proxy/main.svelte | 16 +++++++++ .../samples/class-state-proxy/_config.js | 15 ++++++++ .../samples/class-state-proxy/main.svelte | 12 +++++++ 6 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 .changeset/tall-books-grin.md create mode 100644 packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/class-state-proxy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/class-state-proxy/main.svelte diff --git a/.changeset/tall-books-grin.md b/.changeset/tall-books-grin.md new file mode 100644 index 000000000000..5587b6103c78 --- /dev/null +++ b/.changeset/tall-books-grin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure class constructor values are proxied diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index f22bf8065be3..a2956f5afc51 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -157,16 +157,45 @@ export function serialize_set_binding(node, context, fallback) { let left = node.left; + // Handle class private/public state assignment cases while (left.type === 'MemberExpression') { - if (left.object.type === 'ThisExpression' && left.property.type === 'PrivateIdentifier') { - if (context.state.private_state.has(left.property.name) && !state.in_constructor) { - const value = get_assignment_value(node, context); + if ( + left.object.type === 'ThisExpression' && + left.property.type === 'PrivateIdentifier' && + context.state.private_state.has(left.property.name) + ) { + const value = get_assignment_value(node, context); + if (state.in_constructor) { + // See if we should wrap value in $.proxy + if (context.state.analysis.runes && should_proxy(value)) { + const assignment = fallback(); + if (assignment.type === 'AssignmentExpression') { + assignment.right = b.call('$.proxy', value); + return assignment; + } + } + } else { return b.call( '$.set', left, context.state.analysis.runes && should_proxy(value) ? b.call('$.proxy', value) : value ); } + } else if ( + left.object.type === 'ThisExpression' && + left.property.type === 'Identifier' && + context.state.public_state.has(left.property.name) && + state.in_constructor + ) { + const value = get_assignment_value(node, context); + // See if we should wrap value in $.proxy + if (context.state.analysis.runes && should_proxy(value)) { + const assignment = fallback(); + if (assignment.type === 'AssignmentExpression') { + assignment.right = b.call('$.proxy', value); + return assignment; + } + } } // @ts-expect-error left = left.object; diff --git a/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/_config.js new file mode 100644 index 000000000000..436ce9979876 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/_config.js @@ -0,0 +1,15 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/main.svelte new file mode 100644 index 000000000000..7359f153047b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-private-state-proxy/main.svelte @@ -0,0 +1,16 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-proxy/_config.js new file mode 100644 index 000000000000..436ce9979876 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-proxy/_config.js @@ -0,0 +1,15 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-proxy/main.svelte new file mode 100644 index 000000000000..bf24921321ba --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-proxy/main.svelte @@ -0,0 +1,12 @@ + + + From 7238e1d3ceaaef7b44cc328cebeeb6da5cc671a2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 11 Dec 2023 15:18:16 +0000 Subject: [PATCH 06/24] fix: improve each block index handling (#9889) --- .changeset/wise-dancers-hang.md | 5 +++++ .../phases/3-transform/client/visitors/template.js | 8 +++++++- packages/svelte/src/internal/client/validate.js | 7 ++++--- .../samples/each-block-keyed-index/_config.js | 5 +++++ .../samples/each-block-keyed-index/main.svelte | 3 +++ 5 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 .changeset/wise-dancers-hang.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/main.svelte diff --git a/.changeset/wise-dancers-hang.md b/.changeset/wise-dancers-hang.md new file mode 100644 index 000000000000..0ed0f40a7590 --- /dev/null +++ b/.changeset/wise-dancers-hang.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve each block index handling diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index dc420fc805cc..d62f1fcb490a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2236,6 +2236,12 @@ export const template_visitors = { const item = b.id(each_node_meta.item_name); const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(item.name)); binding.expression = each_item_is_reactive ? b.call('$.unwrap', item) : item; + if (node.index) { + const index_binding = /** @type {import('#compiler').Binding} */ ( + context.state.scope.get(node.index) + ); + index_binding.expression = each_item_is_reactive ? b.call('$.unwrap', index) : index; + } /** @type {import('estree').Statement[]} */ const declarations = []; @@ -2291,7 +2297,7 @@ export const template_visitors = { const key_function = node.key && ((each_type & EACH_ITEM_REACTIVE) !== 0 || context.state.options.dev) ? b.arrow( - [node.context.type === 'Identifier' ? node.context : b.id('$$item')], + [node.context.type === 'Identifier' ? node.context : b.id('$$item'), index], b.block( declarations.concat( b.return(/** @type {import('estree').Expression} */ (context.visit(node.key))) diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 084d51d8f5f9..e3316891ebe3 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,4 +1,5 @@ -import { untrack } from './runtime.js'; +import { EACH_INDEX_REACTIVE } from '../../constants.js'; +import { source, untrack } from './runtime.js'; import { is_array } from './utils.js'; /** regex of all html void element names */ @@ -65,7 +66,7 @@ export function validate_dynamic_element_tag(tag_fn) { /** * @param {() => any} collection - * @param {(item: any) => string} key_fn + * @param {(item: any, index: number) => string} key_fn * @returns {void} */ export function validate_each_keys(collection, key_fn) { @@ -78,7 +79,7 @@ export function validate_each_keys(collection, key_fn) { : Array.from(maybe_array); const length = array.length; for (let i = 0; i < length; i++) { - const key = key_fn(array[i]); + const key = key_fn(array[i], i); if (keys.has(key)) { throw new Error( `Cannot have duplicate keys in a keyed each: Keys at index ${keys.get( diff --git a/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/_config.js b/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/_config.js new file mode 100644 index 000000000000..9e4c77c8be3a --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
0
1
` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/main.svelte b/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/main.svelte new file mode 100644 index 000000000000..0bda97bc691f --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/each-block-keyed-index/main.svelte @@ -0,0 +1,3 @@ +{#each {length: 2} as item, i (`${i}`)} +
{i}
+{/each} From 2ca3c87d1831e648c547115c5df2c50b5f13fd69 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Dec 2023 11:34:59 +0000 Subject: [PATCH 07/24] fix: correctly reflect readonly proxy marker (#9893) --- .changeset/rotten-bags-type.md | 5 +++++ packages/svelte/src/internal/client/proxy/proxy.js | 8 ++++++-- .../samples/state-readonly/Component.svelte | 11 +++++++++++ .../samples/state-readonly/Component2.svelte | 5 +++++ .../runtime-runes/samples/state-readonly/_config.js | 13 +++++++++++++ .../samples/state-readonly/main.svelte | 12 ++++++++++++ 6 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .changeset/rotten-bags-type.md create mode 100644 packages/svelte/tests/runtime-runes/samples/state-readonly/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/state-readonly/Component2.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/state-readonly/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/state-readonly/main.svelte diff --git a/.changeset/rotten-bags-type.md b/.changeset/rotten-bags-type.md new file mode 100644 index 000000000000..0a69089e9345 --- /dev/null +++ b/.changeset/rotten-bags-type.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly reflect readonly proxy marker diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js index eb392749e058..105bbe4e205d 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -149,8 +149,9 @@ const handler = { }, get(target, prop, receiver) { - if (DEV && prop === READONLY_SYMBOL) return target[READONLY_SYMBOL]; - + if (DEV && prop === READONLY_SYMBOL) { + return Reflect.get(target, READONLY_SYMBOL); + } const metadata = target[STATE_SYMBOL]; let s = metadata.s.get(prop); @@ -184,6 +185,9 @@ const handler = { }, has(target, prop) { + if (DEV && prop === READONLY_SYMBOL) { + return Reflect.has(target, READONLY_SYMBOL); + } if (prop === STATE_SYMBOL) { return true; } diff --git a/packages/svelte/tests/runtime-runes/samples/state-readonly/Component.svelte b/packages/svelte/tests/runtime-runes/samples/state-readonly/Component.svelte new file mode 100644 index 000000000000..9916a9abe045 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-readonly/Component.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/state-readonly/Component2.svelte b/packages/svelte/tests/runtime-runes/samples/state-readonly/Component2.svelte new file mode 100644 index 000000000000..88575acd3c26 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-readonly/Component2.svelte @@ -0,0 +1,5 @@ + + +{state} diff --git a/packages/svelte/tests/runtime-runes/samples/state-readonly/_config.js b/packages/svelte/tests/runtime-runes/samples/state-readonly/_config.js new file mode 100644 index 000000000000..a1f7effeae94 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-readonly/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, `\n[object Object]`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-readonly/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-readonly/main.svelte new file mode 100644 index 000000000000..ec6cc9aeba5b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-readonly/main.svelte @@ -0,0 +1,12 @@ + + + +{#if state} + +{/if} From daa19173b12af537dc581758a00eda8dcee57199 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Dec 2023 12:46:32 +0000 Subject: [PATCH 08/24] fix: improve html tag svg behaviour (#9894) --- .changeset/strong-gifts-smoke.md | 5 +++++ packages/svelte/src/internal/client/reconciler.js | 4 +++- .../samples/svg-html-tag4/_config.js | 14 ++++++++++++++ .../samples/svg-html-tag4/main.svelte | 7 +++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/strong-gifts-smoke.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/main.svelte diff --git a/.changeset/strong-gifts-smoke.md b/.changeset/strong-gifts-smoke.md new file mode 100644 index 000000000000..202efbb48094 --- /dev/null +++ b/.changeset/strong-gifts-smoke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve html tag svg behaviour diff --git a/packages/svelte/src/internal/client/reconciler.js b/packages/svelte/src/internal/client/reconciler.js index c96d9460a941..80c221e448c5 100644 --- a/packages/svelte/src/internal/client/reconciler.js +++ b/packages/svelte/src/internal/client/reconciler.js @@ -89,6 +89,8 @@ export function reconcile_html(dom, value, svg) { } var clone = content.cloneNode(true); frag_nodes = Array.from(clone.childNodes); - target.before(svg ? /** @type {Node} */ (clone.firstChild) : clone); + frag_nodes.forEach((node) => { + target.before(node); + }); return /** @type {Array} */ (frag_nodes); } diff --git a/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/_config.js b/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/_config.js new file mode 100644 index 000000000000..1e59eab130e5 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/_config.js @@ -0,0 +1,14 @@ +import { ok, test } from '../../test'; + +export default test({ + test({ assert, target, component }) { + let svg = target.querySelector('svg'); + ok(svg); + + assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg'); + assert.htmlEqual( + svg.outerHTML, + '' + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/main.svelte b/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/main.svelte new file mode 100644 index 000000000000..c8d313689996 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/svg-html-tag4/main.svelte @@ -0,0 +1,7 @@ + + + + {@html content} + From 56de55bb77cb2ad7b3575f9f68ac7e11b2fe48e4 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Dec 2023 17:25:47 +0000 Subject: [PATCH 09/24] fix: improve unstate type definition (#9895) * fix: improve unstate type definition * tweak * tweak --- .changeset/purple-dragons-peel.md | 5 +++++ packages/svelte/src/internal/client/proxy/proxy.js | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/purple-dragons-peel.md diff --git a/.changeset/purple-dragons-peel.md b/.changeset/purple-dragons-peel.md new file mode 100644 index 000000000000..a7e2574d20f9 --- /dev/null +++ b/.changeset/purple-dragons-peel.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve unstate type definition diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js index 105bbe4e205d..4453404dc3b6 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -100,12 +100,12 @@ function unwrap(value, already_unwrapped = new Map()) { } /** - * @template {StateObject} T + * @template T * @param {T} value - * @returns {Record} + * @returns {T} */ export function unstate(value) { - return unwrap(value); + return /** @type {T} */ (unwrap(/** @type {StateObject} */ (value))); } /** From a8e5cc83cd6ff8e82cb669e698feacf9e931f486 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:27:28 +0000 Subject: [PATCH 10/24] Version Packages (next) (#9856) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 7 +++++++ packages/svelte/CHANGELOG.md | 18 ++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index e8a33f2f70d1..3fed7de778be 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -56,6 +56,7 @@ "kind-deers-lay", "kind-eagles-join", "large-clouds-carry", + "late-crabs-lay", "lazy-masks-sit", "lazy-months-knock", "lazy-spiders-think", @@ -81,10 +82,12 @@ "poor-eggs-enjoy", "poor-seahorses-flash", "popular-mangos-rest", + "purple-dragons-peel", "quiet-camels-mate", "rare-pears-whisper", "rich-sheep-burn", "rich-tables-sing", + "rotten-bags-type", "rotten-buckets-develop", "selfish-tools-hide", "serious-socks-cover", @@ -96,6 +99,7 @@ "shiny-baboons-play", "shiny-shrimps-march", "slimy-clouds-talk", + "slow-chefs-dream", "small-papayas-laugh", "smart-parents-swim", "soft-clocks-remember", @@ -104,11 +108,13 @@ "sour-rules-march", "spicy-plums-admire", "stale-comics-look", + "strong-gifts-smoke", "strong-lemons-provide", "sweet-mangos-beg", "swift-donkeys-perform", "swift-ravens-hunt", "swift-seahorses-deliver", + "tall-books-grin", "tall-shrimps-worry", "tall-tigers-wait", "tasty-numbers-perform", @@ -128,6 +134,7 @@ "wet-games-fly", "wicked-clouds-exercise", "wicked-doors-train", + "wise-dancers-hang", "wise-donkeys-marry", "witty-camels-warn" ] diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 7512da7db9a5..6a2534e75759 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,23 @@ # svelte +## 5.0.0-next.23 + +### Patch Changes + +- feat: add `gamepadconnected` and `gamepaddisconnected` events ([#9861](https://github.com/sveltejs/svelte/pull/9861)) + +- fix: improve unstate type definition ([#9895](https://github.com/sveltejs/svelte/pull/9895)) + +- fix: correctly reflect readonly proxy marker ([#9893](https://github.com/sveltejs/svelte/pull/9893)) + +- chore: improve each block fast-path heuristic ([#9855](https://github.com/sveltejs/svelte/pull/9855)) + +- fix: improve html tag svg behaviour ([#9894](https://github.com/sveltejs/svelte/pull/9894)) + +- fix: ensure class constructor values are proxied ([#9888](https://github.com/sveltejs/svelte/pull/9888)) + +- fix: improve each block index handling ([#9889](https://github.com/sveltejs/svelte/pull/9889)) + ## 5.0.0-next.22 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index d2d8422eb478..62b8c3110007 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.0.0-next.22", + "version": "5.0.0-next.23", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 53b5b7a1f662..7b3472a2209e 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.22'; +export const VERSION = '5.0.0-next.23'; export const PUBLIC_VERSION = '5'; From 0236cf87e7fa8a4cb9c88dbf4ca0493a2d29bacb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Dec 2023 21:32:01 +0000 Subject: [PATCH 11/24] fix: better support for top-level snippet declarations (#9898) --- .changeset/ten-peaches-sleep.md | 5 +++++ .../phases/3-transform/client/visitors/template.js | 9 ++++++++- .../runtime-runes/samples/snippet-top-level/_config.js | 8 ++++++++ .../runtime-runes/samples/snippet-top-level/main.svelte | 7 +++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 .changeset/ten-peaches-sleep.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-top-level/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-top-level/main.svelte diff --git a/.changeset/ten-peaches-sleep.md b/.changeset/ten-peaches-sleep.md new file mode 100644 index 000000000000..fa130171122e --- /dev/null +++ b/.changeset/ten-peaches-sleep.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better support for top-level snippet declarations diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index d62f1fcb490a..7c06cac973fe 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2477,7 +2477,14 @@ export const template_visitors = { body = /** @type {import('estree').BlockStatement} */ (context.visit(node.body)); } - context.state.init.push(b.const(node.expression, b.arrow(args, body))); + const path = context.path; + // If we're top-level, then we can create a function for the snippet so that it can be referenced + // in the props declaration (default value pattern). + if (path.length === 1 && path[0].type === 'Fragment') { + context.state.init.push(b.function_declaration(node.expression, args, body)); + } else { + context.state.init.push(b.const(node.expression, b.arrow(args, body))); + } if (context.state.options.dev) { context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression))); } diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-top-level/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-top-level/_config.js new file mode 100644 index 000000000000..129a5734028c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-top-level/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true // Render in dev mode to check that the validation error is not thrown + }, + html: `

hello world

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-top-level/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-top-level/main.svelte new file mode 100644 index 000000000000..b5da34accba2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-top-level/main.svelte @@ -0,0 +1,7 @@ + +{@render children()} +{#snippet snippet()} +

hello world

+{/snippet} From a9a5b11c78bdf77fe956244f547e4760cee40e16 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Dec 2023 21:54:49 +0000 Subject: [PATCH 12/24] fix: improve props aliasing (#9900) --- .changeset/beige-rabbits-shave.md | 5 +++ .../phases/3-transform/client/utils.js | 3 ++ .../samples/props-alias/Counter.svelte | 5 +++ .../samples/props-alias/_config.js | 32 +++++++++++++++++++ .../samples/props-alias/main.svelte | 15 +++++++++ 5 files changed, 60 insertions(+) create mode 100644 .changeset/beige-rabbits-shave.md create mode 100644 packages/svelte/tests/runtime-runes/samples/props-alias/Counter.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-alias/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/props-alias/main.svelte diff --git a/.changeset/beige-rabbits-shave.md b/.changeset/beige-rabbits-shave.md new file mode 100644 index 000000000000..b63c13d06c0f --- /dev/null +++ b/.changeset/beige-rabbits-shave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve props aliasing diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index a2956f5afc51..305dbcd543ac 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -81,6 +81,9 @@ export function serialize_get_binding(node, state) { return b.call(node); } + if (binding.prop_alias) { + return b.member(b.id('$$props'), b.id(binding.prop_alias)); + } return b.member(b.id('$$props'), node); } diff --git a/packages/svelte/tests/runtime-runes/samples/props-alias/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/props-alias/Counter.svelte new file mode 100644 index 000000000000..a0a4ebd14cbf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-alias/Counter.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/props-alias/_config.js b/packages/svelte/tests/runtime-runes/samples/props-alias/_config.js new file mode 100644 index 000000000000..0fc80f0df5b7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-alias/_config.js @@ -0,0 +1,32 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

0 0 0 0

+ + + + + `, + + async test({ assert, target, component }) { + const [b1, b2, b3, b4] = target.querySelectorAll('button'); + + b1.click(); + b2.click(); + b3.click(); + b4.click(); + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` +

1 1 0 0

+ + + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-alias/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-alias/main.svelte new file mode 100644 index 000000000000..c3908f72fa11 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-alias/main.svelte @@ -0,0 +1,15 @@ + + +

{bound} {bound_nested.count} {unbound} {unbound_nested.count}

+ + + + + From 436a6c3dc48d0336b91da33728b0858c320a86cb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Dec 2023 02:06:38 +0000 Subject: [PATCH 13/24] fix: improve $inspect batching (#9902) * fix: improve $inspect batching * fix dev bug * simplify * simplify --- .changeset/heavy-ears-rule.md | 5 +++ .../svelte/src/internal/client/proxy/proxy.js | 16 ++++++-- .../svelte/src/internal/client/runtime.js | 37 +++++++++++++++++-- 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 .changeset/heavy-ears-rule.md diff --git a/.changeset/heavy-ears-rule.md b/.changeset/heavy-ears-rule.md new file mode 100644 index 000000000000..7dcd27070ad2 --- /dev/null +++ b/.changeset/heavy-ears-rule.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve $inspect batching diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js index 4453404dc3b6..57c21787981c 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -7,7 +7,8 @@ import { source, updating_derived, UNINITIALIZED, - mutable_source + mutable_source, + batch_inspect } from '../runtime.js'; import { define_property, @@ -166,8 +167,17 @@ const handler = { metadata.s.set(prop, s); } - const value = s !== undefined ? get(s) : Reflect.get(target, prop, receiver); - return value === UNINITIALIZED ? undefined : value; + if (s !== undefined) { + const value = get(s); + return value === UNINITIALIZED ? undefined : value; + } + + if (DEV) { + if (typeof target[prop] === 'function' && prop !== Symbol.iterator) { + return batch_inspect(target, prop, receiver); + } + } + return Reflect.get(target, prop, receiver); }, getOwnPropertyDescriptor(target, prop) { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 428c03322986..6fb4cc927a02 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -37,6 +37,8 @@ let current_scheduler_mode = FLUSH_MICROTASK; // Used for handling scheduling let is_micro_task_queued = false; let is_task_queued = false; +// Used for $inspect +export let is_batching_effect = false; // Handle effect queues @@ -62,8 +64,8 @@ let current_dependencies = null; let current_dependencies_index = 0; /** @type {null | import('./types.js').Signal[]} */ let current_untracked_writes = null; -// Handling capturing of signals from object property getters -let current_should_capture_signal = false; +/** @type {null | import('./types.js').Signal} */ +let last_inspected_signal = null; /** If `true`, `get`ting the signal should not register it as a dependency */ export let current_untracking = false; /** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */ @@ -110,6 +112,29 @@ function is_runes(context) { return component_context !== null && component_context.r; } +/** + * @param {import("./proxy/proxy.js").StateObject} target + * @param {string | symbol} prop + * @param {any} receiver + */ +export function batch_inspect(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + return function () { + const previously_batching_effect = is_batching_effect; + is_batching_effect = true; + try { + return Reflect.apply(value, receiver, arguments); + } finally { + is_batching_effect = previously_batching_effect; + if (last_inspected_signal !== null) { + // @ts-expect-error + for (const fn of last_inspected_signal.inspect) fn(); + last_inspected_signal = null; + } + } + }; +} + /** * @param {null | import('./types.js').ComponentContext} context_stack_item * @returns {void} @@ -1053,8 +1078,12 @@ export function set_signal_value(signal, value) { // @ts-expect-error if (DEV && signal.inspect) { - // @ts-expect-error - for (const fn of signal.inspect) fn(); + if (is_batching_effect) { + last_inspected_signal = signal; + } else { + // @ts-expect-error + for (const fn of signal.inspect) fn(); + } } } } From 4e61db7201c5320c6cf0bb323fa82659c56c2f37 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Dec 2023 02:11:44 +0000 Subject: [PATCH 14/24] chore: improve readonly prop messaging (#9901) --- .changeset/large-turkeys-deny.md | 5 +++++ packages/svelte/src/internal/client/proxy/readonly.js | 2 +- .../samples/proxy-prop-default-readonly/_config.js | 2 +- .../runtime-runes/samples/proxy-prop-readonly/_config.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/large-turkeys-deny.md diff --git a/.changeset/large-turkeys-deny.md b/.changeset/large-turkeys-deny.md new file mode 100644 index 000000000000..6dcd227f7f5b --- /dev/null +++ b/.changeset/large-turkeys-deny.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: improve readonly prop messaging diff --git a/packages/svelte/src/internal/client/proxy/readonly.js b/packages/svelte/src/internal/client/proxy/readonly.js index 3fce78339769..abc2163d9724 100644 --- a/packages/svelte/src/internal/client/proxy/readonly.js +++ b/packages/svelte/src/internal/client/proxy/readonly.js @@ -42,7 +42,7 @@ export function readonly(value) { */ const readonly_error = (_, prop) => { throw new Error( - `Non-bound props cannot be mutated — use \`bind:={...}\` to make \`${prop}\` settable. Fallback values can never be mutated.` + `Non-bound props cannot be mutated — to make the \`${prop}\` settable, ensure the object it is used within is bound as a prop \`bind:={...}\`. Fallback values can never be mutated.` ); }; diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-default-readonly/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-default-readonly/_config.js index 352d874648c2..d78db389d74e 100644 --- a/packages/svelte/tests/runtime-runes/samples/proxy-prop-default-readonly/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-default-readonly/_config.js @@ -15,5 +15,5 @@ export default test({ }, runtime_error: - 'Non-bound props cannot be mutated — use `bind:={...}` to make `count` settable. Fallback values can never be mutated.' + 'Non-bound props cannot be mutated — to make the `count` settable, ensure the object it is used within is bound as a prop `bind:={...}`. Fallback values can never be mutated.' }); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js index 352d874648c2..d78db389d74e 100644 --- a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js @@ -15,5 +15,5 @@ export default test({ }, runtime_error: - 'Non-bound props cannot be mutated — use `bind:={...}` to make `count` settable. Fallback values can never be mutated.' + 'Non-bound props cannot be mutated — to make the `count` settable, ensure the object it is used within is bound as a prop `bind:={...}`. Fallback values can never be mutated.' }); From b0511a59660c9ec4c717a5e823186c9b292bfb44 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Dec 2023 14:15:26 +0000 Subject: [PATCH 15/24] fix: improve attribute directive reactivity detection (#9907) --- .changeset/happy-suits-film.md | 5 ++ .../3-transform/client/visitors/template.js | 90 +++++++++++-------- packages/svelte/src/internal/client/render.js | 38 +++++++- .../_config.js | 16 ++++ .../main.svelte | 18 ++++ 5 files changed, 128 insertions(+), 39 deletions(-) create mode 100644 .changeset/happy-suits-film.md create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/main.svelte diff --git a/.changeset/happy-suits-film.md b/.changeset/happy-suits-film.md new file mode 100644 index 000000000000..84fdcbdb82e4 --- /dev/null +++ b/.changeset/happy-suits-film.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve attribute directive reactivity detection diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 7c06cac973fe..6ad1026cc744 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -63,32 +63,48 @@ function get_attribute_name(element, attribute, context) { * @param {boolean} is_attributes_reactive */ function serialize_style_directives(style_directives, element_id, context, is_attributes_reactive) { - if (style_directives.length > 0) { - const values = style_directives.map((directive) => { - let value = - directive.value === true - ? serialize_get_binding({ name: directive.name, type: 'Identifier' }, context.state) - : serialize_attribute_value(directive.value, context)[1]; - return b.stmt( - b.call( - '$.style', - element_id, - b.literal(directive.name), - value, - /** @type {import('estree').Expression} */ ( - directive.modifiers.includes('important') ? b.true : undefined - ) + const state = context.state; + + for (const directive of style_directives) { + let value = + directive.value === true + ? serialize_get_binding({ name: directive.name, type: 'Identifier' }, context.state) + : serialize_attribute_value(directive.value, context)[1]; + const grouped = b.stmt( + b.call( + '$.style', + element_id, + b.literal(directive.name), + value, + /** @type {import('estree').Expression} */ ( + directive.modifiers.includes('important') ? b.true : undefined + ) + ) + ); + const singular = b.stmt( + b.call( + '$.style_effect', + element_id, + b.literal(directive.name), + b.arrow([], value), + /** @type {import('estree').Expression} */ ( + directive.modifiers.includes('important') ? b.true : undefined ) + ) + ); + + const contains_call_expression = + Array.isArray(directive.value) && + directive.value.some( + (v) => v.type === 'ExpressionTag' && v.metadata.contains_call_expression ); - }); - if ( - is_attributes_reactive || - style_directives.some((directive) => directive.metadata.dynamic) - ) { - context.state.update.push(...values.map((v) => ({ grouped: v }))); + if (!is_attributes_reactive && contains_call_expression) { + state.update_effects.push(singular); + } else if (is_attributes_reactive || directive.metadata.dynamic || contains_call_expression) { + state.update.push({ grouped, singular }); } else { - context.state.init.push(...values); + state.init.push(grouped); } } } @@ -123,21 +139,21 @@ function parse_directive_name(name) { * @param {boolean} is_attributes_reactive */ function serialize_class_directives(class_directives, element_id, context, is_attributes_reactive) { - if (class_directives.length > 0) { - const values = class_directives.map((directive) => { - const value = /** @type {import('estree').Expression} */ ( - context.visit(directive.expression) - ); - return b.stmt(b.call('$.class_toggle', element_id, b.literal(directive.name), value)); - }); + const state = context.state; + for (const directive of class_directives) { + const value = /** @type {import('estree').Expression} */ (context.visit(directive.expression)); + const grouped = b.stmt(b.call('$.class_toggle', element_id, b.literal(directive.name), value)); + const singular = b.stmt( + b.call('$.class_toggle_effect', element_id, b.literal(directive.name), b.arrow([], value)) + ); + const contains_call_expression = directive.expression.type === 'CallExpression'; - if ( - is_attributes_reactive || - class_directives.some((directive) => directive.metadata.dynamic) - ) { - context.state.update.push(...values.map((v) => ({ grouped: v }))); + if (!is_attributes_reactive && contains_call_expression) { + state.update_effects.push(singular); + } else if (is_attributes_reactive || directive.metadata.dynamic || contains_call_expression) { + state.update.push({ grouped, singular }); } else { - context.state.init.push(...values); + state.init.push(grouped); } } } @@ -295,7 +311,9 @@ function serialize_element_spread_attributes(attributes, context, element, eleme values.push(/** @type {import('estree').Expression} */ (context.visit(attribute))); } - is_reactive ||= attribute.metadata.dynamic; + is_reactive ||= + attribute.metadata.dynamic || + (attribute.type === 'SpreadAttribute' && attribute.metadata.contains_call_expression); } const lowercase_attributes = diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 93c823ce3364..a963c832c381 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -444,6 +444,20 @@ export function class_toggle(dom, class_name, value) { dom.classList.remove(class_name); } } + +/** + * @param {Element} dom + * @param {string} class_name + * @param {() => boolean} value + * @returns {void} + */ +export function class_toggle_effect(dom, class_name, value) { + render_effect(() => { + const string = value(); + class_toggle(dom, class_name, string); + }); +} + /** * Selects the correct option(s) (depending on whether this is a multiple select) * @template V @@ -2359,13 +2373,31 @@ export function set_custom_element_data(node, prop, value) { * @param {boolean} [important] */ export function style(dom, key, value, important) { + const style = dom.style; + const prev_value = style.getPropertyValue(key); if (value == null) { - dom.style.removeProperty(key); - } else { - dom.style.setProperty(key, value, important ? 'important' : ''); + if (prev_value !== '') { + style.removeProperty(key); + } + } else if (prev_value !== value) { + style.setProperty(key, value, important ? 'important' : ''); } } +/** + * @param {HTMLElement} dom + * @param {string} key + * @param {() => string} value + * @param {boolean} [important] + * @returns {void} + */ +export function style_effect(dom, key, value, important) { + render_effect(() => { + const string = value(); + style(dom, key, string, important); + }); +} + /** * List of attributes that should always be set through the attr method, * because updating them through the property setter doesn't work reliably. diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/_config.js b/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/_config.js new file mode 100644 index 000000000000..e9dae162770d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/_config.js @@ -0,0 +1,16 @@ +import { test } from '../../test'; + +export default test({ + html: `
' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/main.svelte new file mode 100644 index 000000000000..00c337bdfd0d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-call-expressions/main.svelte @@ -0,0 +1,18 @@ + + +
+
+ + From 55656f520dca427de2fd0bfbeb2fa7f168239cbd Mon Sep 17 00:00:00 2001 From: navorite Date: Wed, 13 Dec 2023 16:18:25 +0200 Subject: [PATCH 16/24] feat: add support for {@const} inside snippet block (#9904) * check for snippet block * change the error msg * edit tests * changeset * test --- .changeset/chatty-cups-drop.md | 5 +++++ packages/svelte/src/compiler/errors.js | 2 +- .../src/compiler/phases/2-analyze/validation.js | 1 + .../samples/snippet-const/_config.js | 16 ++++++++++++++++ .../samples/snippet-const/main.svelte | 10 ++++++++++ .../samples/const-tag-placement-1/errors.json | 2 +- .../samples/const-tag-placement-2/errors.json | 2 +- 7 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 .changeset/chatty-cups-drop.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-const/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-const/main.svelte diff --git a/.changeset/chatty-cups-drop.md b/.changeset/chatty-cups-drop.md new file mode 100644 index 000000000000..282a9b25ded2 --- /dev/null +++ b/.changeset/chatty-cups-drop.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add support for `{@const}` inside snippet block diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 73b6f2e86d1d..c083604b87c1 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -333,7 +333,7 @@ const compiler_options = { /** @satisfies {Errors} */ const const_tag = { 'invalid-const-placement': () => - `{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ` + `{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ` }; /** @satisfies {Errors} */ diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 8dad40e2386e..08402009abd5 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -476,6 +476,7 @@ export const validation = { grand_parent?.type !== 'SvelteComponent' && grand_parent?.type !== 'EachBlock' && grand_parent?.type !== 'AwaitBlock' && + grand_parent?.type !== 'SnippetBlock' && ((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') || !grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot'))) ) { diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-const/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-const/_config.js new file mode 100644 index 000000000000..8867993dbbc5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-const/_config.js @@ -0,0 +1,16 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + async test({ assert, target }) { + const btn = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, ''); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ''); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ''); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-const/main.svelte new file mode 100644 index 000000000000..cad63519bf75 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-const/main.svelte @@ -0,0 +1,10 @@ + + +{#snippet counter()} + {@const doubled = count * 2} + +{/snippet} + +{@render counter()} diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json index f90fcf80ab47..66ff86663f6d 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-const-placement", - "message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", + "message": "{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", "start": { "line": 5, "column": 0 }, "end": { "line": 5, "column": 18 } } diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json index 1124d0b4f869..6c14fdbaf728 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-const-placement", - "message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", + "message": "{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", "start": { "line": 7, "column": 4 }, "end": { "line": 7, "column": 18 } } From f2d111264cb2b61d20564a44d0878fdd747741f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:26:43 +0000 Subject: [PATCH 17/24] Version Packages (next) (#9899) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 6 ++++++ packages/svelte/CHANGELOG.md | 16 ++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 3fed7de778be..04efeb493413 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -11,8 +11,10 @@ "changesets": [ "afraid-moose-matter", "beige-flies-wash", + "beige-rabbits-shave", "brave-walls-destroy", "brown-spoons-boil", + "chatty-cups-drop", "chatty-taxis-juggle", "chilled-pumas-invite", "chilly-dolphins-lick", @@ -45,7 +47,9 @@ "great-icons-retire", "green-eggs-approve", "green-hounds-play", + "happy-suits-film", "healthy-planes-vanish", + "heavy-ears-rule", "honest-icons-change", "hungry-dots-fry", "hungry-tips-unite", @@ -56,6 +60,7 @@ "kind-deers-lay", "kind-eagles-join", "large-clouds-carry", + "large-turkeys-deny", "late-crabs-lay", "lazy-masks-sit", "lazy-months-knock", @@ -119,6 +124,7 @@ "tall-tigers-wait", "tasty-numbers-perform", "ten-foxes-repeat", + "ten-peaches-sleep", "ten-worms-reflect", "thin-foxes-lick", "thirty-flowers-sit", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 6a2534e75759..a12ae4c138c8 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,21 @@ # svelte +## 5.0.0-next.24 + +### Patch Changes + +- fix: improve props aliasing ([#9900](https://github.com/sveltejs/svelte/pull/9900)) + +- feat: add support for `{@const}` inside snippet block ([#9904](https://github.com/sveltejs/svelte/pull/9904)) + +- fix: improve attribute directive reactivity detection ([#9907](https://github.com/sveltejs/svelte/pull/9907)) + +- fix: improve $inspect batching ([#9902](https://github.com/sveltejs/svelte/pull/9902)) + +- chore: improve readonly prop messaging ([#9901](https://github.com/sveltejs/svelte/pull/9901)) + +- fix: better support for top-level snippet declarations ([#9898](https://github.com/sveltejs/svelte/pull/9898)) + ## 5.0.0-next.23 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 62b8c3110007..6b12263d4a65 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.0.0-next.23", + "version": "5.0.0-next.24", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 7b3472a2209e..fb237c730d4e 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.23'; +export const VERSION = '5.0.0-next.24'; export const PUBLIC_VERSION = '5'; From 3a4a09102c059b98c8fa26d7601c27d0612f7eeb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Dec 2023 11:23:48 +0000 Subject: [PATCH 18/24] fix: improve whitespace handling (#9912) revert --- .changeset/dull-mangos-wave.md | 5 +++++ packages/svelte/src/compiler/phases/3-transform/utils.js | 5 +++-- .../runtime-runes/samples/snippet-whitespace/_config.js | 8 ++++++++ .../runtime-runes/samples/snippet-whitespace/main.svelte | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .changeset/dull-mangos-wave.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-whitespace/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte diff --git a/.changeset/dull-mangos-wave.md b/.changeset/dull-mangos-wave.md new file mode 100644 index 000000000000..18ba4e3c8c59 --- /dev/null +++ b/.changeset/dull-mangos-wave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve whitespace handling diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 5b3a5e7c857c..9c7a559394bd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -177,8 +177,9 @@ export function clean_nodes( node.data = node.data.replace(regex_whitespaces_strict, ' '); node.raw = node.raw.replace(regex_whitespaces_strict, ' '); if ( - (last_text === null || !regex_ends_with_whitespaces.test(last_text.data)) && - (!can_remove_entirely || node.data !== ' ') + (last_text === null && !can_remove_entirely) || + node.data !== ' ' || + node.data.charCodeAt(0) === 160 // non-breaking space ) { trimmed.push(node); } diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/_config.js new file mode 100644 index 000000000000..6c130a0bfbe0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true // Render in dev mode to check that the validation error is not thrown + }, + html: `A\nB\nC\nD` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte new file mode 100644 index 000000000000..a85a932602a5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte @@ -0,0 +1,5 @@ +A +{#snippet snip()}C{/snippet} +B +{@render snip()} +D From b1efd8c4cd8224d6d59cd9f30f9c1491d731abee Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Dec 2023 12:35:23 +0000 Subject: [PATCH 19/24] fix: improve each block fallback handling (#9914) --- .changeset/four-flies-hammer.md | 5 +++++ packages/svelte/src/internal/client/each.js | 22 +++++++++---------- .../samples/each-fallback/_config.js | 17 ++++++++++++++ .../samples/each-fallback/main.svelte | 22 +++++++++++++++++++ 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 .changeset/four-flies-hammer.md create mode 100644 packages/svelte/tests/runtime-runes/samples/each-fallback/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-fallback/main.svelte diff --git a/.changeset/four-flies-hammer.md b/.changeset/four-flies-hammer.md new file mode 100644 index 000000000000..b6d3770a4c95 --- /dev/null +++ b/.changeset/four-flies-hammer.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve each block fallback handling diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index 0602d789f7a0..c37183d7bfab 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -119,6 +119,14 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re current_fallback = fallback; }; + /** @param {import('./types.js').EachBlock} block */ + const clear_each = (block) => { + const flags = block.f; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const anchor_node = block.a; + reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); + }; + const each = render_effect( () => { /** @type {V[]} */ @@ -137,7 +145,9 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re if (fallback_fn !== null) { if (length === 0) { if (block.v.length !== 0 || render === null) { + clear_each(block); create_fallback_effect(); + return; } } else if (block.v.length === 0 && current_fallback !== null) { const fallback = current_fallback; @@ -160,17 +170,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re false ); - render = render_effect( - /** @param {import('./types.js').EachBlock} block */ - (block) => { - const flags = block.f; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const anchor_node = block.a; - reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); - }, - block, - true - ); + render = render_effect(clear_each, block, true); push_destroy_fn(each, () => { const flags = block.f; diff --git a/packages/svelte/tests/runtime-runes/samples/each-fallback/_config.js b/packages/svelte/tests/runtime-runes/samples/each-fallback/_config.js new file mode 100644 index 000000000000..183184e34194 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-fallback/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, component }) { + const [b1] = target.querySelectorAll('button'); + assert.htmlEqual(target.innerHTML, '
abc
'); + flushSync(() => { + b1.click(); + }); + assert.htmlEqual(target.innerHTML, '
Fallback
'); + flushSync(() => { + b1.click(); + }); + assert.htmlEqual(target.innerHTML, '
abc
'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-fallback/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-fallback/main.svelte new file mode 100644 index 000000000000..b0fcc9a86f2a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-fallback/main.svelte @@ -0,0 +1,22 @@ + +
+ {#each Object.keys(data) as key} + {key} + {:else} + Fallback + {/each} +
+ From 2608e621d6e7f81efc317cef8f52768511d980e3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Dec 2023 15:05:32 +0000 Subject: [PATCH 20/24] add test (#9917) --- .changeset/wild-foxes-wonder.md | 5 ++ packages/svelte/src/internal/client/each.js | 26 +++++--- .../samples/animation-flip-2/_config.js | 60 +++++++++++++++++++ .../samples/animation-flip-2/main.svelte | 28 +++++++++ 4 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 .changeset/wild-foxes-wonder.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/animation-flip-2/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/animation-flip-2/main.svelte diff --git a/.changeset/wild-foxes-wonder.md b/.changeset/wild-foxes-wonder.md new file mode 100644 index 000000000000..b7e57608686d --- /dev/null +++ b/.changeset/wild-foxes-wonder.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: cleanup each block animations on destroy diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index c37183d7bfab..e493241feda7 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -744,17 +744,27 @@ export function destroy_each_item_block( const transitions = block.s; if (apply_transitions && transitions !== null) { - trigger_transitions(transitions, 'out'); - if (transition_block !== null) { - transition_block.push(block); + // We might have pending key transitions, if so remove them first + for (let other of transitions) { + if (other.r === 'key') { + transitions.delete(other); + } } - } else { - const dom = block.d; - if (!controlled && dom !== null) { - remove(dom); + if (transitions.size === 0) { + block.s = null; + } else { + trigger_transitions(transitions, 'out'); + if (transition_block !== null) { + transition_block.push(block); + } + return; } - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); } + const dom = block.d; + if (!controlled && dom !== null) { + remove(dom); + } + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); } /** diff --git a/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/_config.js new file mode 100644 index 000000000000..28238df40287 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/_config.js @@ -0,0 +1,60 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + async test({ assert, target, window }) { + const button = target.querySelector('button'); + ok(button); + + assert.htmlEqual( + target.innerHTML, + ` +
` + ); + + flushSync(() => { + button.click(); + }); + + assert.htmlEqual( + target.innerHTML, + ` +
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/main.svelte b/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/main.svelte new file mode 100644 index 000000000000..99795007e801 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/animation-flip-2/main.svelte @@ -0,0 +1,28 @@ + + + + +
+ {#each todos as todo (todo.id)} + + {/each} +
From b779e72eb6887895e2a7a136116542df0def574b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:06:45 +0000 Subject: [PATCH 21/24] Version Packages (next) (#9913) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 3 +++ packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 04efeb493413..01fe363029a3 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -26,6 +26,7 @@ "curly-lizards-dream", "dirty-garlics-design", "dirty-tips-add", + "dull-mangos-wave", "early-ads-tie", "eight-steaks-shout", "eighty-bikes-camp", @@ -38,6 +39,7 @@ "five-tigers-search", "flat-melons-protect", "forty-comics-invent", + "four-flies-hammer", "fresh-weeks-trade", "friendly-lies-camp", "funny-wombats-argue", @@ -140,6 +142,7 @@ "wet-games-fly", "wicked-clouds-exercise", "wicked-doors-train", + "wild-foxes-wonder", "wise-dancers-hang", "wise-donkeys-marry", "witty-camels-warn" diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index a12ae4c138c8..cf54f4399b1b 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.0.0-next.25 + +### Patch Changes + +- fix: improve whitespace handling ([#9912](https://github.com/sveltejs/svelte/pull/9912)) + +- fix: improve each block fallback handling ([#9914](https://github.com/sveltejs/svelte/pull/9914)) + +- fix: cleanup each block animations on destroy ([#9917](https://github.com/sveltejs/svelte/pull/9917)) + ## 5.0.0-next.24 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6b12263d4a65..98fb4b0c7def 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.0.0-next.24", + "version": "5.0.0-next.25", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index fb237c730d4e..85bb068ce90f 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.24'; +export const VERSION = '5.0.0-next.25'; export const PUBLIC_VERSION = '5'; From 59c7487f3699b768d25b897e0030b096c7369223 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Dec 2023 17:11:57 +0000 Subject: [PATCH 22/24] fix: better handle array property deletion reactivity (#9921) --- .changeset/lovely-carpets-lick.md | 5 ++ .../svelte/src/internal/client/proxy/proxy.js | 19 ++++++- .../samples/each-mutation-2/_config.js | 52 +++++++++++++++++++ .../samples/each-mutation-2/main.svelte | 15 ++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 .changeset/lovely-carpets-lick.md create mode 100644 packages/svelte/tests/runtime-runes/samples/each-mutation-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-mutation-2/main.svelte diff --git a/.changeset/lovely-carpets-lick.md b/.changeset/lovely-carpets-lick.md new file mode 100644 index 000000000000..bd86e2ef447b --- /dev/null +++ b/.changeset/lovely-carpets-lick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better handle array property deletion reactivity diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js index 57c21787981c..ae993267d6be 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -140,13 +140,28 @@ const handler = { deleteProperty(target, prop) { const metadata = target[STATE_SYMBOL]; - const s = metadata.s.get(prop); + const is_array = metadata.a; + const boolean = delete target[prop]; + + // If we have mutated an array directly, and the deletion + // was successful we will also need to update the length + // before updating the field or the version. This is to + // ensure any effects observing length can execute before + // effects that listen to the fields – otherwise they will + // operate an an index that no longer exists. + if (is_array && boolean) { + const ls = metadata.s.get('length'); + const length = target.length - 1; + if (ls !== undefined && ls.v !== length) { + set(ls, length); + } + } if (s !== undefined) set(s, UNINITIALIZED); if (prop in target) update(metadata.v); - return delete target[prop]; + return boolean; }, get(target, prop, receiver) { diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation-2/_config.js b/packages/svelte/tests/runtime-runes/samples/each-mutation-2/_config.js new file mode 100644 index 000000000000..34ffe2ee1491 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation-2/_config.js @@ -0,0 +1,52 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `

1

2

3

`, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

1

2

3

4

` + ); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

1

2

3

4

5

` + ); + + flushSync(() => { + btn2.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

1

2

3

4

` + ); + + flushSync(() => { + btn2.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

1

2

3

` + ); + + flushSync(() => { + btn2.click(); + }); + + assert.htmlEqual(target.innerHTML, `

1

2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-mutation-2/main.svelte new file mode 100644 index 000000000000..530532ccf2f6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation-2/main.svelte @@ -0,0 +1,15 @@ + + + + + + +{#each numbers as number} +

{number.id}

+{/each} From b8f3c49e5ff7378df2196d0c9ae24a58e282bb0a Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 15 Dec 2023 17:10:31 +0000 Subject: [PATCH 23/24] fix: improve event delegation handler hoisting (#9929) * fix: improve event delegation handler hoisting * fixes --- .changeset/real-guests-do.md | 5 +++++ .../src/compiler/phases/1-parse/utils/html.js | 14 ++++++++++++++ .../svelte/src/compiler/phases/2-analyze/index.js | 11 +++++++++++ .../compiler/phases/3-transform/client/utils.js | 3 +-- .../samples/store-reference/_config.js | 7 +++++++ .../samples/store-reference/main.svelte | 12 ++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .changeset/real-guests-do.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte diff --git a/.changeset/real-guests-do.md b/.changeset/real-guests-do.md new file mode 100644 index 000000000000..3f0c26a477d5 --- /dev/null +++ b/.changeset/real-guests-do.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve event delegation handler hoisting diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/html.js b/packages/svelte/src/compiler/phases/1-parse/utils/html.js index de2776942bc6..bb45f9d6db4b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/html.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/html.js @@ -121,6 +121,16 @@ function validate_code(code) { // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission +const interactive_elements = new Set([ + 'a', + 'button', + 'iframe', + 'embed', + 'input', + 'select', + 'textarea' +]); + /** @type {Record>} */ const disallowed_contents = { li: new Set(['li']), @@ -143,6 +153,10 @@ const disallowed_contents = { th: new Set(['td', 'th', 'tr']) }; +for (const interactive_element of interactive_elements) { + disallowed_contents[interactive_element] = interactive_elements; +} + // can this be a child of the parent element, or does it implicitly // close it, like `
  • one
  • two`? diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index bd43aca39846..35b7bbc170dd 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -166,6 +166,7 @@ function get_delegated_event(node, context) { return non_hoistable; } + const visited_references = new Set(); const scope = target_function.metadata.scope; for (const [reference] of scope.references) { // Bail-out if the arguments keyword is used @@ -174,6 +175,15 @@ function get_delegated_event(node, context) { } const binding = scope.get(reference); + // If we have multiple references to the same store using $ prefix, bail out. + if ( + binding !== null && + binding.kind === 'store_sub' && + visited_references.has(reference.slice(1)) + ) { + return non_hoistable; + } + if ( binding !== null && // Bail-out if the the binding is a rest param @@ -188,6 +198,7 @@ function get_delegated_event(node, context) { ) { return non_hoistable; } + visited_references.add(reference); } return { type: 'hoistable', function: target_function }; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 305dbcd543ac..7a0f8af075cd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -363,8 +363,7 @@ function get_hoistable_params(node, context) { binding.kind === 'prop' && !binding.reassigned && binding.initial === null && - !context.state.analysis.accessors && - context.state.analysis.runes + !context.state.analysis.accessors ) { // Handle $$props.something use-cases if (!added_props) { diff --git a/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js b/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js new file mode 100644 index 000000000000..eceba7a93f8e --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { dev: true }, // tests `@validate_store` code generation + + html: `` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte b/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte new file mode 100644 index 000000000000..2afc46d257b1 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte @@ -0,0 +1,12 @@ + + + From eab690d31a7203ac23334460c9ae68844d43dc26 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 17 Dec 2023 14:49:06 +0000 Subject: [PATCH 24/24] Version Packages (next) (#9922) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 2 ++ packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 01fe363029a3..c978e2128138 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -70,6 +70,7 @@ "lemon-geese-drum", "light-pens-watch", "long-crews-return", + "lovely-carpets-lick", "lovely-items-turn", "lovely-rules-eat", "lucky-schools-hang", @@ -92,6 +93,7 @@ "purple-dragons-peel", "quiet-camels-mate", "rare-pears-whisper", + "real-guests-do", "rich-sheep-burn", "rich-tables-sing", "rotten-bags-type", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index cf54f4399b1b..c6e4e7555b33 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.0.0-next.26 + +### Patch Changes + +- fix: better handle array property deletion reactivity ([#9921](https://github.com/sveltejs/svelte/pull/9921)) + +- fix: improve event delegation handler hoisting ([#9929](https://github.com/sveltejs/svelte/pull/9929)) + ## 5.0.0-next.25 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 98fb4b0c7def..6947dbc16920 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.0.0-next.25", + "version": "5.0.0-next.26", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 85bb068ce90f..06e4186895a8 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.25'; +export const VERSION = '5.0.0-next.26'; export const PUBLIC_VERSION = '5';