diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index b7cf1aef51..7f345097f2 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -277,11 +277,9 @@ export default class ElementWrapper extends Wrapper { const tag = this.node.tag_expr.manipulate(block); block.add_variable(previous_tag, tag); - const has_children_of_dynamic_element = block.get_unique_name('has_children_of_dynamic_element'); - block.add_variable(has_children_of_dynamic_element, x`${this.node.children.length > 0 ? 'true' : 'false'}`); - block.chunks.init.push(b` - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag}, ${has_children_of_dynamic_element});`} + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); `); @@ -312,7 +310,8 @@ export default class ElementWrapper extends Wrapper { ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); } else if (${not_equal}(${previous_tag}, ${tag})) { ${this.var}.d(1); - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag}, ${has_children_of_dynamic_element});`} + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 46335e7f24..7f7672349e 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -9,7 +9,7 @@ import remove_whitespace_children from './utils/remove_whitespace_children'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; import { start_newline } from '../../../utils/patterns'; -import { Expression as ESExpression } from 'estree'; +import { Node, Expression as ESExpression } from 'estree'; export default function (node: Element, renderer: Renderer, options: RenderOptions) { @@ -24,6 +24,10 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio node.attributes.some((attribute) => attribute.name === 'contenteditable') ); + if (node.is_dynamic_element) { + renderer.push(); + } + renderer.add_string('<'); add_tag_name(); @@ -192,10 +196,24 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio renderer.add_string('\n'); } } + if (node.is_dynamic_element) renderer.push(); renderer.render(children, options); + if (node.is_dynamic_element) { + const children = renderer.pop(); + renderer.add_expression(x`@is_void(#tag) ? '' : ${children}`); + } add_close_tag(); } + if (node.is_dynamic_element) { + let content: Node = renderer.pop(); + if (options.dev && node.children.length > 0) content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`; + renderer.add_expression(x`((#tag) => { + ${options.dev && x`@validate_dynamic_element(#tag)`} + return #tag ? ${content} : ''; + })(${node.tag_expr.node})`); + } + function add_close_tag() { if (node.tag_expr.node.type === 'Literal') { if (!is_void(node.tag_expr.node.value as string)) { @@ -205,7 +223,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio } return; } - renderer.add_expression(x`((tag) => @is_void(String(tag || '')) ? '' : \`\`)(${node.tag_expr.node})`); + renderer.add_expression(x`@is_void(#tag) ? '' : \`\``); } function add_tag_name() { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 2c31256193..3144911672 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -108,13 +108,16 @@ export function validate_slots(name, slot, keys) { } } -export function validate_dynamic_element(tag: unknown, has_children: boolean) { +export function validate_dynamic_element(tag: unknown) { const is_string = typeof tag === 'string'; if (tag && !is_string) { throw new Error(' expects "this" attribute to be a string.'); } - if (is_string && is_void(tag as string) && has_children) { - throw new Error(`<${tag}> element is self-closing and cannot have content.`); +} + +export function validate_void_dynamic_element(tag: undefined | string) { + if (tag && is_void(tag)) { + throw new Error(` is self-closing and cannot have content.`); } } diff --git a/test/runtime/samples/dynamic-element-void-with-content1/_config.js b/test/runtime/samples/dynamic-element-void-with-content1/_config.js index dc5a954a0e..4016725e21 100644 --- a/test/runtime/samples/dynamic-element-void-with-content1/_config.js +++ b/test/runtime/samples/dynamic-element-void-with-content1/_config.js @@ -5,5 +5,5 @@ export default { props: { tag: 'br' }, - error: '
element is self-closing and cannot have content.' + error: ' is self-closing and cannot have content.' }; diff --git a/test/runtime/samples/dynamic-element-void-with-content2/_config.js b/test/runtime/samples/dynamic-element-void-with-content2/_config.js index dc5a954a0e..4016725e21 100644 --- a/test/runtime/samples/dynamic-element-void-with-content2/_config.js +++ b/test/runtime/samples/dynamic-element-void-with-content2/_config.js @@ -5,5 +5,5 @@ export default { props: { tag: 'br' }, - error: '
element is self-closing and cannot have content.' + error: ' is self-closing and cannot have content.' }; diff --git a/test/runtime/samples/dynamic-element-void-with-content3/_config.js b/test/runtime/samples/dynamic-element-void-with-content3/_config.js index dc5a954a0e..4016725e21 100644 --- a/test/runtime/samples/dynamic-element-void-with-content3/_config.js +++ b/test/runtime/samples/dynamic-element-void-with-content3/_config.js @@ -5,5 +5,5 @@ export default { props: { tag: 'br' }, - error: '
element is self-closing and cannot have content.' + error: ' is self-closing and cannot have content.' }; diff --git a/test/runtime/samples/dynamic-element-void-with-content4/Nested.svelte b/test/runtime/samples/dynamic-element-void-with-content4/Nested.svelte new file mode 100644 index 0000000000..e62294a135 --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content4/Nested.svelte @@ -0,0 +1 @@ +
This is nested
diff --git a/test/runtime/samples/dynamic-element-void-with-content4/_config.js b/test/runtime/samples/dynamic-element-void-with-content4/_config.js new file mode 100644 index 0000000000..4b2a279aaa --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content4/_config.js @@ -0,0 +1,6 @@ +export default { + props: { + tag: 'br' + }, + html: '
' +}; diff --git a/test/runtime/samples/dynamic-element-void-with-content4/main.svelte b/test/runtime/samples/dynamic-element-void-with-content4/main.svelte new file mode 100644 index 0000000000..320f50ea91 --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content4/main.svelte @@ -0,0 +1,6 @@ + + +