From 7a8cf3a9a17320dad4b25a2ff9b9edb947f79551 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 24 Jul 2024 22:47:24 +0100 Subject: [PATCH] fix: ensure directives run in sequential order (#12591) Co-authored-by: Rich Harris --- .changeset/plenty-turkeys-raise.md | 5 +++ .../3-transform/client/visitors/template.js | 36 ++++++++++++------- packages/svelte/src/internal/client/index.js | 1 + .../apply-directives-in-order-2/_config.js | 34 +++--------------- 4 files changed, 35 insertions(+), 41 deletions(-) create mode 100644 .changeset/plenty-turkeys-raise.md diff --git a/.changeset/plenty-turkeys-raise.md b/.changeset/plenty-turkeys-raise.md new file mode 100644 index 0000000000..daaa149551 --- /dev/null +++ b/.changeset/plenty-turkeys-raise.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure directives run in sequential order 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 d4cf97042b..3e8fff654f 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 @@ -1233,8 +1233,8 @@ function serialize_event_handler(node, metadata, { state, visit }) { function serialize_event(node, metadata, context) { const state = context.state; - /** @type {Statement} */ - let statement; + /** @type {Expression} */ + let expression; if (node.expression) { let handler = serialize_event_handler(node, metadata, context); @@ -1303,19 +1303,23 @@ function serialize_event(node, metadata, context) { } // Events need to run in order with bindings/actions - statement = b.stmt(b.call('$.event', ...args)); + expression = b.call('$.event', ...args); } else { - statement = b.stmt( - b.call( - '$.event', - b.literal(node.name), - state.node, - serialize_event_handler(node, metadata, context) - ) + expression = b.call( + '$.event', + b.literal(node.name), + state.node, + serialize_event_handler(node, metadata, context) ); } const parent = /** @type {SvelteNode} */ (context.path.at(-1)); + const has_action_directive = + parent.type === 'RegularElement' && parent.attributes.find((a) => a.type === 'UseDirective'); + const statement = b.stmt( + has_action_directive ? b.call('$.effect', b.thunk(expression)) : expression + ); + if ( parent.type === 'SvelteDocument' || parent.type === 'SvelteWindow' || @@ -3083,12 +3087,20 @@ export const template_visitors = { } } + const parent = /** @type {import('#compiler').SvelteNode} */ (context.path.at(-1)); + const has_action_directive = + parent.type === 'RegularElement' && parent.attributes.find((a) => a.type === 'UseDirective'); + // Bindings need to happen after attribute updates, therefore after the render effect, and in order with events/actions. // bind:this is a special case as it's one-way and could influence the render effect. if (node.name === 'this') { - state.init.push(b.stmt(call_expr)); + state.init.push( + b.stmt(has_action_directive ? b.call('$.effect', b.thunk(call_expr)) : call_expr) + ); } else { - state.after_update.push(b.stmt(call_expr)); + state.after_update.push( + b.stmt(has_action_directive ? b.call('$.effect', b.thunk(call_expr)) : call_expr) + ); } }, Component(node, context) { diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index b7ab028fb1..edfa40cdd4 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -101,6 +101,7 @@ export { legacy_pre_effect_reset, render_effect, template_effect, + effect, user_effect, user_pre_effect } from './reactivity/effects.js'; diff --git a/packages/svelte/tests/runtime-legacy/samples/apply-directives-in-order-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/apply-directives-in-order-2/_config.js index d414ae1f24..bdf3e70919 100644 --- a/packages/svelte/tests/runtime-legacy/samples/apply-directives-in-order-2/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/apply-directives-in-order-2/_config.js @@ -21,18 +21,16 @@ export default test({ flushSync(); } - // Svelte 5 breaking change, use:action now fires - // in effect phase. So they will occur AFTER the others. assert.deepEqual(value, [ + '1', '2', '3', - '1', + '4', '5', '6', - '4', '7', - '9', '8', + '9', '10', '11', '12', @@ -40,30 +38,8 @@ export default test({ '14', '15', '16', - '18', - '17' + '17', + '18' ]); - - // Previously - // assert.deepEqual(value, [ - // '1', - // '2', - // '3', - // '4', - // '5', - // '6', - // '7', - // '8', - // '9', - // '10', - // '11', - // '12', - // '13', - // '14', - // '15', - // '16', - // '17', - // '18', - // ]); } });