diff options
author | 2021-06-15 11:33:27 -0500 | |
---|---|---|
committer | 2021-06-15 11:33:27 -0500 | |
commit | 490f2bebbcd354b7b9d85020b9ce3abe5f376f13 (patch) | |
tree | 09dc384f84198e78bafcc573d8b73f7d767b8d84 | |
parent | 28c2d74dc3434985e0cb30972e58724eb6fddb7a (diff) | |
download | astro-490f2bebbcd354b7b9d85020b9ce3abe5f376f13.tar.gz astro-490f2bebbcd354b7b9d85020b9ce3abe5f376f13.tar.zst astro-490f2bebbcd354b7b9d85020b9ce3abe5f376f13.zip |
Add `<>` fragment support for expressions (#433)
* feat: add support for `<>` and `</>` Fragments
* docs: explain Fragments
* test: add fragment test
-rw-r--r-- | .changeset/mean-ligers-nail.md | 6 | ||||
-rw-r--r-- | docs/syntax.md | 60 | ||||
-rw-r--r-- | packages/astro-parser/src/parse/state/tag.ts | 4 | ||||
-rw-r--r-- | packages/astro/src/compiler/codegen/index.ts | 6 | ||||
-rw-r--r-- | packages/astro/test/astro-expr.test.js | 11 | ||||
-rw-r--r-- | packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro | 12 |
6 files changed, 78 insertions, 21 deletions
diff --git a/.changeset/mean-ligers-nail.md b/.changeset/mean-ligers-nail.md new file mode 100644 index 000000000..68739635f --- /dev/null +++ b/.changeset/mean-ligers-nail.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/parser': patch +--- + +Add support for Fragments with `<>` and `</>` syntax diff --git a/docs/syntax.md b/docs/syntax.md index edf854046..358644f17 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -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` | - -### TODO: Styling +| 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: Composition (Slots) diff --git a/packages/astro-parser/src/parse/state/tag.ts b/packages/astro-parser/src/parse/state/tag.ts index b8c3e63ad..28783df67 100644 --- a/packages/astro-parser/src/parse/state/tag.ts +++ b/packages/astro-parser/src/parse/state/tag.ts @@ -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' diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts index 140d3bfc5..b31fbf4a0 100644 --- a/packages/astro/src/compiler/codegen/index.ts +++ b/packages/astro/src/compiler/codegen/index.ts @@ -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': diff --git a/packages/astro/test/astro-expr.test.js b/packages/astro/test/astro-expr.test.js index 485d58d9b..727abd1c8 100644 --- a/packages/astro/test/astro-expr.test.js +++ b/packages/astro/test/astro-expr.test.js @@ -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); diff --git a/packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro b/packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro index fb0fafd4a..4e0b5e746 100644 --- a/packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro +++ b/packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro @@ -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>
\ No newline at end of file +</html> |