{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Jan Miksovsky’s blog",
  "description": "Writings on the craft of user interface design and development",
  "feed_url": "https://jan.miksovsky.com/feed.json",
  "home_page_url": "https://jan.miksovsky.com",
  "items": [
    {
      "content_html": "<p>This post is the fourth and last in a series comparing the same sample blog in <a href=\"https://weborigami.org\">Web Origami</a> and <a href=\"https://www.11ty.dev\">Eleventy</a>:</p>\n<ul>\n<li><strong>Eleventy version:</strong> <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog\">Source code</a> and <a href=\"https://original-eleventy-blog.netlify.app\">Demo</a></li>\n<li><strong>Origami version:</strong> <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog\">Source code</a> and <a href=\"https://origami-eleventy-blog.netlify.app\">Demo</a></li>\n</ul>\n<p>As a very crude metric, the conciseness of legible code can roughly correlate with simplicity, so in this final post, let&#8217;s measure how large the projects are. That analysis is followed with an Appendix of other notes from this experiment.</p>\n<p>I totaled the size of all source files in each project: configuration code, data, scripts, and templates. I did not count markdown content as source code, and in any event, both projects use the same markdown.</p>\n<p>In a code size comparison, Eleventy has latent advantages: its folder structure implicitly encodes behavior that Origami must spell out in code, and its use of configurable plugins should in theory require less code.</p>\n<p>Counting source code in bytes (with <code>wc -c</code>) for Eleventy:</p>\n<pre><code>    2836 base.njk\n      74 blog.11tydata.js\n     157 blog.njk\n      49 content.11tydata.js\n    4610 eleventy.config.js\n     475 eleventyDataSchema.js\n    1380 filters.js\n      54 home.njk\n     864 index.njk\n     294 metadata.js\n    1199 post.njk\n     528 postslist.njk\n     519 sitemap.xml.njk\n     608 tag-pages.njk\n     235 tags.njk\n   13882 total\n</code></pre><p>And for Origami:</p>\n<pre><code>    1613 base.ori.html\n     144 blogIndex.ori.html\n     603 feed.ori\n     310 images.ori\n     403 index.ori.html\n     243 metadata.yaml\n     851 post.ori.html\n     501 postList.ori.html\n    1463 posts.ori\n     502 readableDate.js\n    1328 site.ori\n     201 tag.ori.html\n     251 tagIndex.ori.html\n     159 tags.ori\n    8572 total\n</code></pre><p>The Origami version, which is doing much of the work from scratch, is <em>smaller!</em></p>\n<p><img src=\"/images/2026/03/sourceSize.svg\" alt=\"\"></p>\n<p>It seems Origami&#8217;s smaller size can be attributed in part to:</p>\n<ul>\n<li>JavaScript template literals (3763 bytes) are significantly more concise than the equivalent Nunjucks templates (7000 bytes). Both Origami and Eleventy let you use other template engines, so templates could be factored out of this analysis.</li>\n<li>JavaScript templates can inline JavaScript expressions directly instead of requiring separate registration as &#8220;shortcodes&#8221; or &#8220;filters&#8221; (see Appendix below)</li>\n<li>Some Eleventy files include instructional commented-out code blocks which could be trimmed</li>\n<li>Origami has a built-in function to create a sitemap</li>\n<li>The Origami site definition in <code>site.ori</code> (which defines the top-level structure of the site) is 30% the size of <code>eleventy.config.js</code> (which configures the main behavior of Eleventy&#8217;s static site generator).</li>\n</ul>\n<p>In the domain of site creation, code is more concise than configuration.</p>\n<p>Static site generators presume that sites are so complex that it&#8217;s better to generate a site with an engine whose behavior you configure. But <em>sites just aren&#8217;t that complex!</em> If you know how to write HTML and CSS, you can put together a site using zero magic. The code to do so is <em>smaller</em> than the configuration code required to influence a static site generator.</p>\n<p>Speaking of metrics, performance should probably be no more than a secondary concern for you when evaluating blogging tools. Most static site generators are quite fast, especially for personal sites.</p>\n<p>That said, I timed builds of both approaches via <code>time npm run build</code> on a 2024 MacBook Air M3. I threw the first (longest) time away, then averaged the <code>real</code> time of the next three builds.</p>\n<p>Both Origami and Eleventy build this tiny blog project in less than a second:</p>\n<p><img src=\"/images/2026/03/buildTime.svg\" alt=\"\"></p>\n<p>While performance shouldn&#8217;t be your primary concern, in the case of this sample blog Origami comes out ahead. It&#8217;s entirely possible that Eleventy has higher startup costs as it digs through your project looking for files, so for all I know, once those startup tasks are accomplished it could process a larger blog faster. Regardless, the performance here shows that Origami&#8217;s fundamental approach is well-suited for this task.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Summing up this post series, I beleive this experiment shows that an explicit, code-oriented solution like Origami is easier to follow, more coherent, more expressive, and more concise than one predicated on configuration like Eleventy. I expect the same comparison holds true for the countless other static site generators that rely on a combination of configuration, naming conventions, and folder structure.</p>\n<p>Not every person wants to code, or has the time or energy to learn to code. But I think configuration-based site generators cover up the complexity of code with a system that is ultimately just as hard to understand. If you&#8217;re capable of configuring a tool like Eleventy, you are just as capable of coding in Web Origami.</p>\n<p>I don&#8217;t know the Eleventy team personally, but they seem like perfectly nice people who care deeply about their users and want to create good tools for them. They also have what looks (to an outsider like me) like a dynamic, supportive user community. If you pick Eleventy for your site, it&#8217;ll probably work out fine.</p>\n<p>If you&#8217;re interested in trying Origami for a blog, I think you&#8217;ll like it. You can start with the corresponding <a href=\"https://github.com/WebOrigami/origami-blog-start\"><code>origami-blog-start</code></a> template project. If you&#8217;re interested but want help or have questions, <a href=\"/contact.html\">let me know</a>.</p>\n<p>Other posts in this series:</p>\n<ol>\n<li><a href=\"/posts/2026/03-17-code-is-easier-to-follow-than-configuration.html\">Code is easier to follow than configuration</a></li>\n<li><a href=\"/posts/2026/03-18-code-is-more-coherent-than-configuration.html\">Code is more coherent than configuration</a></li>\n<li><a href=\"/posts/2026/03-19-code-is-more-expressive-than-configuration.html\">Code is more expressive than configuration</a></li>\n<li>Code is more concise than configuration [this post]</li>\n</ol>\n<h2 id=\"appendix\">Appendix</h2>\n<p>The following are small points I noticed while studying the Eleventy blog; none are as important as the main points above.</p>\n<h3 id=\"bugs\">Bugs</h3>\n<p>I found three very minor possible issues in the sample <code>eleventy-base-blog</code> project. Although the issues are small and debatable, any bugs in a template project will be endlessly copied into new blogs, so their potential impact is magnified.</p>\n<p>I want Eleventy to continue growing and their new users to have great experiences, so I reported these to Eleventy (<a href=\"https://github.com/11ty/eleventy-base-blog/issues/227\">issue</a>, <a href=\"https://github.com/11ty/eleventy-base-blog/issues/228\">issue</a>, <a href=\"https://github.com/11ty/eleventy-base-blog/issues/229\">issue</a>).</p>\n<h3 id=\"javascript-as-a-template-language\">JavaScript as a template language</h3>\n<p>Template languages like Nunjucks are common, but they become another language you need to learn. If you already know JavaScript, that’s enough to be able to do anything you want in a template in Origami.</p>\n<p>As a bonus, this means that you don’t have to do special things to invoke JavaScript from a template. When the Eleventy version wants to insert a timestamp onto a page, it registers a small JavaScript function as a <a href=\"https://www.11ty.dev/docs/shortcodes/\">shortcode</a>:</p>\n<pre><code class=\"hljs language-js\">eleventyConfig.<span class=\"hljs-title function_\">addShortcode</span>(<span class=\"hljs-string\">&quot;currentBuildDate&quot;</span>, <span class=\"hljs-function\">() =&gt;</span> {\n  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">Date</span>().<span class=\"hljs-title function_\">toISOString</span>();\n});\n</code></pre><p>This shortcode can then be called by name from a Nunjucks template:</p>\n<pre><code>built on {% currentBuildDate %}\n</code></pre><p>In Origami, a template is a JavaScript template literal stored in its own file, so you can skip all that registration complexity above and just inline the desired JavaScript into the template:</p>\n<pre><code class=\"hljs language-js\">built on ${ <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">Date</span>().<span class=\"hljs-title function_\">toISOString</span>() }\n</code></pre><p>If the code were longer, you could put it in its own JavaScript file and call that by file name. The Origami version uses that technique to implement the Eleventy <code>readableDate</code> function; Origami templates then call that function with</p>\n<pre><code>${ readableDate.js(post.date) }\n</code></pre><h3 id=\"passing-data-to-templates\">Passing data to templates</h3>\n<p>A number of the Nunjucks templates in the Eleventy blog include lines like this:</p>\n<pre><code>{% set postslist = collections.posts %}\n{% include &quot;postslist.njk&quot; %}\n</code></pre><p>As I understand it, a Nunjucks <code>include</code> doesn’t let you pass data directly, so you have to pass data via what&#8217;s effectively a global variable. That approach is so fraught with the high potential for errors that it&#8217;s hard for me to recommend any system that requires it. (Eleventy allows the use of template engines other than Nunjucks; perhaps those are better.)</p>\n<p>Origami templates are functions, so you can pass data to them directly:</p>\n<pre><code>${ postList.ori.html(posts) }\n</code></pre><h3 id=\"focusing-on-representing-pages\">Focusing on representing pages</h3>\n<p>Blogging tools like Eleventy use a project&#8217;s folder structure to determine the resulting site structure. That approach focuses on complete file resources, such as a page for an individual post.</p>\n<p>But blog posts in this project actually have three representations:</p>\n<ol>\n<li>The post page in the <code>blog</code> area</li>\n<li>A post entry in lists of posts: home page, <code>blog/index.html</code>, and tag pages</li>\n<li>A post entry in the feed at <code>feed/feed.xml</code></li>\n</ol>\n<p>The folder structure only gives you a way to conveniently express the first representation. The Origami code doesn&#8217;t have any particular focus on pages; all post representations can be defined in a variety of ways.</p>\n<p>Meanwhile, using folder structure to represent site structure has limits. It took me a while to realize that the single file <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog/blob/main/content/tag-pages.njk\"><code>tag-pages.njk</code></a> isn&#8217;t just a template for a tag page; an embedded block of JavaScript at the top of the file appears to also generate the collection of pages like <code>tags/second-tag/index.html</code>.</p>\n<p>In contrast, the Origami <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog/blob/main/src/site.ori\"><code>site.ori</code></a> file includes an explicit definition of the <code>tags</code> area.</p>\n<h3 id=\"inlining-css\">Inlining CSS</h3>\n<p>The Eleventy blog inlines the main CSS stylesheet into every page instead of linking to it. Origami can easily do both, but as a matter of preference, I had the Origami version link to the main stylesheet. Among other things, that keeps each built page smaller and easier to read.</p>\n<h3 id=\"navigation-links\">Navigation links</h3>\n<p>The Eleventy version uses an <a href=\"https://www.11ty.dev/docs/plugins/navigation/\">Eleventy navigation plugin</a>. I&#8217;m probably missing something, but in this project it looks like the plugin is used to add an <code>aria-current</code> attribute to three links.</p>\n<p>Perhaps in other contexts the plugin saves time, but here it seems like overkill. I implemented this in the Origami version by adding conditions to the three links in question:</p>\n<pre><code class=\"hljs language-js\">${ _.<span class=\"hljs-property\">area</span> === <span class=\"hljs-string\">&quot;Home&quot;</span> ? <span class=\"hljs-string\">`aria-current=&quot;page&quot;`</span> : <span class=\"hljs-string\">&quot;&quot;</span> }\n</code></pre><p>This does the job, and is a lot easier for me (someone familiar with JavaScript) to understand. This could be scaled up into a helper function if necessary.</p>\n<h3 id=\"html-rewrites\">HTML rewrites</h3>\n<p>I was baffled by this template fragment in the Eleventy version:</p>\n<pre><code>Go &lt;a href=&quot;index.njk&quot;&gt;home&lt;/a&gt;.\n</code></pre><p>I just couldn’t figure out what this was doing — what would it even mean to navigate to a Nunjucks template? Looking at the running site, I could see that the link was magically being rewritten. But even closely reading the project’s source code couldn&#8217;t help me see how or why this was happening.</p>\n<p>Claude Code identified this fragment as something handled by the <a href=\"https://www.11ty.dev/docs/plugins/inputpath-to-url/\">Eleventy InputPath to URL plugin</a>.</p>\n<p>Some people love such magic; I’m not one of them.</p>\n<h3 id=\"sitemaps\">Sitemaps</h3>\n<p>The original Eleventy project defined a <code>sitemap.xml</code> file so I implemented one for the Origami version. That said, a sitemap seems unnecessary for this blog; all the content is trivially discoverable by a search engine. The code to generate the sitemap ends up being both a distraction and a possible maintenance burden.</p>\n<h3 id=\"plugins-are-general-features-bound-to-specific-projects\">Plugins are general features bound to specific projects</h3>\n<p>The original Eleventy blog uses a number of plugins to:</p>\n<ul>\n<li>Rewrite URLs to inject a path prefix</li>\n<li>Generate ID attributes for heading elements</li>\n<li>Transform input file paths to output URLs</li>\n<li>Optimize images</li>\n<li>Help construct navigation elements</li>\n<li>Define an RSS feed</li>\n<li>Apply syntax highlighting to blocks of code in posts</li>\n</ul>\n<p>All these tasks performed by the Eleventy plugins have one thing in common: <em>the tasks have nothing to do with Eleventy.</em> Every one of them is something you might want to do on any static site, regardless of which tool you’re using to make it.</p>\n<p>It’s in the nature of tools with proprietary interior workings to require tool-specific plugins. The lost opportunity is that, instead of sharing general-purpose code that can work with many tools, we collectively waste time rewriting the same ideas over and over for different tools.</p>\n<p>Extensibility in Origami is provided by calling functions that can be written as generally as possible. There are no internal data structures that require proprietary plugins to manipulate. As the creator, your own code generates the requisite objects; you can pass those directly to third-party functions.</p>\n<p>The aforementioned Origami package for <a href=\"https://github.com/WebOrigami/json-feed-to-rss\">turning a data object into an RSS feed</a> is a plain JavaScript function that has nothing to do with Origami at all. Other Origami packages are written around the use of the <a href=\"https://weborigami.org/async-tree/\">standard Map class as the basis for tree structures</a>, an approach that’s at least theoretically adoptable by other tools.</p>\n",
      "date_published": "2026-03-20T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/03-20-code-is-more-concise-than-configuration.html",
      "url": "https://jan.miksovsky.com/posts/2026/03-20-code-is-more-concise-than-configuration.html",
      "title": "Code is more concise than configuration: comparing a sample blog in Web Origami and Eleventy"
    },
    {
      "content_html": "<p>This post is the third in a series comparing the same sample blog in <a href=\"https://weborigami.org\">Web Origami</a> and <a href=\"https://www.11ty.dev\">Eleventy</a>:</p>\n<ul>\n<li><strong>Eleventy version:</strong> <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog\">Source code</a> and <a href=\"https://original-eleventy-blog.netlify.app\">Demo</a></li>\n<li><strong>Origami version:</strong> <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog\">Source code</a> and <a href=\"https://origami-eleventy-blog.netlify.app\">Demo</a></li>\n</ul>\n<p>This post looks at another advantage of code over configuration: the degree to which you can easily express your ideas without limits.</p>\n<p>As one example, let’s look at the code required to give this blog a feed. The Eleventy version uses the <a href=\"https://www.11ty.dev/docs/plugins/rss/\">Eleventy RSS plugin</a>, which in this project is configured this way:</p>\n<pre><code class=\"hljs language-js\">eleventyConfig.<span class=\"hljs-title function_\">addPlugin</span>(feedPlugin, {\n  <span class=\"hljs-attr\">type</span>: <span class=\"hljs-string\">&quot;atom&quot;</span>, <span class=\"hljs-comment\">// or &quot;rss&quot;, &quot;json&quot;</span>\n  <span class=\"hljs-attr\">outputPath</span>: <span class=\"hljs-string\">&quot;/feed/feed.xml&quot;</span>,\n  <span class=\"hljs-attr\">templateData</span>: {\n    <span class=\"hljs-attr\">eleventyNavigation</span>: {\n      <span class=\"hljs-attr\">key</span>: <span class=\"hljs-string\">&quot;Feed&quot;</span>,\n      <span class=\"hljs-attr\">order</span>: <span class=\"hljs-number\">4</span>,\n    },\n  },\n  <span class=\"hljs-attr\">collection</span>: {\n    <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">&quot;posts&quot;</span>,\n    <span class=\"hljs-attr\">limit</span>: <span class=\"hljs-number\">10</span>,\n  },\n  <span class=\"hljs-attr\">metadata</span>: {\n    <span class=\"hljs-attr\">language</span>: <span class=\"hljs-string\">&quot;en&quot;</span>,\n    <span class=\"hljs-attr\">title</span>: <span class=\"hljs-string\">&quot;Blog Title&quot;</span>,\n    <span class=\"hljs-attr\">subtitle</span>: <span class=\"hljs-string\">&quot;This is a longer description about your blog.&quot;</span>,\n    <span class=\"hljs-attr\">base</span>: <span class=\"hljs-string\">&quot;https://example.com/&quot;</span>,\n    <span class=\"hljs-attr\">author</span>: {\n      <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">&quot;Your Name&quot;</span>,\n    },\n  },\n});\n</code></pre><p>In contrast, the Origami project uses a function that <a href=\"https://github.com/WebOrigami/json-feed-to-rss\">generates an RSS feed</a> from a data object created this way:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-comment\">// The posts in JSON Feed format</span>\n(posts) =&gt; {\n  <span class=\"hljs-attr\">version</span>: <span class=\"hljs-string\">&quot;https://jsonfeed.org/version/1.1&quot;</span>\n  <span class=\"hljs-attr\">title</span>: metadata.<span class=\"hljs-property\">yaml</span>/title\n  <span class=\"hljs-attr\">description</span>: metadata.<span class=\"hljs-property\">yaml</span>/description\n  <span class=\"hljs-attr\">feed_url</span>: <span class=\"hljs-string\">`<span class=\"hljs-subst\">${ metadata.yaml/url }</span>/feed.json`</span>\n  <span class=\"hljs-attr\">home_page_url</span>: metadata.<span class=\"hljs-property\">yaml</span>/url\n  \n  <span class=\"hljs-comment\">// Map the post data to JSON Feed items</span>\n  <span class=\"hljs-attr\">items</span>: <span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">values</span>(<span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">map</span>(posts, <span class=\"hljs-function\">(<span class=\"hljs-params\">post, slug</span>) =&gt;</span> {\n    <span class=\"hljs-comment\">// Patch image URLs to be absolute</span>\n    <span class=\"hljs-attr\">content_html</span>: post.<span class=\"hljs-property\">_body</span>.<span class=\"hljs-title function_\">replaceAll</span>(<span class=\"hljs-string\">&#x27;src=&quot;.\\/&#x27;</span>, <span class=\"hljs-string\">`src=&quot;<span class=\"hljs-subst\">${ metadata.yaml/url }</span>/blog/<span class=\"hljs-subst\">${ slug }</span>/`</span>)\n    <span class=\"hljs-attr\">date_published</span>: post.<span class=\"hljs-property\">date</span>\n    <span class=\"hljs-attr\">id</span>: url\n    <span class=\"hljs-attr\">title</span>: post.<span class=\"hljs-property\">title</span>\n    <span class=\"hljs-attr\">url</span>: <span class=\"hljs-string\">`<span class=\"hljs-subst\">${ metadata.yaml/url }</span>/blog/<span class=\"hljs-subst\">${ slug }</span>`</span>\n  }))\n}\n</code></pre><p>In both projects, you build the feed with code — but in completely different ways:</p>\n<ul>\n<li>In Eleventy you write code to configure parameters for a feed-generation plugin whose internal workings are opaque to you that will create the feed. Your ability to customize that feed is limited to the extent the plugin&#8217;s developers have correctly anticipated your needs.</li>\n<li>In Origami you write code to create the feed. The Origami code above is slightly denser but it&#8217;s <em>doing nearly all the work the Eleventy plugin itself does</em>. Your ability to customize that feed is limited to the extent you can describe what you want in code. (The code generates the feed in the JSON Feed schema, which is then directly translatable to RSS with a built-in function.)</li>\n</ul>\n<p>The expressiveness of code gives you the freedom to tackle things the way you want to — and the code required to make the change is proportional to the complexity of the change. If you want to change what the Origami feed uses as an item <code>id</code>, you change that part of the code.</p>\n<p>A long-term benefit of coding things is that you learn transferrable knowledge. Your potential mastery of the Eleventy RSS plugin data schema won&#8217;t help you in a different blog tool, or even using a different Eleventy plugin. In contrast, learning an interchange format like RSS or (here) JSON Feed is knowledge you can apply elsewhere, as are the data manipulation techniques employed in the code above.</p>\n<p><em>[As I was finishing this post series, I discovered that the Eleventy RSS plugin allows you to specify a <a href=\"https://www.11ty.dev/docs/plugins/rss/#sample-feed-templates\">feed template</a>, giving you the same degree of expressiveness as Origami for a feed specifically although not as concisely. That only makes me wonder why the plugin has to exist at all — the feed template itself isn&#8217;t that much longer than the plugin configuration code. In any event, there&#8217;s no guarantee that other plugins will give you the same kind of escape hatch through code.]</em></p>\n<p>The expressiveness of code comes into play at every level of the Origami site. At the site&#8217;s highest level, I could readily use Origami to support the Eleventy sample blog&#8217;s preferred folder layout:</p>\n<ul>\n<li>Posts are stored in a top-level <code>content</code> folder, like <code>content/firstpost.md</code>.</li>\n<li>Posts with images are stored in a subfolder holding both the post (<code>content/fourthpost/fourthpost.md</code>) and associated images (<code>content/fourthpost/possum.png</code>).</li>\n</ul>\n<p>I&#8217;ve never used that particular layout for a project before, but it was straightforward to express in code.</p>\n<p>I point this out because folder-based, configuration-focused tools impose very particular expectations about how you should organize your content and source files. I have no idea whether it would be possible to configure, say, Astro to work with the content layout of this Eleventy project, or for Eleventy to work with the content layout of Astro&#8217;s starter blog.</p>\n<p>You might not care about that, or you may care about that a lot. In an Origami project, there&#8217;s nothing special about the source file names or organization; structure them however makes sense to you.</p>\n<p>To be clear, <em>both</em> approaches require too much code! I hope someday you can make a great blog for yourself with much less coding — or none at all. So you can design whatever you want <em>and</em> retain complete control <em>and</em> avoid paying a monthly ransom on your own writings via a subscription.</p>\n<p>But we have to start somewhere. You&#8217;re either writing code to 1) configure an engine or 2) do the actual work. Think about what kind of code you want to learn, and whether it will let you create what you want without limits.</p>\n<p>Other posts in this series:</p>\n<ol>\n<li><a href=\"/posts/2026/03-17-code-is-easier-to-follow-than-configuration.html\">Code is easier to follow than configuration</a></li>\n<li><a href=\"/posts/2026/03-18-code-is-more-coherent-than-configuration.html\">Code is more coherent than configuration</a></li>\n<li>Code is more expressive than configuration [this post]</li>\n<li><a href=\"/posts/2026/03-20-code-is-more-concise-than-configuration.html\">Code is more concise than configuration</a></li>\n</ol>\n",
      "date_published": "2026-03-19T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/03-19-code-is-more-expressive-than-configuration.html",
      "url": "https://jan.miksovsky.com/posts/2026/03-19-code-is-more-expressive-than-configuration.html",
      "title": "Code is more expressive than configuration: comparing a sample blog in Web Origami and Eleventy"
    },
    {
      "content_html": "<p>This post is the second in a series comparing the same sample blog in <a href=\"https://weborigami.org\">Web Origami</a> and <a href=\"https://www.11ty.dev\">Eleventy</a>:</p>\n<ul>\n<li><strong>Eleventy version:</strong> <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog\">Source code</a> and <a href=\"https://original-eleventy-blog.netlify.app\">Demo</a></li>\n<li><strong>Origami version:</strong> <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog\">Source code</a> and <a href=\"https://origami-eleventy-blog.netlify.app\">Demo</a></li>\n</ul>\n<p>Today let&#8217;s look at how both projects define the overall structure of the site and consider whether they can present a coherent picture of what you&#8217;re building.</p>\n<p>Like most static site generators, Eleventy leverages the tree-like structure of a folder hierarchy to approximate the tree-like structure of a site. The good news is that the file system itself gives you the best picture you&#8217;re going to get of the resulting site. That&#8217;s also the bad news.</p>\n<p>Here&#8217;s the folder structure of this Eleventy project, including the relevant source files:</p>\n<pre><code>_config/\n  filters.js\n_data/\n  eleventyDataSchema.js\n  metadata.js\n_includes/\n  layouts/\n    base.njk\n    home.njk\n    post.njk\n  postslist.njk\ncontent/\n  blog/\n    blog.11tydata.js\n  blog.njk\n  content.11tydata.js\n  index.njk\n  sitemap.xml.njk\n  tag-pages.njk\n  tags.njk\neleventy.config.js\n</code></pre><p>The above organization alone may not mean much to the uninitiated, and sadly folders on their own can&#8217;t have comments. Nevertheless, experienced Eleventy developers can probably envision the resulting site, especially if they also scan <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog/blob/main/eleventy.config.js\">the lengthy configuration file</a>.</p>\n<p>There are also many little files that configure different parts of the site&#8217;s construction, like <code>content/content.11tydata.js</code>:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> {\n  <span class=\"hljs-attr\">layout</span>: <span class=\"hljs-string\">&quot;layouts/home.njk&quot;</span>,\n};\n</code></pre><p>Most of these configuration files have no explanatory comments, by which I only conclude that we&#8217;re not expected to look at them. But if the average user isn&#8217;t expected to look at these files, why <em>not</em> have comments for those users that do look at them?</p>\n<p>The above file sets a path to a Nunjucks layout, but I couldn&#8217;t see how it was used. As discussed last time, configuration-oriented tools tend toward &#8220;action at a distance&#8221; behavior that is hard to intuit. It&#8217;s also the case that there&#8217;s just not much to go on here when searching for answers.</p>\n<p>I eventually learned that a file called <code>content.11tydata.js</code> is a <a href=\"https://www.11ty.dev/docs/data-template-dir/\">directory-specific data file</a> that implicitly associates its exported data with the containing folder. In this case, it defines a default <code>layout</code> property that will be applied as the base template for <em>other</em> templates in the <code>content</code> folder, like <code>content/index.njk</code>.</p>\n<p>Most of the Eleventy configuration code feels like this. The site builds a blog as advertised, but it feels like substantial work to piece together the site&#8217;s construction to the point where you could change it.</p>\n<p>In contrast, the premise of a coding-focused approach like Origami is that you describe what you want in code. Given that freedom, most Origami users elect to define their site&#8217;s top-level tree of resources in a single file, providing a coherent map of the project. Here&#8217;s the whole <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog/blob/main/src/site.ori\"><code>site.ori</code></a> file for the sample blog:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-comment\">// This file defines the structure of the entire blog site</span>\n{\n  <span class=\"hljs-attr\">about</span>: {\n    <span class=\"hljs-comment\">// About page</span>\n    index.<span class=\"hljs-property\">html</span>: templates/base.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(<span class=\"hljs-title class_\">Origami</span>.<span class=\"hljs-title function_\">mdHtml</span>(about.<span class=\"hljs-property\">md</span>))\n  }\n\n  <span class=\"hljs-comment\">// Static assets like stylesheets</span>\n  assets/\n\n  <span class=\"hljs-comment\">// Blog area</span>\n  <span class=\"hljs-attr\">blog</span>: {\n    <span class=\"hljs-comment\">// Blog index page</span>\n    index.<span class=\"hljs-property\">html</span> = templates/blogIndex.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(posts.<span class=\"hljs-property\">ori</span>)\n\n    <span class=\"hljs-comment\">// Create a folder for each post</span>\n    ...<span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">map</span>(posts.<span class=\"hljs-property\">ori</span>, {\n      <span class=\"hljs-attr\">key</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">post, key</span>) =&gt;</span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${ key }</span>/`</span>\n      <span class=\"hljs-attr\">value</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">post, key, tree</span>) =&gt;</span> {\n        <span class=\"hljs-comment\">// Index page for post folder</span>\n        index.<span class=\"hljs-property\">html</span>: templates/post.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(post, key, tree)\n        <span class=\"hljs-comment\">// Any associated images</span>\n        ...post.<span class=\"hljs-property\">images</span>\n      }\n    })\n  }\n\n  <span class=\"hljs-attr\">feed</span>: {\n    <span class=\"hljs-comment\">// Blog feed in RSS format</span>\n    feed.<span class=\"hljs-property\">xml</span> = <span class=\"hljs-title class_\">Origami</span>.<span class=\"hljs-title function_\">rss</span>(feed.<span class=\"hljs-title function_\">ori</span>(posts.<span class=\"hljs-property\">ori</span>))\n  }\n\n  <span class=\"hljs-comment\">// Home page</span>\n  index.<span class=\"hljs-property\">html</span> = templates/index.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(posts.<span class=\"hljs-property\">ori</span>)\n\n  <span class=\"hljs-comment\">// Tags area</span>\n  <span class=\"hljs-attr\">tags</span>: {\n    <span class=\"hljs-comment\">// Tag index page</span>\n    index.<span class=\"hljs-property\">html</span>: templates/tagIndex.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(tags.<span class=\"hljs-property\">ori</span>)\n\n    <span class=\"hljs-comment\">// Create a folder for each tag</span>\n    ...<span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">map</span>(tags.<span class=\"hljs-property\">ori</span>, {\n      <span class=\"hljs-attr\">key</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">group, tag</span>) =&gt;</span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${ Origami.slug(tag) }</span>/`</span>,\n      <span class=\"hljs-attr\">value</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">group, tag</span>) =&gt;</span> {\n        index.<span class=\"hljs-property\">html</span>: templates/tag.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(group, tag)\n      }\n    })\n  }\n\n  <span class=\"hljs-comment\">// Not Found page</span>\n  <span class=\"hljs-number\">404.</span>html = templates/base.<span class=\"hljs-property\">ori</span>.<span class=\"hljs-title function_\">html</span>(<span class=\"hljs-title class_\">Origami</span>.<span class=\"hljs-title function_\">mdHtml</span>(<span class=\"hljs-number\">404.</span>md))\n}\n\n<span class=\"hljs-comment\">// Add a sitemap for all of that</span>\n→ (site) =&gt; {\n  ...site\n  sitemap.<span class=\"hljs-property\">xml</span> = <span class=\"hljs-title class_\">Origami</span>.<span class=\"hljs-title function_\">sitemap</span>(site, { <span class=\"hljs-attr\">base</span>: metadata.<span class=\"hljs-property\">yaml</span>/url })\n}\n</code></pre><p>Even if you don&#8217;t know Origami or JavaScript, you can probably squint and perceive the structure of the final site. All references here are explicit and by name. </p>\n<p>For example, at the top you can see that the <code>about</code> area contains a page called <code>index.html</code>. The formula for that page may be unclear to you, but it contains references to the files <code>templates/base.ori.html</code> and <code>about.md</code>, so you can look at those. Even if you don&#8217;t know what something does, you at least have a name to search for. Searching the Web Origami documentation for the remaining term in that formula finds the built-in function, <a href=\"https://weborigami.org/builtins/origami/mdhtml\"><code>Origami.mdHtml</code></a>.</p>\n<p>This single <code>site.ori</code> file pulls together the bulk of the logic behind the site. I think such a coherent, text-based map of the site is enormously helpful in understanding and remembering how the parts fit together. You can also ask Origami to draw a <a href=\"/images/2026/03/site.svg\">visual diagram of the running site</a> to confirm your understanding.</p>\n<p>Other posts in this series:</p>\n<ol>\n<li><a href=\"/posts/2026/03-17-code-is-easier-to-follow-than-configuration.html\">Code is easier to follow than configuration</a></li>\n<li>Code is more coherent than configuration [this post]</li>\n<li><a href=\"/posts/2026/03-19-code-is-more-expressive-than-configuration.html\">Code is more expressive than configuration</a></li>\n<li><a href=\"/posts/2026/03-20-code-is-more-concise-than-configuration.html\">Code is more concise than configuration</a></li>\n</ol>\n",
      "date_published": "2026-03-18T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/03-18-code-is-more-coherent-than-configuration.html",
      "url": "https://jan.miksovsky.com/posts/2026/03-18-code-is-more-coherent-than-configuration.html",
      "title": "Code is more coherent than configuration: comparing a sample blog in Web Origami and Eleventy"
    },
    {
      "content_html": "<p>This post series is for people who want to build or rebuild a site.</p>\n<p>You may have heard of <a href=\"https://www.11ty.dev\">Eleventy</a>, a popular static site generator, and maybe heard it&#8217;s simple to use. To evaluate that simplicity, I&#8217;ll compare a sample blog in Eleventy to the same blog in <a href=\"https://weborigami.org\">Web Origami</a>. This will be similar to my comparison last year of <a href=\"https://jan.miksovsky.com/posts/2025/04-14-astro\">Astro and Origami</a>.</p>\n<p>If you&#8217;re shopping for a site-building tool, I hope this series can help inform your decision. If you already use Eleventy, I’m happy you’ve found something that works for you. As I said last year, anything that makes people more likely to create a site is <em>fantastic</em>.</p>\n<h2 id=\"a-difference-in-strategy\">A difference in strategy</h2>\n<p>Eleventy works like most static site generators: you run the tool, it searches inside your project for certain folders and files, then processes them to create an output directory with your site’s HTML pages and other resources. You influence this process through <em>configuration</em>, setting various parameters to adjust what Eleventy does. You generally set those parameters through JavaScript files, although the emphasis in those files is on defining parameterized objects or enabling plugins.</p>\n<p>In Web Origami you focus on defining the site you want with <em>code</em>. You do this in standard JavaScript or the smaller <a href=\"https://weborigami.org/language/expressions\">Origami dialect of JavaScript</a>, which is essentially JavaScript expressions with embedded file paths. The code does whatever you tell it to do. In this case, it defines a blog site&#8217;s tree of resources, transforming the markdown posts into browsable HTML and a feed.</p>\n<p>This difference between configuration and coding is similar to the difference between working with numbers in Intuit QuickBooks and Microsoft Excel. The former is configured; the latter lets you calculate whatever you want.</p>\n<p>Configuration is generally sold as simpler than coding, and most people intuitively feel that should be the case. But I believe that, for making sites, coding is superior in four specific ways:</p>\n<ol>\n<li>Code is easier to follow than configuration.</li>\n<li>Code is more coherent than configuration.</li>\n<li>Code is more expressive than configuration.</li>\n<li>Code is more concise than configuration.</li>\n</ol>\n<p>Configuration can certainly let you achieve impressive results in complicated domains that you probably couldn&#8217;t code yourself, but <em>sites just aren&#8217;t that complicated</em>. It&#8217;s actually easier to code your own site from scratch than to create one by configuring a tool.</p>\n<h2 id=\"experiment-setup\">Experiment setup</h2>\n<p>I copied Eleventy&#8217;s recommended starting point for new blogs, the <a href=\"https://github.com/11ty/eleventy-base-blog\"><code>eleventy-base-blog</code></a> template project, studied that until I felt I understood its construction, then ported it to Web Origami. This gave me two versions of the same blog:</p>\n<ul>\n<li><strong>Eleventy version:</strong> <a href=\"https://github.com/JanMiksovsky/eleventy-base-blog\">Source code</a> and <a href=\"https://original-eleventy-blog.netlify.app\">Demo</a></li>\n<li><strong>Origami version:</strong> <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog\">Source code</a> and <a href=\"https://origami-eleventy-blog.netlify.app\">Demo</a></li>\n</ul>\n<p>Both demos are about as close as I can easily make them. For a cleaner comparison, I made a few modifications to the original Eleventy project:</p>\n<ol>\n<li>The original project had an introductory message with instructions to remove it, so I removed it.</li>\n<li>The original used a plugin for image optimization, but reproducing the effects of that would complicate this analysis, so I removed it.</li>\n<li>I removed the original&#8217;s XSLT stylesheet for the blog feed, as XSLT is being deprecated by Chrome; WebKit and Gecko will likely follow. (I&#8217;m not saying the deprecation is warranted, but given the state of things I felt the stylesheet was a distraction.)</li>\n</ol>\n<p>Beyond that I tried to port all observable behavior; some minor differences remain. For example, the Eleventy project uses PrismJS for syntax highlighting while the Origami solution uses the slightly different HighlightJS. With more work, the sites could be made even more identical, but I don&#8217;t think that would change the overall results of this experiment.</p>\n<h2 id=\"code-is-easier-to-follow-than-configuration\">Code is easier to follow than configuration</h2>\n<p>With two versions of the same project in hand, let&#8217;s start evaluating them by considering which version is easier to follow. If you&#8217;re coming fresh to the project, can you answer the question: <em>How does it work?</em></p>\n<p>That can be a hard question, so let&#8217;s start with a simpler one: <em>What is calling what?</em></p>\n<p>For template projects like <code>eleventy-base-blog</code>, the README typically instructs you to build the site with a command like <code>npm run build</code>. That&#8217;s the main entry point to the build process. I tried to search forward from there and follow links to related files.</p>\n<p>I got stuck.</p>\n<ul>\n<li>The build command for <code>eleventy-base-blog</code> is <code>npx @11ty/eleventy</code>. That invokes Eleventy, but beyond that point it&#8217;s not obvious how Eleventy does what it does.</li>\n<li>A promising <code>eleventy.config.js</code> file makes specific references to a folder called <code>content</code> and a file called <code>filters.js</code>, but there I hit a dead end.</li>\n<li>I ended up searching through the entire project looking for source files, then looking in those for references to other source files.</li>\n</ul>\n<p>I constructed a partial map of what calls what:</p>\n<p><img src=\"/images/2026/03/eleventy.svg\" alt=\"\"></p>\n<p>The files floating in space aren’t directly referenced by any other files. Some of the file names suggest what roles those files play, but it was still mysterious to me how they actually played those roles.</p>\n<p>I eventually found an Eleventy documentation page called <a href=\"https://www.11ty.dev/docs/advanced-order/\">Order of Operations</a> providing an &#8220;advanced&#8221; description of most (but not all) of what was going on. I then had Claude Code guess/explain how the remaining files worked. This clarified that, e.g., Eleventy lets you register JavaScript functions as &#8220;filters&#8221; you can call from templates. I hadn&#8217;t been able to work out for myself that many of the <code>.njk</code> files were invoking code in <code>filters.js</code>.</p>\n<p>I was then able to flesh out the above dependency diagram, adding what I understand to be the implicit connections as dashed lines:</p>\n<p><img src=\"/images/2026/03/eleventy2.svg\" alt=\"\"></p>\n<p>Many of the connections in this project are dashed lines representing “action at a distance” — if you don&#8217;t already know how the system works, such connections are hard to discover or intuit. This may be acceptable for something you will use all the time, but it certainly does make learning the system (or coming back to it) more difficult.</p>\n<p>Let&#8217;s now try to answer the &#8220;What is calling what?&#8221; question for the Web Origami blog, again starting from the <code>build</code> command:</p>\n<pre><code>ori copy src/site.ori, clear files:build\n</code></pre><p>Even if the <a href=\"https://weborigami.org/cli/incantations#building-a-site-as-static-files\">meaning of that command</a> is unclear, you can still see an explicit reference to the file <a href=\"https://github.com/JanMiksovsky/origami-eleventy-blog/blob/main/src/site.ori\"><code>site.ori</code></a>. If you open that file, you’ll see it contains references to all the files it calls.</p>\n<p>You can repeat that process, following links from one file to another, to recover the <em>entire</em> graph of source file calls:</p>\n<p><img src=\"/images/2026/03/origami.svg\" alt=\"\"></p>\n<p>The Origami project has no hidden associations, so all the lines are solid. Everything happens because an explicit line of code makes it happen.</p>\n<p>This property of an Origami project makes it much easier to follow what the project does. When I read someone else’s Origami project, it doesn’t matter how they’ve written it. I can <em>always</em> start at the <code>build</code> command and work forward to find all the code. The project&#8217;s author also benefits from this same guarantee when they read their own project after some time away from it.</p>\n<p>Other posts in this series:</p>\n<ol>\n<li>Code is easier to follow than configuration [this post]</li>\n<li><a href=\"/posts/2026/03-18-code-is-more-coherent-than-configuration.html\">Code is more coherent than configuration</a></li>\n<li><a href=\"/posts/2026/03-19-code-is-more-expressive-than-configuration.html\">Code is more expressive than configuration</a></li>\n<li><a href=\"/posts/2026/03-20-code-is-more-concise-than-configuration.html\">Code is more concise than configuration</a></li>\n</ol>\n",
      "date_published": "2026-03-17T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/03-17-code-is-easier-to-follow-than-configuration.html",
      "url": "https://jan.miksovsky.com/posts/2026/03-17-code-is-easier-to-follow-than-configuration.html",
      "title": "Code is easier to follow than configuration: comparing a sample blog in Web Origami and Eleventy"
    },
    {
      "content_html": "<p>I’m interested in helping to create a shared JavaScript library for letting Electron apps authenticate with Netlify (and potentially GitHub/GitLab pages) via OAuth for the purpose of creating new projects and uploading files to existing projects.</p>\n<p>The users in the web publishing ecosystem are benefiting from Electron becoming a de facto standard for user-facing tools. As Niki <a href=\"https://tonsky.me/blog/fall-of-native/\">relates</a>, Electron is winning because &#8220;native has nothing to offer&#8221;. Many developers are voting for Electron these days; I&#8217;m one of them.</p>\n<p>One task I want my Electron app to perform for end users is helping them select or create a site on a static site host and later deploy locally-built files to that site. Netlify is an attractive target because it supports OAuth; GitHub Pages and GitLab Pages are others.</p>\n<p>As it stands today, many tools that want to perform this task on the user&#8217;s behalf msut guide the user through creating an account with a host, obtaining a developer credential such as an access token, then copying the token and various other details into the tool. This is complex enough for a developer — and <em>ridiculously</em> complex for a non-developer. I think <a href=\"https://getpublii.com/docs/build-a-static-website-with-netlify.html\">Publii&#8217;s walkthrough of this process</a> is as clear as possible and it&#8217;s probably still daunting to many people that might want to create a site.</p>\n<p>Netlify offers developers the possibility of an OAuth-based UI flow, but that&#8217;s a non-trivial thing to create from scratch:</p>\n<ul>\n<li>Create a window or dialog in the Electron renderer</li>\n<li>Define an HTML-based wizard that walks the user through the process, asking the absolute minimum number of questions</li>\n<li>Negotiate the OAuth exchange (as I understand it, this requires a server)</li>\n<li>Figure out what files need to be uploaded for a Manual Deploy, or bundle files for a ZIP deploy</li>\n<li>Upload the files</li>\n<li>Display progress feedback to the user</li>\n<li>Communicate success and errors in a meaningful way</li>\n</ul>\n<p>Even with the help of AI, creating and maintaining this would be some work. But much of this work would be generic — so it could be implemented in a library shared by multiple tools.</p>\n<p>A conceptual, back-of-the-envelope API sketch for a hypothetical <code>NetlifyPublish</code> library:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-comment\">// Select the site or create a new one</span>\n<span class=\"hljs-keyword\">const</span> siteDetails = <span class=\"hljs-keyword\">await</span> <span class=\"hljs-title class_\">NetlifyPublish</span>.<span class=\"hljs-title function_\">selectSite</span>({ <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">&quot;My blog&quot;</span> });\n\n<span class=\"hljs-keyword\">if</span> (siteDetails) {\n  <span class=\"hljs-comment\">// Successful, deploy build to site</span>\n  <span class=\"hljs-keyword\">const</span> files = <span class=\"hljs-keyword\">await</span> <span class=\"hljs-title function_\">doTheBuild</span>(); <span class=\"hljs-comment\">// However the tool wants to do that</span>\n  <span class=\"hljs-keyword\">const</span> success = <span class=\"hljs-keyword\">await</span> <span class=\"hljs-title class_\">NetlifyPublish</span>.<span class=\"hljs-title function_\">deploySite</span>(siteDetails, files);\n}\n</code></pre><p>There are many details to hammer out — how is work split across the main Electron process and the renderer? To what extent can the server component be generalized and shared as a community service? On the renderer side, how is the UI made modal: a <code>&lt;dialog&gt;</code> or a separate <code>BrowserWindow</code>? How are the built files represented? How is the UI themed? etc.</p>\n<p>If you work on a tool that would might benefit from such a shared library, or are interested in participating in its design and/or implementation, please get in touch!</p>\n",
      "date_published": "2026-03-05T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/03-05-netlify-dialog-library.html",
      "url": "https://jan.miksovsky.com/posts/2026/03-05-netlify-dialog-library.html",
      "title": "Who else would use a shared Electron library to create and deploy Netlify sites?"
    },
    {
      "content_html": "<p>To increase awareness of cool features in <a href=\"https://weborigami.org\">Web Origami</a>, I kicked off a weekly comic series with a <a href=\"https://fosstodon.org/@JanMiksovsky/115843624038177218\">Mastodon post</a>:</p>\n<a href=\"https://fosstodon.org/@JanMiksovsky/115843624038177218\">\n  <img src=\"/images/2026/01/comicPost.png\" alt=\"4 comic panels\" class=\"screenshot\">\n</a>\n\n<p>Each 4-panel comic will deliver a short, standalone story. I thought a 4-panel comic would be a perfect format for Twitter-like sites that allow 4 images per post. The images should add visual interest to a user&#8217;s feed, and the user can read the comic right there. Each comic will also be <a href=\"https://weborigami.org/comics/inline-paths\">available as regular HTML</a> on the Origami site.</p>\n<p>It takes a couple of hours to rough out a little story, come up with code examples, ensure they work, and revise as necessary. I write the <a href=\"https://github.com/WebOrigami/docs/blob/main/comics/001%20Inline%20paths/comic.yaml\">comic script in YAML</a>, indicating who is talking, what they&#8217;re saying, and what should appear in the panel.</p>\n<p>The rest of the process is automated:</p>\n<ol>\n<li>It&#8217;s easy to compile that YAML script to HTML using Origami itself.</li>\n<li>Origami can easily runs the code samples and inlines the results directly into the comic so that the terminal session and browser panels are 100% accurate.</li>\n<li>To confirm code continues to work as Origami evolves, I use Origami&#8217;s <a href=\"https://weborigami.org/builtins/dev/changes.html#using-changes-for-testing-static-sites\"><code>Dev.changes</code></a> builtin to test the site and flag any changes in code output.</li>\n<li>I capture HTML comic panels as PNG images using Origami&#8217;s <a href=\"https://github.com/WebOrigami/extensions/tree/main/screenshot\">screenshot extension</a>.</li>\n<li>I use Origami and JavaScript to upload the images to Mastodon and make a post using additional information from the YAML script.</li>\n</ol>\n",
      "date_published": "2026-01-08T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2026/01-08-comics.html",
      "url": "https://jan.miksovsky.com/posts/2026/01-08-comics.html",
      "title": "Promoting a design and development tool through comics"
    },
    {
      "content_html": "<h2 id=\"goals-for-2025\">Goals for 2025</h2>\n<p>My top-line goals for 2025 were:</p>\n<p>😐 <strong>Nurture a small, healthy, respectful user community.</strong> I&#8217;m happy that the small circle of existing Origami users seem happy with the system and continue using it. A number of new people tried Origami as well, although most of them fell into the &#8220;lurker&#8221; category; I have no idea whether they are still using it or, if not, why it didn&#8217;t meet their needs. So pragmatically speaking, I didn&#8217;t do as well on this goal as I&#8217;d hoped. One new Origami site, the <a href=\"https://wesl-lang.dev/\">documentation for the open-source WESL project</a>, was interesting to me because it uses a GitHub wiki as the content management system.</p>\n<p>😃 <strong>Make it easier for users to make sites they’re happy with</strong> through docs, bug fixes, and features. Most of the work I did this year was driven by user feedback. Some existing users moved additional projects of theirs from other systems to Origami, a good indication of user satisfaction.</p>\n<p>The big feature investments I aimed to make in 2025 were:</p>\n<p>😃 <strong>Language Server Protocol implementation</strong>. I wanted to implement an <a href=\"https://en.wikipedia.org/wiki/Language_Server_Protocol\">LSP</a> server so that development tools like Microsoft VS Code (and others) could provide code completion and inline error feedback. An LSP is a complex beast, and tackling this goal was a significant undertaking. The LSP is now part of the <a href=\"https://github.com/WebOrigami/origami-vscode-extension\">Origami VS Code extension</a> and <a href=\"https://marketplace.visualstudio.com/items?itemName=WebOrigami.origami-vscode-extension\">published in the VS Code marketplace</a>.</p>\n<p>😃 <strong>Screencast engine.</strong> I wanted a way of introducing new users to Origami through a media somewhere between a video and traditional documentation. A video is insanely time-consuming to produce and can&#8217;t be kept up to date; traditional docs are easier to maintain but less interesting to newcomers. I developed a <a href=\"https://jan.miksovsky.com/posts/2025/01-22-motion-comic-origami-introduction\">system for creating comics</a> and published an initial <a href=\"https://origami-comics.netlify.app/\">introductory comic</a>. I&#8217;ve always loved comics and am delighted with how this turned out. I have more plans for the comics system in 2026.</p>\n<p>😐 <strong>Feature complete for JavaScript expressions.</strong> I mostly accomplished what I wanted, which was feature parity with JavaScript expressions, but during the year I moved the goal posts. I ended up investing far more in this area than I&#8217;d expected; see below. There are still a few remaining <a href=\"https://weborigami.org/language/expressions#unsupported-javascript-features\">unsupported JavaScript features in Origami</a> that I&#8217;m looking forward to implementing.</p>\n<p>☹️ <strong>Inspector/debugger.</strong> My plan was to create a browser-hosted tool that lets a user interactively inspect the call tree of an Origami file so they can more easily diagnose errors and better understand how their code works. I spent a month on this, but it proved to be a bigger job than I&#8217;d expected and I ultimately set it aside to pursue other priorities. I plan on trying this again in 2026.</p>\n<h2 id=\"javascript-with-paths\">JavaScript with paths</h2>\n<p>The Origami language has evolved in stages, growing closer and closer to JavaScript, but at the beginning of 2025 there were still distinct differences.</p>\n<p>Chief among them was that Origami required the <code>/</code> slash operator as a way of extracting a property from an object. Instead of writing <code>post.title</code> like in JavaScript, you had to write <code>post/title</code>. Origami used a slash to preserve the use of the <code>.</code> period as a valid character in file names like <code>data.json</code>.</p>\n<p>Feedback from Origami users indicated that this difference in syntax presented a persistent stumbling block, and I eventually developed a <a href=\"https://weborigami.org/language/expressions#file-name-heuristic\">file name heuristic</a> that lets the Origami parser determine whether a <code>.</code> period represents part of a file name or a property access. The parser can handle expressions like <code>data.yaml[0].name</code> and generally do what users expect.</p>\n<p>This change was a small but important step forward towards turning Origami into a dialect of JavaScript: &#8220;JavaScript with paths&#8221;. To that end, other important changes included:</p>\n<ul>\n<li>Redefining all builtins as global objects, just like JavaScript, so the old syntax <code>tree:map</code> became the JS-style call <code>Tree.map()</code>.</li>\n<li>Supporting JavaScript keywords like <code>new</code>. Old syntax: <code>new:@js/Date(&quot;2025-12-31&quot;)</code> is now <code>new Date(&quot;2025-12-31&quot;)</code>, just like JavaScript.</li>\n</ul>\n<h2 id=\"map-based-trees\">Map-based trees</h2>\n<p>Another significant foundational change this year was in the way Origami represents hierarchical tree-like structures. The higher levels of the Web Origami project rest on a foundational notion of a tree of nodes that may require asynchronous calls to traverse. For the past few years, these trees have been defined by a small proprietary interface, essentially a stripped-down version of the standard JavaScript <code>Map</code> class. Origami didn&#8217;t use the <code>Map</code> class itself because it was some quirks.</p>\n<p>However, experiments suggested ways to work around those quirks, so I rewrote the foundations of Origami to use <code>Map</code> <a href=\"https://weborigami.org/async-tree/interface\">as a general interface</a>.</p>\n<p>Now most of the tree structures you work with in Origami, including those based on in-memory objects, the file system, and local data like JSON/YAML files, are represented with standard <code>Map</code> instances. Because <code>Map</code> doesn&#8217;t support async calls, Origami uses an async variation called <code>AsyncMap</code> to represent network resources.</p>\n<p>These architectural changes also necessitated extensive revision of Origami&#8217;s documentation, including a <a href=\"https://weborigami.org/pattern/\">completely rewritten pattern walkthrough</a> discussing the use of <code>Map</code>-based trees to represent a wide variety of data sources and operations.</p>\n<h2 id=\"other-additions\">Other additions</h2>\n<p>Along the way there were many other additions to Origami:</p>\n<ul>\n<li><a href=\"https://weborigami.org/builtins/tree/filter\"><code>Tree.filter</code></a> builtin for filtering a tree based on a condition</li>\n<li><a href=\"https://weborigami.org/builtins/tree/mask\"><code>Tree.mask</code></a> builtin for filtering a tree based on conditions defined in a second tree</li>\n<li><a href=\"https://weborigami.org/builtins/tree/globkeys\"><code>Tree.globKeys</code></a> builtin to support filtering based on Unix-style <a href=\"https://en.wikipedia.org/wiki/Glob_(programming)\">glob</a> patterns like <code>*.jpeg</code></li>\n<li><a href=\"https://weborigami.org/builtins/tree/regexpkeys\"><code>Tree.regExpKeys</code></a> builtin to support filtering based on JavaScript regular expressions</li>\n<li><a href=\"https://weborigami.org/builtins/origami/mdoutline\"><code>Origami.mdOutline</code></a> builtin to extract the tree structure of markdown content, e.g., to generate navigation elements</li>\n<li><a href=\"https://weborigami.org/language/expressions#non-enumerable-properties\">non-enumerable property</a> syntax</li>\n<li>More forms of <a href=\"https://weborigami.org/language/templatedocuments\">Origami template documents</a></li>\n<li><a href=\"https://weborigami.org/language/filetypes#comma-separated-values-files\">CSV</a> file support for comma-separated values</li>\n<li><a href=\"https://weborigami.org/language/filetypes#tab-separated-values-files\">TSV</a> file support for tab-separated values</li>\n<li><a href=\"https://weborigami.org/language/filetypes#shell-script-files\">Shell script</a> file support for calling shell scripts</li>\n<li>Substantially improved web crawler for <a href=\"https://weborigami.org/builtins/dev/audit\"><code>Dev.audit</code></a> and <a href=\"https://weborigami.org/builtins/dev/crawl\"><code>Dev.crawl</code></a></li>\n<li><a href=\"https://weborigami.org/language/expressions#angle-brackets\">Angle bracket paths</a> like <code>&lt;path/to/file.txt&gt;</code> for unambiguous file references</li>\n<li>JavaScript expression operators: <code>await</code>, <code>in</code>, <code>instanceof</code>, <code>new</code>, <code>typeof</code>, <code>void</code></li>\n<li><a href=\"https://github.com/WebOrigami/extensions/tree/main/esbuild\">esbuild extension</a> for bundling resources</li>\n<li><a href=\"https://github.com/WebOrigami/extensions/tree/main/json-schema\">json-schema extension</a> for data validation</li>\n</ul>\n",
      "date_published": "2025-12-22T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2025/12-22-web-origami-year-end-report.html",
      "url": "https://jan.miksovsky.com/posts/2025/12-22-web-origami-year-end-report.html",
      "title": "2025 Web Origami year-end report"
    },
    {
      "content_html": "<p>JavaScript has a <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map\"><code>Map</code></a> class for holding key/value pairs, but it’s underused and underappreciated. If you fix the class’ limitations, you can use <code>Map</code> as a building block to create really interesting things.</p>\n<p><img src=\"/images/2025/11/blocks.jpg\" alt=\"A pattern of yellow blocks of color with black edges\">\n<em>Photo: Cun Mo, Unsplash</em></p>\n<h2 id=\"a-quick-review-of-map\">A quick review of <code>Map</code></h2>\n<p>The <code>Map</code> class lets you associate keys with values:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">const</span> m = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">Map</span>();\n\nm.<span class=\"hljs-title function_\">add</span>(<span class=\"hljs-string\">&quot;a&quot;</span>, <span class=\"hljs-number\">1</span>);\nm.<span class=\"hljs-title function_\">add</span>(<span class=\"hljs-string\">&quot;b&quot;</span>, <span class=\"hljs-number\">2</span>);\n\nm.<span class=\"hljs-title function_\">get</span>(<span class=\"hljs-string\">&quot;a&quot;</span>); <span class=\"hljs-comment\">// 1</span>\nm.<span class=\"hljs-title function_\">get</span>(<span class=\"hljs-string\">&quot;b&quot;</span>); <span class=\"hljs-comment\">// 2</span>\nm.<span class=\"hljs-title function_\">get</span>(<span class=\"hljs-string\">&quot;c&quot;</span>); <span class=\"hljs-comment\">// undefined</span>\n\nm.<span class=\"hljs-title function_\">keys</span>(); <span class=\"hljs-comment\">// &quot;a&quot;, &quot;b&quot;</span>\nm.<span class=\"hljs-title function_\">values</span>(); <span class=\"hljs-comment\">// 1, 2</span>\n</code></pre><p>The <code>Map</code> class has a number of <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#objects_vs._maps\">advantages over plain objects</a> for storing data, but JavaScript syntax makes it easier to create and work with objects so <code>Map</code> doesn’t get used as often as it should.</p>\n<h2 id=\"extending-map\">Extending <code>Map</code></h2>\n<p><code>Map</code> also has one key advantage over a plain object: <code>Map</code> is a class you can extend, so you can expose any key/value data store as a <code>Map</code>.</p>\n<p>For example, we can write a <code>FileMap</code> class that makes the contents of a file system folder available as a <code>Map</code>. This ignores the map’s built-in storage, and instead gets the keys and values from the file system:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">import</span> * <span class=\"hljs-keyword\">as</span> fs <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">&quot;node:fs&quot;</span>;\n<span class=\"hljs-keyword\">import</span> path <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">&quot;node:path&quot;</span>;\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-keyword\">class</span> <span class=\"hljs-title class_\">FileMap</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title class_ inherited__\">Map</span> {\n  <span class=\"hljs-title function_\">constructor</span>(<span class=\"hljs-params\">dirname</span>) {\n    <span class=\"hljs-variable language_\">super</span>();\n    <span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-property\">dirname</span> = path.<span class=\"hljs-title function_\">resolve</span>(process.<span class=\"hljs-title function_\">cwd</span>(), dirname);\n  }\n\n  <span class=\"hljs-title function_\">get</span>(<span class=\"hljs-params\">key</span>) {\n    <span class=\"hljs-keyword\">const</span> filePath = path.<span class=\"hljs-title function_\">resolve</span>(<span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-property\">dirname</span>, key);\n    <span class=\"hljs-keyword\">let</span> stats;\n    <span class=\"hljs-keyword\">try</span> {\n      stats = fs.<span class=\"hljs-title function_\">statSync</span>(filePath);\n    } <span class=\"hljs-keyword\">catch</span> (error) {\n      <span class=\"hljs-keyword\">if</span> (error.<span class=\"hljs-property\">code</span> === <span class=\"hljs-string\">&quot;ENOENT&quot;</span>) {\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">undefined</span>; <span class=\"hljs-comment\">// File not found</span>\n      }\n      <span class=\"hljs-keyword\">throw</span> error;\n    }\n\n    <span class=\"hljs-keyword\">return</span> stats.<span class=\"hljs-title function_\">isDirectory</span>()\n      ? <span class=\"hljs-keyword\">new</span> <span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-title function_\">constructor</span>(<span class=\"hljs-params\">filePath</span>) <span class=\"hljs-comment\">// Return subdirectory as a map</span>\n      : fs.<span class=\"hljs-title function_\">readFileSync</span>(filePath); <span class=\"hljs-comment\">// Return file contents</span>\n  }\n\n  *<span class=\"hljs-title function_\">keys</span>(<span class=\"hljs-params\"></span>) {\n    <span class=\"hljs-keyword\">try</span> {\n      <span class=\"hljs-keyword\">yield</span>* fs.<span class=\"hljs-title function_\">readdirSync</span>(<span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-property\">dirname</span>);\n    } <span class=\"hljs-keyword\">catch</span> (error) {\n      <span class=\"hljs-keyword\">if</span> (error.<span class=\"hljs-property\">code</span> === <span class=\"hljs-string\">&quot;ENOENT&quot;</span>) {\n        <span class=\"hljs-comment\">// Directory doesn&#x27;t exist yet; treat as empty</span>\n      } <span class=\"hljs-keyword\">else</span> {\n        <span class=\"hljs-keyword\">throw</span> error;\n      }\n    }\n  }\n}\n</code></pre><p>This is pretty amazing! You can easily work with files in code.</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">const</span> markdown = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">FileMap</span>(<span class=\"hljs-string\">&quot;./markdown&quot;</span>);\n\nmarkdown.<span class=\"hljs-title function_\">keys</span>(); <span class=\"hljs-comment\">// [&quot;post1.md&quot;, &quot;post2.md&quot;, …]</span>\nmarkdown.<span class=\"hljs-title function_\">get</span>(<span class=\"hljs-string\">&quot;post1.md&quot;</span>); <span class=\"hljs-comment\">// &quot;This is **post 1**.&quot;</span>\n</code></pre><p>Most of the time you work with files in code, you’re just reading a list of files and getting their contents, so something like this is much easier to work with than the full file system API.</p>\n<p>Better yet, you can immediately pass this kind of file system <code>Map</code> to any code that understands a <code>Map</code>. That provides a desired separation between data storage and code that works on data.</p>\n<h2 id=\"limitations-to-work-around\">Limitations to work around</h2>\n<p>Sadly, the <code>Map</code> class is cumbersome to extend: its standard methods like <code>clear()</code>, <code>entries()</code>, and <code>values()</code> will ignore your <code>get()</code> and <code>keys()</code> methods.</p>\n<pre><code class=\"hljs language-js\">markdown.<span class=\"hljs-title function_\">entries</span>(); <span class=\"hljs-comment\">// empty array ☹️</span>\n</code></pre><p>For comparison, the Python language provides a <a href=\"https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes\"><code>Mapping</code> abstract base class</a> that is much more helpful than JavaScript&#8217;s <code>Map</code>. When you inherit from <code>Mapping</code>, you only need to define a small set of core methods, and the base class uses your definitions to provide the remaining methods.</p>\n<p>To compensate for this and <a href=\"https://weborigami.org/async-tree/syncmap#problems-extending-the-standard-map-class\">other issues with <code>Map</code></a>, we can create our own base class that inherits from <code>Map</code> but provides all the expected methods:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-keyword\">class</span> <span class=\"hljs-title class_\">BetterMapBase</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title class_ inherited__\">Map</span> {\n  <span class=\"hljs-comment\">// Override entries() method to call overridden get() and keys()</span>\n  *<span class=\"hljs-title function_\">entries</span>(<span class=\"hljs-params\"></span>) {\n    <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">const</span> key <span class=\"hljs-keyword\">of</span> <span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-title function_\">keys</span>()) {\n      <span class=\"hljs-keyword\">const</span> value = <span class=\"hljs-variable language_\">this</span>.<span class=\"hljs-title function_\">get</span>(key);\n      <span class=\"hljs-keyword\">yield</span> [key, value];\n    }\n  }\n  …\n}\n</code></pre><p>If we derive our <code>FileMap</code> class from this improved base class, the full set of <code>Map</code> methods work as expected:</p>\n<pre><code class=\"hljs language-js\">markdown.<span class=\"hljs-title function_\">entries</span>(); <span class=\"hljs-comment\">// [[&quot;post1.md&quot;, &lt;contents&gt;], …] 🤩</span>\n</code></pre><h2 id=\"transforming-a-map\">Transforming a map</h2>\n<p>Once your data is in <code>Map</code> form, you can manipulate it in interesting ways without having to worry about how the original data is defined. For example, you can <a href=\"https://weborigami.org/pattern/transform\">transform a map of markdown to a map of HTML</a>.</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">const</span> html = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">HtmlMap</span>(markdown);\n\nhtml.<span class=\"hljs-title function_\">keys</span>(); <span class=\"hljs-comment\">// [&quot;post1.html&quot;, &quot;post2.html&quot;, …]</span>\nhtml.<span class=\"hljs-title function_\">get</span>(<span class=\"hljs-string\">&quot;post1.html&quot;</span>); <span class=\"hljs-comment\">// &quot;&lt;p&gt;This is &lt;strong&gt;post 1&lt;/strong&gt;.&lt;/p&gt;\\n&quot;</span>\n</code></pre><p>Transforming the values of data often implies a transformation of the keys, so maps are ideal representations of such operations.</p>\n<h2 id=\"creating-a-build-system-with-maps\">Creating a build system with maps</h2>\n<p>This <code>Map</code> approach is a pattern you can <a href=\"https://weborigami.org/pattern\">explore in more detail</a>. Because it’s a pattern, you can use it without taking on any new dependencies.</p>\n<p>But you can take advantage of the <a href=\"https://weborigami.org\">Web Origami</a> project’s <a href=\"https://weborigami.org/async-tree\"><code>async-tree</code></a> library, which includes:</p>\n<ul>\n<li>A better <code>Map</code> base class along these lines, called <a href=\"https://weborigami.org/async-tree/SyncMap.html\"><code>SyncMap</code></a></li>\n<li>An asynchronous variant called <a href=\"https://weborigami.org/async-tree/AsyncMap.html\"><code>AsyncMap</code></a> that can represent network data sources</li>\n<li>A more complete <a href=\"https://weborigami.org/async-tree/FileMap.html\"><code>FileMap</code></a> class that supports write operations</li>\n<li>A large <a href=\"https://weborigami.org/builtins/tree/\">collection of map-based operations</a> for concisely expressing transformations like the one shown above</li>\n</ul>\n<p>You can use this library to construct things like a build system for generating the static files for a site. A <a href=\"https://github.com/WebOrigami/pondlife-async-tree\">sample blog project</a> uses the library to represent the various stages of the build process as <code>Map</code>-based trees:</p>\n<figure style=\"text-align: center;\">\n  1. Content tree  →  2. Site tree  →  3. Build tree\n</figure>\n\n<ol>\n<li>The source content in the file system is represented as a tree of <code>Map</code> objects.</li>\n<li>These are transformed with map-based operations and composed into the desired tree of site resources.</li>\n<li>The site tree is copied directly into a <code>Map</code>-based representation of the folder that will hold the build output.</li>\n</ol>\n<p>The entire <a href=\"https://github.com/WebOrigami/pondlife-async-tree/blob/main/src/build.js\"><code>build.js</code></a> build process is very concise and boils down to a copy operation:</p>\n<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">import</span> { <span class=\"hljs-title class_\">FileMap</span>, <span class=\"hljs-title class_\">Tree</span> } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">&quot;@weborigami/async-tree&quot;</span>;\n<span class=\"hljs-keyword\">import</span> site <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">&quot;./site.js&quot;</span>;\n\n<span class=\"hljs-comment\">// Build process writes the site resources to the build folder</span>\n<span class=\"hljs-keyword\">const</span> buildTree = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-title class_\">FileMap</span>(<span class=\"hljs-keyword\">new</span> <span class=\"hljs-title function_\">URL</span>(<span class=\"hljs-string\">&quot;../build&quot;</span>, <span class=\"hljs-keyword\">import</span>.<span class=\"hljs-property\">meta</span>.<span class=\"hljs-property\">url</span>).<span class=\"hljs-property\">pathname</span>);\n<span class=\"hljs-keyword\">await</span> <span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">clear</span>(buildTree); <span class=\"hljs-comment\">// Erase any existing files</span>\n<span class=\"hljs-keyword\">await</span> <span class=\"hljs-title class_\">Tree</span>.<span class=\"hljs-title function_\">assign</span>(buildTree, site); <span class=\"hljs-comment\">// Copy site to build folder</span>\n</code></pre><p>By using <code>Map</code> as the fundamental building block, this sample project generates the site’s static files using only two dependencies, the <code>async-tree</code> library and a markdown processor.</p>\n<p>The <code>AsyncMap</code> variant lets you represent a network data source as an object that has the same methods as a <code>Map</code> but the methods are asynchronous. This lets you pull content from sources like <a href=\"https://github.com/WebOrigami/extensions/tree/main/dropbox\">Dropbox</a> or <a href=\"https://github.com/WebOrigami/extensions/tree/main/gdrive\">Google Drive</a> using a much simpler API, so you can incorporate content directly from the network into your build process.</p>\n",
      "date_published": "2025-11-19T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2025/11-19-maps.html",
      "url": "https://jan.miksovsky.com/posts/2025/11-19-maps.html",
      "title": "Fixing the under-appreciated JavaScript Map class and using it to construct a build system"
    },
    {
      "content_html": "<p>I’ve extended my series of <a href=\"/posts/2025/05-02-concise-expressions.html\">blog architecture comparison</a> posts from earlier this year by porting the <a href=\"https://github.com/WebOrigami/pondlife\">sample reference blog</a> from <a href=\"https://weborigami.org\">Origami</a> and <a href=\"/posts/2025/04-17-zero-dependencies.html\">plain JavaScript</a> to create a Python version (<a href=\"https://github.com/JanMiksovsky/pondlife-python\">source</a>, <a href=\"https://pondlife-python.netlify.app/\">demo</a>).</p>\n<p>The occasion was reading about improvements in async features in today&#8217;s release of Python 3.14, which prompted me to write some Python code for the first time in a long while.</p>\n<p>The sample blog doesn’t actually need to make async network requests, so it ended up only using sync functions — but all those sync functions are still <em>lazy</em> and do work only when necessary. And with the improvements in Python 3.14, it might be possible to create an <code>AsyncMapping</code> abstract base class as a variation of <code>Mapping</code> to handle the sorts of async network operations that Origami can do, e.g., reading post content directly out of Dropbox or Google Drive.</p>\n<h2 id=\"creating-a-static-site\">Creating a static site</h2>\n<p>Python’s audience has always included people who don’t think of themselves primarily as programmers. That aligns with what I’m trying to do in Origami, so it’s interesting to explore using Python as a possible substrate for Origami ideas.</p>\n<p>I’m not familiar with Python static site generators, but they seem to generally take the same <a href=\"https://weborigami.org/language/model#use-a-website-framework\">framework approach</a> as their Node.js counterparts: impose a particular folder structure, provide a magic transformation of that structure to static files, and offer a degree of customization through configuration.</p>\n<p>Like all the blog implementations in this series, this Python project rejects that approach entirely. Instead, the focus is on creating useful functions and abstractions for defining the site you want to create. It leaves you to put the parts together in a way that makes sense to you. You are always in control and can entirely satisfy your requirements.</p>\n<h2 id=\"lazy-maps\">Lazy maps</h2>\n<p>Like the JavaScript versions of the sample blog, the Python version attempts to use native language constructions whenever possible. It makes heavy use of Python’s <a href=\"https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping\"><code>Mapping</code></a> abstract base class to represent collections which are fundamentally <em>lazy</em>: they do essentially no work when constructed. Only when asked for their keys, or for a particular value for a key, will they do substantive work.</p>\n<p>An example of this is the project’s <a href=\"https://github.com/JanMiksovsky/pondlife-python/blob/main/src/map_origami/folder.py\"><code>Folder</code></a> class, which wraps a file system folder as a lazy <code>Mapping</code> (specifically, a <code>MutableMapping</code>, which can be updated after it’s created).</p>\n<p>Another example of this are the project’s operations that take one <code>Mapping</code> as input and return a new, transformed <code>Mapping</code>. For example, <a href=\"https://github.com/JanMiksovsky/pondlife-python/blob/main/src/map_origami/map_extensions.py\"><code>map_extensions</code></a>, can convert a virtual collection of <code>.md</code> files into a corresponding collection of <code>.html</code> files.</p>\n<pre><code class=\"hljs language-python\">post_html_docs = map_extensions(folder, <span class=\"hljs-string\">&quot;.md-&gt;.html&quot;</span>, md_doc_to_html)\n</code></pre><p>When backed by a <code>Folder</code> containing <code>post.md</code>, the resulting map-of-a-map <em>says</em> that it contains a <code>post.html</code> &#8211; but it hasn’t done the real work for that yet. When you ask for <code>post.html</code>, it will ask the underlying <code>Folder</code> for <code>post.md</code>, translate the markdown content to HTML, then return that result. (The actual data pipeline is slightly more complex; see the <a href=\"https://github.com/JanMiksovsky/pondlife-python/blob/main/README.md\">ReadMe</a>.)</p>\n<p>You can use a debugger to inspect the value of a map like this at runtime with a command like:</p>\n<pre><code class=\"hljs language-python\"><span class=\"hljs-built_in\">list</span>(post_html_docs.items())\n</code></pre><p>This lets you confirm that the map’s keys and values are what you expect.</p>\n<p>The beauty of working at this abstract <code>Mapping</code> level is that your code doesn’t need to care how a particular collection is defined &#8211; your code can handle real files or generated-on-demand files in exactly the same way.</p>\n<h2 id=\"defining-a-site-tree-with-maps\">Defining a site tree with maps</h2>\n<p>At the project’s highest level, <a href=\"https://github.com/JanMiksovsky/pondlife-python/blob/main/src/blog_demo/site_tree.py\"><code>site_tree.py</code></a> defines the root of the site’s tree of resources as a <code>Mapping</code>. Most of those top-level parts of the sites are also <code>Mapping</code> instances: some like <code>assets</code> and <code>images</code> are real folders; others are virtual collections like <code>posts</code>.</p>\n<pre><code class=\"hljs language-python\"><span class=\"hljs-comment\"># from src/blog_demo/site_tree.py</span>\n\n<span class=\"hljs-comment\"># The site tree is a tree with Mappings for interior nodes and the desired</span>\n<span class=\"hljs-comment\"># resources as leaves. The top level of the tree can invoke functions to</span>\n<span class=\"hljs-comment\"># generate the content on demand. Areas which are just Mappings are inherently</span>\n<span class=\"hljs-comment\"># lazy and are defined directly.</span>\nsite_tree = invoke_fns({\n    <span class=\"hljs-string\">&quot;about.html&quot;</span>: <span class=\"hljs-keyword\">lambda</span>: about_html(),\n    <span class=\"hljs-string\">&quot;assets&quot;</span>: Folder(here / <span class=\"hljs-string\">&quot;assets&quot;</span>),\n    <span class=\"hljs-string\">&quot;feed.json&quot;</span>: <span class=\"hljs-keyword\">lambda</span>: json.dumps(feed(), indent=<span class=\"hljs-number\">2</span>),\n    <span class=\"hljs-string\">&quot;feed.xml&quot;</span>: <span class=\"hljs-keyword\">lambda</span>: json_feed_to_rss(feed()),\n    <span class=\"hljs-string\">&quot;images&quot;</span>: Folder(here / <span class=\"hljs-string\">&quot;..&quot;</span> / <span class=\"hljs-string\">&quot;..&quot;</span> / <span class=\"hljs-string\">&quot;images&quot;</span>),\n    <span class=\"hljs-string\">&quot;index.html&quot;</span>: <span class=\"hljs-keyword\">lambda</span>: pages_area()[<span class=\"hljs-string\">&quot;1.html&quot;</span>],  <span class=\"hljs-comment\"># same as pages/1.html</span>\n    <span class=\"hljs-string\">&quot;pages&quot;</span>: <span class=\"hljs-keyword\">lambda</span>: pages_area(),\n    <span class=\"hljs-string\">&quot;posts&quot;</span>: posts_area(),\n})\n</code></pre><p>This programmatic approach lets you dictate precisely how you want to compose the parts of your site; you’re not trapped within the confines of a framework and someone else’s expectations.</p>\n<h2 id=\"building-and-serving\">Building and serving</h2>\n<p>As in Origami, the site’s tree of resources is used in two very different ways.</p>\n<p>First, a small web server that accepts any <code>Mapping</code>-based tree converts URL requests like <code>/assets/styles.css</code> into an array of keys <code>[&quot;assets&quot;, &quot;styles.css&quot;]</code> that are used to traverse the tree. Given the <code>site_tree.py</code> code above, the first <code>”assets”</code> key retrieves the <code>Folder</code> wrapper for the real <code>assets</code> folder; the second key retrieves the <code>styles.css</code> file in that folder. Because the site tries to be as lazy as possible, the site only does the work necessary to fulfill the specific request.</p>\n<p>Second, building the static site files is simply a matter of copying the site’s virtual tree of resources into a real tree in the file system.</p>\n<pre><code class=\"hljs language-python\"><span class=\"hljs-comment\"># from src/blog_demo/__main__.py</span>\n\n<span class=\"hljs-keyword\">def</span> <span class=\"hljs-title function_\">build</span>(<span class=\"hljs-params\">m: Mapping</span>):\n    <span class=\"hljs-string\">&quot;&quot;&quot;\n    Given a mapping representing the site structure, copy the entire tree into\n    the build folder to create static files.\n    &quot;&quot;&quot;</span>\n    build_folder = Folder(<span class=\"hljs-string\">&quot;build&quot;</span>)\n    build_folder.clear()\n    build_folder.update(m)\n</code></pre><p>A <code>MutableMapping</code> is created for the <code>build</code> output folder; it’s mutable so that files can be written into it.</p>\n<p>The site’s tree of resources is copied directly into it using the completely standard <code>update()</code> method to copy one map into another. That boils down the essence of a static site generator to the single line:</p>\n<pre><code class=\"hljs language-python\">build_folder.update(m)\n</code></pre><p>This walks through the source tree <code>m</code>, calling <code>__getitem__</code> to get each resource. That will trigger the generation of said resource. The result will be passed to the build’ folder’s <code>__setitem__</code> method to create the corresponding output file.</p>\n<h2 id=\"assessment\">Assessment</h2>\n<p>Let’s compare this Python blog port with the earlier Origami and JavaScript versions.</p>\n<p>In terms of source code size, the Python version is written as a demo application and a separate library for map manipulations. That makes it roughly comparable to the <a href=\"/posts/2025/04-23-async-tree.html\">blog using Origami’s async-tree library</a>. Accordingly, I’ll factor out the library portion of the code, and the reusable JSON feed-to-RSS translation to measure the non-reusable bytes in the demo application, including both Python (<code>.py</code>) files and Jinja (<code>.j2</code>) templates.</p>\n<p><img src=\"/images/2025/10/pythonSourceCode.png\" alt=\"\"></p>\n<p>The Python version comes in at 10021 bytes, just a bit more than the 9450 bytes for the async-tree version. For this blog application, at least, both Python and JavaScript are comparably expressive. (Origami is still more concise.)</p>\n<p>Python and Node.js are completely different environments, so it’s not possible to make an apples-to-apples comparison of the weight of the project dependencies. And in any case, measuring dependencies by total file size can only give an extremely coarse approximation of potential complexity. Still, I thought it was interesting to measure the total size on disk of the Python project’s <code>site-packages</code> as an analogue for node_modules.</p>\n<p><img src=\"/images/2025/10/pythonDependencies.png\" alt=\"\"></p>\n<p>The Python version weighs more than the <code>async-tree</code> version. It’s less than Origami, but Origami is also doing a lot more. (Astro is still the most complex answer to the problem, and I don’t believe it’s actually doing enough interesting work to justify its size.)</p>\n<p>Finally, let’s look at the time required to build the blog’s static files:</p>\n<p><img src=\"/images/2025/10/pythonBuildTime.png\" alt=\"\"></p>\n<p>Python comes in at 0.24s — a hair faster than the zero-dependency JavaScript version, making it the fastest of all the blog versions I’ve created so far.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>This was a really interesting experiment! It was a ton of fun to write Python code again.</p>\n<p>It seems completely feasible to serve and build a site in Python in a lightweight fashion using a library (under your control) instead of a big framework (that takes control from you). Python seems like a great substrate for Origami ideas. It’s well-designed and widely-used <code>Mapping</code> abstract base class is a natural way to represent your source content as a tree that you can transform into the tree of resources you want for your site.</p>\n",
      "date_published": "2025-10-07T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2025/10-07-python-blog.html",
      "url": "https://jan.miksovsky.com/posts/2025/10-07-python-blog.html",
      "title": "Creating a simple blog in Python with Origami concepts"
    },
    {
      "content_html": "<p>Over this summer I’ve shifted the <a href=\"https://weborigami.org/language\">Origami expression language</a> to be a closer dialect of JavaScript expressions. Feedback from early adopters indicated that some differences from JavaScript created trouble; eliminating those would improve the language.</p>\n<p>Origami is now essentially <a href=\"https://weborigami.org/language/expressions.html\">JavaScript expressions plus paths</a>, with a few additional syntactic features that make it easier to define a site.</p>\n<p><img src=\"/images/2025/09/blogCode.png\" alt=\"\"></p>\n<p>The language supports a conceptual model called <a href=\"https://weborigami.org/language/model\">Content/Transformation</a> you may find helpful in thinking about how to build a site.</p>\n<p>The syntax changes to the language included:</p>\n<ul>\n<li>Supporting standard JavaScript <code>.</code> syntax for accessing a property. This required developing a <a href=\"https://weborigami.org/language/expressions#file-name-heuristic\">heuristic</a> that could reliably tell that <code>post.title</code> is a property reference but <code>image.jpg</code> is a file name — even though both use the same period syntax.</li>\n<li>Consolidating Origami’s <a href=\"https://weborigami.org/builtins\">built-in functions</a> into a small number of collections exposed as regular globals, like <code>Tree</code> for functions that work with trees and <code>Origami</code> for most everything else.</li>\n<li>Supporting JavaScript’s <code>new</code> syntax. I’d tried hard to avoid having reserved words, but <code>new</code> is so ingrained in JavaScript developers’ minds that any other syntax is hard to remember.</li>\n</ul>\n<p>Origami is still missing some esoteric JavaScript expression features (e.g., function parameter destructuring), but those are all on the list tackle. The goal is that you should be able to copy any JavaScript expression and evaluate it as an Origami expression.</p>\n<p>Why introduce an alternative expression language to JavaScript?</p>\n<ul>\n<li>Origami’s use of paths, including support for file extensions and its assumption that any expression might be <code>async</code>, lets you load or fetch files and get data out of them more concisely than with JavaScript. The resulting expression can be 20-40% shorter, making the intent of code easier to see.</li>\n<li>Expressions generally don’t have side effects, so restricting your code to expressions tends to keep you in the happy world of pure functions and immutable data — where things are much easier to reason about.</li>\n<li>With a few <a href=\"https://weborigami.org/cli/shorthand.html\">shorthands</a> to accommodate a command line, Origami makes an excellent shell language, turning your OS terminal into a JavaScript console.</li>\n</ul>\n<p>Watch the <a href=\"https://origami-comics.netlify.app/\">motion comic</a> introducing the language, or walk through <a href=\"https://weborigami.org/language/tutorial.html\">how to create a basic blog</a>.</p>\n",
      "date_published": "2025-09-15T08:00:00.000Z",
      "id": "https://jan.miksovsky.com/posts/2025/09-15-syntax-changes.html",
      "url": "https://jan.miksovsky.com/posts/2025/09-15-syntax-changes.html",
      "title": "Making Origami expressions easier to learn and use"
    }
  ]
}