Add <>
fragment support for expressions (#433)
* feat: add support for `<>` and `</>` Fragments * docs: explain Fragments * test: add fragment test
This commit is contained in:
parent
28c2d74dc3
commit
490f2bebbc
6 changed files with 77 additions and 20 deletions
6
.changeset/mean-ligers-nail.md
Normal file
6
.changeset/mean-ligers-nail.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'astro': patch
|
||||
'@astrojs/parser': patch
|
||||
---
|
||||
|
||||
Add support for Fragments with `<>` and `</>` syntax
|
|
@ -113,28 +113,52 @@ export let name;
|
|||
</main>
|
||||
```
|
||||
|
||||
### Fragments
|
||||
|
||||
At the top-level of an `.astro` file, you may render any number of elements.
|
||||
|
||||
```html
|
||||
<!-- Look, no Fragment! -->
|
||||
<div id="a" />
|
||||
<div id="b" />
|
||||
<div id="c" />
|
||||
```
|
||||
|
||||
Inside of an expression, you must wrap multiple elements in a Fragment. Fragments must open with `<>` and close with `</>`.
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
{[0, 1, 2].map(id => (
|
||||
<>
|
||||
<div id={`a-${id}`} />
|
||||
<div id={`b-${id}`} />
|
||||
<div id={`c-${id}`} />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
### `.astro` versus `.jsx`
|
||||
|
||||
`.astro` files can end up looking very similar to `.jsx` files, but there are a few key differences. Here's a comparison between the two formats.
|
||||
|
||||
| Feature | Astro | JSX |
|
||||
| ---------------------------- | ---------------------------------------- | -------------------------------------------------- |
|
||||
| File extension | `.astro` | `.jsx` or `.tsx` |
|
||||
| User-Defined Components | `<Capitalized>` | `<Capitalized>` |
|
||||
| Expression Syntax | `{}` | `{}` |
|
||||
| Spread Attributes | `{...props}` | `{...props}` |
|
||||
| Boolean Attributes | `autocomplete` === `autocomplete={true}` | `autocomplete` === `autocomplete={true}` |
|
||||
| Inline Functions | `{items.map(item => <li>{item}</li>)}` | `{items.map(item => <li>{item}</li>)}` |
|
||||
| IDE Support | WIP - [VS Code][code-ext] | Phenomenal |
|
||||
| Requires JS import | No | Yes, `jsxPragma` (`React` or `h`) must be in scope |
|
||||
| Fragments | Automatic | Wrap with `<Fragment>` or `<>` |
|
||||
| Multiple frameworks per-file | Yes | No |
|
||||
| Modifying `<head>` | Just use `<head>` | Per-framework (`<Head>`, `<svelte:head>`, etc) |
|
||||
| Comment Style | `<!-- HTML -->` | `{/* JavaScript */}` |
|
||||
| Special Characters | ` ` | `{'\xa0'}` or `{String.fromCharCode(160)}` |
|
||||
| Attributes | `dash-case` | `camelCase` |
|
||||
| Feature | Astro | JSX |
|
||||
| ---------------------------- | -------------------------------------------- | -------------------------------------------------- |
|
||||
| File extension | `.astro` | `.jsx` or `.tsx` |
|
||||
| User-Defined Components | `<Capitalized>` | `<Capitalized>` |
|
||||
| Expression Syntax | `{}` | `{}` |
|
||||
| Spread Attributes | `{...props}` | `{...props}` |
|
||||
| Boolean Attributes | `autocomplete` === `autocomplete={true}` | `autocomplete` === `autocomplete={true}` |
|
||||
| Inline Functions | `{items.map(item => <li>{item}</li>)}` | `{items.map(item => <li>{item}</li>)}` |
|
||||
| IDE Support | WIP - [VS Code][code-ext] | Phenomenal |
|
||||
| Requires JS import | No | Yes, `jsxPragma` (`React` or `h`) must be in scope |
|
||||
| Fragments | Automatic top-level, `<>` inside functions | Wrap with `<Fragment>` or `<>` |
|
||||
| Multiple frameworks per-file | Yes | No |
|
||||
| Modifying `<head>` | Just use `<head>` | Per-framework (`<Head>`, `<svelte:head>`, etc) |
|
||||
| Comment Style | `<!-- HTML -->` | `{/* JavaScript */}` |
|
||||
| Special Characters | ` ` | `{'\xa0'}` or `{String.fromCharCode(160)}` |
|
||||
| Attributes | `dash-case` | `camelCase` |
|
||||
|
||||
### TODO: Styling
|
||||
|
||||
### TODO: Composition (Slots)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
|
|||
|
||||
const meta_tags = new Map([
|
||||
['astro:head', 'Head'],
|
||||
// ['slot:body', 'Body'],
|
||||
['', 'SlotTemplate'],
|
||||
// ['astro:options', 'Options'],
|
||||
// ['astro:window', 'Window'],
|
||||
// ['astro:body', 'Body'],
|
||||
|
@ -118,7 +118,7 @@ export default function tag(parser: Parser) {
|
|||
? meta_tags.get(name)
|
||||
: /[A-Z]/.test(name[0]) || name === 'astro:self' || name === 'astro:component'
|
||||
? 'InlineComponent'
|
||||
: name === 'astro:fragment'
|
||||
: name === ''
|
||||
? 'SlotTemplate'
|
||||
: name === 'title' && parent_is_head(parser.stack)
|
||||
? 'Title'
|
||||
|
|
|
@ -494,6 +494,11 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
return;
|
||||
case 'Fragment':
|
||||
break;
|
||||
case 'SlotTemplate': {
|
||||
buffers[curr] += `h(Fragment, null, children`;
|
||||
paren++;
|
||||
return;
|
||||
}
|
||||
case 'Slot':
|
||||
case 'Head':
|
||||
case 'InlineComponent': {
|
||||
|
@ -630,6 +635,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
case 'CodeSpan':
|
||||
case 'CodeFence':
|
||||
return;
|
||||
case 'SlotTemplate':
|
||||
case 'Slot':
|
||||
case 'Head':
|
||||
case 'Body':
|
||||
|
|
|
@ -58,6 +58,17 @@ Expressions('Allows multiple JSX children in mustache', async ({ runtime }) => {
|
|||
assert.ok(result.contents.includes('#f') && !result.contents.includes('#t'));
|
||||
});
|
||||
|
||||
Expressions('Allows <> Fragments in expressions', async ({ runtime }) => {
|
||||
const result = await runtime.load('/multiple-children');
|
||||
if (result.error) throw new Error(result.error);
|
||||
const $ = doc(result.contents);
|
||||
|
||||
assert.equal($('#fragment').children().length, 3);
|
||||
assert.equal($('#fragment').children('#a').length, 1);
|
||||
assert.equal($('#fragment').children('#b').length, 1);
|
||||
assert.equal($('#fragment').children('#c').length, 1);
|
||||
})
|
||||
|
||||
Expressions('Does not render falsy values using &&', async ({ runtime }) => {
|
||||
const result = await runtime.load('/falsy');
|
||||
if (result.error) throw new Error(result.error);
|
||||
|
|
|
@ -10,5 +10,15 @@ let title = 'My Site';
|
|||
<h1>{title}</h1>
|
||||
|
||||
{false ? <h1>#t</h1> : <h1>#f</h1>}
|
||||
|
||||
<div id="fragment">
|
||||
{(
|
||||
<>
|
||||
<div id="a" />
|
||||
<div id="b" />
|
||||
<div id="c" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue