davidwiles.net (This Website)
January 2nd, 2023
This website was created without the use of a static site generator or other tools that work "out-of-the-box". This
is partly due to my desire to create something new, but mostly because I didn't want to learn a new framework just
to create a website. I wanted to create a website that was fast, cheap,
and easy to maintain. I use yarn
for project management and webpack
to
process JavaScript to make maintenance and reproducibility a bit simpler.
This website is just HTML, which makes it as fast as possible. My index.html loads a total of 80.48kB over 4 requests (the html, the fonts, and the favicon). If you already have the fonts and favicon cached by your browser, the page load will only be 4.39kB! I am able to limit the number of requests per page load by inlining all JS and CSS.
I host everything on my site from my site, no CDNs used at all. This makes it a bit slower
since it is all hosted from a free-tier static site on Render, but it ensures
complete privacy for you. Loading index.html for me with a private window in Firefox takes
about 1.33s: 810ms for DOMContentLoaded
to fire and ~0.5s to load the fonts and favicon.
A cached page
load will be 419ms: 107ms for DOMContentLoaded
and ~300ms for the rest; not bad for a
website hosted for free.
This project is composed of three parts (as is any website): HTML, CSS, and JavaScript.
- HTML: No static site generators, just my custom template engine.
- CSS: I use SCSS to reduce the amount of duplicated selectors in my code. Yes, this site would be less prone to versioning issues down the road with pure CSS, but I think the source is much more maintainable when SCSS is used over CSS.
- JavaScript: The JS is 100% Vanilla (i.e. no frameworks) and minified with Webpack. One library is used for code highlighting (Prism), but only on pages which have code examples.
HTML: Custom Template Engine
My custom template engine is written with Scala Native and uses a syntax based on the Go template syntax. The only features it has are:
- Replaces template placeholders with the contents of the specified template file.
- Replaces variables with pre-defined environment variables.
These features are just enough to minimize duplicated code while still making it as easy as writing pure HTML. You
can find the source code for the project at github.com/david-wiles/templater.
I currently use this template engine with a short bash script that walks through the html
directory and
builds the HTML files.
SCSS to CSS
node-sass
is easily the most heavyweight dependency of this project, but I do think that
it is necessary if only for the benefit of maintainability. This adds variables, macros, modules, and selector
nesting to CSS. Making a change to a nested selector in CSS is a nightmare, but in SCSS it can be a single-line
change. Although there is talk of updating the CSS standard, SCSS probably won't be going away due to how CSS parsing works.
Vanilla JavaScript
There isn't really any need for JavaScript on my site, but it is nice to have some interactive features. Other than prismJS for code highlighting, the only JavaScript on the page is used for theme toggling (check out the moon icon in the upper-righthand corner!).
Script and StyleSheet Inlining
CSS and JS inlining is somewhat of a "secret sauce" to get sites to load faster. Replacing <link> and <script> tags
with the actual source for those links prevents the browser from needing to make extra HTTP requests and lets the
browser's parser continue through the page without needing to stop. It is relatively easy to add this to a project.
I have a script, inline.js, in my project which does this in a separate step during my yarn
run
build:prod
compilation.
First, the script reads the given directory's files:
let files = fs.readdirSync(dir);
files.forEach((file) => { })
If the "file" is a directory, we recursively call the script's main function again. Otherwise, we search for links and scripts in the text:
let html = fs.readFileSync(file).toString();
let links = findLinks(html);
findLinks
will use regex to search the text
let scripts = html.matchAll(/<script\s+(defer){0}\s*src="([.a-zA-Z0-9-\/]+\.js)"\s*(defer){0}\s*><\/script>/gm);
let result = scripts.next();
// snip
let stylesheets = html.matchAll(/<link\s+rel="stylesheet"\s+href="([.a-zA-Z0-9-\/]+\.css)"\s*\/>/gm);
result = stylesheets.next();
Finally, the matching file's contents will replace those link and script tags
links.forEach((link) => {
if (cache[link.file] === undefined) {
cache[link.file] = link.type === "js" ?
"<script>" + fs.readFileSync(base + link.file).toString() + "</script>" :
"<style>" + fs.readFileSync(base + link.file).toString() + "</style>";
}
html = html.replace(link.tag, function () { return cache[link.file]; });
});
This file can be found at inline.js, and the entire source code of this website is at github.com/david-wiles/davidwiles.net.