From 1ff9c0f2b9e7edeb4a5dd0437c1afbb875962ca3 Mon Sep 17 00:00:00 2001 From: Nguyen Tran <88808276+ngtr6788@users.noreply.github.com> Date: Tue, 9 Jan 2024 04:24:50 -0500 Subject: [PATCH 01/12] fix: support destructurings containing await (#9962) Adds a traversion mechanism to found out if destructured expressions contain await Fixes #9686 Fixes #9312 Fixes #9982 --- .changeset/nasty-yaks-peel.md | 5 + .../phases/3-transform/client/utils.js | 128 ++++++++++++++++-- .../svelte/src/compiler/utils/builders.js | 18 +++ .../destructure-async-assignments/_config.js | 75 ++++++++++ .../destructure-async-assignments/main.svelte | 89 ++++++++++++ 5 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 .changeset/nasty-yaks-peel.md create mode 100644 packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte diff --git a/.changeset/nasty-yaks-peel.md b/.changeset/nasty-yaks-peel.md new file mode 100644 index 0000000000..267e4458a5 --- /dev/null +++ b/.changeset/nasty-yaks-peel.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: support async/await in destructuring assignments 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 ca652801a7..2ecec2eeb3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -115,6 +115,103 @@ export function serialize_get_binding(node, state) { return node; } +/** + * @param {import('estree').Expression | import('estree').Pattern} expression + * @returns {boolean} + */ +function is_expression_async(expression) { + switch (expression.type) { + case 'AwaitExpression': { + return true; + } + case 'ArrayPattern': { + return expression.elements.some((element) => element && is_expression_async(element)); + } + case 'ArrayExpression': { + return expression.elements.some((element) => { + if (!element) { + return false; + } else if (element.type === 'SpreadElement') { + return is_expression_async(element.argument); + } else { + return is_expression_async(element); + } + }); + } + case 'AssignmentPattern': + case 'AssignmentExpression': + case 'BinaryExpression': + case 'LogicalExpression': { + return is_expression_async(expression.left) || is_expression_async(expression.right); + } + case 'CallExpression': + case 'NewExpression': { + return ( + (expression.callee.type !== 'Super' && is_expression_async(expression.callee)) || + expression.arguments.some((element) => { + if (element.type === 'SpreadElement') { + return is_expression_async(element.argument); + } else { + return is_expression_async(element); + } + }) + ); + } + case 'ChainExpression': { + return is_expression_async(expression.expression); + } + case 'ConditionalExpression': { + return ( + is_expression_async(expression.test) || + is_expression_async(expression.alternate) || + is_expression_async(expression.consequent) + ); + } + case 'ImportExpression': { + return is_expression_async(expression.source); + } + case 'MemberExpression': { + return ( + (expression.object.type !== 'Super' && is_expression_async(expression.object)) || + (expression.property.type !== 'PrivateIdentifier' && + is_expression_async(expression.property)) + ); + } + case 'ObjectPattern': + case 'ObjectExpression': { + return expression.properties.some((property) => { + if (property.type === 'SpreadElement') { + return is_expression_async(property.argument); + } else if (property.type === 'Property') { + return ( + (property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) || + is_expression_async(property.value) + ); + } + }); + } + case 'RestElement': { + return is_expression_async(expression.argument); + } + case 'SequenceExpression': + case 'TemplateLiteral': { + return expression.expressions.some((subexpression) => is_expression_async(subexpression)); + } + case 'TaggedTemplateExpression': { + return is_expression_async(expression.tag) || is_expression_async(expression.quasi); + } + case 'UnaryExpression': + case 'UpdateExpression': { + return is_expression_async(expression.argument); + } + case 'YieldExpression': { + return expression.argument ? is_expression_async(expression.argument) : false; + } + default: + return false; + } +} + /** * @template {import('./types').ClientTransformState} State * @param {import('estree').AssignmentExpression} node @@ -153,17 +250,28 @@ export function serialize_set_binding(node, context, fallback) { return fallback(); } - return b.call( - b.thunk( - b.block([ - b.const(tmp_id, /** @type {import('estree').Expression} */ (visit(node.right))), - b.stmt(b.sequence(assignments)), - // return because it could be used in a nested expression where the value is needed. - // example: { foo: ({ bar } = { bar: 1 })} - b.return(b.id(tmp_id)) - ]) - ) + const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right)); + + const iife_is_async = + is_expression_async(rhs_expression) || + assignments.some((assignment) => is_expression_async(assignment)); + + const iife = b.arrow( + [], + b.block([ + b.const(tmp_id, rhs_expression), + b.stmt(b.sequence(assignments)), + // return because it could be used in a nested expression where the value is needed. + // example: { foo: ({ bar } = { bar: 1 })} + b.return(b.id(tmp_id)) + ]) ); + + if (iife_is_async) { + return b.await(b.call(b.async(iife))); + } else { + return b.call(iife); + } } if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') { diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index f0907f293f..1595f79879 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -44,6 +44,23 @@ export function assignment(operator, left, right) { return { type: 'AssignmentExpression', operator, left, right }; } +/** + * @template T + * @param {T & import('estree').BaseFunction} func + * @returns {T & import('estree').BaseFunction} + */ +export function async(func) { + return { ...func, async: true }; +} + +/** + * @param {import('estree').Expression} argument + * @returns {import('estree').AwaitExpression} + */ +export function await_builder(argument) { + return { type: 'AwaitExpression', argument }; +} + /** * @param {import('estree').BinaryOperator} operator * @param {import('estree').Expression} left @@ -573,6 +590,7 @@ export function throw_error(str) { } export { + await_builder as await, new_builder as new, let_builder as let, const_builder as const, diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js new file mode 100644 index 0000000000..e7d3c80cfd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js @@ -0,0 +1,75 @@ +import { test } from '../../test'; + +export default test({ + html: ` + +

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+

0

+ `, + + async test({ assert, target, window }) { + const btn = target.querySelector('button'); + const clickEvent = new window.Event('click', { bubbles: true }); + await btn?.dispatchEvent(clickEvent); + for (let i = 1; i <= 42; i += 1) { + await Promise.resolve(); + } + + assert.htmlEqual( + target.innerHTML, + ` + +

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte new file mode 100644 index 0000000000..2d2a6538d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte @@ -0,0 +1,89 @@ + + + +

{a}

+

{b}

+

{c}

+

{d}

+

{e}

+

{f}

+

{g}

+

{h}

+

{i}

+

{j}

+

{k}

+

{l}

+

{m}

+

{n}

+

{o}

+

{p}

+

{q}

+

{r}

+

{s}

+

{t}

+

{u}

+

{v}

+

{w}

+

{x}

+

{y}

+

{z}

\ No newline at end of file From b6238904391cac97f28637595a9d2abbc8962828 Mon Sep 17 00:00:00 2001 From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:11:40 +0545 Subject: [PATCH 02/12] chore(repl): hide globals (#10125) --- .../src/lib/Output/Viewer.svelte | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte index 7ae1d4d05d..214846f62e 100644 --- a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte +++ b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte @@ -111,26 +111,30 @@ ${styles} - const styles = document.querySelectorAll('style[id^=svelte-]'); - - let i = styles.length; - while (i--) styles[i].parentNode.removeChild(styles[i]); - - if (window.unmount) { - try { - window.unmount(); - } catch (err) { - console.error(err); + { + const styles = document.querySelectorAll('style[id^=svelte-]'); + + let i = styles.length; + while (i--) styles[i].parentNode.removeChild(styles[i]); + + if (window.__unmount_previous) { + try { + window.__unmount_previous(); + } catch (err) { + console.error(err); + } } - } - document.body.innerHTML = ''; - window._svelteTransitionManager = null; - - const { mount, App } = ${$bundle.client?.code}; - const [, destroy] = mount(App, { target: document.body }); + document.body.innerHTML = ''; + window._svelteTransitionManager = null; + } - window.unmount = destroy; + const __repl_exports = ${$bundle.client?.code}; + { + const { mount, App } = __repl_exports; + const [, destroy] = mount(App, { target: document.body }); + window.__unmount_previous = destroy; + } //# sourceURL=playground:output `); error = null; From 3624a4c2a00cda7e18ddf6331015995dabd30932 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:29:23 +0100 Subject: [PATCH 03/12] feat: allow modifiying derived props (#10080) It's an unnecessary restruction because it can be worked around (hide it behind a getter/setter), already works for bind:x and prevents valid use cases --- .changeset/hungry-trees-travel.md | 5 +++++ .../src/compiler/phases/2-analyze/validation.js | 14 +++++++------- .../runes-no-derived-assignment/main.svelte | 4 ++-- .../samples/runes-no-derived-update/main.svelte | 4 ++-- .../samples/derived-proxy/_config.js | 15 +++++++++++++++ .../samples/derived-proxy/main.svelte | 7 +++++++ 6 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 .changeset/hungry-trees-travel.md create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte diff --git a/.changeset/hungry-trees-travel.md b/.changeset/hungry-trees-travel.md new file mode 100644 index 0000000000..d90a448daa --- /dev/null +++ b/.changeset/hungry-trees-travel.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: allow modifiying derived props diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 40244f97d0..f64b0268ab 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -854,6 +854,13 @@ function validate_assignment(node, argument, state) { let left = /** @type {import('estree').Expression | import('estree').Super} */ (argument); + if (left.type === 'Identifier') { + const binding = state.scope.get(left.name); + if (binding?.kind === 'derived') { + error(node, 'invalid-derived-assignment'); + } + } + /** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */ let property = null; @@ -862,13 +869,6 @@ function validate_assignment(node, argument, state) { left = left.object; } - if (left.type === 'Identifier') { - const binding = state.scope.get(left.name); - if (binding?.kind === 'derived') { - error(node, 'invalid-derived-assignment'); - } - } - if (left.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') { if (state.private_derived_state.includes(property.name)) { error(node, 'invalid-derived-assignment'); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte index f89cc7b6ba..3bf836f6c5 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte +++ b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte @@ -1,5 +1,5 @@ diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte index a4aca48089..d266c95bb8 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte +++ b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte @@ -1,5 +1,5 @@ diff --git a/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js new file mode 100644 index 0000000000..32340d018a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js @@ -0,0 +1,15 @@ +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/derived-proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte new file mode 100644 index 0000000000..ddbc56aa7b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte @@ -0,0 +1,7 @@ + + + + From f5dc562ee76fdd1c4dcd7ae7683cc0f4b44c7fa5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Jan 2024 10:11:49 +0000 Subject: [PATCH 04/12] fix: ensure nested blocks are inert during outro transitions (#10126) * fix: ensure nested blocks are inert during outro transitions * lint --- .changeset/spotty-pens-agree.md | 5 ++++ .../svelte/src/internal/client/runtime.js | 23 +++++++++++++++++++ .../samples/if-transition-inert/_config.js | 16 +++++++++++++ .../samples/if-transition-inert/main.svelte | 17 ++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 .changeset/spotty-pens-agree.md create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte diff --git a/.changeset/spotty-pens-agree.md b/.changeset/spotty-pens-agree.md new file mode 100644 index 0000000000..2b7f76d696 --- /dev/null +++ b/.changeset/spotty-pens-agree.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure nested blocks are inert during outro transitions diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8631529e62..38c7e3d12b 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -10,6 +10,7 @@ import { } from '../../constants.js'; import { readonly } from './proxy/readonly.js'; import { READONLY_SYMBOL, STATE_SYMBOL, proxy, unstate } from './proxy/proxy.js'; +import { EACH_BLOCK, IF_BLOCK } from './block.js'; export const SOURCE = 1; export const DERIVED = 1 << 1; @@ -1019,6 +1020,28 @@ export function mark_subtree_inert(signal, inert) { if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) { schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false); } + // Nested if block effects + const block = signal.b; + if (block !== null) { + const type = block.t; + if (type === IF_BLOCK) { + const consequent_effect = block.ce; + if (consequent_effect !== null) { + mark_subtree_inert(consequent_effect, inert); + } + const alternate_effect = block.ae; + if (alternate_effect !== null) { + mark_subtree_inert(alternate_effect, inert); + } + } else if (type === EACH_BLOCK) { + const items = block.v; + for (let { e: each_item_effect } of items) { + if (each_item_effect !== null) { + mark_subtree_inert(each_item_effect, inert); + } + } + } + } } const references = signal.r; if (references !== null) { diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js new file mode 100644 index 0000000000..7a1673786c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `
hello
`, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(target.innerHTML, `
hello
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte new file mode 100644 index 0000000000..0a0cc2bdf6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte @@ -0,0 +1,17 @@ + + + + +{#if state} +
+ {#if true} + {state} + {/if} +
+{/if} + + From cd2263fdabd97dcb5e7d99b891580a0757359e3b Mon Sep 17 00:00:00 2001 From: navorite Date: Tue, 9 Jan 2024 12:50:12 +0200 Subject: [PATCH 05/12] fix: infer `svg` namespace correctly (#10027) Add recursive check for logic blocks, ignore things such as ConstTags and Comments closes #10025 --------- Co-authored-by: Simon Holthausen --- .changeset/dull-roses-relate.md | 5 ++ .../src/compiler/phases/3-transform/utils.js | 76 +++++++++++++++---- .../destructure-async-assignments/main.svelte | 6 +- .../svg-namespace-infer/Wrapper.svelte | 13 ++++ .../samples/svg-namespace-infer/_config.js | 26 +++++++ .../samples/svg-namespace-infer/main.svelte | 7 ++ 6 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 .changeset/dull-roses-relate.md create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte diff --git a/.changeset/dull-roses-relate.md b/.changeset/dull-roses-relate.md new file mode 100644 index 0000000000..b0da71fe59 --- /dev/null +++ b/.changeset/dull-roses-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: infer `svg` namespace correctly diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index b7592f6f81..8ce6c0fd1b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -211,23 +211,67 @@ export function infer_namespace(namespace, parent, nodes, path) { parent_node.type === 'SvelteFragment' || parent_node.type === 'SnippetBlock') ) { - // Heuristic: Keep current namespace, unless we find a regular element, - // in which case we always want html, or we only find svg nodes, - // in which case we assume svg. - let only_svg = true; - for (const node of nodes) { - if (node.type === 'RegularElement') { - if (!node.metadata.svg) { - namespace = 'html'; - only_svg = false; - break; - } - } else if (node.type !== 'Text' || node.data.trim() !== '') { - only_svg = false; - } + const new_namespace = check_nodes_for_namespace(nodes, 'keep'); + if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') { + namespace = new_namespace; } - if (only_svg) { - namespace = 'svg'; + } + + return namespace; +} + +/** + * Heuristic: Keep current namespace, unless we find a regular element, + * in which case we always want html, or we only find svg nodes, + * in which case we assume svg. + * @param {import('#compiler').SvelteNode[]} nodes + * @param {import('#compiler').Namespace | 'keep' | 'maybe_html'} namespace + */ +function check_nodes_for_namespace(nodes, namespace) { + for (const node of nodes) { + if (node.type === 'RegularElement') { + if (!node.metadata.svg) { + namespace = 'html'; + break; + } else if (namespace === 'keep') { + namespace = 'svg'; + } + } else if ( + (node.type === 'Text' && node.data.trim() !== '') || + node.type === 'HtmlTag' || + node.type === 'RenderTag' + ) { + namespace = 'maybe_html'; + } else if (node.type === 'EachBlock') { + namespace = check_nodes_for_namespace(node.body.nodes, namespace); + if (namespace === 'html') break; + if (node.fallback) { + namespace = check_nodes_for_namespace(node.fallback.nodes, namespace); + if (namespace === 'html') break; + } + } else if (node.type === 'IfBlock') { + namespace = check_nodes_for_namespace(node.consequent.nodes, namespace); + if (namespace === 'html') break; + if (node.alternate) { + namespace = check_nodes_for_namespace(node.alternate.nodes, namespace); + if (namespace === 'html') break; + } + } else if (node.type === 'AwaitBlock') { + if (node.pending) { + namespace = check_nodes_for_namespace(node.pending.nodes, namespace); + if (namespace === 'html') break; + } + if (node.then) { + namespace = check_nodes_for_namespace(node.then.nodes, namespace); + if (namespace === 'html') break; + } + if (node.catch) { + namespace = check_nodes_for_namespace(node.catch.nodes, namespace); + if (namespace === 'html') break; + } + } else if (node.type === 'KeyBlock') { + namespace = check_nodes_for_namespace(node.fragment.nodes, namespace); + if (namespace === 'html') break; } } diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte index 2d2a6538d5..0e3eec9d9e 100644 --- a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte @@ -34,6 +34,10 @@ return Promise.resolve([24, 25]); } + const some = { + fn: () => {} + } + const update = async () => { [a, b] = [1, await Promise.resolve(2)]; ({ c = await Promise.resolve(3), d } = { d: 4 }); @@ -49,7 +53,7 @@ [l = obj[await Promise.resolve("prop")] + 1] = []; [m] = [`${1}${await Promise.resolve("3")}`]; [n] = [-(await Promise.resolve(-14))]; - [o] = [(console.log(15), await Promise.resolve(15))]; + [o] = [(some.fn(), await Promise.resolve(15))]; ({ anotherprop: p = await Promise.resolve(16) } = obj); let val1, val2; ({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 } diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte new file mode 100644 index 0000000000..41f3f38fa8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte @@ -0,0 +1,13 @@ +outside + +{#if true} + true +{:else} + false +{/if} + +{#each Array(3).fill(0) as item, idx} + {idx} +{/each} + + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js new file mode 100644 index 0000000000..46118026af --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js @@ -0,0 +1,26 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ` + + outside + true + 0 + 1 + 2 + +`, + test({ assert, target }) { + const svg = target.querySelector('svg'); + ok(svg); + + assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg'); + + const text_elements = target.querySelectorAll('text'); + + assert.equal(text_elements.length, 5); + + for (const { namespaceURI } of text_elements) + assert.equal(namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte new file mode 100644 index 0000000000..7c1253ea7e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte @@ -0,0 +1,7 @@ + + + + + From 901cfc9f15561500c6d78b5060266d648f118270 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 9 Jan 2024 10:54:29 +0000 Subject: [PATCH 06/12] fix: improve ssr template literal generation (#10127) --- .changeset/wicked-hairs-cheer.md | 5 +++++ .../phases/3-transform/server/transform-server.js | 2 +- .../runtime-runes/samples/attribute-parts/_config.js | 6 ++++++ .../runtime-runes/samples/attribute-parts/main.svelte | 9 +++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .changeset/wicked-hairs-cheer.md create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte diff --git a/.changeset/wicked-hairs-cheer.md b/.changeset/wicked-hairs-cheer.md new file mode 100644 index 0000000000..532d81260b --- /dev/null +++ b/.changeset/wicked-hairs-cheer.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve ssr template literal generation 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 f00091a20b..5cb479b91a 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 @@ -705,7 +705,7 @@ function serialize_attribute_value( /** @type {import('estree').Expression} */ (context.visit(node.expression)) ) ); - if (i === attribute_value.length) { + if (i === attribute_value.length || attribute_value[i]?.type !== 'Text') { quasis.push(b.quasi('', true)); } } diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js new file mode 100644 index 0000000000..dabe420686 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js @@ -0,0 +1,6 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `
` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte new file mode 100644 index 0000000000..3bbd04cb7a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte @@ -0,0 +1,9 @@ + + +
+ + From aa5a62396a6dfda896051535dfd5d2a2c55d8fd2 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:25:29 +0100 Subject: [PATCH 07/12] fix: legacy reactive dependencies tweak (#10128) take into account member expressions when determining legacy reactive dependencies fixes #9954 --- .changeset/quiet-crabs-nail.md | 5 +++++ .../src/compiler/phases/2-analyze/index.js | 21 ++++++++++++++++--- .../_config.js | 5 +++++ .../main.svelte | 12 +++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 .changeset/quiet-crabs-nail.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte diff --git a/.changeset/quiet-crabs-nail.md b/.changeset/quiet-crabs-nail.md new file mode 100644 index 0000000000..78ea5ba14e --- /dev/null +++ b/.changeset/quiet-crabs-nail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: take into account member expressions when determining legacy reactive dependencies diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index dc98fb31e6..781ccdfc60 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -500,7 +500,15 @@ const legacy_scope_tweaker = { node.body.type === 'ExpressionStatement' && node.body.expression.type === 'AssignmentExpression' ) { - for (const id of extract_identifiers(node.body.expression.left)) { + let ids = extract_identifiers(node.body.expression.left); + if (node.body.expression.left.type === 'MemberExpression') { + const id = object(node.body.expression.left); + if (id !== null) { + ids = [id]; + } + } + + for (const id of ids) { const binding = state.scope.get(id.name); if (binding?.kind === 'legacy_reactive') { // TODO does this include `let double; $: double = x * 2`? @@ -511,8 +519,15 @@ const legacy_scope_tweaker = { }, AssignmentExpression(node, { state, next }) { if (state.reactive_statement && node.operator === '=') { - for (const id of extract_identifiers(node.left)) { - state.reactive_statement.assignments.add(id); + if (node.left.type === 'MemberExpression') { + const id = object(node.left); + if (id !== null) { + state.reactive_statement.assignments.add(id); + } + } else { + for (const id of extract_identifiers(node.left)) { + state.reactive_statement.assignments.add(id); + } } } diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js new file mode 100644 index 0000000000..9c66e2e6db --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `1 1` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte new file mode 100644 index 0000000000..6dabf61707 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte @@ -0,0 +1,12 @@ + + +{button.title} {button.label} From 14dbc1be1720ff69e6f3c407e43c9c0765b0c140 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Jan 2024 15:33:25 +0100 Subject: [PATCH 08/12] fix: make `ComponentType` generic optional fixes #9975 --- .changeset/red-doors-own.md | 5 +++++ packages/svelte/src/main/public.d.ts | 2 +- packages/svelte/types/index.d.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/red-doors-own.md diff --git a/.changeset/red-doors-own.md b/.changeset/red-doors-own.md new file mode 100644 index 0000000000..4cd8d80791 --- /dev/null +++ b/.changeset/red-doors-own.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: make `ComponentType` generic optional diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index 5849835ea2..2e8f7a6f00 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -177,7 +177,7 @@ export type ComponentProps = Comp extends SvelteCo * * ``` */ -export type ComponentType = (new ( +export type ComponentType = (new ( options: ComponentConstructorOptions< Comp extends SvelteComponent ? Props : Record > diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 7baeab4189..c2a22a6e9b 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -178,7 +178,7 @@ declare module 'svelte' { * * ``` */ - export type ComponentType = (new ( + export type ComponentType = (new ( options: ComponentConstructorOptions< Comp extends SvelteComponent ? Props : Record > From d171a39b0ad97e2a05de1f38bc76a3d345e2b3d5 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Jan 2024 16:15:16 +0100 Subject: [PATCH 09/12] fix: keep intermediate number value representations fixes #9959 --- .changeset/fast-weeks-clean.md | 5 +++ packages/svelte/src/internal/client/render.js | 36 ++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 .changeset/fast-weeks-clean.md diff --git a/.changeset/fast-weeks-clean.md b/.changeset/fast-weeks-clean.md new file mode 100644 index 0000000000..4ce7f286b3 --- /dev/null +++ b/.changeset/fast-weeks-clean.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep intermediate number value representations diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 5670ef582b..30da7f35bb 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -926,32 +926,50 @@ export function selected(dom) { } /** - * @param {Element} dom + * @param {HTMLInputElement} dom * @param {() => unknown} get_value * @param {(value: unknown) => void} update * @returns {void} */ export function bind_value(dom, get_value, update) { dom.addEventListener('input', () => { - // @ts-ignore + /** @type {any} */ let value = dom.value; - // @ts-ignore - const type = dom.type; - if (type === 'number' || type === 'range') { - value = value === '' ? null : +value; + if (is_numberlike_input(dom)) { + value = to_number(value); } update(value); }); + render_effect(() => { const value = get_value(); - const coerced_value = value == null ? null : value + ''; - // @ts-ignore - dom.value = coerced_value; // @ts-ignore dom.__value = value; + + if (is_numberlike_input(dom) && value === to_number(dom.value)) { + // handles 0 vs 00 case (see https://github.com/sveltejs/svelte/issues/9959) + return; + } + + dom.value = stringify(value); }); } +/** + * @param {HTMLInputElement} dom + */ +function is_numberlike_input(dom) { + const type = dom.type; + return type === 'number' || type === 'range'; +} + +/** + * @param {string} value + */ +function to_number(value) { + return value === '' ? null : +value; +} + /** * @param {HTMLSelectElement} dom * @param {() => unknown} get_value From dda4ad510f1907a114a16227c3412eb00bd21738 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Jan 2024 16:54:45 +0100 Subject: [PATCH 10/12] fix: silence false positive state warning the continue was essentially a noop because it targeted the wrong for loop --- .changeset/short-buses-camp.md | 5 +++++ packages/svelte/src/compiler/phases/2-analyze/index.js | 4 ++-- .../validator/samples/runes-referenced-nonstate/input.svelte | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/short-buses-camp.md diff --git a/.changeset/short-buses-camp.md b/.changeset/short-buses-camp.md new file mode 100644 index 0000000000..b7c0976bc8 --- /dev/null +++ b/.changeset/short-buses-camp.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence false positive state warning diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 781ccdfc60..3a4e323a77 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -428,7 +428,7 @@ export function analyze_component(root, options) { for (const scope of [module.scope, instance.scope]) { outer: for (const [name, binding] of scope.declarations) { if (binding.kind === 'normal' && binding.reassigned) { - for (const { path } of binding.references) { + inner: for (const { path } of binding.references) { if (path[0].type !== 'Fragment') continue; for (let i = 1; i < path.length; i += 1) { const type = path[i].type; @@ -437,7 +437,7 @@ export function analyze_component(root, options) { type === 'FunctionExpression' || type === 'ArrowFunctionExpression' ) { - continue; + continue inner; } } diff --git a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte index fd9d6c3173..c3a2b38fa7 100644 --- a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte +++ b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte @@ -2,9 +2,11 @@ let a = $state(1); let b = 2; let c = 3; + let d = 4; +

{a} + {b} + {c} = {a + b + c}

From c05e94f26e771f5d0ff43df3de9a42a02f96f25e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:12:15 +0100 Subject: [PATCH 11/12] Version Packages (next) (#10122) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 10 ++++++++++ packages/svelte/CHANGELOG.md | 24 ++++++++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 0b32a281d3..ea45a1e6e1 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -34,12 +34,14 @@ "dry-eggs-play", "dry-eggs-retire", "dull-mangos-wave", + "dull-roses-relate", "early-ads-tie", "eight-steaks-shout", "eighty-bikes-camp", "empty-crabs-think", "fair-crabs-check", "famous-knives-sneeze", + "fast-weeks-clean", "few-mugs-fail", "fifty-rice-wait", "fifty-steaks-float", @@ -64,6 +66,7 @@ "honest-icons-change", "hungry-dots-fry", "hungry-tips-unite", + "hungry-trees-travel", "itchy-beans-melt", "itchy-kings-deliver", "itchy-lions-wash", @@ -83,6 +86,7 @@ "light-pens-watch", "long-buckets-lay", "long-crews-return", + "loud-cheetahs-flow", "lovely-carpets-lick", "lovely-items-turn", "lovely-rules-eat", @@ -90,6 +94,7 @@ "moody-frogs-exist", "moody-owls-cry", "nasty-lions-double", + "nasty-yaks-peel", "neat-dingos-clap", "new-boats-wait", "ninety-dingos-walk", @@ -111,8 +116,10 @@ "pretty-ties-help", "purple-dragons-peel", "quiet-camels-mate", + "quiet-crabs-nail", "rare-pears-whisper", "real-guests-do", + "red-doors-own", "rich-sheep-burn", "rich-tables-sing", "rotten-bags-type", @@ -127,6 +134,7 @@ "sharp-tomatoes-learn", "shiny-baboons-play", "shiny-shrimps-march", + "short-buses-camp", "slimy-clouds-talk", "slimy-walls-draw", "slow-chefs-dream", @@ -138,6 +146,7 @@ "sour-forks-stare", "sour-rules-march", "spicy-plums-admire", + "spotty-pens-agree", "stale-books-perform", "stale-comics-look", "strong-gifts-smoke", @@ -172,6 +181,7 @@ "wet-games-fly", "wicked-clouds-exercise", "wicked-doors-train", + "wicked-hairs-cheer", "wild-foxes-wonder", "wise-dancers-hang", "wise-donkeys-marry", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 184030c083..82098fc762 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,29 @@ # svelte +## 5.0.0-next.31 + +### Patch Changes + +- fix: infer `svg` namespace correctly ([#10027](https://github.com/sveltejs/svelte/pull/10027)) + +- fix: keep intermediate number value representations ([`d171a39b0`](https://github.com/sveltejs/svelte/commit/d171a39b0ad97e2a05de1f38bc76a3d345e2b3d5)) + +- feat: allow modifiying derived props ([#10080](https://github.com/sveltejs/svelte/pull/10080)) + +- fix: improve signal consumer tracking behavior ([#10121](https://github.com/sveltejs/svelte/pull/10121)) + +- fix: support async/await in destructuring assignments ([#9962](https://github.com/sveltejs/svelte/pull/9962)) + +- fix: take into account member expressions when determining legacy reactive dependencies ([#10128](https://github.com/sveltejs/svelte/pull/10128)) + +- fix: make `ComponentType` generic optional ([`14dbc1be1`](https://github.com/sveltejs/svelte/commit/14dbc1be1720ff69e6f3c407e43c9c0765b0c140)) + +- fix: silence false positive state warning ([`dda4ad510`](https://github.com/sveltejs/svelte/commit/dda4ad510f1907a114a16227c3412eb00bd21738)) + +- fix: ensure nested blocks are inert during outro transitions ([#10126](https://github.com/sveltejs/svelte/pull/10126)) + +- fix: improve ssr template literal generation ([#10127](https://github.com/sveltejs/svelte/pull/10127)) + ## 5.0.0-next.30 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 736fdc7e45..d2c29459bc 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.30", + "version": "5.0.0-next.31", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 74d15d26b8..17bb8b9464 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.30'; +export const VERSION = '5.0.0-next.31'; export const PUBLIC_VERSION = '5'; From f3265c580c1f15d4adabe8bb4ac58729c130adf3 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Jan 2024 17:22:48 +0100 Subject: [PATCH 12/12] chore: better test case closes #10129 --- .../_config.js | 34 ++++++++++++++++++- .../main.svelte | 21 +++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js index 9c66e2e6db..fed7568b62 100644 --- a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js @@ -1,5 +1,37 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - html: `1 1` + html: ``, + test({ assert, target }) { + const [button1, button2, button3, button4] = target.querySelectorAll('button'); + + button1.click(); + flushSync(); + assert.htmlEqual( + target.innerHTML, + `` + ); + + button2.click(); + flushSync(); + assert.htmlEqual( + target.innerHTML, + `` + ); + + button3.click(); + flushSync(); + assert.htmlEqual( + target.innerHTML, + `` + ); + + button4.click(); + flushSync(); + assert.htmlEqual( + target.innerHTML, + `` + ); + } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte index 6dabf61707..2ff764892e 100644 --- a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte @@ -1,12 +1,15 @@ -{button.title} {button.label} +{#each array as row, i} + {#each row as item, j} + + {/each} +{/each}