summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Nate Moore <natemoo-re@users.noreply.github.com> 2021-06-15 11:33:27 -0500
committerGravatar GitHub <noreply@github.com> 2021-06-15 11:33:27 -0500
commit490f2bebbcd354b7b9d85020b9ce3abe5f376f13 (patch)
tree09dc384f84198e78bafcc573d8b73f7d767b8d84
parent28c2d74dc3434985e0cb30972e58724eb6fddb7a (diff)
downloadastro-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.md6
-rw-r--r--docs/syntax.md60
-rw-r--r--packages/astro-parser/src/parse/state/tag.ts4
-rw-r--r--packages/astro/src/compiler/codegen/index.ts6
-rw-r--r--packages/astro/test/astro-expr.test.js11
-rw-r--r--packages/astro/test/fixtures/astro-expr/src/pages/multiple-children.astro12
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 | `&nbsp;` | `{'\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 | `&nbsp;` | `{'\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>