Skip to content

Commit a4a789d

Browse files
authored
fix: improve ssr output of dynamic textarea elements (#10638)
* fix: improve ssr output of dynamic textarea elements * format
1 parent ee4b1f2 commit a4a789d

File tree

9 files changed

+80
-34
lines changed

9 files changed

+80
-34
lines changed

.changeset/nice-avocados-move.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: improve ssr output of dynamic textarea elements

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,22 @@ const validation = {
609609
error(arg, 'invalid-render-spread-argument');
610610
}
611611
}
612+
const is_inside_textarea = context.path.find((n) => {
613+
return (
614+
n.type === 'SvelteElement' &&
615+
n.name === 'svelte:element' &&
616+
n.tag.type === 'Literal' &&
617+
n.tag.value === 'textarea'
618+
);
619+
});
620+
if (is_inside_textarea) {
621+
error(
622+
node,
623+
'invalid-tag-placement',
624+
'inside <textarea> or <svelte:element this="textarea">',
625+
node.expression.name
626+
);
627+
}
612628
},
613629
SvelteHead(node) {
614630
const attribute = node.attributes[0];

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,36 +1274,32 @@ const template_visitors = {
12741274
context.state.init.push(el_anchor);
12751275
context.state.template.push(t_expression(anchor_id));
12761276

1277-
const [inner_anchor, inner_id] = serialize_anchor(context.state);
1278-
inner_context.state.init.push(inner_anchor);
1279-
inner_context.state.template.push(t_string('<'), t_expression(tag));
1280-
serialize_element_attributes(node, inner_context);
1281-
inner_context.state.template.push(t_string('>'));
1282-
1283-
const before = serialize_template(inner_context.state.template);
12841277
const main = create_block(node, node.fragment.nodes, {
12851278
...context,
12861279
state: { ...context.state, metadata }
12871280
});
1288-
const after = serialize_template([
1289-
t_expression(inner_id),
1290-
t_string('</'),
1291-
t_expression(tag),
1292-
t_string('>')
1293-
]);
1281+
1282+
serialize_element_attributes(node, inner_context);
12941283

12951284
context.state.template.push(
12961285
t_statement(
12971286
b.if(
12981287
tag,
1299-
b.block([
1300-
...inner_context.state.init,
1301-
...before,
1302-
b.if(
1303-
b.unary('!', b.call('$.VoidElements.has', tag)),
1304-
b.block([...serialize_template([t_expression(inner_id)]), ...main, ...after])
1288+
1289+
b.stmt(
1290+
b.call(
1291+
'$.element',
1292+
b.id('$$payload'),
1293+
tag,
1294+
b.thunk(
1295+
b.block([
1296+
...inner_context.state.init,
1297+
...serialize_template(inner_context.state.template)
1298+
])
1299+
),
1300+
b.thunk(b.block(main))
13051301
)
1306-
])
1302+
)
13071303
)
13081304
),
13091305
t_expression(anchor_id)

packages/svelte/src/internal/server/index.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,31 @@ export function assign_payload(p1, p2) {
7979
p1.anchor = p2.anchor;
8080
}
8181

82+
/**
83+
* @param {Payload} payload
84+
* @param {string} tag
85+
* @param {() => void} attributes_fn
86+
* @param {() => void} children_fn
87+
* @returns {void}
88+
*/
89+
export function element(payload, tag, attributes_fn, children_fn) {
90+
payload.out += `<${tag} `;
91+
attributes_fn();
92+
payload.out += `>`;
93+
94+
if (!VoidElements.has(tag)) {
95+
const anchor = tag !== 'textarea' ? create_anchor(payload) : null;
96+
if (anchor !== null) {
97+
payload.out += anchor;
98+
}
99+
children_fn();
100+
if (anchor !== null) {
101+
payload.out += anchor;
102+
}
103+
payload.out += `</${tag}>`;
104+
}
105+
}
106+
82107
/**
83108
* Array of `onDestroy` callbacks that should be called at the end of the server render function
84109
* @type {Function[]}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: '<textarea></textarea>',
5+
6+
test({ assert, target }) {
7+
assert.htmlEqual(
8+
target.innerHTML,
9+
`
10+
<textarea></textarea>
11+
`
12+
);
13+
}
14+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<svelte:element this="textarea"></svelte:element>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* index.svelte.js generated by Svelte VERSION */
22
import * as $ from "svelte/internal";
33

4-
export const object = $.proxy({ ok: true });
4+
export const object = $.proxy({ ok: true });
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* index.svelte.js generated by Svelte VERSION */
22
import * as $ from "svelte/internal/server";
33

4-
export const object = { ok: true };
4+
export const object = { ok: true };

packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,7 @@ export default function Svelte_element($$payload, $$props) {
99
const anchor = $.create_anchor($$payload);
1010

1111
$$payload.out += `${anchor}`;
12-
13-
if (tag) {
14-
const anchor_1 = $.create_anchor($$payload);
15-
16-
$$payload.out += `<${tag}>`;
17-
18-
if (!$.VoidElements.has(tag)) {
19-
$$payload.out += `${anchor_1}`;
20-
$$payload.out += `${anchor_1}</${tag}>`;
21-
}
22-
}
23-
12+
if (tag) $.element($$payload, tag, () => {}, () => {});
2413
$$payload.out += `${anchor}`;
2514
$.bind_props($$props, { tag });
2615
$.pop();

0 commit comments

Comments
 (0)