<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>kylemacquarrie.co.uk</title><description>Kyle Macquarrie&apos;s website feed</description><link>https://kylemacquarrie.co.uk/</link><item><title>Styling a Description List with CSS Grid</title><link>https://kylemacquarrie.co.uk/blog/dl-grid-layout/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/dl-grid-layout/</guid><description>&lt;p&gt;import DescriptionList from &apos;./_DescriptionList.tsx&apos;&lt;/p&gt;
&lt;p&gt;An underrated HTML element is the &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl&quot;&gt;description list&lt;/a&gt;, ideal for doing &lt;code&gt;key: value&lt;/code&gt; style lists of things when a one-dimensional &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; doesn&apos;t quite cut it.&lt;/p&gt;
&lt;p&gt;It consists of a description list &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; tag, which contains &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt; (description term) and &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt; (description details), most often in matched pairs which is what we&apos;ll deal with here.&lt;/p&gt;
&lt;p&gt;When styling a list like this, you often want some kind of visual layout that looks something like a table; however, since there&apos;s no wrapper element for each key/value pair (like a &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; table row element), this has historically not been so simple. Fortunately, with CSS Grid this becomes much simpler. (Technically you can use a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl#wrapping_name-value_groups_in_div_elements&quot;&gt;to wrap each key/value group&lt;/a&gt;, but now we don&apos;t have to!)&lt;/p&gt;
&lt;p&gt;Here&apos;s the HTML we&apos;ll be styling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;dl&amp;gt;
  &amp;lt;dt&amp;gt;description title&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;description data&amp;lt;/dd&amp;gt;
  &amp;lt;dt&amp;gt;short&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;short data&amp;lt;/dd&amp;gt;
  &amp;lt;dt&amp;gt;A long title that takes up more space&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;short&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And some basic styling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dl,
dt,
dd {
  border: 1px dotted currentColor;
}

dt,
dd {
  padding: 0.25em;
}

dt::after {
  /* gives us a title: description format  */
  content: &apos;:&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Default layout&lt;/h2&gt;
&lt;p&gt;Without any extra styling, everything has &lt;code&gt;display: block&lt;/code&gt; by default. The &lt;code&gt;:&lt;/code&gt; we added as a pseudo-element and the browser default indentation on the description details are the only visual indication of how the items relate to each other. So far, so brutalist.&lt;/p&gt;
&lt;p&gt;&amp;lt;DescriptionList /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Old Style Floats&lt;/h2&gt;
&lt;p&gt;We can tidy that up a bit using floats to give us a table row type layout, but the &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s all have different sizes, so the alignment of items looks a bit random; it&apos;s not easy to scan the list quickly. You also have to do some kind of clearfix or risk other bits of the layout going a bit mad.&lt;/p&gt;
&lt;p&gt;You could tidy it up a bit by fixing the width of the &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt; but it&apos;s always going to be a compromise, especially if you don&apos;t know what data you&apos;re going to be dealing with ahead of time.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dl {
  overflow: hidden; /* clearfix hack */
}

dd {
  margin: 0; /* remove browser default margin */
}

dt,
dd {
  float: left;
}

dt {
  clear: both;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;DescriptionList className=&amp;quot;floated&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Modern CSS Grid&lt;/h2&gt;
&lt;p&gt;With CSS grid, we can get our table-style layout to fully align the titles, without having to hard code a width, using less code, and with less chance of doing something unexpected. &lt;code&gt;auto 1fr&lt;/code&gt; sets the first column to fit the content and the second to use all the remaining space in the row. You get responsive behaviour out of the box, without having to special case anything.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dl {
  display: grid;
  grid-template-columns: auto 1fr;
}

dd {
  margin: 0; /* remove browser default margin */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;DescriptionList className=&amp;quot;grid&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Being able to nail that layout, without requiring any extra wrapper elements, with just a few lines of CSS would have been unimaginable not very long ago. I&apos;ve not had much cause or opportunity to really dig into CSS Grid properly yet but just this simple use case has really piqued my interest.&lt;/p&gt;
</description><pubDate>Thu, 15 Jun 2023 00:00:00 GMT</pubDate></item><item><title>Optimising webfonts with Glyphhanger</title><link>https://kylemacquarrie.co.uk/blog/optimise-fonts-glyphhanger/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/optimise-fonts-glyphhanger/</guid><description>&lt;p&gt;So Webfonts are cool, but you know what else is cool? Not sending loads of bytes over the wire that aren&apos;t required to display a web page. I remember hearing about &lt;a href=&quot;https://github.com/zachleat/glyphhanger&quot;&gt;Glyphhanger&lt;/a&gt; many years ago but I could never figure out how to get it to work in a reasonable way. Seeing Simon Dann &lt;a href=&quot;https://photogabble.co.uk/tutorials/font-subsetting-with-eleventyjs/&quot;&gt;posting about doing it with Eleventy&lt;/a&gt; reminded me of it, and I finally managed to get it set up in a reasonable(?) fashion.&lt;/p&gt;
&lt;p&gt;There are some interesting tradeoffs around how and when to run this process - I&apos;ve optimised for fast builds, at the expense of having to run the subsetting script manually and commit the output. If I forget and a couple of characters end up in the wrong font, it&apos;s not the end of the world.&lt;/p&gt;
&lt;h2&gt;Installing in WSL&lt;/h2&gt;
&lt;p&gt;I&apos;m using WSL2 in Windows 11, which generally works very well but has occasional quirks. The Glyphhanger documentation only really covers MacOS, so here&apos;s how I went about getting it working. I wouldn&apos;t try this under native Windows, but if I was setting this up on MacOS I&apos;d recommend using Homebrew there too.&lt;/p&gt;
&lt;h3&gt;Install Puppeteer dependencies with apt&lt;/h3&gt;
&lt;p&gt;(This step should be Linux/WSL only, I expect this to work out the box on MacOS.) Glyphhanger uses &lt;a href=&quot;https://pptr.dev/&quot;&gt;Puppeteer&lt;/a&gt; under the hood (there is a &lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;JSDOM&lt;/a&gt; option (&lt;code&gt;--jsdom&lt;/code&gt;) but it still seems to require Puppeteer to be installed - possibly a bug?). Since Puppeteer is driving a headless Chrome instance, you need to be able to install and Chrome. So based on the &lt;a href=&quot;https://pptr.dev/troubleshooting#running-puppeteer-on-wsl-windows-subsystem-for-linux&quot;&gt;troubleshooting guide&lt;/a&gt;, install the required dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo apt install libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install fonttools with brew&lt;/h3&gt;
&lt;p&gt;Glyphhanger also uses &lt;a href=&quot;https://github.com/fonttools/fonttools&quot;&gt;&lt;code&gt;fonttools&lt;/code&gt;&lt;/a&gt;, which is a Python package. I would rather saw off my own hand than try and navigate the minefield of getting Python working by hand, but fortunately some kind soul thought to put it on &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;, which works on Linux (including WSL) as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;brew install fonttools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I cannot reiterate this enough. Getting a Python environment set up is the closest to a nervous breakdown a computer has ever brought me. Just use Homebrew.&lt;/p&gt;
&lt;h2&gt;Getting URLs&lt;/h2&gt;
&lt;p&gt;Before we can subset the font, we need to get a list of all the characters we need to include in the subset. If you only have a couple of pages you can pass them directly on the CLI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;glyphhanger http://kylemacquarrie.co.uk http://kylemacquarrie.co.uk/blog --subset=./path/to/fonts/*.ttf --output=./path/to/fonts/subset --formats=woff2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a larger site, you can pass a single URL (e.g. your home page) and &lt;code&gt;--spider&lt;/code&gt;, which will recursively follow all relative links it finds. Unfortunately this is slightly fragile - on my site, it follows the link to the RSS feed then blows up because Puppeteer can&apos;t execute anything on that page; there are other issues reported on Github relating to &lt;code&gt;href:tel&lt;/code&gt; phone number links and the like. The current filthy hack I&apos;m using is to parse the sitemap and pass a list of every URL in. I think I prefer this to having to comment out the RSS link, or adding special code into the application to handle this (e.g. hide the RSS link based on an environment variable or similar), but it&apos;s not ideal and won&apos;t scale well if you have more or less than one sitemap.&lt;/p&gt;
&lt;p&gt;It&apos;s also worth noting you have to use TTF files as the input - trying to subset an existing WOFF2 won&apos;t work.&lt;/p&gt;
&lt;h2&gt;Subset&lt;/h2&gt;
&lt;p&gt;Here&apos;s the script I&apos;m using to build up and run the CLI command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// scripts/subset/index.mjs
import { readFile } from &apos;node:fs/promises&apos;
import { resolve } from &apos;node:path&apos;

// read the sitemap XML
const sitemap = await readFile(resolve(&apos;./dist/sitemap-0.xml&apos;)).then((data) =&amp;gt;
  data.toString()
)

// split into lines like &amp;lt;url&amp;gt;&amp;lt;loc&amp;gt;https://kylemacquarrie.co.uk/blog/2/&amp;lt;/loc&amp;gt;&amp;lt;/url&amp;gt;
const lines = sitemap.split(&apos;&amp;lt;url&amp;gt;&apos;)

// discard the first line which is all XML metadata
lines.shift()

// for each line, strip everything except the URL
const urls = lines
  .map((line) =&amp;gt;
    line
      .replace(&apos;&amp;lt;loc&amp;gt;&apos;, &apos;&apos;)
      .replace(&apos;&amp;lt;/urlset&amp;gt;&apos;, &apos;&apos;)
      .replace(&apos;&amp;lt;/loc&amp;gt;&amp;lt;/url&amp;gt;&apos;, &apos;&apos;)
      // the sitemap builds with the production url but we&apos;re running this locally
      .replace(&apos;https://kylemacquarrie.co.uk&apos;, &apos;http://localhost:3000&apos;)
  )
  // make it into one big string
  .join(&apos; &apos;)

const fontPath = `./src/assets/fonts`

// build the command
const command = [
  &apos;glyphhanger&apos;,
  urls,
  `--subset=${fontPath}/*.ttf`,
  `--output=${fontPath}/subset`,
  &apos;--formats=woff2&apos;,
].join(&apos; &apos;)

// write it to stdout so we can pipe it into bash in the next step
process.stdout.write(command)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run it with &lt;code&gt;node scripts/subset/index.mjs | bash&lt;/code&gt;
or add it as an npm script&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &amp;quot;scripts&amp;quot;: { &amp;quot;subset&amp;quot;: &amp;quot;node scripts/subset/index.mjs | bash&amp;quot; } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and run it with &lt;code&gt;npm run subset&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The total size of the &lt;code&gt;woff2&lt;/code&gt; files went from 65kb to 28kb. I did have to adjust some line-heights as the conversion that FontSquirrel uses is obviously a bit different, but nothing too major.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;That&apos;s a pretty decent saving. We could go further and use the &lt;code&gt;--family&lt;/code&gt; option to only use characters that use a specific font, but we&apos;d have to run the script once for each font we use which gets a bit tedious. It might be worth it for the smallest possible size though.&lt;/p&gt;
&lt;p&gt;You could automate this as part of your build process, but as with my &lt;a href=&quot;/blog/shell-scripts-node&quot;&gt;previous scripts&lt;/a&gt;, I don&apos;t really want to add additional dependencies that will slow down the build (especially if it might involve debugging a Python installation on a machine I don&apos;t own) so for now I&apos;m just running it manually against a local version - Astro&apos;s default scripts make it easy to check the production build locally by doing &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run preview&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It should be possible to refactor the script to cache the unicode range between runs as well, and only regenerate the fonts when a character has been added/removed. Running it against the site&apos;s markdown content might be better too.&lt;/p&gt;
</description><pubDate>Sun, 16 Apr 2023 00:00:00 GMT</pubDate></item><item><title>Running shell scripts with Node</title><link>https://kylemacquarrie.co.uk/blog/shell-scripts-node/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/shell-scripts-node/</guid><description>&lt;p&gt;Since this site has been around for quite a while now in various incarnations, I&apos;ve been thinking for a while about how to audit all the things I&apos;ve linked to and make sure the links exist (and also that I&apos;m not introducing breaking changes as I develop it). Fortunately before I ended up trying to write something myself, I came across &lt;a href=&quot;https://github.com/Munter/hyperlink&quot;&gt;Hyperlink&lt;/a&gt;, which does the job. (In the process I discovered a lot of 404s, some bad #fragment links, and a distressing number of redirects from http =&amp;gt; https, or /path to /path/ or vice versa without any apparent consistency.) The basic usage is well documented in the readme and the &lt;a href=&quot;https://mntr.dk/2015/check-your-link-rot/&quot;&gt;intro post&lt;/a&gt;, but it took me a bit of experimentation to get it running the way I wanted.&lt;/p&gt;
&lt;p&gt;The basic command I landed on is something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ hyperlink http://localhost:3000 -r --skip http://localhost:3000/client.js | tap-spot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is, run &lt;code&gt;hyperlink&lt;/code&gt; recursively against my local server, skipping a particular URL that fails (it&apos;s an example link in &lt;a href=&quot;/blog/preact-ssr-tutorial&quot;&gt;a tutorial&lt;/a&gt;), then pipe the output into a test reporter.&lt;/p&gt;
&lt;p&gt;I had a few requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;it should be easily run with just one or two commands - I don&apos;t want to have to memorise the list of urls to &lt;code&gt;--skip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it should run with different configurations (e.g. run against local build and run against production site)&lt;/li&gt;
&lt;li&gt;it should not involve hard-coding long cryptic commands&lt;/li&gt;
&lt;li&gt;it must not increase my install/build time&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Hardcoded NPM scripts&lt;/h2&gt;
&lt;p&gt;The obvious solution to me was to &lt;a href=&quot;/blog/simple-deploys-with-npm-and-rsync&quot;&gt;add some scripts&lt;/a&gt; into the &lt;code&gt;package.json&lt;/code&gt; and hard code the different configs in different scripts. To start with we can install the required packages globally - not ideal, but better than wasting CPU time and bandwidth for something I only want to run locally.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;scripts&amp;quot; {
    &amp;quot;test-links&amp;quot;: &amp;quot;hyperlink http://localhost:3000 -r --skip http://localhost:3000/client.js | tap-spot&amp;quot;,
    &amp;quot;test-links:prod&amp;quot;: &amp;quot;hyperlink https://kylemacquarrie.co.uk -r --skip http://localhost:3000 --skip http://localhost:3000/client.js | tap-spot&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but is a bit magicky, hard to maintain, and clutters the &lt;code&gt;package.json&lt;/code&gt;. It clearly fails on point three.&lt;/p&gt;
&lt;h2&gt;Spawn&lt;/h2&gt;
&lt;p&gt;My next thought was to try and use &lt;a href=&quot;https://nodejs.org/docs/latest/api/child_process.html#child_processspawncommand-args-options&quot;&gt;Node&apos;s &lt;code&gt;child_process.spawn&lt;/code&gt;&lt;/a&gt;, similar to the &lt;code&gt;ps ax | grep ssh&lt;/code&gt; example from the docs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// test-links/index.mjs
import { spawn } from &apos;node:child_process&apos;

// pass the options to hyperlink
const hyperlink = spawn(&apos;hyperlink&apos;, [
  &apos;http://localhost:3000&apos;,
  &apos;-r&apos;,
  &apos;--skip&apos;,
  &apos;http://localhost:3000/client.js&apos;,
])

const tapSpot = spawn(&apos;tap-spot&apos;, [])

// pipe hyperlink output into tap-spot
hyperlink.stdout.on(&apos;data&apos;, (data) =&amp;gt; {
  tapSpot.stdin.write(data)
})

hyperlink.on(&apos;close&apos;, (code) =&amp;gt; {
  tapSpot.stdin.end()
})

// log out tap-spot&apos;s output
tapSpot.stdout.on(&apos;data&apos;, (data) =&amp;gt; {
  console.log(data.toString())
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;node test-links&lt;/code&gt; from an npm script does work, and we could use an environment variable (e.g. &lt;code&gt;NODE_ENV&lt;/code&gt;) and add some logic to change the variables based on it. Unfortunately, this ruins &lt;code&gt;tap-spot&lt;/code&gt;&apos;s nice output formatting, and no amount of tinkering with the &lt;code&gt;stdio&lt;/code&gt; option seems to help.&lt;/p&gt;
&lt;h2&gt;Hardcoded shell scripts&lt;/h2&gt;
&lt;p&gt;It occurred to me that I could do the same kind of thing that &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; uses for their install, which is roughly &amp;quot;get a shell script from somewhere and run it&amp;quot;. We can do something similar, by putting the commands in &lt;code&gt;test-links/dev.sh&lt;/code&gt; and &lt;code&gt;test-links/prod.sh&lt;/code&gt;, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# test-links/dev.sh
hyperlink http://localhost:3000 -r --skip http://localhost:3000/client.js --skip | tap-spot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and updating our scripts in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test-links:dev&amp;quot;: &amp;quot;bash test-links/dev.sh&amp;quot;,
  &amp;quot;test-links:prod&amp;quot;: &amp;quot;bash test-links/prod.sh&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That works, and it&apos;s tidied up our scripts nicely, but we&apos;re back to maintaining two separate commands as life&apos;s too short for me to learn how to write proper bash scripts with logic and stuff.&lt;/p&gt;
&lt;h2&gt;Using Node to generate shell scripts&lt;/h2&gt;
&lt;p&gt;A language I &lt;em&gt;do&lt;/em&gt; know how to write logic and stuff with is JavaScript, maybe I can use that? I&apos;ll also move it into a &lt;code&gt;scripts&lt;/code&gt; folder to avoid naming clashes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// scripts/test-links/index.mjs
const isProduction = process.env.NODE_ENV === &apos;production&apos;

const url = isProduction
  ? &apos;https://kylemacquarrie.co.uk&apos;
  : &apos;http://localhost:3000&apos;

// build up the list of `--skip ${url}` commands
const skipUrls = [
  &apos;http://localhost:3000/client.js&apos;,
  isProduction &amp;amp;&amp;amp; &apos;http://localhost:3000&apos;,
]
  .filter(Boolean)
  .reduce((acc, curr) =&amp;gt; `${acc}--skip ${curr} `, &apos;&apos;)

// use npx for node modules we don&apos;t have as dependencies in the project
process.stdout.write(`npx hyperlink ${url} -r ${skipUrls} | npx tap-spot`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets us update our scripts to match:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test-links&amp;quot;: &amp;quot;node scripts/test-links | bash&amp;quot;,
  &amp;quot;test-links:prod&amp;quot;: &amp;quot;NODE_ENV=production npm run test-links&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;process.stdout.write()&lt;/code&gt; means we can just pipe it straight into &lt;code&gt;bash&lt;/code&gt;, and it runs and keeps &lt;code&gt;tap-spot&lt;/code&gt;&apos;s nice formatting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kylemacquarrie.co.uk/images/hyperlink-output.png&quot; alt=&quot;Screenshot of the formatted terminal output from running test-links:prod&quot;&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve used &lt;code&gt;npx&lt;/code&gt; to run the commands so we&apos;re not relying on having them globally. It might be better in the long run to give &lt;code&gt;test-links&lt;/code&gt; its own &lt;code&gt;package.json&lt;/code&gt; and explicitly install them there, separately from the main site, but this should do for now, and I think it meets all the requirements set out earlier.&lt;/p&gt;
</description><pubDate>Sat, 25 Feb 2023 00:00:00 GMT</pubDate></item><item><title>VS Code tip: Match all lines starting with a certain string</title><link>https://kylemacquarrie.co.uk/blog/vs-code-match-line/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/vs-code-match-line/</guid><description>&lt;p&gt;Sometimes you want to batch-edit files to remove certain lines, where you know what the line starts with but not how it ends. In my case I had a legacy &lt;code&gt;slug&lt;/code&gt; value in the frontmatter of a bunch of Markdown files, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;title: A Cool Post
slug: a-cool-post
description: A brief summary of the cool post
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I didn&apos;t want to have to remove that by hand in 60+ files, so I figured there had to be a way to use the regex mode in VS Code&apos;s find &amp;amp; replace to do it. I am far from fluent in regular expressions, so it took me a little while to work this out, and I&apos;m posting it here to refer back to in future, if nothing else.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kylemacquarrie.co.uk/images/regex-find-and-replace.png&quot; alt=&quot;A screenshot of the VS Code search panel using the regex&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using e.g. &lt;code&gt;slug: (.*)\n&lt;/code&gt; will match every line beginning with &lt;code&gt;slug: &lt;/code&gt;, up to and including the new line character.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;slug: &lt;/code&gt; match the exact string (amend to whatever string you&apos;re searching for)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(.*)&lt;/code&gt; match any number of characters, including whitespace&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\n&lt;/code&gt; match a new line character (i.e. the end of the line)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you can replace with an empty string to remove the line.&lt;/p&gt;
&lt;p&gt;If you omit the &lt;code&gt;\n&lt;/code&gt; and do the same, it leaves an empty line, which I really wanted to avoid.&lt;/p&gt;
&lt;p&gt;You can test-drive this on &lt;a href=&quot;https://regex101.com/r/QWN6ub/1&quot;&gt;regex101.com&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Sat, 31 Dec 2022 00:00:00 GMT</pubDate></item><item><title>Porting Content Between Site Versions</title><link>https://kylemacquarrie.co.uk/blog/porting-content-between-sites/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/porting-content-between-sites/</guid><description>&lt;p&gt;Over the years I&apos;ve had a number of different personal websites (not even counting the number of abortive half-built efforts that got abandoned along the way), but each one has started from the same basic content as the previous iteration. Here are some of the scripts and tricks I&apos;ve used for migrating content between different systems. This involved a large degree of &lt;a href=&quot;https://twitter.com/emilyst/status/1371942193875668997&quot;&gt;fucking around &amp;amp;&amp;amp; finding out&lt;/a&gt;, so let&apos;s consolidate it from all the repos on my machine into one place for future reference.&lt;/p&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Many of the implementation details in these scripts are hyper-specific to my use case, but the principles should generalise. I think it&apos;s useful to show how I&apos;ve solved some of these problems by writing one-off scripts, not everything needs to be a clean reusable abstraction. It&apos;s fine to hack together a quick script to get the job done, even if it doesn&apos;t handle errors or edge cases correctly. My Ruby, in particular, is very much hack level.&lt;/p&gt;
&lt;h2&gt;1a: Stacey to ActiveRecord&lt;/h2&gt;
&lt;p&gt;At first I had a &lt;a href=&quot;https://github.com/kolber/stacey&quot;&gt;Stacey&lt;/a&gt; site for my portfolio, with a separate Wordpress site for my blog (see next section). Stacey uses a YAML file with a &lt;code&gt;content&lt;/code&gt; key that you put markdown or HTML in for each post (similar to the markdown + YAML frontmatter format that things like &lt;a href=&quot;https://www.11ty.dev/docs/languages/markdown/&quot;&gt;Eleventy&lt;/a&gt; use.), e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;title: Post Title
date: 2022
description: Quick description of the post
tag: tag1, tag2
content: |
  Some markdown or HTML content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was porting to a &lt;a href=&quot;http://padrinorb.com/&quot;&gt;Padrino&lt;/a&gt; site using ActiveRecord as an ORM, so using a &lt;code&gt;rake&lt;/code&gt; task seemed like a reasonable solution. The &lt;code&gt;Post&lt;/code&gt; model would store HTML in its &lt;code&gt;content&lt;/code&gt; field, and could also have and belong to many tags. Once the &lt;code&gt;Post&lt;/code&gt; and &lt;code&gt;Tag&lt;/code&gt; models are defined, and the Stacey content copied to &lt;code&gt;/app/data/portfolio&lt;/code&gt;, we can add an &lt;code&gt;import.rake&lt;/code&gt; task to loop over the folder and add a new post to the database for each item, as well as any tags required.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;require &apos;yaml&apos;
require &apos;kramdown&apos;

# define the import task
task :import =&amp;gt; :environment do
  portfolio_import
end

def portfolio_import
  Dir.foreach(&apos;./app/data/portfolio&apos;) do |item|
    next if item == &apos;.&apos; or item == &apos;..&apos;
    # load the yml file
    p = YAML.load_file(&amp;quot;./app/data/portfolio/#{item}/project.yml&amp;quot;)
    # get the content field
    raw_content = p[&apos;content&apos;]
    # Everything was inside ULs in the content, so we could tell if
    # the content was markdown or HTML by checking the first character
    if raw_content.first == &apos;-&apos;
    # it&apos;s Markdown, convert to HTML
      content = Kramdown::Document.new(raw_content, entity_output: :as_char).to_html
    else
      # just use the HTML as is
      content = raw_content
    end
    # get the list of tags, split on , and filter any empty items
    tags = p[&apos;tag&apos;].downcase.gsub(&apos;,&apos;, &apos; &apos;).split(&apos; &apos;).reject(&amp;amp;:blank?)

    # build an active record collection of tags
    tag_collection = []
    tags.each do |tag|
      # find or create the tag, and add it to the collection
      t = Tag.find_or_create_by(title: tag)
      tag_collection &amp;lt;&amp;lt; t
    end
    # create a new Post
    post = Post.new(
      title: p[&apos;title&apos;],
      slug: item.to_s.split(&apos;.&apos;).last,
      publish_date: (Date.new(p[&apos;date&apos;]) rescue nil),
      content: content,
      post_type: &apos;project&apos;,
      status: &apos;publish&apos;,
      tags: tag_collection
    )
    # save it
    post.save
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;bundle exec padrino rake import&lt;/code&gt; would run the task. Job half done.&lt;/p&gt;
&lt;h2&gt;1b: WordPress XML to ActiveRecord&lt;/h2&gt;
&lt;p&gt;Next we want to take a WordPress XML export and do the same process. Turns out &lt;code&gt;nokogiri&lt;/code&gt; is useful for something other than making life impossible for people trying to use Ruby on Windows.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;# add to the earlier requires
require &apos;nokogiri&apos;

# add to the import task
task :import =&amp;gt; :environment do
  portfolio_import
  blog_import
end

def blog_import
  # import and parse the xml
  archive = File.open(&amp;quot;./app/data/archive.xml&amp;quot;) { |f| Nokogiri::XML(f) }
  # get each item (page or post)
  archive.css(&apos;item&apos;).each do |item|
    # we only want posts
    if (item.css(&apos;wp|post_type&apos;).first.content rescue nil) == &apos;post&apos;
      # parse the post&apos;s tag names into an array of strings
      tags = []
      item.css(&apos;category&apos;).each do |tag|
        if tag.attributes[&apos;domain&apos;].to_s == &apos;post_tag&apos;
          tags &amp;lt;&amp;lt; tag.attributes[&apos;nicename&apos;]
        end
      end

      # build an active record collection of tags
      tag_collection = []
      tags.each do |tag|
        t = Tag.find_or_create_by(title: tag.value)
        tag_collection &amp;lt;&amp;lt; t
      end

      # create a new Post and populate with data from the XML.
      post = Post.new(
        title: (item.css(&apos;title&apos;).first.content rescue nil),
        slug: (item.css(&apos;title&apos;).first.content.parameterize.dasherize rescue nil),
        publish_date: (Date.parse(item.css(&apos;pubDate&apos;).first.content) rescue nil),
        # WP stores the content as HTML
        content: (item.css(&apos;content|encoded&apos;).first.content rescue nil),
        # published/draft
        status: (item.css(&apos;wp|status&apos;).first.content rescue nil),
        post_type: &apos;blog&apos;,
        tags: tag_collection
      )
      # save it to the DB
      post.save
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now running &lt;code&gt;bundle exec padrino rake import&lt;/code&gt; will import the blog entries into the database.&lt;/p&gt;
&lt;h2&gt;2: Active Record to Markdown files&lt;/h2&gt;
&lt;p&gt;Fast forward a few years, and I&apos;ve decided I&apos;d like to move to a static site. At various times this was going to be in Next.js, Eleventy and eventually Astro, all of which can build pages from Markdown files. So we want to export all the posts from the database into correctly formatted Markdown, and also grab their associated poster images from the &lt;code&gt;Asset&lt;/code&gt; model (which was using &lt;a href=&quot;https://github.com/thoughtbot/paperclip&quot;&gt;&lt;code&gt;paperclip&lt;/code&gt;&lt;/a&gt; under the hood for file uploading).&lt;/p&gt;
&lt;p&gt;This is largely the same process as the import, but in reverse&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;require &apos;kramdown&apos;
require &apos;open-uri&apos;

task :export =&amp;gt; :environment do
  posts_export
  assets_export
end

def assets_export
  # for each Asset in the database
  Asset.all.each do |asset|
    # create a local file with the right name
    File.open(&amp;quot;images/#{asset.file.url.split(&apos;/&apos;).last}&amp;quot;, &apos;wb&apos;) do |fo|
      # read the file from the server and write to the local file
      fo.write open(&amp;quot;https://kylemacquarrie.co.uk#{asset.file.url}&amp;quot;).read
    end
  end
end

def posts_export
  Post.all.each do |post|
    # ignore contacts
    next if post.post_type == &apos;contact&apos;

    slug = post.slug.chomp(&apos;/&apos;)
    post_type = &amp;quot;#{post.post_type}#{ post.post_type == &apos;blog&apos; ? &apos;&apos; : &apos;s&apos;}&amp;quot;
    # parse html content back to markdown
    markdown = Kramdown::Document.new(post.content, input: &apos;html&apos;).to_kramdown rescue &apos;&apos;
    # remove attributes that the janky wysiwyg editor added
    markdown = markdown.gsub(&apos;{: target=&amp;quot;_blank&amp;quot;}&apos;, &apos;&apos;)
    markdown = markdown.gsub(&apos;{: .ql-syntax spellcheck=&amp;quot;false&amp;quot;}&apos;, &apos;&apos;)
    # construct a string in the correct markdown + frontmatter format
    string = &amp;quot;---
title: &apos;#{post.title}&apos;
abstract: \&amp;quot;#{post.abstract}\&amp;quot;
status: #{post.status}
published: #{post.publish_date}
tags: #{post.tags.all.map{|t| t.title}.join(&apos;,&apos;)}
image: #{post.main_asset.file.url.split(&apos;/&apos;).last rescue &apos;&apos;}
position: #{post.position}
---

#{markdown}
&amp;quot;
    # write to a new markdown file with the correct path
    File.open(&amp;quot;posts/#{post_type}/#{slug}.md&amp;quot;, &apos;wb&apos;) do |fo|
      fo.write string
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;bundle exec padrino rake export&lt;/code&gt; gives us a couple of folders of Markdown files, and a folder of all the images that we ever uploaded in the Padrino Admin CMS, ready to dump into the fancy new site&apos;s git repo.&lt;/p&gt;
&lt;h2&gt;3: Rewrite Flickr hotlinks to local files&lt;/h2&gt;
&lt;p&gt;For historical reasons (because my original shared hosting didn&apos;t have much space) most of the images that were in the site content were hotlinked from my Flickr account (remember them?).
I was keen to remove that external dependency. Fortunately Flickr allows you to download your archive as a zip file, so we just need to reverse-engineer a way of mapping the CDN link to the original file.&lt;/p&gt;
&lt;p&gt;The Flickr URLs looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://farm6.staticflickr.com/5537/14063836058_5a91d05c09_b.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The matching image in the zip file would be something like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/path/to/archive/original_filename_14063845389_o.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately that still leaves us some work to do to map the CDN link back to the original image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// import.js
import { readdirSync, readFileSync, copyFileSync, writeFileSync } from &apos;fs&apos;

function main() {
  // identify Flickr CDN links. The first 10+ digit section appears to be the original image ID
  // e.g. https://farm6.staticflickr.com/5537/14063836058_5a91d05c09_b.jpg
  const regex =
    /https:\/\/farm\d.static.?flickr.com\/\d{4}\/\d{10,}_.{10,}.jpg/g
  // get a list of all the images in the flickr archive
  const images = readdirSync(&apos;./flickr_archive&apos;)
    // filter out WSL guff - MacOS may need to remove .DS_Store etc
    .filter((file) =&amp;gt; !file.includes(&apos;Zone.Identifier&apos;))

  function doFolder(type) {
    // in this case we have two folders, /blog and /projects.
    // get a list of all posts from that folder
    const posts = readdirSync(`./posts/${type}`)

    posts.forEach((postName) =&amp;gt; {
      const postPath = `./posts/${type}/${postName}`
      // read the post content as a string
      let post = readFileSync(postPath).toString()
      // find any flickr cdn URLs
      const flickrLinks = post.matchAll(regex)
      // for each flickr
      for (const match of flickrLinks) {
        // extract the ID
        const url = match[0]
        const parts = url.replace(&apos;https://&apos;, &apos;&apos;).split(&apos;/&apos;)
        const id = parts[parts.length - 1].split(&apos;_&apos;)[0]
        // find the image with a matching ID
        const img = images.find((i) =&amp;gt; i.match(id))
        if (!img) {
          console.log(`couldn&apos;t find an image matching ${id} in ${postPath}`)
        }
        // copy file to new location
        const from = `./flickr_archive/${img}`
        const to = `./public/images/${img}`
        console.log(`copying ${from} to ${to}`)
        copyFileSync(from, to)

        // update URL in file
        const newUrl = `/images/${img}`
        console.log(`replacing ${url} with ${newUrl}`)
        post = post.replace(url, newUrl)
      }
      // write the post back to disk
      writeFileSync(postPath, post)
    })
  }

  doFolder(&apos;blog&apos;)
  doFolder(&apos;projects&apos;)
}

main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;node import.js&lt;/code&gt; gives us a folder of images that are linked from the content, and updates the links to point to the new paths.&lt;/p&gt;
</description><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate></item><item><title>Install glslang using Homebrew</title><link>https://kylemacquarrie.co.uk/blog/install-glslang-with-brew/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/install-glslang-with-brew/</guid><description>&lt;p&gt;I&apos;ve been going through &lt;a href=&quot;https://bruno-simon.com/&quot;&gt;Bruno Simon&apos;s&lt;/a&gt; great &lt;a href=&quot;https://threejs-journey.com/&quot;&gt;Three.js Journey&lt;/a&gt; course lately. There&apos;s a section on shaders, where Bruno recommends the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=mrjjot.vscode-glsl-linter&quot;&gt;GLSL Linter&lt;/a&gt; extension for VS Code, which requires the &lt;a href=&quot;https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/&quot;&gt;OpenGL Reference Compiler&lt;/a&gt; to be installed and the path to be set in the extension settings. The &lt;a href=&quot;https://www.youtube.com/watch?v=NQ-g6v7GtoI&quot;&gt;video&lt;/a&gt; he references uses a convoluted manual process, but if you&apos;re able to use &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; it becomes much simpler. I&apos;ve tested this on Ubuntu running in WSL, but it should be the same for Mac OS and other Linux flavours.&lt;/p&gt;
&lt;p&gt;Install using Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ brew install glslang
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check it&apos;s installed and in your &lt;code&gt;PATH&lt;/code&gt; - note the capital &lt;code&gt;V&lt;/code&gt; in the command name&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ glslangValidator -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Get the path to the installed binary&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ which glslangValidator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drop that path into the &lt;code&gt;Glsl-linter: Validator Path&lt;/code&gt; setting in VS Code and the extension should pick it up correctly — you may need to reload VS Code.&lt;/p&gt;
&lt;p&gt;If you&apos;re following Bruno&apos;s naming scheme (e.g. &lt;code&gt;fragment.glsl&lt;/code&gt; and &lt;code&gt;vertex.glsl&lt;/code&gt;), you may run into &lt;a href=&quot;https://github.com/Jacajack/vscode-glsl-linter/issues/4#issuecomment-784357156&quot;&gt;this issue&lt;/a&gt; where it can&apos;t work out which shader type type to use to validate. You can either rename the shader files (e.g. &lt;code&gt;fragment.frag&lt;/code&gt; and &lt;code&gt;vertex.vert&lt;/code&gt; should work out the box) or add &lt;code&gt;fragment.glsl&lt;/code&gt; and &lt;code&gt;vertex.glsl&lt;/code&gt; to the &lt;code&gt;Glsl-linter: File Extensions&lt;/code&gt; setting.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;glsl-linter.fileExtensions&amp;quot;: {
  &amp;quot;fragment.glsl&amp;quot;: &amp;quot;frag&amp;quot;,
  &amp;quot;vertex.glsl&amp;quot;: &amp;quot;vert&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
</description><pubDate>Thu, 15 Dec 2022 00:00:00 GMT</pubDate></item><item><title>Guillermo del Toro and the essence of heavy metal</title><link>https://kylemacquarrie.co.uk/blog/essence-of-heavy-metal/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/essence-of-heavy-metal/</guid><description>&lt;p&gt;&lt;em&gt;Pacific Rim&lt;/em&gt; is a ridiculous movie about giant mind-controlled mechs
rocket-punching enormous &lt;a href=&quot;https://en.wikipedia.org/wiki/Kaiju&quot;&gt;kaiju&lt;/a&gt; from another world. It was directed
by Guillermo del Toro, and in the &lt;a href=&quot;https://www.youtube.com/watch?t=5834&amp;amp;v=OcDUKwJCoSk&quot;&gt;director&apos;s commentary&lt;/a&gt; he had this
to say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Ishir%C5%8D_Honda&quot;&gt;Ishirō Honda&lt;/a&gt;, when he started Gojira, he said to his crew, he
gathered everybody in the kitchen of his home and he said &amp;quot;listen
guys, if anyone here doesn&apos;t believe we&apos;re doing a great movie, that
we&apos;re making a great movie with a giant monster, please leave&amp;quot;, and I
demand of myself and my crew and my cast the same thing. We have to be
unironic. We have to never be postmodern about these things we do.
What you see is an exercise in faith and an exercise in law, and I
deliver myself completely to every movie I do without a single shred
of irony and I wanted my cast to feel that way because you can feel
when people don&apos;t believe what they&apos;re doing. You can sense when
people don&apos;t believe in that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was reminded of this by &lt;a href=&quot;https://twitter.com/invisoranges/status/1303177936745488388&quot;&gt;this tweet&lt;/a&gt; from noted metal blog
&lt;a href=&quot;https://www.invisibleoranges.com/&quot;&gt;Invisible Oranges&lt;/a&gt;, referring to the extraordinary &lt;a href=&quot;https://slugdge.bandcamp.com/album/esoteric-malacology&quot;&gt;Slugdge&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;gastropod-themed progressive death metal band slugdge prove not only
that the genre can be ridiculous and savage at the same time, but also
that this synchrony makes the music &lt;em&gt;even better&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This, to me, is the essence of heavy metal. If you stop to think too
hard about e.g. men yelling about vikings over 200bpm blastbeats, the
whole thing falls apart, but bypass niceties like &amp;quot;good taste&amp;quot; and you
can feel the energy. (This is hardly unique to metal, as anyone who&apos;s
been reluctantly dragged to a club, only to find themselves lost to the
dancefloor might attest.) Cast aside your cynicism; be unironic and
un-postmodern, deliver yourself completely to the music, and you&apos;ll be
rewarded.&lt;/p&gt;
&lt;p&gt;While I&apos;m hesitant to make any grand statements about how films like
&lt;em&gt;Pacific Rim&lt;/em&gt; and the best heavy metal qualify as art, they are
absolutely full of &lt;em&gt;craft&lt;/em&gt;, and that craft shines through and connects
in the most visceral way.&lt;/p&gt;
</description><pubDate>Wed, 02 Dec 2020 00:00:00 GMT</pubDate></item><item><title>Build a server-rendered Preact app from scratch</title><link>https://kylemacquarrie.co.uk/blog/preact-ssr-tutorial/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/preact-ssr-tutorial/</guid><description>&lt;p&gt;In this tutorial, we&apos;re going to build a simple server-rendered
&lt;a href=&quot;https://preactjs.com/&quot;&gt;Preact&lt;/a&gt; app, with client-side hydration. We&apos;ll start by serving
plain HTML with &lt;a href=&quot;https://expressjs.com/&quot;&gt;Express&lt;/a&gt;, then build up some components using Preact
and &lt;a href=&quot;https://github.com/developit/htm&quot;&gt;HTM&lt;/a&gt;. After that, we&apos;ll add some interactivity by &lt;em&gt;hydrating&lt;/em&gt;
our components, matching up the server rendered HTML with what the
client-side framework thinks it should be. Along the way we&apos;ll have
some fun (?) with build tools like &lt;a href=&quot;https://nodemon.io/&quot;&gt;Nodemon&lt;/a&gt;, &lt;a href=&quot;https://rollupjs.org/guide/en&quot;&gt;Rollup&lt;/a&gt;, and NPM
scripts.&lt;/p&gt;
&lt;p&gt;None of the individual parts of this are (relatively speaking) that
complex, but there are a lot of moving parts here and it can be hard to
find an example that puts them all together. This is an intermediate
level tutorial though, so I won&apos;t be explaining much of the syntax or
JavaScript language features. You&apos;ll need to be reasonably comfortable
with the terminal, NPM and JavaScript basics.&lt;/p&gt;
&lt;h2&gt;Why server rendered? Why Preact?&lt;/h2&gt;
&lt;p&gt;Basically, it&apos;s fast. Sending as much as you can via HTML means less
work to do on the client to make it work; using a lightweight framework
like Preact means you&apos;re sending less JavaScript on the wire, which
translates to a faster user experience, especially on slower devices
(i.e. most Android phones - start with &lt;a href=&quot;https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/&quot;&gt;this article by Alex Russell&lt;/a&gt;
if you want to delve into the numbers). For content-heavy sites, it&apos;s
almost invariably better to get as much of your content onscreen in HTML
as quickly as possible. A lot of JavaScript framework SSR approaches
involve building a single-page app then hacking in server rendering, but
we&apos;re going to start with plain HTML and try to progressively enhance
it.&lt;/p&gt;
&lt;p&gt;It&apos;s also a good exercise in seeing what&apos;s going on under the hood --
in my day job I work on a moderately complex &lt;a href=&quot;https://nextjs.org/&quot;&gt;React/Next.js&lt;/a&gt;
application, and there&apos;s a lot of magic that goes on under the surface.
Understanding what your tools are doing for you is always useful.&lt;/p&gt;
&lt;h2&gt;Table of contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#an-express-app-that-serves-html&quot;&gt;An express app that serves HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#setting-up-nodemon-and-npm-scripts&quot;&gt;Setting up Nodemon and NPM scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#setting-up-rollup&quot;&gt;Setting up Rollup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#using-preact-to-render-to-html&quot;&gt;Using Preact to render to HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#components-and-composition&quot;&gt;Components and Composition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#keeping-hydrated&quot;&gt;Keeping hydrated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#making-it-interactive&quot;&gt;Making it interactive&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;An Express app that serves HTML&lt;/h2&gt;
&lt;p&gt;Let&apos;s start with a minimum viable Express server. Create a new folder
and switch into it, then initialise NPM - the default options are fine.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ mkdir preact-ssr
$ cd preact-ssr
$ npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;re using git for version control, you probably want to add the
&lt;code&gt;node_modules&lt;/code&gt; folder to your &lt;code&gt;.gitignore&lt;/code&gt; file at this point.&lt;/p&gt;
&lt;p&gt;Now we&apos;ll install some dependencies we need to get started.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install express compression
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strictly speaking we don&apos;t &lt;em&gt;need&lt;/em&gt; compression, but one of reason we&apos;ve
picked Preact and server-rendering is to squeeze out as much performance
as possible, so let&apos;s roll with the best practice here and save a few
bytes at basically no cost to us.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;src&lt;/code&gt; folder, with a &lt;code&gt;server.js&lt;/code&gt; file inside it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ mkdir src
$ touch src/server.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s set up our basic app in &lt;code&gt;src/server.js&lt;/code&gt; to serve some static
HTML.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const express = require(&apos;express&apos;)
const compression = require(&apos;compression&apos;)

const app = express() // create the express app
app.use(compression()) // use gzip for all requests

// some basic html to show
const layout = `
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
`

app.get(&apos;/&apos;, (request, response) =&amp;gt; {
  // listen for requests to the root path
  response.send(layout) // send the HTML string
})

app.listen(3000) // listen for requests on port 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ node src/server.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then visit http://localhost:3000. All being well, you should see the
HTML rendered out. Check out the &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/tree/554ca38b12b44d80ebb9b55e6c5a734fb4602cbe&quot;&gt;demo repo at this point&lt;/a&gt; if you
need to check what it should look like.&lt;/p&gt;
&lt;p&gt;There is a slight issue here. Try changing the text in the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag
and refresh your page. You should see that it doesn&apos;t update until you
kill your server (using ctrl + c in your terminal) and restart it. That
will get annoying fast, so let&apos;s take the time now to fix our workflow.&lt;/p&gt;
&lt;h2&gt;Setting up Nodemon and NPM scripts&lt;/h2&gt;
&lt;p&gt;Nodemon is a tool that listens for changes to specified files and
restarts the Node process when it sees one. Let&apos;s add it as a
development dependency.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install nodemon --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll add an NPM script so we don&apos;t have to remember the right
incantation every time. You can do this all via the CLI if you like, but
let&apos;s automate as much as we can. In the &lt;code&gt;&amp;quot;scripts&amp;quot;&lt;/code&gt; block of your
&lt;code&gt;package.json&lt;/code&gt;, let&apos;s add a new entry.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;nodemon&amp;quot;: &amp;quot;nodemon --watch src/server.js src/server.js&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This translates roughly to &amp;quot;run &lt;code&gt;node src/server.js&lt;/code&gt;, but also watch
&lt;code&gt;src/server.js&lt;/code&gt; and rerun the command whenever you see that file has
changed&amp;quot;.&lt;/p&gt;
&lt;p&gt;You should now be able to start the app using &lt;code&gt;npm run nodemon&lt;/code&gt;, change
some text in the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and refresh to see it in the browser
immediately. All being well, your app should look something like &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/tree/cba6cfd70be46be97145fd3035d7ff2b73716d0a&quot;&gt;the
demo at this commit&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Setting up Rollup&lt;/h2&gt;
&lt;p&gt;So far, we&apos;ve been using Node&apos;s &lt;code&gt;require&lt;/code&gt; syntax to load dependencies,
but we&apos;re going to want to add packages that are designed to use ES6&apos;s
&lt;code&gt;import&lt;/code&gt;. Let&apos;s use a bundler to paper over the differences between
them so we can avoid gazing too deeply into that particular abyss.
Webpack is a popular bundler, but configuring it is baffling at the best
of times, so we&apos;ll go with Rollup. We don&apos;t need much, just the
ability to use both &lt;code&gt;require&lt;/code&gt; and &lt;code&gt;import&lt;/code&gt; as needed, on both the server
and the client. How hard can it be, right?&lt;/p&gt;
&lt;p&gt;First up, we&apos;ll install &lt;code&gt;rollup&lt;/code&gt;, as well as the &lt;code&gt;node-resolve&lt;/code&gt; plugin.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm install --save-dev rollup @rollup/plugin-node-resolve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let&apos;s tell Rollup to take our &lt;code&gt;src/server.js&lt;/code&gt; file and compile it
into &lt;code&gt;build/server.js&lt;/code&gt;. Create a &lt;code&gt;rollup.config.js&lt;/code&gt; file at the root of
your project. Note that we&apos;re exporting an array of config objects, as
we&apos;ll be adding a client bundle soon. I&apos;d also recommend adding the
&lt;code&gt;build&lt;/code&gt; folder to your &lt;code&gt;.gitignore&lt;/code&gt; file, although it&apos;s not mandatory -
some people prefer to check in the built files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// rollup.config.js

import resolve from &apos;@rollup/plugin-node-resolve&apos;

export default [
  {
    input: &apos;src/server.js&apos;, // take our source file
    output: {
      file: &apos;build/server.js&apos;, // compile it into this file
      format: &apos;cjs&apos;, // use the CommonJS format, which works with Node
    },
    plugins: [resolve()], // use the node-resolve plugin so dependencies get imported properly
  },
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add another script to the &lt;code&gt;package.json&lt;/code&gt; for Rollup. This tells Rollup
to use the config file we just created, watch for changes to imported
files and recompile when it sees a change.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;rollup&amp;quot;: &amp;quot;rollup --config --watch&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update the Nodemon script to use the built file, so it will restart the
server every time Rollup finishes compiling.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;nodemon&amp;quot;: &amp;quot;nodemon --watch build/server.js build/server.js&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have two NPM scripts, &lt;code&gt;rollup&lt;/code&gt; and &lt;code&gt;nodemon&lt;/code&gt;, that we want to run
in the background. At first glance, you might want to try &lt;code&gt;$ npm run rollup &amp;amp;&amp;amp; npm run nodemon&lt;/code&gt;; that won&apos;t work though, as the &lt;code&gt;rollup&lt;/code&gt;
watcher never exits, so the &lt;code&gt;nodemon&lt;/code&gt; script never starts. We need a way
to run them in parallel. You could do this by hand, but fortunately,
there&apos;s a package for doing it in a single script. Let&apos;s install it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install --save-dev npm-run-all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we&apos;ll add a &lt;code&gt;start&lt;/code&gt; script that uses it to call both of the
scripts we prepared earlier, in parallel.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;start&amp;quot;: &amp;quot;npm-run-all --parallel nodemon rollup&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can start both processes with a single command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There were quite a lot of moving parts in there, so compare against &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/tree/b68f8bb75e2ff4ba003a771a9c6643ff8dcb625a&quot;&gt;the
demo repo at this point&lt;/a&gt; if something&apos;s not working.&lt;/p&gt;
&lt;p&gt;If you have a look at your &lt;code&gt;build/server.js&lt;/code&gt; file, it should look pretty
much like &lt;code&gt;src/server.js&lt;/code&gt;, but now we&apos;re all set up for importing ES6
modules, so let&apos;s do that.&lt;/p&gt;
&lt;h2&gt;Using Preact to render to HTML&lt;/h2&gt;
&lt;p&gt;Time to install some more dependencies. We&apos;ll install Preact itself,
the Preact server-side renderer, and &lt;code&gt;htm&lt;/code&gt;, which lets us use
JavaScript&apos;s tagged template strings to build up our components (you
can also use &lt;a href=&quot;https://reactjs.org/docs/introducing-jsx.html&quot;&gt;JSX&lt;/a&gt;, but that requires an additional compile step in
your build process). If you&apos;ve used JSX with React or another framework
before, you&apos;ll probably have to be careful your muscle memory doesn&apos;t
take over here; the syntax isn&apos;t that complicated, but it feels more
like &lt;a href=&quot;https://github.com/mde/ejs&quot;&gt;EJS&lt;/a&gt; (or ERB, for the Rubyists) than JSX sometimes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install preact preact-render-to-string htm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your &lt;code&gt;src/server.js&lt;/code&gt; file, import what we need at the top of the
file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import render from &apos;preact-render-to-string&apos;
import { html } from &apos;htm/preact&apos; // use the provided preact binding
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate some markup and render it to a string using the functions we
imported from &lt;code&gt;preact-render-to-string&lt;/code&gt; and &lt;code&gt;htm&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const body = render(html`&amp;lt;h1&amp;gt;Hello from Preact&amp;lt;/h1&amp;gt;`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then interpolate that variable into our final HTML document.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const layout = `
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
      ${body}
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refresh your browser and you should see the text from your &lt;code&gt;body&lt;/code&gt;
variable, except now we&apos;ve rendered it using Preact before turning it
into HTML. &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/commit/d775be9941d3d0574c33eedcff60697311159873&quot;&gt;Have a look at the repo at this point&lt;/a&gt; if not.&lt;/p&gt;
&lt;h2&gt;Components and Composition&lt;/h2&gt;
&lt;p&gt;One of the reasons Preact, React and other JavaScript frameworks are
popular is because they make it easy to create small, separate
components, then compose those components together to create larger
applications. Let&apos;s refactor our single &lt;code&gt;server.js&lt;/code&gt; file into
components. We&apos;ll then render the whole component tree on the server,
before adding the client-side JavaScript to make it interactive.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;src/components&lt;/code&gt; folder, and add &lt;code&gt;List.js&lt;/code&gt; and &lt;code&gt;PreactApp.js&lt;/code&gt;
files to it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ mkdir src/components
$ touch src/components/List.js
$ touch src/components/PreactApp.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;90% of web development is looping over lists of things, so in
&lt;code&gt;src/components/List.js&lt;/code&gt;, we&apos;ll create a component that takes an array
of data and renders it in a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { html } from &apos;htm/preact&apos;

const List = ({ data }) =&amp;gt; {
  // takes a data prop
  return html`
    &amp;lt;ul&amp;gt;
      &amp;lt;!-- loop over data array --&amp;gt;
      ${data.map(
        (i) =&amp;gt; html`
          &amp;lt;li&amp;gt;
            &amp;lt;!-- render out each item --&amp;gt;
            ${i}
          &amp;lt;/li&amp;gt;
        `
      )}
    &amp;lt;/ul&amp;gt;
  `
}

export default List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;src/components/PreactApp.js&lt;/code&gt;, we&apos;ll import &lt;code&gt;List.js&lt;/code&gt; and pass it
some data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { html } from &apos;htm/preact&apos;
import List from &apos;./List&apos;

const dataArray = [&apos;Item one&apos;, &apos;Item two&apos;, &apos;Item three&apos;]

const PreactApp = () =&amp;gt; {
  return html` &amp;lt;${List} data=${dataArray} /&amp;gt; `
}

export default PreactApp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in &lt;code&gt;src/server.js&lt;/code&gt;, we&apos;ll import &lt;code&gt;src/components/PreactApp.js&lt;/code&gt; and
render that to a string, instead of just writing it inline.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import PreactApp from &apos;./components/PreactApp&apos;
...
const body = render(html`
  &amp;lt;h1&amp;gt;Hello from Preact&amp;lt;/h1&amp;gt;
  &amp;lt;${PreactApp} /&amp;gt;
`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you refresh, you should see the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, followed by the list of
data from our &lt;code&gt;PreactApp&lt;/code&gt; and &lt;code&gt;List&lt;/code&gt; components. Compare against &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/commit/38b201508eadb9df76eeeeff5e26766c9c13d386&quot;&gt;the
repo at this point&lt;/a&gt; if required.&lt;/p&gt;
&lt;p&gt;Next up, let&apos;s add some interactivity to our &lt;code&gt;List&lt;/code&gt; component.&lt;/p&gt;
&lt;h2&gt;Keeping hydrated&lt;/h2&gt;
&lt;p&gt;Hydration is the process of reconciling the server-rendered DOM
structure with what our client-side app thinks should be happening. When
a framework like Preact sets up an app, it works out what it thinks the
DOM should look like and works out the most efficient set of changes to
get it into that state, then adds things like event listeners to the
right elements. When we use hydration, we&apos;re saying to Preact &amp;quot;Don&apos;t
worry about working out what changes are required, the DOM&apos;s already in
the right state. Just add your event listeners and carry on.&amp;quot;&lt;/p&gt;
&lt;p&gt;Before we can do that, we need to load some client-side JavaScript, as
currently we&apos;re just sending HTML with no &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags in sight. We
have to do a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a source file for our client bundle&lt;/li&gt;
&lt;li&gt;Configure Rollup to compile it into the right format for the browser&lt;/li&gt;
&lt;li&gt;Configure Express to serve it as a static file&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag into the HTML Express is serving&lt;/li&gt;
&lt;li&gt;Add some interactive elements into our app!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Start by creating the source file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ touch src/client.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;src/client.js&lt;/code&gt;, import the &lt;code&gt;PreactApp&lt;/code&gt; component, and tell Preact
where in the DOM we want to consider as our app.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { hydrate } from &apos;preact&apos;
import PreactApp from &apos;./components/PreactApp&apos;

hydrate(PreactApp(), document.getElementById(&apos;root&apos;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;src/server.js&lt;/code&gt;, add a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a matching ID around the
&lt;code&gt;PreactApp&lt;/code&gt; component.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;
  &amp;lt;${PreactApp} /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;rollup.config.js&lt;/code&gt;, add a second config object into the array. This
has different settings from the &lt;code&gt;server.js&lt;/code&gt; file, as the browser
doesn&apos;t understand Node&apos;s CommonJS syntax natively.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import resolve from &apos;@rollup/plugin-node-resolve&apos;

export default [
  {
    input: &apos;src/server.js&apos;,
    output: {
      file: &apos;build/server.js&apos;,
      format: &apos;cjs&apos;, // CommonJS format for Node
    },
    plugins: [resolve()],
  },
  {
    input: &apos;src/client.js&apos;,
    output: {
      file: &apos;build/client.js&apos;,
      format: &apos;es&apos;, // ES Module format for modern browsers
      name: &apos;client&apos;,
    },
    plugins: [resolve()],
  },
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you run &lt;code&gt;npm run start&lt;/code&gt;, you should see a &lt;code&gt;client.js&lt;/code&gt; popping
into the &lt;code&gt;build&lt;/code&gt; folder alongside &lt;code&gt;server.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, let&apos;s tell Express how to serve our client bundle as a static
file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.get(&apos;/client.js&apos;, (request, response) =&amp;gt; {
  response.sendFile(&apos;client.js&apos;, {
    root: __dirname, // this will be the build folder
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can visit http://localhost:3000/client.js and see some compiled
JavaScript. Let&apos;s pop that in a script tag in &lt;code&gt;src/server.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const layout = `
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
      ${body}
      &amp;lt;script type=&amp;quot;module&amp;quot; src=&amp;quot;client.js&amp;quot; async&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you refresh http://localhost:3000, you have a working, hydrated
Preact application that does... precisely nothing (if you don&apos;t, &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/commit/1f328b78aca795471af3b53c7426da9b3097101d&quot;&gt;have
a look at the example repo at this stage&lt;/a&gt;). Let&apos;s fix that.&lt;/p&gt;
&lt;h2&gt;Making it interactive&lt;/h2&gt;
&lt;p&gt;All we need to do now is add some functionality into our
&lt;code&gt;src/components/List.js&lt;/code&gt; component. On the server it gets compiled to
HTML, while our client bundle will look out for the matching DOM
elements and set up the required listeners when it hydrates. We&apos;ll add
a button to each list item, another item to show how many times they
were clicked, and we&apos;ll use Preact&apos;s &lt;code&gt;useState&lt;/code&gt; hook to track that
data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { html } from &apos;htm/preact&apos;
import { useState } from &apos;preact/hooks&apos;

const List = ({ data }) =&amp;gt; {
  // takes a data prop
  // how many clicks have we counted? Default to 0
  const [count, setCount] = useState(0)

  // shared event handler
  const handleClick = () =&amp;gt; {
    setCount(count + 1)
  }

  return html`
    &amp;lt;ul&amp;gt;
      ${data &amp;amp;&amp;amp;
      data.map(
        (i) =&amp;gt; html`
          &amp;lt;li&amp;gt;
            &amp;lt;!-- listen for button clicks --&amp;gt;
            ${i}: &amp;lt;button onClick=${handleClick}&amp;gt;Click me&amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        `
      )}
      &amp;lt;li&amp;gt;
        &amp;lt;!-- list how many clicks we&apos;ve seen, with the right plural --&amp;gt;
        ${count} ${count === 1 ? &apos;click&apos; : &apos;clicks&apos;} counted
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  `
}

export default List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have a functional Preact application that still does as
much of its work up-front on the server as possible. Again, if you&apos;re
having issues, try &lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/commit/172be9630f3c9c01933e8784bc84102e04c3b5c1&quot;&gt;comparing against the example repo&lt;/a&gt;. Hopefully
that is a useful starting point - there&apos;s still a lot of low-hanging
fruit that we could build into this (Things like compiling CSS,
minifying our scripts, transpiling JavaScript using Babel, and rendering
more complex state), but I&apos;ll save those for a future tutorial.&lt;/p&gt;
&lt;p&gt;If you have feedback or suggestions, then hit me up &lt;a href=&quot;https://twitter.com/k_macquarrie&quot;&gt;on Twitter&lt;/a&gt; or
&lt;a href=&quot;https://github.com/velvetkevorkian/preact-ssr/issues&quot;&gt;open an issue on the repo&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 02 Apr 2020 00:00:00 GMT</pubDate></item><item><title>Testing server-side Javascript with Jest</title><link>https://kylemacquarrie.co.uk/blog/testing-server-side-js/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/testing-server-side-js/</guid><description>&lt;p&gt;If you&apos;re using something like Next.js to render JavaScript on the
server you might have written something along these lines, to get the
user-agent string from the request object if run on the server, or from
the global window object if we&apos;re in the browser. This will blow up on
the server if you receive a request without a user-agent header. (I have
no idea what is sending requests with no user-agent header but something
was, and it was generating a lot of 500 errors in production.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const userAgent = req.headers[&apos;user-agent&apos;] || window.navigator.userAgent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s a pretty naive implementation, so let&apos;s refactor it into a
function. We can use some &lt;a href=&quot;https://ponyfoo.com/articles/null-propagation-operator&quot;&gt;null propagation operators&lt;/a&gt; to catch undefined properties, and wrap it in a
try/catch for good measure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function getUserAgent(req) {
  try {
    return req?.headers?.[&apos;user-agent&apos;] || window?.navigator?.userAgent
  } catch (err) {
    return &apos;&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hardly the most elegant solution, but it does the job. To check it does
in fact do the job, let&apos;s write some tests, using Jest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;it(&apos;returns the user agent header if one is supplied&apos;, () =&amp;gt; {
  const result = getUserAgent({
    headers: {
      &apos;user-agent&apos;: &apos;foo&apos;,
    },
  })
  expect(result).toBe(&apos;foo&apos;)
})

it(&amp;quot;falls back to window.navigator if the header isn&apos;t present&amp;quot;, () =&amp;gt; {
  const result = getUserAgent({
    headers: {},
  })
  expect(result).toBe(window.navigator.userAgent)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, so far so good, but how can we test the case where there&apos;s no
&lt;code&gt;user-agent&lt;/code&gt; header and no &lt;code&gt;window.navigator&lt;/code&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;it(&apos;returns an empty string&apos;, () =&amp;gt; {
  const result = getUserAgent({})
  expect(result).toBe(&apos;&apos;)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will fail, since Jest runs with JSDOM by default. &lt;code&gt;window&lt;/code&gt; is part
of the global namespace, and just like in a real browser
&lt;code&gt;window.navigator&lt;/code&gt; is read-only, so you can&apos;t set it to &lt;code&gt;undefined&lt;/code&gt;. I
couldn&apos;t get Jest to mock it properly either.&lt;/p&gt;
&lt;p&gt;The easiest solution seems to be to extract the server-side test cases
to a separate file, where you can tell Jest it&apos;s running in a Node
environment. Add the Jest environment pragma to the top of your server
test files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * @jest-environment node
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way, JSDOM isn&apos;t even set up for that test suite and you can test
weird isomorphic edge cases to your heart&apos;s content. The only other
thing to watch out for is if you refer to JSDOM in your test setup
files, you might need to catch that too:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (typeof jsdom !== &apos;undefined&apos;) {
  jsdom.reconfigure({
    url: &apos;https://www.foo.com/bar&apos;,
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actually, the best solution is not to read the user-agent string at all,
but sometimes it has to be done :(.&lt;/p&gt;
</description><pubDate>Wed, 30 Oct 2019 00:00:00 GMT</pubDate></item><item><title>Simple deploys with NPM and rsync</title><link>https://kylemacquarrie.co.uk/blog/simple-deploys-with-npm-and-rsync/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/simple-deploys-with-npm-and-rsync/</guid><description>&lt;p&gt;If you&apos;re building a JavaScript heavy static site (perhaps you&apos;re
using something like &lt;code&gt;vue-cli&lt;/code&gt; or &lt;code&gt;create-react-app&lt;/code&gt;, or perhaps you&apos;re
rolling your own Webpack config like I&apos;m doing in the examples), using
something like Capistrano for deployment is a lot of extra work, while
messing about with FTP is annoying and error-prone. Let&apos;s split the
difference, and use some simple tools to make life easier.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A machine with &lt;code&gt;rsync&lt;/code&gt; (any Mac, most (all?) flavours of Linux,
Windows 10 Linux subsystem)&lt;/li&gt;
&lt;li&gt;Node JS with &lt;code&gt;npm&lt;/code&gt; (or &lt;code&gt;yarn&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;SSH access to your host&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Initial implementation&lt;/h2&gt;
&lt;p&gt;Throughout the examples, I&apos;m assuming that &lt;code&gt;npm run build&lt;/code&gt; will compile
your project into a &lt;code&gt;project-name&lt;/code&gt; folder with an &lt;code&gt;index.html&lt;/code&gt;, which
you can then serve from the path of your choice on the server. To start
with, we can run the build step, then manually run the command to copy
the files to the remote host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
&amp;quot;scripts&amp;quot;: {
  &amp;quot;build&amp;quot;: &amp;quot;webpack --config webpack.production.js&amp;quot;
}

// terminal:
$ rsync -avz --delete project-name deployuser@server.domain:/path/to/static/files/&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but means you have to keep track of that deploy command and
all those &lt;code&gt;rsync&lt;/code&gt; flags are just begging for a typo. Let&apos;s automate it
a little.&lt;/p&gt;
&lt;h2&gt;Make it a script&lt;/h2&gt;
&lt;p&gt;You can run shell commands from NPM scripts, so we&apos;ll clean that up
into a single, self-documenting, easily typed command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
&amp;quot;scripts&amp;quot;: {
  &amp;quot;build&amp;quot;: &amp;quot;webpack --config webpack.production.js&amp;quot;,
  &amp;quot;deploy&amp;quot;: &amp;quot;webpack --config webpack.production.js &amp;amp;&amp;amp; rsync -avz --delete project-name deployuser@server.domain:/path/to/static/files/&amp;quot;
}

// terminal
$ npm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Scripts can call other scripts&lt;/h2&gt;
&lt;p&gt;NPM scripts can not only call shell commands, they can call other NPM
scripts. We&apos;ll use a dedicated &lt;code&gt;transfer&lt;/code&gt; script, and call that along
with &lt;code&gt;build&lt;/code&gt; when we &lt;code&gt;deploy&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
&amp;quot;scripts&amp;quot;: {
  &amp;quot;build&amp;quot;: &amp;quot;webpack --config webpack.production.js&amp;quot;,
  &amp;quot;transfer&amp;quot;: &amp;quot;rsync -avz --delete project-name deployuser@server.domain:/path/to/static/files/&amp;quot;,
  &amp;quot;deploy&amp;quot;: &amp;quot;npm run build &amp;amp;&amp;amp; npm run transfer&amp;quot;
}

// terminal
$ npm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Refactor paths into NPM variables&lt;/h2&gt;
&lt;p&gt;As a final step, if you&apos;re sharing your code publicly you might want to
avoid hard-coding the deploy user and path into the repo. NPM lets you
add additional global config variables via the &lt;code&gt;.npmrc&lt;/code&gt; file, which
defaults to &lt;code&gt;~/.npmrc&lt;/code&gt;. We can make our deploy path part of that config
and refer to it in &lt;code&gt;package.json&lt;/code&gt; with the &lt;code&gt;$npm_config_&lt;/code&gt; prefix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// .npmrc
deploy_path = deployuser@server.domain:/path/to/static/files/

// package.json
&amp;quot;scripts&amp;quot;: {
  &amp;quot;build&amp;quot;: &amp;quot;webpack --config webpack.production.js&amp;quot;,
  &amp;quot;transfer&amp;quot;: &amp;quot;rsync -avz --delete project-name $npm_config_deploy_path&amp;quot;,
  &amp;quot;deploy&amp;quot;: &amp;quot;npm run build &amp;amp;&amp;amp; npm run transfer&amp;quot;
}

// terminal
$ npm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also have an &lt;code&gt;.npmrc&lt;/code&gt; file in your project directory, if you
don&apos;t mind either checking it in to version control or adding it to
your project&apos;s &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;Bonus tip for Ruby users&lt;/h2&gt;
&lt;p&gt;It&apos;s worth noting you can do much the same thing using Ruby and &lt;code&gt;rake&lt;/code&gt;
instead of Node and &lt;code&gt;npm&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;# Rakefile

namespace :deploy do
  desc &amp;quot;Build the website from source&amp;quot;
  task :build do
    status = system(&amp;quot;npm run build&amp;quot;)
    puts status ? &amp;quot;OK&amp;quot; : &amp;quot;FAILED&amp;quot;
  end

  desc &amp;quot;Deploy website via rsync&amp;quot;
  task :push do
    status = system(&amp;quot;rsync -avz --delete project-name deployuser@server.domain:/path/to/static/files/&amp;quot;)
    puts status ? &amp;quot;OK&amp;quot; : &amp;quot;FAILED&amp;quot;
  end
end

desc &amp;quot;Build and deploy website&amp;quot;
  task :deploy =&amp;gt; [&amp;quot;deploy:build&amp;quot;, &amp;quot;deploy:push&amp;quot;] do
end

# terminal
$ rake deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;re using NPM already I don&apos;t see much reason to use &lt;code&gt;rake&lt;/code&gt;
instead but it might be useful if you&apos;re using other Ruby tools.&lt;/p&gt;
</description><pubDate>Tue, 20 Nov 2018 00:00:00 GMT</pubDate></item><item><title>Web Archaeology</title><link>https://kylemacquarrie.co.uk/blog/web-archaeology/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/web-archaeology/</guid><description>&lt;p&gt;There&apos;s something wonderful and magical about old web design books. Old
is a relative term, of course: in this case, old is a mere thirteen
years. But these are records of a bygone age, and most of the subjects
have disappeared. Some survive, their old bones reshaped and dressed
with new flesh to suit the modern age. Some remain untouched, like flies
preserved in amber. But mostly, they are gone, and we can only infer
their former existence with the help of secondary sources.&lt;/p&gt;
&lt;p&gt;I&apos;m drawn to these tomes. Despite their age, they have something of the
&lt;a href=&quot;http://new-aesthetic.tumblr.com/&quot;&gt;New Aesthetic&lt;/a&gt; about them. Their subject matter
is fundamentally ill-suited to being depicted on the printed page. Shorn
of their interactivity, their links to each other, websites lose all the
things which set them apart from print. Like trying to understand a
ballet&apos;s choreography through half a dozen photos, you get fascinating
flashes of an idea. The essence, though, is gone from them, as surely as
the essence is gone from the stuffed animal relics in the museum.&lt;/p&gt;
&lt;p&gt;The books they are contained in have become artefacts in their own
right. Freed from the screen, they form a fascinating collage, contents
apparently selected for aesthetic value rather than how well they convey
any sense of information. Haphazardly scattered across the page, they
transcend the limitations of their source material. A still from a
grainy pre-Youtube video goes from a tiny window on a bulky CRT screen
to a full page spread. Dithered and pixelated almost beyond recognition,
they burst from the screen on to the page. They have a curious vitality
to them — a reminder, perhaps, of the potential for expression this
medium holds. These early pioneers, mapping that landscape, getting lost
in the in the digital desert and dying of underexposure, so that others
might find what&apos;s left behind and take up the quest anew, in a land
safer for the sacrifices made.&lt;/p&gt;
&lt;p&gt;At the collision of old and new media (and was there ever a term less
appropriate? Perhaps it should be old media and less old media, in this
case) we can dig through these documents, and marvel at the ingenuity of
the ancients.&lt;/p&gt;
</description><pubDate>Wed, 15 Jan 2014 00:00:00 GMT</pubDate></item><item><title>What can games learn from UX?</title><link>https://kylemacquarrie.co.uk/blog/what-can-games-learn-from-ux/</link><guid isPermaLink="true">https://kylemacquarrie.co.uk/blog/what-can-games-learn-from-ux/</guid><description>&lt;p&gt;It started, as it often does, with a tweet. &amp;quot;What can UX learn from
gaming?&amp;quot; asked &lt;a href=&quot;https://twitter.com/TheDrum/status/396064422617444352&quot;&gt;@TheDrum&lt;/a&gt;, followed by a link to
&lt;a href=&quot;https://www.thedrum.com/news/2013/10/31/what-can-ux-learn-gaming&quot;&gt;this piece&lt;/a&gt;. Frankly, anyone who believes games
offer a good model for UX to imitate probably hasn&apos;t actually tried
playing that many games, especially on the &lt;a href=&quot;https://knowyourmeme.com/photos/508702-the-glorious-pc-gaming-master-race&quot;&gt;One True Format&lt;/a&gt;, although a cursory Twitter search for &amp;quot;PS3 Update&amp;quot;
should demonstrate that consoles are far from immune from the scourge of
thoughtless design. The question needs to be reversed — &amp;quot;What can
gaming learn from UX?&amp;quot;&lt;/p&gt;
&lt;p&gt;The answer, I think, is quite a lot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kylemacquarrie.co.uk/images/bud-is-confident-about-ux.png&quot; alt=&quot;Bud is confident about his
UX&quot;&gt;&lt;/p&gt;
&lt;p&gt;On the web, we live in an age where people designing and building things
want to make them work as well as possible, &lt;a href=&quot;https://alistapart.com/article/responsive-web-design/&quot;&gt;regardless of what device
you&apos;re on&lt;/a&gt;. Even &lt;a href=&quot;https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/&quot;&gt;old browsers&lt;/a&gt; should still get most of what&apos;s on offer. Recently,
the idea of &lt;a href=&quot;http://web.archive.org/web/20131224043652/https://blog.hood.ie/2013/11/say-hello-to-offline-first/&quot;&gt;Offline First&lt;/a&gt; design has taken root—
that is, web apps which will still work and behave reasonably &lt;em&gt;even on a
flaky or non-existent connection&lt;/em&gt;. Let me repeat that: &lt;strong&gt;web apps&lt;/strong&gt; that
work with the connection the user has, not the connection you want them
to have.&lt;/p&gt;
&lt;p&gt;Meanwhile, in the land of AAA, multi-million dollar game releases:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kylemacquarrie.co.uk/images/error37.jpg&quot; alt=&quot;Diablo 3 Error 37&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://kylemacquarrie.co.uk/images/simcityk-1024x576.jpg&quot; alt=&quot;Sim City error message&quot;&gt;&lt;/p&gt;
&lt;p&gt;Both of these games can be played single player, but require you to be
constantly online regardless. Neither of these games have any particular
need to be online; before they were released questions about the
reasoning behind making them always online were raised, and went without
substantive answers. Both had catastrophic clusterfucks for launches,
with thousands of users unable to play the game they paid significant
sums of money for until days and sometimes weeks later.&lt;/p&gt;
&lt;p&gt;Where are the people in EA and Blizzard looking out for the users? They
either don&apos;t exist, or they&apos;re being ignored by the people making the
architectural decisions. Neither leaves a good taste.&lt;/p&gt;
&lt;p&gt;EA is, of course, a serial and a serious offender. One of the things
that prompted me to write this screed was &lt;a href=&quot;https://www.rockpapershotgun.com/so-i-thought-id-play-battlefield-4s-single-player-about-that&quot;&gt;John Walker&apos;s account on
Rock Paper Shotgun&lt;/a&gt; of his attempt to play
Battlefield 4&apos;s single player campaign. Even if we leave aside any
server or connection issues (which we shouldn&apos;t, but bear with me)
there are so many things wrong with this process it&apos;s difficult to know
where to start. Requiring both your own digital distribution service and
a fucking &lt;em&gt;browser plugin&lt;/em&gt; just to launch the single player campaign is
just such a bizarre idea to anyone outside of EA&apos;s senior management.
There are so many hoops to jump through which add nothing to the
experience of most players, and so many possible points of failure just
waiting for a little server glitch to throw everything out of sync.
Anyone familiar with design will probably have heard that &amp;quot;Perfection
is achieved, not when there is nothing more to add, but when there is
nothing left to take away&amp;quot;. This is pretty clearly the opposite of
that.&lt;/p&gt;
&lt;p&gt;It&apos;s worth thinking about how Origin fits in this context. Conceived as
a competitor for Steam, it is fundamentally the result of EA&apos;s
compulsion to squeeze every dollar possible from their customers. EA
look at the revenue Steam generates for Valve, and think &amp;quot;I want more
of that&amp;quot;. Fair enough — they, unlike Valve, are a public company who
have a responsibility to maximise shareholder profit. In this day and
age, however, you cannot simply compete on features — you have to
compete on &lt;em&gt;experience&lt;/em&gt;. Steam, for all its many and well documented
flaws, offers both superior features and a better experience.&lt;/p&gt;
&lt;p&gt;Speaking of things Steam is better than, perhaps the crowning glory of
inexplicably shit, totally broken game-related UX is the hateful bundle
of inscrutable idiocy that comprises Games For Windows Live. While
Microsoft are experts in the &lt;a href=&quot;https://arstechnica.com/information-technology/2013/11/its-the-little-things-how-small-conundrums-make-many-hate-computers/&quot;&gt;death by a thousand cuts approach to
UX&lt;/a&gt;, GFWL was more like a &lt;a href=&quot;https://www.rockpapershotgun.com/a-brief-moment-of-perverse-gratitude-to-gfwl&quot;&gt;savage beating of
ineptitude&lt;/a&gt;. While it does now appear to have been
&lt;a href=&quot;https://www.pcgamer.com/games-for-windows-live-may-shut-down-next-year/&quot;&gt;taken out and put out of its misery&lt;/a&gt;, anyone
who&apos;s had the misfortune of using the &amp;quot;service&amp;quot; which, like Origin,
aimed to replicate most of the features of Steam, will probably have
tales to tell of bizarre login loops, game-blocking client updates,
server problems, saves going missing, and more. The sting in the tail
here is, naturally, that it provides &lt;em&gt;absolutely no added value to
players&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Even absent massive cock-ups like GFWL, a litany of more minor
complaints can be found in games of all shapes and sizes. Minor UI
elements and affordances we take for granted on the web are absent or
incomplete. Required password or content codes use fields that can&apos;t be
pasted into. I&apos;m not even getting into actual gameplay — I could
probably write a book on crappy checkpointing alone, but fortunately for
all concerned I&apos;d probably have an aneurysm first.&lt;/p&gt;
&lt;p&gt;There are glimmers of hope. Steam at least attempts to add value for the
user — notably, by competing on price in their legendary sales.
Elsewhere, the Humble Bundle retail experience is one of the best online
shopping experiences I&apos;ve had, and their Android app is a good
alternative to manually installing a bunch of APKs. The actual gameplay
of most games is strong enough to create compelling experiences which
can&apos;t be replicated in any other medium.&lt;/p&gt;
&lt;p&gt;Too often, though, layers of thoughtless decisions transform what should
be an enjoyable experience into one that must be endured. If games
really want to be seen as leaders in user experience, they need to look
at the work being done in other areas of interactive design. More
importantly, players need to have someone looking out for their
interests during development. Otherwise we&apos;re doomed to repeat the
mistakes of the past, stuck in GFWL login loop purgatory for ever.&lt;/p&gt;
</description><pubDate>Sun, 08 Dec 2013 00:00:00 GMT</pubDate></item></channel></rss>