Skip to content

Commit 9b33413

Browse files
authored
fix: handle spreads within static strings (#9554)
Previously, if a part of the template was determined be be optimizable using innerHTML, it would error if there's a spread on an attribute
1 parent 1fd0b18 commit 9b33413

File tree

6 files changed

+79
-3
lines changed

6 files changed

+79
-3
lines changed

.changeset/gentle-spies-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: handle spreads within static strings

packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,9 +1312,19 @@ function to_html(wrappers, block, literal, state, can_use_raw_text) {
13121312
// The value attribute of <textarea> renders as content.
13131313
return;
13141314
}
1315-
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
1316-
to_html_for_attr_value(attr, block, literal, state);
1317-
state.quasi.value.raw += '"';
1315+
1316+
if (attr instanceof SpreadAttributeWrapper) {
1317+
literal.quasis.push(state.quasi);
1318+
literal.expressions.push(x`@stringify_spread(${attr.node.expression.manipulate(block)})`);
1319+
state.quasi = {
1320+
type: 'TemplateElement',
1321+
value: { raw: '' }
1322+
};
1323+
} else {
1324+
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
1325+
to_html_for_attr_value(attr, block, literal, state);
1326+
state.quasi.value.raw += '"';
1327+
}
13181328
});
13191329
if (!wrapper.void) {
13201330
state.quasi.value.raw += '>';

packages/svelte/src/runtime/internal/dom.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,36 @@ export function attribute_to_object(attributes) {
11801180
return result;
11811181
}
11821182

1183+
const escaped = {
1184+
'"': '&quot;',
1185+
'&': '&amp;',
1186+
'<': '&lt;'
1187+
};
1188+
1189+
const regex_attribute_characters_to_escape = /["&<]/g;
1190+
1191+
/**
1192+
* Note that the attribute itself should be surrounded in double quotes
1193+
* @param {any} attribute
1194+
*/
1195+
function escape_attribute(attribute) {
1196+
return String(attribute).replace(regex_attribute_characters_to_escape, (match) => escaped[match]);
1197+
}
1198+
1199+
/**
1200+
* @param {Record<string, string>} attributes
1201+
*/
1202+
export function stringify_spread(attributes) {
1203+
let str = ' ';
1204+
for (const key in attributes) {
1205+
if (attributes[key] != null) {
1206+
str += `${key}="${escape_attribute(attributes[key])}" `;
1207+
}
1208+
}
1209+
1210+
return str;
1211+
}
1212+
11831213
/**
11841214
* @param {HTMLElement} element
11851215
* @returns {{}}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default {
2+
html: `
3+
<div>
4+
<p class="tooltip">static stuff</p>
5+
</div>
6+
<div>
7+
<p class="tooltip">dynamic stuff</p>
8+
</div>
9+
<button>unused</button>
10+
`
11+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
import { spread } from './spread.js';
3+
let dynamic = 'dynamic';
4+
</script>
5+
6+
<div>
7+
<p {...spread()}>static stuff</p>
8+
</div>
9+
10+
<div>
11+
<p {...spread()}>{dynamic} stuff</p>
12+
</div>
13+
14+
<button on:click={() => dynamic = ''}>unused</button>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function spread() {
2+
return {
3+
class: 'tooltip',
4+
id: null
5+
};
6+
}

0 commit comments

Comments
 (0)