diff --git a/.changeset/rare-jars-dance.md b/.changeset/rare-jars-dance.md new file mode 100644 index 000000000..5c4ae440a --- /dev/null +++ b/.changeset/rare-jars-dance.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where void elements are rendered with opening and closing tags. diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 37a7dc2e2..07acb0ba0 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -7,6 +7,8 @@ import { serializeListValue } from './util.js'; export { createMetadata } from './metadata.js'; export type { Metadata } from './metadata'; +const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; + // INVESTIGATE: // 2. Less anys when possible and make it well known when they are needed. @@ -223,7 +225,9 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr // This is a custom element without a renderer. Because of that, render it // as a string and the user is responsible for adding a script tag for the component definition. if (!html && typeof Component === 'string') { - html = await renderAstroComponent(await render`<${Component}${spreadAttributes(props)}>${children}`); + html = await renderAstroComponent( + await render`<${Component}${spreadAttributes(props)}${(children == null || children == '') && voidElementNames.test(Component) ? `/>` : `>${children}`}` + ); } // This is used to add polyfill scripts to the page, if the renderer needs them. diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index e1991634b..72668820b 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -83,6 +83,26 @@ describe('Astro basics', () => { }); }); + it('Supports void elements whose name is a string (#2062)', async () => { + const html = await fixture.readFile('/input/index.html'); + const $ = cheerio.load(html); + + // + expect($('body > :nth-child(1)').prop('outerHTML')).to.equal(''); + + // + expect($('body > :nth-child(2)').prop('outerHTML')).to.equal(''); + + // + expect($('body > :nth-child(3)').prop('outerHTML')).to.equal(''); + + // + expect($('body > :nth-child(4)').prop('outerHTML')).to.equal(''); + + // textarea + expect($('body > :nth-child(5)').prop('outerHTML')).to.equal(''); + }); + describe('preview', () => { it('returns 200 for valid URLs', async () => { const result = await fixture.fetch('/'); diff --git a/packages/astro/test/fixtures/astro-basic/src/components/Input.astro b/packages/astro/test/fixtures/astro-basic/src/components/Input.astro new file mode 100644 index 000000000..459d2ce3a --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/components/Input.astro @@ -0,0 +1,18 @@ +--- +const { + type: initialType, + ...props +} = { + ...Astro.props +} as { + [K: string]: any; +}; + +const isSelect = /^select$/i.test(initialType); +const isTextarea = /^textarea$/i.test(initialType); + +const Control = isSelect ? 'select' : isTextarea ? 'textarea' : 'input'; + +if (Control === 'input' && initialType) props.type = initialType; +--- +{'default' in Astro.slots ? : null} \ No newline at end of file diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/input.astro b/packages/astro/test/fixtures/astro-basic/src/pages/input.astro new file mode 100644 index 000000000..6b09005a7 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/input.astro @@ -0,0 +1,16 @@ +--- +import Input from '../components/Input.astro'; +--- + + + + + + + + + + + textarea + +