<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>My blog</title>
<description>Full of cool stuff</description>
<link>https://cmdcolin.github.io/pox/</link>
<atom:link href="https://cmdcolin.github.io/pox/feed.xml" rel="self" type="application/rss+xml" />
<language>en</language>
<lastBuildDate>Mon, 14 Sep 2026 05:00:59 +0000</lastBuildDate>
<generator>pox</generator>
<item>
<title>JSX without JavaScript</title>
<link>https://cmdcolin.github.io/pox/posts/2026-09-06-jsx-without-javascript</link>
<guid isPermaLink="true">https://cmdcolin.github.io/pox/posts/2026-09-06-jsx-without-javascript</guid>
<pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate>
<description>&lt;p&gt;This blog is generated by a couple of thousand lines of Perl driven by
POSIX shell. There is no Node, no bundler, and not a single byte of JavaScript
in the output. It still speaks JSX, in two separate senses worth keeping
apart:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reading it.&lt;/strong&gt; Fenced code blocks tagged &lt;code&gt;jsx&lt;/code&gt; or &lt;code&gt;tsx&lt;/code&gt; go through
arborium, which parses them with the same tree-sitter grammars an
editor uses and colours tags, attributes and expressions accordingly.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing it.&lt;/strong&gt; Every template in the repository — layouts, pages and
the components in &lt;code&gt;_components/&lt;/code&gt; — is written in Astro syntax and
expanded at build time into plain HTML.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second one is why this paragraph is followed by a box:&lt;/p&gt;
&lt;aside class="callout callout-note"&gt;
&lt;strong&gt;This is a component&lt;/strong&gt;
&lt;p&gt;The tag written above was &lt;code&gt;&amp;lt;Note title="This is a component"&amp;gt;&lt;/code&gt;, and its
children are &lt;strong&gt;ordinary markdown&lt;/strong&gt;, rendered as markdown.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id="highlighting-jsx"&gt;&lt;a href="#highlighting-jsx" style="margin-right: 10px"&gt;#&lt;/a&gt;Highlighting JSX&lt;/h2&gt;
&lt;p&gt;The generator doesn't highlight anything itself. It hands each code block
to the &lt;code&gt;arborium&lt;/code&gt; CLI, which parses it with a tree-sitter grammar and
wraps every token in a short custom element: &lt;code&gt;&amp;lt;a-k&amp;gt;&lt;/code&gt; for a keyword,
&lt;code&gt;&amp;lt;a-s&amp;gt;&lt;/code&gt; for a string, &lt;code&gt;&amp;lt;a-cr&amp;gt;&lt;/code&gt; for a constructor. A stylesheet then
colours those elements with GitHub's light and dark themes.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-jsx"&gt;&lt;a-k&gt;import&lt;/a-k&gt; &lt;a-p&gt;{&lt;/a-p&gt; &lt;a-v&gt;useState&lt;/a-v&gt; &lt;a-p&gt;}&lt;/a-p&gt; &lt;a-k&gt;from&lt;/a-k&gt; &lt;a-s&gt;&amp;quot;react&amp;quot;&lt;/a-s&gt;&lt;a-p&gt;;&lt;/a-p&gt;

&lt;a-c&gt;// A component, with a few of the shapes JSX allows&lt;/a-c&gt;
&lt;a-k&gt;export&lt;/a-k&gt; &lt;a-k&gt;default&lt;/a-k&gt; &lt;a-k&gt;function&lt;/a-k&gt; &lt;a-cr&gt;Card&lt;/a-cr&gt;&lt;a-p&gt;({&lt;/a-p&gt; title&lt;a-p&gt;,&lt;/a-p&gt; href&lt;a-p&gt;,&lt;/a-p&gt; tags &lt;a-o&gt;=&lt;/a-o&gt; &lt;a-p&gt;[]&lt;/a-p&gt; &lt;a-p&gt;})&lt;/a-p&gt; &lt;a-p&gt;{&lt;/a-p&gt;
  &lt;a-k&gt;const&lt;/a-k&gt; &lt;a-p&gt;[&lt;/a-p&gt;&lt;a-v&gt;open&lt;/a-v&gt;&lt;a-p&gt;,&lt;/a-p&gt; &lt;a-v&gt;setOpen&lt;/a-v&gt;&lt;a-p&gt;]&lt;/a-p&gt; &lt;a-o&gt;=&lt;/a-o&gt; &lt;a-f&gt;useState&lt;/a-f&gt;&lt;a-p&gt;(&lt;/a-p&gt;&lt;a-co&gt;false&lt;/a-co&gt;&lt;a-p&gt;);&lt;/a-p&gt;

  &lt;a-k&gt;return&lt;/a-k&gt; &lt;a-p&gt;(&lt;/a-p&gt;
    &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;article&lt;/a-v&gt; &lt;a-pr&gt;className&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-s&gt;&amp;quot;card&amp;quot;&lt;/a-s&gt; &lt;a-pr&gt;data-open&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;open&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
      &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;a&lt;/a-v&gt; &lt;a-pr&gt;href&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;href&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt; &lt;a-pr&gt;onClick&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{()&lt;/a-p&gt; &lt;a-o&gt;=&amp;gt;&lt;/a-o&gt; &lt;a-f&gt;setOpen&lt;/a-f&gt;&lt;a-p&gt;(&lt;/a-p&gt;&lt;a-o&gt;!&lt;/a-o&gt;&lt;a-v&gt;open&lt;/a-v&gt;&lt;a-p&gt;)}&lt;/a-p&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
        &lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;title&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;
      &amp;lt;/&lt;a-v&gt;a&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
      &lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;open&lt;/a-v&gt; &lt;a-o&gt;&amp;amp;&amp;amp;&lt;/a-o&gt; &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;p&lt;/a-v&gt; &lt;a-pr&gt;className&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-s&gt;&amp;quot;body&amp;quot;&lt;/a-s&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-s&gt;`Opened &lt;/a-s&gt;&lt;a-p&gt;${&lt;/a-p&gt;&lt;a-v&gt;title&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;&lt;a-s&gt;`&lt;/a-s&gt;&lt;a-p&gt;}&lt;/a-p&gt;&amp;lt;/&lt;a-v&gt;p&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;&lt;a-p&gt;}&lt;/a-p&gt;
      &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;ul&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
        &lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;tags&lt;/a-v&gt;&lt;a-p&gt;.&lt;/a-p&gt;&lt;a-f&gt;map&lt;/a-f&gt;&lt;a-p&gt;((&lt;/a-p&gt;&lt;a-v&gt;tag&lt;/a-v&gt;&lt;a-p&gt;)&lt;/a-p&gt; &lt;a-o&gt;=&amp;gt;&lt;/a-o&gt; &lt;a-p&gt;(&lt;/a-p&gt;
          &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;li&lt;/a-v&gt; &lt;a-pr&gt;key&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;tag&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
            &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-cr&gt;Tag&lt;/a-cr&gt; &lt;a-pr&gt;name&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;tag&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt; /&amp;gt;
          &amp;lt;/&lt;a-v&gt;li&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
        &lt;a-p&gt;))}&lt;/a-p&gt;
      &amp;lt;/&lt;a-v&gt;ul&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
    &amp;lt;/&lt;a-v&gt;article&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
  &lt;a-p&gt;);&lt;/a-p&gt;
&lt;a-p&gt;}&lt;/a-p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the grammar actually parses the code, the colours follow its
structure rather than guessing from characters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;article&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;ul&lt;/code&gt; and &lt;code&gt;li&lt;/code&gt; are intrinsic elements and are coloured
as variables. &lt;code&gt;Tag&lt;/code&gt; is capitalised, so the grammar treats it as a
component and colours it as a constructor, the same distinction React
makes.
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;className&lt;/code&gt;, &lt;code&gt;data-open&lt;/code&gt;, &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;onClick&lt;/code&gt; and &lt;code&gt;key&lt;/code&gt; are coloured as
properties, separately from both the tag and the value.
&lt;/li&gt;
&lt;li&gt;Inside every &lt;code&gt;{ ... }&lt;/code&gt; expression the grammar is back to ordinary
JavaScript, so &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;!open&lt;/code&gt; and the arrow function highlight
normally, and the nested &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; inside &lt;code&gt;tags.map&lt;/code&gt; is parsed as JSX
again rather than as a less-than sign.
&lt;/li&gt;
&lt;li&gt;The template literal inside that expression keeps its own nesting, so
&lt;code&gt;${title}&lt;/code&gt; is highlighted inside the backticks inside the braces.
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tsx"&gt;&lt;a href="#tsx" style="margin-right: 10px"&gt;#&lt;/a&gt;TSX&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tsx&lt;/code&gt; uses the TypeScript grammar, which colours types wherever they
appear and gives tags and attribute names their own elements:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-tsx"&gt;&lt;a-k&gt;type&lt;/a-k&gt; &lt;a-t&gt;CardProps&lt;/a-t&gt; &lt;a-o&gt;=&lt;/a-o&gt; &lt;a-p&gt;{&lt;/a-p&gt;
  &lt;a-pr&gt;title&lt;/a-pr&gt;: &lt;a-t&gt;string&lt;/a-t&gt;&lt;a-p&gt;;&lt;/a-p&gt;
  &lt;a-pr&gt;href&lt;/a-pr&gt;: &lt;a-t&gt;string&lt;/a-t&gt;&lt;a-p&gt;;&lt;/a-p&gt;
  &lt;a-pr&gt;tags&lt;/a-pr&gt;?: &lt;a-k&gt;readonly&lt;/a-k&gt; &lt;a-t&gt;string&lt;/a-t&gt;&lt;a-p&gt;[];&lt;/a-p&gt;
&lt;a-p&gt;};&lt;/a-p&gt;

&lt;a-k&gt;const&lt;/a-k&gt; &lt;a-t&gt;Card&lt;/a-t&gt; &lt;a-o&gt;=&lt;/a-o&gt; &lt;a-p&gt;({&lt;/a-p&gt; title&lt;a-p&gt;,&lt;/a-p&gt; href &lt;a-p&gt;}&lt;/a-p&gt;: &lt;a-t&gt;CardProps&lt;/a-t&gt;&lt;a-p&gt;)&lt;/a-p&gt;: &lt;a-t&gt;JSX&lt;/a-t&gt;&lt;a-p&gt;.&lt;/a-p&gt;&lt;a-t&gt;Element&lt;/a-t&gt; &lt;a-o&gt;=&amp;gt;&lt;/a-o&gt; &lt;a-p&gt;(&lt;/a-p&gt;
  &lt;a-p&gt;&amp;lt;&lt;/a-p&gt;&lt;a-tg&gt;a&lt;/a-tg&gt; &lt;a-at&gt;className&lt;/a-at&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-s&gt;&amp;quot;card&amp;quot;&lt;/a-s&gt; &lt;a-at&gt;href&lt;/a-at&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;href&lt;/a-v&gt;&lt;a-p&gt;}&amp;gt;&lt;/a-p&gt;
    &lt;a-p&gt;{&lt;/a-p&gt;&lt;a-v&gt;title&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;
  &lt;a-p&gt;&amp;lt;/&lt;/a-p&gt;&lt;a-tg&gt;a&lt;/a-tg&gt;&lt;a-p&gt;&amp;gt;&lt;/a-p&gt;
&lt;a-p&gt;);&lt;/a-p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="astro"&gt;&lt;a href="#astro" style="margin-right: 10px"&gt;#&lt;/a&gt;Astro&lt;/h3&gt;
&lt;p&gt;arborium has no Astro grammar, so an &lt;code&gt;astro&lt;/code&gt; block prints as plain text:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;---
import Layout from "../layouts/Layout.astro";
const posts = await Astro.glob("../posts/*.md");
---

&amp;lt;Layout title="Blog"&amp;gt;
  &amp;lt;ul&amp;gt;
    {posts.map((post) =&amp;gt; (
      &amp;lt;li&amp;gt;
        &amp;lt;a href={post.url}&amp;gt;{post.frontmatter.title}&amp;lt;/a&amp;gt;
      &amp;lt;/li&amp;gt;
    ))}
  &amp;lt;/ul&amp;gt;
&amp;lt;/Layout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="fragments-and-self-closing-tags"&gt;&lt;a href="#fragments-and-self-closing-tags" style="margin-right: 10px"&gt;#&lt;/a&gt;Fragments and self-closing tags&lt;/h3&gt;
&lt;p&gt;Two cases a hand-written lexer easily gets wrong come free with a real
parser. A self-closing tag doesn't change the nesting depth, and a
fragment has no tag name at all:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-jsx"&gt;&lt;a-k&gt;const&lt;/a-k&gt; &lt;a-cr&gt;Row&lt;/a-cr&gt; &lt;a-o&gt;=&lt;/a-o&gt; &lt;a-p&gt;()&lt;/a-p&gt; &lt;a-o&gt;=&amp;gt;&lt;/a-o&gt; &lt;a-p&gt;(&lt;/a-p&gt;
  &lt;a-o&gt;&amp;lt;&amp;gt;&lt;/a-o&gt;
    &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-cr&gt;Icon&lt;/a-cr&gt; &lt;a-pr&gt;name&lt;/a-pr&gt;&lt;a-o&gt;=&lt;/a-o&gt;&lt;a-s&gt;&amp;quot;star&amp;quot;&lt;/a-s&gt; /&amp;gt;
    &lt;a-o&gt;&amp;lt;&lt;/a-o&gt;&lt;a-v&gt;span&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;after the self-closing tag&amp;lt;/&lt;a-v&gt;span&lt;/a-v&gt;&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
  &amp;lt;/&lt;a-o&gt;&amp;gt;&lt;/a-o&gt;
&lt;a-p&gt;);&lt;/a-p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="writing-astro"&gt;&lt;a href="#writing-astro" style="margin-right: 10px"&gt;#&lt;/a&gt;Writing Astro&lt;/h2&gt;
&lt;p&gt;The templates are not merely highlighted as Astro, they are written in
it. Here is the whole layout that wraps every page on this site:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang={site.lang}&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;Head /&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;slot /&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{expression}&lt;/code&gt; interpolates and escapes. &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; is where the wrapped
content goes. &lt;code&gt;&amp;lt;Head /&amp;gt;&lt;/code&gt; is a component, which is also how partials work
— there is no separate include mechanism.&lt;/p&gt;
&lt;h3 id="expressions"&gt;&lt;a href="#expressions" style="margin-right: 10px"&gt;#&lt;/a&gt;Expressions&lt;/h3&gt;
&lt;p&gt;The expression language is a small, deliberate subset of JavaScript:
paths, string and template literals, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;, &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;,
the ternary, and function calls.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;title&amp;gt;{page.seoTitle}&amp;lt;/title&amp;gt;
&amp;lt;meta name="description" content={page.seoDescription}&amp;gt;
&amp;lt;link rel="canonical" href={absUrl(page.url)}&amp;gt;
&amp;lt;aside class={`callout callout-${props.kind}`}&amp;gt;
{page.date &amp;amp;&amp;amp; (&amp;lt;time datetime={dateIso(page.date)}&amp;gt;{dateLong(page.date)}&amp;lt;/time&amp;gt;)}
{site.inlineCss ? (&amp;lt;style set:html={site.css}&amp;gt;&amp;lt;/style&amp;gt;) : (&amp;lt;link rel="stylesheet" href={url("/assets/css/main.css")}&amp;gt;)}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions stand in for what a template language would call filters:
&lt;code&gt;url&lt;/code&gt;, &lt;code&gt;absUrl&lt;/code&gt;, &lt;code&gt;upper&lt;/code&gt;, &lt;code&gt;lower&lt;/code&gt;, &lt;code&gt;stripHtml&lt;/code&gt;, &lt;code&gt;xml&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;,
&lt;code&gt;dateLong&lt;/code&gt;, &lt;code&gt;dateRfc822&lt;/code&gt; and &lt;code&gt;dateIso&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;{expression}&lt;/code&gt; escapes by default, raw HTML needs saying so.
&lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; inserts already-rendered content, and &lt;code&gt;set:html&lt;/code&gt; does the same
for a value, which is how the stylesheet gets inlined above.&lt;/p&gt;
&lt;h3 id="lists"&gt;&lt;a href="#lists" style="margin-right: 10px"&gt;#&lt;/a&gt;Lists&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.map()&lt;/code&gt; is the only iteration construct, and it looks like the real
thing. &lt;code&gt;content.posts&lt;/code&gt; is every document under &lt;code&gt;content/posts/&lt;/code&gt;, newest
first — a directory is a list without being declared anywhere. This is
the entire component that renders the index on the home page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;ul class="post-list"&amp;gt;
{content.posts.map(post =&amp;gt; (&amp;lt;li&amp;gt;&amp;lt;a href={url(post.url)}&amp;gt;{post.date} - {post.title}&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;))}
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside a map body, &lt;code&gt;index&lt;/code&gt;, &lt;code&gt;first&lt;/code&gt; and &lt;code&gt;last&lt;/code&gt; are in scope. Which
produces, live, on this page:&lt;/p&gt;
&lt;ul class="post-list"&gt;
&lt;li&gt;&lt;a href="/pox/posts/2026-09-06-jsx-without-javascript"&gt;2026-09-06 - JSX without JavaScript&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="/pox/posts/2023-04-08-first-post"&gt;2023-04-08 - First post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="components-and-props"&gt;&lt;a href="#components-and-props" style="margin-right: 10px"&gt;#&lt;/a&gt;Components and props&lt;/h3&gt;
&lt;p&gt;A component is a file in &lt;code&gt;_components/&lt;/code&gt;. Its own &lt;code&gt;---&lt;/code&gt; frontmatter sets
prop defaults, &lt;code&gt;{props.x}&lt;/code&gt; reads a prop, and &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; is whatever the
tag wrapped:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;---
kind: note
title: Note
---
&amp;lt;aside class={`callout callout-${props.kind}`}&amp;gt;
&amp;lt;strong&amp;gt;{props.title}&amp;lt;/strong&amp;gt;
&amp;lt;slot /&amp;gt;
&amp;lt;/aside&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Attribute values come in three flavours: a quoted value is a literal, a
braced value is an expression, and a bare attribute is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;Note title="Fixed text" /&amp;gt;
&amp;lt;Note title={`Interpolated: ${site.title}`} /&amp;gt;
&amp;lt;Note title={content.posts.0.title} /&amp;gt;
&amp;lt;Note wide /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Components nest, and each gets its own scope, so an inner component's
props do not leak into the outer one:&lt;/p&gt;
&lt;aside class="callout callout-warning"&gt;
&lt;strong&gt;Nesting&lt;/strong&gt;
&lt;p&gt;Inside this warning is another component:&lt;/p&gt;
&lt;aside class="callout callout-note"&gt;
&lt;strong&gt;The inner one&lt;/strong&gt;
Its own props, its own slot.
&lt;/aside&gt;
&lt;p&gt;and the outer &lt;code&gt;props.title&lt;/code&gt; is still &lt;code&gt;Nesting&lt;/code&gt; after it closes.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id="where-it-stops"&gt;&lt;a href="#where-it-stops" style="margin-right: 10px"&gt;#&lt;/a&gt;Where it stops&lt;/h2&gt;
&lt;p&gt;The honest limits, because a component system that expands at build time
is not React:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No client behaviour.&lt;/strong&gt; There is no runtime, no state, no hydration.
&lt;code&gt;onClick&lt;/code&gt; in a code block is a string being coloured, not a handler.
Anything interactive has to be written as HTML and CSS.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The expression language is a subset.&lt;/strong&gt; No arithmetic, no arbitrary
method calls, no array literals. &lt;code&gt;.map()&lt;/code&gt; is special-cased, and paths
index groups positionally (&lt;code&gt;content.posts.0.title&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One pass, in one order.&lt;/strong&gt; A post body is expanded as a template
first, then rendered as markdown. Fenced blocks and inline code spans
are masked out before that pass, which is the only reason every example
above survived long enough to be printed.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slots are text, not a tree.&lt;/strong&gt; A component receives its children as
rendered markup and drops them in. It cannot inspect them, count them,
or render them twice.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capitalisation is the whole resolution rule.&lt;/strong&gt; &lt;code&gt;&amp;lt;Note /&amp;gt;&lt;/code&gt; is a
component if &lt;code&gt;_components/Note.astro&lt;/code&gt; exists, and passes through
untouched if it does not.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that has cost me anything yet. The build for this site takes
under a second, ships no JavaScript, and the only reason JSX appears in
it at all is that the syntax turns out to be a pleasant way to call a
partial written in Perl.&lt;/p&gt;</description>
</item><item>
<title>First post</title>
<link>https://cmdcolin.github.io/pox/posts/2023-04-08-first-post</link>
<guid isPermaLink="true">https://cmdcolin.github.io/pox/posts/2023-04-08-first-post</guid>
<pubDate>Sat, 08 Apr 2023 00:00:00 +0000</pubDate>
<description>&lt;p&gt;Hello world!&lt;/p&gt;
&lt;p&gt;This is some code&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-typescript"&gt;&lt;a-k&gt;function&lt;/a-k&gt; &lt;a-f&gt;hello&lt;/a-f&gt;&lt;a-p&gt;(&lt;/a-p&gt;&lt;a-v&gt;arg&lt;/a-v&gt;: &lt;a-t&gt;name&lt;/a-t&gt;&lt;a-p&gt;)&lt;/a-p&gt; &lt;a-p&gt;{&lt;/a-p&gt;
  &lt;a-v&gt;console&lt;/a-v&gt;&lt;a-p&gt;.&lt;/a-p&gt;&lt;a-f&gt;log&lt;/a-f&gt;&lt;a-p&gt;(&lt;/a-p&gt;&lt;a-s&gt;`Hello &lt;/a-s&gt;&lt;a-p&gt;${&lt;/a-p&gt;&lt;a-v&gt;arg&lt;/a-v&gt;&lt;a-p&gt;}&lt;/a-p&gt;&lt;a-s&gt;`&lt;/a-s&gt;&lt;a-p&gt;);&lt;/a-p&gt;
&lt;a-p&gt;}&lt;/a-p&gt;&lt;/code&gt;&lt;/pre&gt;</description>
</item>
</channel>
</rss>