Skip to content

Commit 3462c54

Browse files
authored
fix: improve compiled output of multiple call expression in single text node (#11097)
* fix: improve compiled output of multiple call expression in single text node * fix
1 parent 22494be commit 3462c54

File tree

6 files changed

+88
-11
lines changed

6 files changed

+88
-11
lines changed

.changeset/popular-walls-hunt.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: improve compiled output of multiple call expression in single text node

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,7 @@ function process_children(nodes, expression, is_element, { visit, state }) {
13961396

13971397
state.template.push(' ');
13981398

1399-
const [contains_call_expression, value] = serialize_template_literal(sequence, visit);
1399+
const [contains_call_expression, value] = serialize_template_literal(sequence, visit, state);
14001400

14011401
const update = b.stmt(b.call('$.set_text', text_id, value));
14021402

@@ -1511,25 +1511,39 @@ function serialize_attribute_value(attribute_value, context) {
15111511
}
15121512
}
15131513

1514-
return serialize_template_literal(attribute_value, context.visit);
1514+
return serialize_template_literal(attribute_value, context.visit, context.state);
15151515
}
15161516

15171517
/**
15181518
* @param {Array<import('#compiler').Text | import('#compiler').ExpressionTag>} values
15191519
* @param {(node: import('#compiler').SvelteNode) => any} visit
1520+
* @param {import("../types.js").ComponentClientTransformState} state
15201521
* @returns {[boolean, import('estree').TemplateLiteral]}
15211522
*/
1522-
function serialize_template_literal(values, visit) {
1523+
function serialize_template_literal(values, visit, state) {
15231524
/** @type {import('estree').TemplateElement[]} */
15241525
const quasis = [];
15251526

15261527
/** @type {import('estree').Expression[]} */
15271528
const expressions = [];
15281529
let contains_call_expression = false;
1530+
let contains_multiple_call_expression = false;
15291531
quasis.push(b.quasi(''));
15301532

15311533
for (let i = 0; i < values.length; i++) {
15321534
const node = values[i];
1535+
1536+
if (node.type === 'ExpressionTag' && node.metadata.contains_call_expression) {
1537+
if (contains_call_expression) {
1538+
contains_multiple_call_expression = true;
1539+
}
1540+
contains_call_expression = true;
1541+
}
1542+
}
1543+
1544+
for (let i = 0; i < values.length; i++) {
1545+
const node = values[i];
1546+
15331547
if (node.type === 'Text') {
15341548
const last = /** @type {import('estree').TemplateElement} */ (quasis.at(-1));
15351549
last.value.raw += sanitize_template_string(node.data);
@@ -1539,11 +1553,23 @@ function serialize_template_literal(values, visit) {
15391553
last.value.raw += sanitize_template_string(node.expression.value + '');
15401554
}
15411555
} else {
1542-
if (node.type === 'ExpressionTag' && node.metadata.contains_call_expression) {
1543-
contains_call_expression = true;
1544-
}
1556+
if (contains_multiple_call_expression) {
1557+
const id = b.id(state.scope.generate('stringified_text'));
15451558

1546-
expressions.push(b.call('$.stringify', visit(node.expression)));
1559+
state.init.push(
1560+
b.const(
1561+
id,
1562+
b.call(
1563+
// In runes mode, we want things to be fine-grained - but not in legacy mode
1564+
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
1565+
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
1566+
)
1567+
)
1568+
);
1569+
expressions.push(b.call('$.get', id));
1570+
} else {
1571+
expressions.push(b.call('$.stringify', visit(node.expression)));
1572+
}
15471573
quasis.push(b.quasi('', i + 1 === values.length));
15481574
}
15491575
}
@@ -1586,7 +1612,7 @@ export const template_visitors = {
15861612
declaration.id,
15871613
b.call(
15881614
// In runes mode, we want things to be fine-grained - but not in legacy mode
1589-
state.options.runes ? '$.derived' : '$.derived_safe_equal',
1615+
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
15901616
b.thunk(/** @type {import('estree').Expression} */ (visit(declaration.init)))
15911617
)
15921618
)
@@ -1623,7 +1649,7 @@ export const template_visitors = {
16231649

16241650
state.init.push(
16251651
// In runes mode, we want things to be fine-grained - but not in legacy mode
1626-
b.const(tmp, b.call(state.options.runes ? '$.derived' : '$.derived_safe_equal', fn))
1652+
b.const(tmp, b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', fn))
16271653
);
16281654

16291655
// we need to eagerly evaluate the expression in order to hit any
@@ -2972,7 +2998,7 @@ export const template_visitors = {
29722998
b.assignment(
29732999
'=',
29743000
b.member(b.id('$.document'), b.id('title')),
2975-
serialize_template_literal(/** @type {any} */ (node.fragment.nodes), visit)[1]
3001+
serialize_template_literal(/** @type {any} */ (node.fragment.nodes), visit, state)[1]
29763002
)
29773003
)
29783004
);

packages/svelte/tests/runtime-legacy/samples/reactive-value-function/main.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script>
22
let foo = () => 1;
33
4-
function bar() {
4+
var bar = function() {
55
return 2;
66
}
77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
import { log } from './log.js';
4+
5+
export default test({
6+
before_test() {
7+
log.length = 0;
8+
},
9+
10+
async test({ assert, target }) {
11+
const [b1, b2] = target.querySelectorAll('button');
12+
13+
flushSync(() => {
14+
b1.click();
15+
});
16+
17+
flushSync(() => {
18+
b2.click();
19+
});
20+
21+
assert.deepEqual(log, ['x', 'y', 'x', 'y']);
22+
}
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** @type {any[]} */
2+
export const log = [];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
import { log } from './log.js';
3+
4+
let x = $state(0);
5+
let y = $state(0);
6+
7+
function getX() {
8+
log.push('x')
9+
return x;
10+
}
11+
12+
function getY() {
13+
log.push('y')
14+
return y;
15+
}
16+
</script>
17+
18+
<button on:click={() => x++}>{x}</button>
19+
<button on:click={() => y++}>{y}</button>
20+
21+
{getX()}|{getY()}

0 commit comments

Comments
 (0)