About this blog 3
Published
It's been a while since the last entry about how I've built this blog. Since it is constantly evolving, now seems as good a time as ever to write about some of the changes I've made.
Netlify
The biggest shift has been the move from a DigitalOcean droplet, with a linux install managed by me, to Netlify. The service they provides hooks into GitHub, and builds when new things are pushed. It also lets me try new things out on hard-to-guess subdomains by opening pull requests (useful for trying things out which might otherwise be risky, like updated security headers).
Webmentions
I've said before that I'm not a fan of comments. They're complex to manage and open to abuse. However, I do like the IndieWeb convention of using webmentions from site a to site b to notify b that a links to it. I use a Netlify deploy hook to call a glitch. This little service scans the updated blog for new links to other sites, and sends those sites webmentions. This is currently disabled and I'm monitoring the logs to make sure it doesn't do anything unintentional. It'll probably go active this weekend once I've enabled signature checking.
On the receiving side, my blog has a webmention form which Netlify provides a
simple backend to. I can act upon each web mention from there. This form also
allows me to include a manual webmention form at the bottom of each blog post.
Other blogs can discover the form by looking for a link tag with
rel="webmention"
, which is found in the head:
<link href="/webmention" rel="webmention">
Since I don't expect a large volume of mentions, I'll add each to the front matter of the blog post being mentioned. When a post has mentions, a section listing them is added at the end of the post.
Payments
A nice CLI utility called thanks
was recently published. It scans
your project for all the modules it uses and lists, in order of most relied
upon, collectives and authors along with their payment links. At the time of
writing, it contains a little database of module authors and collectives, and
links to ways to pay them.
There's an obvious scaling problem which is addressed in
this issue. The issue suggests a new field in the
package.json
file of a module which thanks can discover, shifting the burden
onto the module authors.
Since I'm keen on IndieWeb stuff, the issue led me to wonder if there is an
IndieWeb rel
attribute for payments (much like that used for discovering
webmention endpoints). It turns out that there is and it's rel="payment"
. It
doesn't see too much use in the wild, and it should not be the primary means of
discovering payment methods by thanks
, but as a fallback it could be useful.
thanks
would check for payment links on the project home page (from the
homepage
property of the package.json
file), followed by author URLs if
provided.
I've added a link tag with this attribute to the blog as well. I don't expect it to get used, but one can always hope! Perhaps I'll become a professional blogger.
Better links
I write my posts in markdown, and use marked to process them. Marked
doesn't know which anchors will link out to external domains, so it compiles
then to <a>
tags with only an href
attribute. When linking to external
sources, the current best practice is to add a rel="noopener"
attribute. To do this, I've slightly customised the anchor renderer of marked to
add these in:
const marked = require('marked');
const urlResolve = require('url').resolve;
const renderer = new marked.Renderer();
const oldLinkRenderer = renderer.link;
// In the actual code, this is fed in to a function, not a module-scoped const.
const baseUrl = 'https://qubyte.codes';
renderer.link = (href, title, text) => {
const fullyQualified = urlResolve(baseUrl, href);
const rendered = oldLinkRenderer.call(renderer, href, title, text);
if (fullyQualified.startsWith(baseUrl)) {
return rendered;
}
return rendered.replace('<a ', '<a target="_blank" rel="noopener" ');
};
marked.setOptions({
// Some unrelated stuff skipped.
renderer
});
It's a hack for sure, but it works!
Mathematics
Toward the end of last year, I wrote a post that required some
typeset mathematics. I used to write a lot in LaTeX, and so I already knew of
MathJax (and was saddened to find that MathML in the browser failed
to catch on). I knew I needed to convince marked to use it, but marked currently
lacks extensibility so I couldn't define new blocks. Instead I adjusted the
behaviour of code blocks to treat blocks labelled as mathematics
differently:
const mathjax = require('mathjax-node');
const xml2js = require('xml2js');
const marked = require('marked');
const highlight = require('highlight.js');
const renderer = new marked.Renderer();
const codeRenderer = renderer.code;
// This function is synchronous, so I couldn't call MathJax
// in here (it's async), and must do in the highlight method.
renderer.code = function (code, lang, escaped) {
if (lang === 'mathematics') {
return code;
}
return codeRenderer.call(this, code, lang, escaped);
};
function highlight(code, language, callback) {
// Non-mathematics code is syntax highlighted.
if (language !== 'mathematics') {
return callback(null, highlight.highlight(language, code).value);
}
// Render with MathJax.
mathjax.typeset({ math: code, format: 'TeX', svg: true }, ({ errors, svg }) => {
if (errors) {
return callback(errors);
}
// MathJax puts some unnecessary inline style in the output
// which my server response headers disagree with. The below
// strips those out, and adds a "mathematics" classname to
// the resultant SVG for styling.
xml2js.parseString(rendered, (err, xmlobj) => {
if (err) {
return callback(err);
}
delete xmlobj.svg.$.style;
xmlobj.svg.$.class = 'mathematics';
const builder = new xml2js.Builder({ headless: true });
callback(null, builder.buildObject(xmlobj));
});
});
},
marked.setOptions({
highlight,
renderer
});
I'm very happy with how it turned out, but I do look forward to a more direct means to add functionality to marked.
Not a progressive web app
For a while this blog had a service worker and an app manifest. You could even "install" it on android phones. The caching strategy I was using was a bit wonky though. Any time hashes changed (common when I updated CSS, since each update to the CSS gets a new filename for caching reasons), a visit to any page would lead to the entire blog being downloaded. In the end I came to the conclusion that being a progressive web app was not good for readers bandwidth in this case and the only reason for me to have it was vanity.
A gutted service worker remains purely to clear the cache, so that a new script can be loaded which will uninstall the service worker for good.
Refactoring
Since the blog software (the static site generator) was built, async-await became a feature of JavaScript. The generator has been substantially revised to make use of promises and async-await, and is far more compact and readable as a result. The code comes in at a little under 300 source lines, not counting templates.