I guess this is my first blog post. Might as well write about the tools I used at the beginning to create this blog. I’m pretty sure I overcomplicated everything, basically creating a static site generator from scratch, but I’m hoping that maybe it will serve as a template for future work. Or at least a lesson learned in building custom applications.

Metalsmith

Metalsmith is a really simple static site generator, in that it doesn’t do much on it’s own. But it is really easy to extend it with plugins to manipulate files and associated metadata, and creating my own plugins for custom file transformations was really simple. It’s not that many lines either.

function plugin () {
  return (files, metalsmith/*, done */) => {
    Object.keys(files).forEach(file => { /* ... */ })
  }
}

const ms = Metalmsith(__dirname)
  // ...
  .use(plugin())
  // ...

I made use of this plugin API quite often, from compiling templates to optimizing assets, and it’s the flexibility and simplicity that’s what makes Metalsmith so powerful.

hyperscript

There are a lot of templating engines for JavaScript in the wild, each with their own syntaxes and quirks. After hopping from one engine to another engine to another engine, I eventually I just gave up on all of them and decided to write my “templates” with hyperscript. Hyperscript allows me to create HTML with JavaScript. I found that writing templates with JavaScript a much nicer experience, because I can include whatever modules I want easily without being forced to use a exotic register or import syntax, and I didn’t have to learn another language to manage logic in my templates.

const h = require('hyperscript')
const {article, header, h1, time, div} = require('hyperscript-helpers')(h)
const strftime = require('strftime')

module.exports = function post ({title, date, contents}) {
  const datetime = strftime('%Y-%m-%d', date)
  const displaydate = strftime('%d %B %Y', date)
  return article('.post', [
    header('.post__metadata', [
      time('.post__date', {datetime, title: datetime}, displaydate),
      h1('.post__title', title)
    ]),
    div('.post__content.markup', {innerHTML: contents})
  ])
}

A layout or a page can be written in JavaScript, and my Metalsmith plugin can render the JavaScript into HTML in just a few lines of code.

const data = Object.assign({}, metalsmith.metadata(), files[file])
files[file].contents = new Buffer('<!DOCTYPE html>' + template(data).outerHTML)

I also could have used JSX with React, but I didn’t feel like another compiler was worth the effort. And it’s not like I need React for anything else—pushState isn’t really my jam. (Then again, I did spend many hours putting this setup together. Maybe a JSX setup would have been faster.)

markdown-it

I went with markdown-it to render my Markdown files, with plugins to add custom attributes and heading anchors, and used highlight.js for syntax highlighting. I ended up writing a custom Metalsmith Markdown plugin mainly because it was literally just iterating over the files and applying the same renderer on each Markdown file. The Markdown contents would then be included in a template, set using {innerHTML: contents}.

PostCSS

Stylesheets are processed through PostCSS and separate from Metalsmith. At the moment, I’m only using the postcss-import and postcss-reporter plugins, so it’s a bit overkill for now, but I might want this to be a sort of template, and adding more plugins is easy.

I’m using the PostCSS API directly; as I’m not using any specialized build tool, this will give me greatest flexibility when integrating with my custom setup.

const isProduction = process.env.NODE_ENV === 'production'

const processor = postcss([
  require('postcss-import'),
  require('postcss-reporter')({
    clearReportedMessages: true,
    throwError: isProduction
  })
])

function processCSS (opts) {
  opts = Object.assign({map: !isProduction}, opts)
  readFile(opts.from)
    .then(css => processor.process(css, opts))
    .then(result => {
      result.warnings().forEach(w => { console.warn(w.toString()) })
      return [result.css, result.map && result.map.toString()]
    })
    .then(([css, map]) => Promise.all([
      writeFile(opts.to, css),
      map ? writeFile(opts.to + '.map') : null
    ]))
    .catch(err => {
      if (err.name === 'CssSyntaxError') {
        console.error(err.message)
        console.error(err.showSourceCode())
      } else {
        console.error(err)
      }
    })
}

Optimizations

Minifiers (html-minifier, clean-css) are included in the scripts that generate the HTML and CSS, but are only used when NODE_ENV is set to "production".

After producing the minified files, a second Metalsmith runner takes all the minified files, revisions the assets by generating a hash, replaces references to revisioned assets, and then creates a gzipped copy of compressible files before finally saving the optimized files.

BrowserSync

BrowserSync is used for CSS injection and HTML reloading. It is useful for tweaking stylesheets or writings posts as it automatically injects or refreshes files when files change and are rebuilt. However BrowserSync also comes with many other features I don’t really use or already come with browser developer tools (network throttling, multiple device syncing, remote debugger, CSS outlining, though I do imagine they are very useful to other people), and I could probably replace BrowserSync with a simpler LiveReload server. But as BrowserSync was really easy to setup, I really could go either way.

Future

These tools aren’t set in stone yet. Maybe I’ll take out BrowserSync. Maybe I can even get rid of Metalsmith and process all my files on one go. As far as blogs go, this one is supposed to be rather simple, and I really hope I can simplify the build process. Because as it stands, maybe I’m simply overthinking the reinvention of the wheel.