A Remarkable Discovery
I’ve been silent for a while because of a confluence of events: an onslaught of urgent tasks at work, several important tasks for A Viral World, a lot of time spent getting to know observability, a full evening of working my way through Go By Example, and, least enjoyable of all, the reoccurrence and brief worsening of a chronic health issue which made me quite miserable for a week. But that’s not to say I didn’t find time for a major refactoring.
The abortive attempt to properly harness the power of markdown-it
I’ve written previously about how I built my responsive images solution using PostHTML:
Problems aside, I’m happy to report I can now write this in my entries:
<image-link key="emacs-rust-syntax-highlighting.png" alt="An Emacs buffer showing a properly highlighted Rust file." />
And have it automatically turned into this:
<a class="image-link" href="/assets/images/emacs-rust-syntax-highlighting.png"><img class="image-image" alt="An Emacs buffer showing a properly highlighted Rust file." src="/assets/images/emacs-rust-syntax-highlighting.400w.png" srcset="/assets/images/emacs-rust-syntax-highlighting.400w.png 400w, /assets/images/emacs-rust-syntax-highlighting.800w.png 800w" loading="lazy" sizes="(min-width: 32em) 75vw, (min-width: 64em) 28em, 50vw"></a>
I later extended it to image groups and added sic
and
ellipsis
tags in the same way. It worked, but it was delicate. I had to be very careful not to add
any whitespace in the wrong places, I had to put the image tags in their own paragraph and
surround them with <p></p>
, and if I accidentally used self-closing tags like <sic/>
instead of
<sic></sic>
, it would swallow the rest of that block. What’s more, because PostHTML’s API
isn’t too flexible and doesn’t provide context, every single new tag was another full tree traversal
after the Markdown stage.
I was using PostHTML to generate heading permalinks as well, so I was quite pleased a week and a half ago when someone on the Eleventy Discord linked a post about automatically generating them. I followed the link to Amber Wilson’s post on the subject of accessible anchor links. The two together naturally led to my replacing the PostHTML processing with markdown-it-anchor. I found myself wanting to customize it, so I started writing a rendering function. That was when I remembered I really didn’t want to try to make sense of the markdown-it API.
Rewrite! Rewrite! Rewrite!
In the middle of all this, I found remark. It seemed to offer a polished, composable API that was uniform across several kinds of markup languages. It had a directory of what also looked like polished, composable, and well-maintained plugins. It was supported by Eleventy. Best of all, there was a remark-directive plugin that provided a simple, consistent syntax and API for custom elements!
I removed markdown-it, installed remark and the plugins, and began rewriting my code. I almost gave up a few hours in because Eleventy doesn’t support ES Modules, which are the sole form of packaging for several core remark & rehype modules, but the esm shim module has a major outstanding bug. There’s even a longstanding pull request to fix it. Fortunately, Agoric had published the fixed code in their fork (which I felt more confident using than the branch in the aforementioned PR) and I was able to get everything to work.
remark is fairly simple to use in and of itself. I quickly converted my site. Writing the custom
directives, in contrast, took a while, because the ‘Introduction to syntax trees’ document is ‘not
yet written’.[1] I wasted hours
trying to understand what data I needed to return where. I still don’t know where to use
data.hProperties
, data.hChildren
, etc. and where to use properties
, children
, etc. It looked
like a jumble to me, and all I could do was try first one then the other until everything worked.
Perhaps the rule is that the when you add an HTML element to a tree under a regular element,
that HTML element must have hProperties
&c. but its children needn’t. Then again, even in
those cases I sometimes needed hProperties
and sometimes needed properties
, and I always needed
hName
. There needs to be more guidance.
Nevertheless, as always, an hour or two of trial & error combined with the documentation and a few existing plugins resolved my problems. I was delighted to be able to use this much simpler syntax:
:::imagegroup
::image[Old homepage]{key="20210503/homepage-before.png"}
::image[New homepage]{key="20210503/homepage-after.png"}
:::
Instead of this mess:
<p><image-link key="20210503/homepage-before.png">Old homepage</image-link><image-link
key="20210503/homepage-after.png">New homepage</image-link></p>
I wouldn’t even need the :::imagegroup
directive if there were an equivalent to
markdown-it-attrs, but I don’t mind too much since it
helps to, well, group the images.
The few other customizations I made were much easier. Now I can write this:
> :ellipsis{} and said STahp! :sic{}
::video{controls="controls" loop="loop" src="/assets/media/20210503/orphan-prevention.mp4" type="video/mp4"}
To produce this:
<blockquote>
<p><ins class="editorial">[…]</ins> and said STahp! <ins class="editorial">[sic]</ins></p>
</blockquote>
<video controls="" loop="">
<source src="/assets/media/20210503/orphan-prevention.mp4" type="video/mp4">Sorry, your browser doesn’t support embedded videos.
</video>
(The {}
after the inline directives isn’t required but it helps disambiguate them.)
Nothing is perfect
When I first started using remark, I noticed it slowed my builds down from roughly 50ms per
file to 250ms, or from 8 to 50 seconds overall. Through the process of elimination, I deduced
the culprit was remark-prism. A little
console.time
magic in
node_modules
[2]
showed this was because of re-creating the highlighter for each
call.
I submitted a pull request to allow it to be
reused. This brought the time back down to
normal.
Another minor nuisance is that remark-prism wraps the output in <div class="remark-highlight">
. At
first I thought this might let me put things like the language, a copy button, and other metadata
next to the code, but the div
is just a useless
wrapper,
so I wrote a small rehype-unwrap
plugin to remove it.
I wanted to use remark-gfm for the tables and strikethrough, but it has an issue with blockquotes, so I’m using the micromark strikethrough plugin directly. Considering the remark-gfm author’s explanation, I’m better off not using the full module anyway.
Another problem I faced was that any raw HTML in my Markdown was being removed, so I couldn’t
use things like <kbd>
and <q>
. Since I control the content, I want all the HTML to be
reproduced verbatim. I switched to using
remark-rehype directly with allowDangerousHtml
, but
that wasn’t enough—I also had to tell
rehype-stringify the same thing.
Miscellany
-
I like Proxima Nova better now than when I started using it. I still need to set up a typographic scale.
-
I want to make some major visual changes, which I think I can do once I establish the scale. This hastily-chosen ‘temporary’ background has endured long enough.
-
I may need to fix the
sizes
attributes on floated images. They were incorrect earlier, as it turns out, and I haven’t bothered to check whether what’s there now should be there. -
I want a better code highlighter I can add to, but I haven’t found any that both have the features I want and support the languages I need.
-
I made footnotes more prominent using another custom rehype plugin. They were too unobtrusive.
-
Underlines should look much nicer.
-
The amusing thing about all the refactoring is that I haven’t yet added any plugins to create anchor links around headings, so the thing that led to all of it is the thing that remains undone.Headings are once again automatically turned into anchors. I had to write my own plugin because I want to wrap the headings and add an SVG icon.
After all my adventures in monitoring and Go, I have exciting ideas for implementing auto-archival and sending Webmentions (when I have a few moments).[3] I’ll reuse existing code for the underlying logic; the exciting part is that I’m going to do it in a complex way as an excuse to try new things.
- I see now that there’s a syntax-tree/unist repository with documentation.↩
- Easy temporary modifications to library code are my favourite thing about Node.↩
- Testing suggests Archive.org’s rate limiting will be a major bottleneck for auto-archival.↩
Next in series: The Old Ways Are The Best Ways