From ebbb5dd65556101a0be19c78344447039eaa0263 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Jan 2024 12:59:12 +0000 Subject: [PATCH 1/8] fix: improve internal signal dependency checking logic (#10111) --- .changeset/olive-kangaroos-brake.md | 5 +++++ .../svelte/src/internal/client/runtime.js | 14 ++++++++---- .../samples/each-updates/_config.js | 2 +- .../samples/text-effect-multi-deps/_config.js | 22 +++++++++++++++++++ .../text-effect-multi-deps/main.svelte | 12 ++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 .changeset/olive-kangaroos-brake.md create mode 100644 packages/svelte/tests/runtime-runes/samples/text-effect-multi-deps/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/text-effect-multi-deps/main.svelte diff --git a/.changeset/olive-kangaroos-brake.md b/.changeset/olive-kangaroos-brake.md new file mode 100644 index 0000000000..68f3d40704 --- /dev/null +++ b/.changeset/olive-kangaroos-brake.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve internal signal dependency checking logic diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 946a6c3196..1e5301a970 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -349,15 +349,21 @@ function execute_signal_fn(signal) { if (current_dependencies !== null) { let i; if (dependencies !== null) { - const dep_length = dependencies.length; + // Include any dependencies up until the current_dependencies_index. + const full_dependencies = + current_dependencies_index === 0 + ? dependencies + : dependencies.slice(0, current_dependencies_index).concat(current_dependencies); + const dep_length = full_dependencies.length; // If we have more than 16 elements in the array then use a Set for faster performance // TODO: evaluate if we should always just use a Set or not here? - const current_dependencies_set = dep_length > 16 ? new Set(current_dependencies) : null; + const current_dependencies_set = dep_length > 16 ? new Set(full_dependencies) : null; + for (i = current_dependencies_index; i < dep_length; i++) { - const dependency = dependencies[i]; + const dependency = full_dependencies[i]; if ( (current_dependencies_set !== null && !current_dependencies_set.has(dependency)) || - !current_dependencies.includes(dependency) + !full_dependencies.includes(dependency) ) { remove_consumer(signal, dependency, false); } diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates/_config.js b/packages/svelte/tests/runtime-runes/samples/each-updates/_config.js index a6cdafd20f..13b0b4aed7 100644 --- a/packages/svelte/tests/runtime-runes/samples/each-updates/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/each-updates/_config.js @@ -33,7 +33,7 @@ export default test({ assert.htmlEqual( target.innerHTML, - `

test costs $1

test 2 costs $2000

test costs $1

test 2 costs $2000

` + `

test costs $1

test 2 costs $2

test costs $1

test 2 costs $2

` ); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/text-effect-multi-deps/_config.js b/packages/svelte/tests/runtime-runes/samples/text-effect-multi-deps/_config.js new file mode 100644 index 0000000000..7dba287f5a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/text-effect-multi-deps/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `

0 - 0

+ From 6acf7f3fc3609c28395c77d7953a66509c857db5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Jan 2024 13:18:53 +0000 Subject: [PATCH 2/8] fix: apply key animations on proxied arrays (#10113) * fix: apply key animations on proxed arrays * fix: apply key animations on proxed arrays * fix: apply key animations on proxed arrays --- .changeset/itchy-terms-guess.md | 5 +++++ packages/svelte/src/internal/client/each.js | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/itchy-terms-guess.md diff --git a/.changeset/itchy-terms-guess.md b/.changeset/itchy-terms-guess.md new file mode 100644 index 0000000000..b101a6c138 --- /dev/null +++ b/.changeset/itchy-terms-guess.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply key animations on proxied arrays diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index 8d9ef0e7c7..c51af78572 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -417,7 +417,9 @@ function reconcile_tracked_array( insert_each_item_block(block, dom, is_controlled, null); } } else { - var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; + var is_animated = (flags & EACH_IS_ANIMATED) !== 0; + var should_update_block = + (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0 || is_animated; var start = 0; /** @type {null | Text | Element | Comment} */ @@ -500,7 +502,6 @@ function reconcile_tracked_array( mark_lis(sources); } // If keys are animated, we need to do updates before actual moves - var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var should_create; if (is_animated) { var i = b_length; From 92408e1506ba4e629282a533974cf545dd75bd49 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:24:55 +0100 Subject: [PATCH 3/8] fix: get spread + bind working (#10091) fixes #10033 --- .changeset/stale-books-perform.md | 5 +++++ packages/svelte/src/internal/client/render.js | 9 ++++++-- .../samples/bind-and-spread/_config.js | 21 +++++++++++++++++++ .../samples/bind-and-spread/button.svelte | 6 ++++++ .../samples/bind-and-spread/main.svelte | 12 +++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 .changeset/stale-books-perform.md create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-and-spread/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-and-spread/button.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-and-spread/main.svelte diff --git a/.changeset/stale-books-perform.md b/.changeset/stale-books-perform.md new file mode 100644 index 0000000000..ca79821d1d --- /dev/null +++ b/.changeset/stale-books-perform.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: take into account setters when spreading and binding diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 301a7480c9..5670ef582b 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2646,8 +2646,13 @@ const spread_props_handler = { if (typeof p === 'object' && p !== null && key in p) return p[key]; } }, - getOwnPropertyDescriptor() { - return { enumerable: true, configurable: true }; + getOwnPropertyDescriptor(target, key) { + let i = target.props.length; + while (i--) { + let p = target.props[i]; + if (is_function(p)) p = p(); + if (typeof p === 'object' && p !== null && key in p) return get_descriptor(p, key); + } }, has(target, key) { for (let p of target.props) { diff --git a/packages/svelte/tests/runtime-runes/samples/bind-and-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/_config.js new file mode 100644 index 0000000000..604ca6a8d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + `` + ); + + await btn2?.click(); + assert.htmlEqual( + target.innerHTML, + `` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-and-spread/button.svelte b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/button.svelte new file mode 100644 index 0000000000..45c1abced8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/button.svelte @@ -0,0 +1,6 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/bind-and-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/main.svelte new file mode 100644 index 0000000000..974470a44e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-and-spread/main.svelte @@ -0,0 +1,12 @@ + + + From b3d185da298c83c4799d92d3e4f372e4a776bd34 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:55:27 +0100 Subject: [PATCH 4/8] fix: correctly call exported state (#10114) fixes #10104 also cleans up related code and adds support for destructuring `$state.frozen` --- .changeset/slimy-walls-draw.md | 5 + .../3-transform/client/transform-client.js | 6 +- .../phases/3-transform/client/utils.js | 42 +++----- .../client/visitors/javascript-legacy.js | 28 +++++- .../client/visitors/javascript-runes.js | 97 +++++++++++-------- .../samples/ambiguous-source/_config.js | 10 +- .../samples/ambiguous-source/main.svelte | 3 +- .../runtime-runes/samples/exports3/_config.js | 12 +++ .../samples/exports3/main.svelte | 7 ++ .../runtime-runes/samples/exports3/sub.svelte | 15 +++ 10 files changed, 142 insertions(+), 83 deletions(-) create mode 100644 .changeset/slimy-walls-draw.md create mode 100644 packages/svelte/tests/runtime-runes/samples/exports3/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/exports3/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte diff --git a/.changeset/slimy-walls-draw.md b/.changeset/slimy-walls-draw.md new file mode 100644 index 0000000000..fd0ec42dd0 --- /dev/null +++ b/.changeset/slimy-walls-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly call exported state diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index dad08406f0..27069478c8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -7,7 +7,7 @@ import { global_visitors } from './visitors/global.js'; import { javascript_visitors } from './visitors/javascript.js'; import { javascript_visitors_runes } from './visitors/javascript-runes.js'; import { javascript_visitors_legacy } from './visitors/javascript-legacy.js'; -import { serialize_get_binding } from './utils.js'; +import { is_state_source, serialize_get_binding } from './utils.js'; import { remove_types } from '../typescript.js'; /** @@ -242,9 +242,7 @@ export function client_component(source, analysis, options) { const properties = analysis.exports.map(({ name, alias }) => { const binding = analysis.instance.scope.get(name); - const is_source = - (binding?.kind === 'state' || binding?.kind === 'frozen_state') && - (!state.analysis.immutable || binding.reassigned); + const is_source = binding !== null && is_state_source(binding, state); // TODO This is always a getter because the `renamed-instance-exports` test wants it that way. // Should we for code size reasons make it an init in runes mode and/or non-dev mode? 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 7b7076a69c..ca652801a7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -45,6 +45,18 @@ export function get_assignment_value(node, { state, visit }) { } } +/** + * @param {import('#compiler').Binding} binding + * @param {import('./types').ClientTransformState} state + * @returns {boolean} + */ +export function is_state_source(binding, state) { + return ( + (binding.kind === 'state' || binding.kind === 'frozen_state') && + (!state.analysis.immutable || binding.reassigned || state.analysis.accessors) + ); +} + /** * @param {import('estree').Identifier} node * @param {import('./types').ClientTransformState} state @@ -93,8 +105,7 @@ export function serialize_get_binding(node, state) { } if ( - ((binding.kind === 'state' || binding.kind === 'frozen_state') && - (!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) || + is_state_source(binding, state) || binding.kind === 'derived' || binding.kind === 'legacy_reactive' ) { @@ -491,33 +502,6 @@ export function get_prop_source(binding, state, name, initial) { return b.call('$.prop', ...args); } -/** - * Creates the output for a state declaration. - * @param {import('estree').VariableDeclarator} declarator - * @param {import('../../scope').Scope} scope - * @param {import('estree').Expression} value - */ -export function create_state_declarators(declarator, scope, value) { - // in the simple `let count = $state(0)` case, we rewrite `$state` as `$.source` - if (declarator.id.type === 'Identifier') { - return [b.declarator(declarator.id, b.call('$.mutable_source', value))]; - } - - const tmp = scope.generate('tmp'); - const paths = extract_paths(declarator.id); - return [ - b.declarator(b.id(tmp), value), // TODO inject declarator for opts, so we can use it below - ...paths.map((path) => { - const value = path.expression?.(b.id(tmp)); - const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name); - return b.declarator( - path.node, - binding?.kind === 'state' ? b.call('$.mutable_source', value) : value - ); - }) - ]; -} - /** @param {import('estree').Expression} node */ export function should_proxy_or_freeze(node) { if ( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js index 7471fb860c..ba09d5f04e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js @@ -1,7 +1,33 @@ import { is_hoistable_function } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import { extract_paths } from '../../../../utils/ast.js'; -import { create_state_declarators, get_prop_source, serialize_get_binding } from '../utils.js'; +import { get_prop_source, serialize_get_binding } from '../utils.js'; + +/** + * Creates the output for a state declaration. + * @param {import('estree').VariableDeclarator} declarator + * @param {import('../../../scope.js').Scope} scope + * @param {import('estree').Expression} value + */ +function create_state_declarators(declarator, scope, value) { + if (declarator.id.type === 'Identifier') { + return [b.declarator(declarator.id, b.call('$.mutable_source', value))]; + } + + const tmp = scope.generate('tmp'); + const paths = extract_paths(declarator.id); + return [ + b.declarator(b.id(tmp), value), + ...paths.map((path) => { + const value = path.expression?.(b.id(tmp)); + const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name); + return b.declarator( + path.node, + binding?.kind === 'state' ? b.call('$.mutable_source', value) : value + ); + }) + ]; +} /** @type {import('../types.js').ComponentVisitors} */ export const javascript_visitors_legacy = { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index 7f656dc690..63c46d8c64 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -2,8 +2,8 @@ import { get_rune } from '../../../scope.js'; import { is_hoistable_function, transform_inspect_rune } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import * as assert from '../../../../utils/assert.js'; -import { create_state_declarators, get_prop_source, should_proxy_or_freeze } from '../utils.js'; -import { unwrap_ts_expression } from '../../../../utils/ast.js'; +import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js'; +import { extract_paths, unwrap_ts_expression } from '../../../../utils/ast.js'; /** @type {import('../types.js').ComponentVisitors} */ export const javascript_visitors_runes = { @@ -223,66 +223,79 @@ export const javascript_visitors_runes = { } const args = /** @type {import('estree').CallExpression} */ (init).arguments; - let value = + const value = args.length === 0 ? b.id('undefined') : /** @type {import('estree').Expression} */ (visit(args[0])); - if (declarator.id.type === 'Identifier') { - if (rune === '$state') { - const binding = /** @type {import('#compiler').Binding} */ ( - state.scope.get(declarator.id.name) - ); + if (rune === '$state' || rune === '$state.frozen') { + /** + * @param {import('estree').Identifier} id + * @param {import('estree').Expression} value + */ + const create_state_declarator = (id, value) => { + const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name)); if (should_proxy_or_freeze(value)) { - value = b.call('$.proxy', value); + value = b.call(rune === '$state' ? '$.proxy' : '$.freeze', value); } - - if (!state.analysis.immutable || state.analysis.accessors || binding.reassigned) { + if (is_state_source(binding, state)) { value = b.call('$.source', value); } - } else if (rune === '$state.frozen') { - const binding = /** @type {import('#compiler').Binding} */ ( - state.scope.get(declarator.id.name) - ); - if (should_proxy_or_freeze(value)) { - value = b.call('$.freeze', value); - } + return value; + }; - if (binding.reassigned) { - value = b.call('$.source', value); - } + if (declarator.id.type === 'Identifier') { + declarations.push( + b.declarator(declarator.id, create_state_declarator(declarator.id, value)) + ); } else { - value = b.call('$.derived', b.thunk(value)); + const tmp = state.scope.generate('tmp'); + const paths = extract_paths(declarator.id); + declarations.push( + b.declarator(b.id(tmp), value), + ...paths.map((path) => { + const value = path.expression?.(b.id(tmp)); + const binding = state.scope.get( + /** @type {import('estree').Identifier} */ (path.node).name + ); + return b.declarator( + path.node, + binding?.kind === 'state' || binding?.kind === 'frozen_state' + ? create_state_declarator(binding.node, value) + : value + ); + }) + ); } - - declarations.push(b.declarator(declarator.id, value)); continue; } if (rune === '$derived') { - const bindings = state.scope.get_bindings(declarator); - const id = state.scope.generate('derived_value'); - declarations.push( - b.declarator( - b.id(id), - b.call( - '$.derived', - b.thunk( - b.block([ - b.let(declarator.id, value), - b.return(b.array(bindings.map((binding) => binding.node))) - ]) + if (declarator.id.type === 'Identifier') { + declarations.push(b.declarator(declarator.id, b.call('$.derived', b.thunk(value)))); + } else { + const bindings = state.scope.get_bindings(declarator); + const id = state.scope.generate('derived_value'); + declarations.push( + b.declarator( + b.id(id), + b.call( + '$.derived', + b.thunk( + b.block([ + b.let(declarator.id, value), + b.return(b.array(bindings.map((binding) => binding.node))) + ]) + ) ) ) - ) - ); - for (let i = 0; i < bindings.length; i++) { - bindings[i].expression = b.member(b.call('$.get', b.id(id)), b.literal(i), true); + ); + for (let i = 0; i < bindings.length; i++) { + bindings[i].expression = b.member(b.call('$.get', b.id(id)), b.literal(i), true); + } } continue; } - - declarations.push(...create_state_declarators(declarator, state.scope, value)); } if (declarations.length === 0) { diff --git a/packages/svelte/tests/runtime-runes/samples/ambiguous-source/_config.js b/packages/svelte/tests/runtime-runes/samples/ambiguous-source/_config.js index 046603c063..2775015dff 100644 --- a/packages/svelte/tests/runtime-runes/samples/ambiguous-source/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/ambiguous-source/_config.js @@ -1,13 +1,11 @@ import { test } from '../../test'; export default test({ - html: ``, + html: ``, - async test({ assert, target, window }) { + async test({ assert, target }) { const btn = target.querySelector('button'); - const clickEvent = new window.Event('click', { bubbles: true }); - await btn?.dispatchEvent(clickEvent); - - assert.htmlEqual(target.innerHTML, ``); + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/ambiguous-source/main.svelte b/packages/svelte/tests/runtime-runes/samples/ambiguous-source/main.svelte index 878cf2e0bd..8cec3dac6d 100644 --- a/packages/svelte/tests/runtime-runes/samples/ambiguous-source/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/ambiguous-source/main.svelte @@ -2,6 +2,7 @@ import { setup } from './utils.js'; let { num } = $state(setup()); + let { num: num_frozen } = $state(setup()); - + diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/_config.js b/packages/svelte/tests/runtime-runes/samples/exports3/_config.js new file mode 100644 index 0000000000..d56d3b306d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/exports3/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + assert.htmlEqual(target.innerHTML, `0 0 `); + const [btn] = target.querySelectorAll('button'); + + btn?.click(); + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, '0 1 '); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/main.svelte b/packages/svelte/tests/runtime-runes/samples/exports3/main.svelte new file mode 100644 index 0000000000..ea5c632343 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/exports3/main.svelte @@ -0,0 +1,7 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte new file mode 100644 index 0000000000..37de78094a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte @@ -0,0 +1,15 @@ + + +{count1.value} +{count2.value} + + + From 05789daff97329ef941a4a1a20561ffd3e8172c4 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Jan 2024 16:37:01 +0000 Subject: [PATCH 5/8] fix: allow transition undefined payload + microtask queue handling (#10117) * fix: allow transition undefined payload * cleanup * cleanup * add microtask queue handling --- .changeset/clever-chefs-relate.md | 5 +++ .../svelte/src/internal/client/runtime.js | 22 ++++++++++ .../svelte/src/internal/client/transitions.js | 31 +++++++++----- .../if-transition-undefined/_config.js | 40 +++++++++++++++++++ .../if-transition-undefined/main.svelte | 16 ++++++++ 5 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 .changeset/clever-chefs-relate.md create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-undefined/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-undefined/main.svelte diff --git a/.changeset/clever-chefs-relate.md b/.changeset/clever-chefs-relate.md new file mode 100644 index 0000000000..46db5c6ae7 --- /dev/null +++ b/.changeset/clever-chefs-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow transition undefined payload diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 1e5301a970..3be0afdb03 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -50,6 +50,8 @@ let current_queued_effects = []; /** @type {Array<() => void>} */ let current_queued_tasks = []; +/** @type {Array<() => void>} */ +let current_queued_microtasks = []; let flush_count = 0; // Handle signal reactivity tree dependencies and consumer @@ -579,6 +581,11 @@ function flush_queued_effects(effects) { function process_microtask() { is_micro_task_queued = false; + if (current_queued_microtasks.length > 0) { + const tasks = current_queued_microtasks.slice(); + current_queued_microtasks = []; + run_all(tasks); + } if (flush_count > 101) { return; } @@ -637,6 +644,18 @@ export function schedule_task(fn) { current_queued_tasks.push(fn); } +/** + * @param {() => void} fn + * @returns {void} + */ +export function schedule_microtask(fn) { + if (!is_micro_task_queued) { + is_micro_task_queued = true; + queueMicrotask(process_microtask); + } + current_queued_microtasks.push(fn); +} + /** * @returns {void} */ @@ -697,6 +716,9 @@ export function flushSync(fn) { if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) { flushSync(); } + if (is_micro_task_queued) { + process_microtask(); + } if (is_task_queued) { process_task(); } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 38225d0346..3317520e6d 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -21,7 +21,7 @@ import { managed_effect, managed_pre_effect, mark_subtree_inert, - schedule_task, + schedule_microtask, untrack } from './runtime.js'; import { raf } from './timing.js'; @@ -279,6 +279,9 @@ function create_transition(dom, init, direction, effect) { // @ts-ignore payload = payload({ direction: curr_direction }); } + if (payload == null) { + return; + } const duration = payload.duration ?? 300; const delay = payload.delay ?? 0; const css_fn = payload.css; @@ -354,11 +357,15 @@ function create_transition(dom, init, direction, effect) { cancelled = false; create_animation(); } - dispatch_event(dom, 'introstart'); - if (needs_reverse) { - /** @type {Animation | TickAnimation} */ (animation).reverse(); + if (animation === null) { + transition.x(); + } else { + dispatch_event(dom, 'introstart'); + if (needs_reverse) { + /** @type {Animation | TickAnimation} */ (animation).reverse(); + } + /** @type {Animation | TickAnimation} */ (animation).play(); } - /** @type {Animation | TickAnimation} */ (animation).play(); }, // out o() { @@ -368,11 +375,15 @@ function create_transition(dom, init, direction, effect) { cancelled = false; create_animation(); } - dispatch_event(dom, 'outrostart'); - if (needs_reverse) { - /** @type {Animation | TickAnimation} */ (animation).reverse(); + if (animation === null) { + transition.x(); } else { - /** @type {Animation | TickAnimation} */ (animation).play(); + dispatch_event(dom, 'outrostart'); + if (needs_reverse) { + /** @type {Animation | TickAnimation} */ (animation).reverse(); + } else { + /** @type {Animation | TickAnimation} */ (animation).play(); + } } }, // cancel @@ -671,7 +682,7 @@ function each_item_animate(block, transitions, index, index_is_reactive) { transition.c(); } } - schedule_task(() => { + schedule_microtask(() => { trigger_transitions(transitions, 'key', from); }); } diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/_config.js b/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/_config.js new file mode 100644 index 0000000000..3098714df4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/_config.js @@ -0,0 +1,40 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

Hello\n!

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

Hello\n!

` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/main.svelte b/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/main.svelte new file mode 100644 index 0000000000..5d5270df94 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-transition-undefined/main.svelte @@ -0,0 +1,16 @@ + + + + +{#if show} +

Hello {name}!

+{/if} From 527d099a0973e6d542a820fc17b9f9104c26f946 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:09:59 +0100 Subject: [PATCH 6/8] fix: transform `{@render ...}` expression (#10116) fixes #10059 --- .changeset/tidy-buses-whisper.md | 5 +++++ .../phases/3-transform/server/transform-server.js | 5 +++-- .../runtime-runes/samples/snippet-store/_config.js | 5 +++++ .../runtime-runes/samples/snippet-store/main.svelte | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .changeset/tidy-buses-whisper.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-store/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-store/main.svelte diff --git a/.changeset/tidy-buses-whisper.md b/.changeset/tidy-buses-whisper.md new file mode 100644 index 0000000000..eaf2950165 --- /dev/null +++ b/.changeset/tidy-buses-whisper.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: transform `{@render ...}` expression diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index e5fcdd415d..f00091a20b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1120,9 +1120,10 @@ const template_visitors = { state.init.push(anchor); state.template.push(t_expression(anchor_id)); + const expression = /** @type {import('estree').Expression} */ (context.visit(node.expression)); const snippet_function = state.options.dev - ? b.call('$.validate_snippet', node.expression) - : node.expression; + ? b.call('$.validate_snippet', expression) + : expression; if (node.argument) { state.template.push( t_statement( diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-store/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-store/_config.js new file mode 100644 index 0000000000..33e7def9f1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-store/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

hello world

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-store/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-store/main.svelte new file mode 100644 index 0000000000..bf84fc2f67 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-store/main.svelte @@ -0,0 +1,11 @@ + + +{#snippet hello()} +

hello world

+{/snippet} + +{@render $snippet()} From 092370b43fcb1b76f1c4cf713d1806b8ecb4932b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:14:15 +0000 Subject: [PATCH 7/8] Version Packages (next) (#10112) 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 e698b5f510..0b32a281d3 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -20,6 +20,7 @@ "chilled-pumas-invite", "chilly-dolphins-lick", "clean-eels-beg", + "clever-chefs-relate", "clever-rockets-burn", "cold-birds-own", "cool-ants-leave", @@ -66,6 +67,7 @@ "itchy-beans-melt", "itchy-kings-deliver", "itchy-lions-wash", + "itchy-terms-guess", "khaki-mails-draw", "khaki-moose-arrive", "kind-deers-lay", @@ -98,6 +100,7 @@ "old-houses-drum", "old-mails-sneeze", "old-oranges-compete", + "olive-kangaroos-brake", "orange-dingos-poke", "polite-dolphins-care", "polite-pumpkins-guess", @@ -125,6 +128,7 @@ "shiny-baboons-play", "shiny-shrimps-march", "slimy-clouds-talk", + "slimy-walls-draw", "slow-chefs-dream", "small-papayas-laugh", "smart-parents-swim", @@ -134,6 +138,7 @@ "sour-forks-stare", "sour-rules-march", "spicy-plums-admire", + "stale-books-perform", "stale-comics-look", "strong-gifts-smoke", "strong-lemons-provide", @@ -156,6 +161,7 @@ "thirty-impalas-repair", "thirty-wombats-relax", "three-suits-grin", + "tidy-buses-whisper", "tiny-kings-whisper", "twelve-dragons-join", "twelve-onions-juggle", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 57240fb631..184030c083 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,21 @@ # svelte +## 5.0.0-next.30 + +### Patch Changes + +- fix: allow transition undefined payload ([#10117](https://github.com/sveltejs/svelte/pull/10117)) + +- fix: apply key animations on proxied arrays ([#10113](https://github.com/sveltejs/svelte/pull/10113)) + +- fix: improve internal signal dependency checking logic ([#10111](https://github.com/sveltejs/svelte/pull/10111)) + +- fix: correctly call exported state ([#10114](https://github.com/sveltejs/svelte/pull/10114)) + +- fix: take into account setters when spreading and binding ([#10091](https://github.com/sveltejs/svelte/pull/10091)) + +- fix: transform `{@render ...}` expression ([#10116](https://github.com/sveltejs/svelte/pull/10116)) + ## 5.0.0-next.29 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a1b62c22cf..736fdc7e45 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.29", + "version": "5.0.0-next.30", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 17b4be4464..74d15d26b8 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.29'; +export const VERSION = '5.0.0-next.30'; export const PUBLIC_VERSION = '5'; From d16f17c306e5434d9c850749c3f2965f3d71808d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 8 Jan 2024 21:30:55 +0000 Subject: [PATCH 8/8] fix: improve signal consumer tracking behavior (#10121) --- .changeset/loud-cheetahs-flow.md | 5 +++ .../svelte/src/internal/client/runtime.js | 6 ++- .../samples/each-updates-2/_config.js | 40 +++++++++++++++++++ .../samples/each-updates-2/main.svelte | 18 +++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 .changeset/loud-cheetahs-flow.md create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-2/main.svelte diff --git a/.changeset/loud-cheetahs-flow.md b/.changeset/loud-cheetahs-flow.md new file mode 100644 index 0000000000..6013f2f4e7 --- /dev/null +++ b/.changeset/loud-cheetahs-flow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve signal consumer tracking behavior diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 3be0afdb03..8631529e62 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -456,10 +456,14 @@ function remove_consumer(signal, dependency, remove_unowned) { function remove_consumers(signal, start_index, remove_unowned) { const dependencies = signal.d; if (dependencies !== null) { + const active_dependencies = start_index === 0 ? null : dependencies.slice(0, start_index); let i; for (i = start_index; i < dependencies.length; i++) { const dependency = dependencies[i]; - remove_consumer(signal, dependency, remove_unowned); + // Avoid removing a consumer if we know that it is active (start_index will not be 0) + if (active_dependencies === null || !active_dependencies.includes(dependency)) { + remove_consumer(signal, dependency, remove_unowned); + } } } } diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-2/_config.js b/packages/svelte/tests/runtime-runes/samples/each-updates-2/_config.js new file mode 100644 index 0000000000..6dfc9c4bf0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-2/_config.js @@ -0,0 +1,40 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [btn1] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `` + ); + + let [btn2, btn3, btn4] = target.querySelectorAll('button'); + + flushSync(() => { + btn4.click(); + btn3.click(); + }); + + assert.htmlEqual(target.innerHTML, ``); + + let [btn5] = target.querySelectorAll('button'); + + flushSync(() => { + btn5.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-updates-2/main.svelte new file mode 100644 index 0000000000..5837c47608 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-2/main.svelte @@ -0,0 +1,18 @@ + + + + +{#each arr as item, i} + +{/each}