Render void elements as self-closing tags (#2064)

* Render void elements as self-closing tags

* changeset

* nit: only check void element names if there is no child content

* nit: only check void element names if there is no child content

* add tests
This commit is contained in:
Jonathan Neal 2021-12-01 15:43:22 -05:00 committed by GitHub
parent b348ca6c9f
commit 5bda895fcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 1 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes an issue where void elements are rendered with opening and closing tags.

View file

@ -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}</${Component}>`);
html = await renderAstroComponent(
await render`<${Component}${spreadAttributes(props)}${(children == null || children == '') && voidElementNames.test(Component) ? `/>` : `>${children}</${Component}>`}`
);
}
// This is used to add polyfill scripts to the page, if the renderer needs them.

View file

@ -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);
// <Input />
expect($('body > :nth-child(1)').prop('outerHTML')).to.equal('<input>');
// <Input type="password" />
expect($('body > :nth-child(2)').prop('outerHTML')).to.equal('<input type="password">');
// <Input type="text" />
expect($('body > :nth-child(3)').prop('outerHTML')).to.equal('<input type="text">');
// <Input type="select"><option>option</option></Input>
expect($('body > :nth-child(4)').prop('outerHTML')).to.equal('<select><option>option</option></select>');
// <Input type="textarea">textarea</Input>
expect($('body > :nth-child(5)').prop('outerHTML')).to.equal('<textarea>textarea</textarea>');
});
describe('preview', () => {
it('returns 200 for valid URLs', async () => {
const result = await fixture.fetch('/');

View file

@ -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;
---
<Control {...props}>{'default' in Astro.slots ? <slot /> : null}</Control>

View file

@ -0,0 +1,16 @@
---
import Input from '../components/Input.astro';
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Input />
<Input type="password" />
<Input type="text" />
<Input type="select"><option>option</option></Input>
<Input type="textarea">textarea</Input>
</body>
</html>