07 August 2018

Tech Used On This Site

The Order of the Stick

Disclaimer: All images in prototype (and above) were taken from Rich Burlew of Giant In The Playground.

Examples

I decided to roll my own static-site generator. I was thinking of grabbing one of the million different frameworks like Hugo or next.js, but wanted more control over some things that had becom pet peeves. I’d seen static-side generators that doubled the amount of data sent because of components left in the JS bundles. I wanted to use markdown for blog-like sections, but not be locked into it as a workflow. It was also important to have good fallback support when JS is disabled.

I was also feeling a little nostaglic for how personal websites were like in the 2000s. They were more open - insecure :p - like they a network-visible Home folder. There was something fun about poking around a site and finding unreleased content or pirated games and music. Most frameworks now obfuscate URIs and return 403/404 for unexpected accesses. It feels sterile.

I didn’t end up needing too much code to get this to work. The bulk of the work is done by typed-html to generate HTML and Surplus for dynamic components. Most of the custom work ended up going into glue scripts. Things like generating Typescript types for URIs, server-side rendering, JS/CSS inlining, markdown rendering, etc.

Features

  • React JSX as a template language
    • typechecks resource URIs at compile time
  • small footprint. The goal JS+CSS budget was < 50 KB gzipped, but ended up way below. The minimum required bundles are 9 KB total on static pages like the blog-posts or home page and 13 KB on others
  • highly customizable page layouts
    • fully static pages if desired (https://www.derekh.ca/)
      • kind of gave up on this and now include a small bundle with logic for preloading resources
    • embed individual dynamic JS components within static content
    • No enforced templates. Common look and feel is created using components. Each page can have its own unique look and scripts aside from a small common runtime bundle.
  • write posts with markdown and create RSS feeds
  • convention over configuration
    • Templates use a .html.tsx file-ending
    • URIs are generated based on the folder and file organization and a folder isolated from others
  • fully navigable when JS disabled (where reasonable)
  • Speed up future page visits by preloading resources (html, JS, CSS, images). Preloading is OPT IN to prevent silliness
  • minimize the size of JS bundles. Split JS at a route level
  • render time optimizations. Inline page-specific CSS, JS into HTML to reduce HTTP requests
  • production build optimization
    • inlineable JS/CSS
    • minifiable HTML using chrome-render and uglifiers
    • automatic cache-busting of static resources based on contents
    • build failure if internal URIs cannot be resolved
  • development ergonomics
    • live reloading
    • validate HTML for errors
    • Typescript used for templates
    • CSS, SCSS, or SASS can be used interchangeably
    • webpack builds

Even Lighthouse is pretty happy. My heaviest page (in terms of JS) still gets tops marks in performance.

lighthouse results

Prototype Comic Viewer And Other Examples

Before writing the rest of the site, I made a comic viewer as a test. It doesn’t look it, but it’s a self-contained single-page application that fetches image resources dynamically to create a snappier user experience. I think I prefer a collection of SPAs over a single global SPAs. Code bundles are smaller, it promotes composeability, and there should be less accidental coupling.

I didn’t go with the typical hashbang in the URI to handle navigation. The URIs look normal because the click event listener is hijacked when the event target is one of the next/previous comic anchor elements. The event is cancelled and an image src holding the comic and other related elements are updated using the Surplus runtime.

This is hidden from the user by using the History API to update the browser history. Unless you were checking the developer tools for network requests you’d probably have no idea it was an SPA.

If JS is disabled the page still works because the individual pages are all created statically. Dynamic JS components are used selectively instead of the entire page being created as a component. From a rendering perspective this is great since rendering JS components will never match the speed of plain DOM. From personal experience - and after using sites with heavy amount of JS components - each new JS-driven component increases the base computation load. Doesn’t matter what gets said about efficient DOM diffing and so on, you can feel a creeping lag.

TODO/New Features

  • Improve compatibility of JSX between typed-html and Surplus. Both are incompatible with each other due to different React replacement imports and compilation steps.
    • Had an idea to just make copies of files and rewrite the import. Problematic because we still need to manually ensure there isn’t code sharing. Also terrible for IDE completion because there are now two versions of a function.
  • better CSS integration with typed HTML
    • currently need to manually provide an index typescript file and link to all CSS files from there. Easy to forget about an important CSS dependency
    • also prevents linking to CSS in typed-html JSX components
  • need to create a separate bundle for the surplus runtime so that it isn’t included in the root bundle. I’ve been forced to include it on every page because of the preload script.
  • package everything together into a webpack plugin?
    • maybe define a new file type (.TML)
    • for now it’s useful being able to see the intermediate generated files. Easy first step is to pull the scripts into their own directory and make an NPM package.
  • charts
    • dynamic chart that can load new data client side
    • leverage chartist or something that creates SVG
  • Server side rendering
    • Need to think about the best way to hydrate with data. Probably need to build a small util library that is exposed on window and checked for on first mount.
    • detect all below the fold images and replaces the src attribute with data-src for use with on-demand image loading libraries.