Sapper vanderzwaan.dev

October 13, 2019 ~ 5 min read

Blog with Sapper


This blog now runs on Sapper. The experiment succeeded but I do miss some quality of life functionality of Hugo.

Why change?

This blog used to be a very simple static website that was generated by Hugo from some Markdown files. It's very convenient to work and I automatically deployed new versions whenever I pushed changes to the repository.

Now, if life was easy why would I change? Because playing with Sapper is fun, specifically building a front end with the underlying framework Svelte is lots of fun.

Svelte and Sapper

Svelte is in the same category as React and Vue. It's a framework to build a front end and stay sane. Building an interactive website with vanilla Javascript usually makes me want to cry.

I've worked with React and Vue before and although it was quite smooth and the documentation is perfect, I always felt I type lots of code but it doesn't do much. Both frameworks were getting in the way and it felt like magic sprinkled with other peoples components. Ugh. Getting an autocomplete to work where I needed not solution x but x' was a horrible few hours.

Svelte felt different. I could write code as I wanted to and Svelte would compile vanilla Javascript to Javascript that interacted with the DOM. It feels great! As a test I wrote a pixel editor that could import files and drag and drop...from scratch. It felt wonderful and very productive.

So far my love story with Svelte.

Sapper is like Next.js but then with Svelte. Normally I build lots of stuff in Python but this tempted me to build applications with Sapper.

I still have to get used to NodeJS libraries and NPM compared to Python. Packages for Python feel complete and robust. In Javascript land...ugh...people really stack lots and lots of libraries on top of each other. It cannot be good if there is a package left-pad that puts the Javascript world in chaos as it's pulled from NPM.

But Sapper tempts me.

Sapper export

Sapper has a command to export the application as a static website. It does so by sniffing out the calls to the server side and links and storing the results.

Seems pretty good, but there is no official support for Markdown, so you need to do some work yourself. I used the official website, and two repositories as a starting point to glean how they made it work: David's blog and from a repository of Maxi Ferreira.

The main idea is to find .md files and put those in a data structure so that a webservice can serve the JSON (with raw HTML for the .md documents) to the client side application.

In the examples the .md files were in a single directory and they didn't use images. Currently images should be in the static folder. The workflow is then to first upload images, and then refer to them from your .md file. Awkward.

Goal

The goal is to support the following directory structure:

/posts/2019/01/09/dogs.md
/posts/2019/11/10/fishes/index.md
/posts/2019/11/10/fishes/happy-fish.png
/posts/2019/11/10/fishes/sad-fish.png

The /posts/2019/11/10/fishes/index.md document refers to the happy-fish.png and sad-fish.png with a relative path. Your favorite editor can then give nice previews.

I couldn't make it work with Sapper to have an arbitrary amount of /'s in the URL, so I made due with - instead.

The slugs in the URL should be:

vanderzwaan.dev/blog/2019-01-09-dogs
vanderzwaan.dev/blog/2019-11-10-fishes

The images should be reachable with urls:

2019-11-10-fishes/happy-fish.png
2019-11-10-fishes/sad-fish.png

Find all Markdown files

Finding the .md files should be easy, something like this will do:

const locations = glob.sync(`${POSTS_DIR}/**/*.md`);
export const posts = locations.map( loc => getPost(loc)).map(post => parsePost(post));

There was a bit of logic to map file names to slugs: a/b/c/index.md to a-b-c and a/doc.md to a-doc. Nothing terribly complicated.

Create an endpoint /blog/[slug]/[img]

We need to be able to serve the images in the blogpost so let's create an endpoint that returns a certain image for a post. But then we also need to have a list of images and their locations.

For each post we also find the images in the same folder and store them:

    const attachments = new Map();
    glob.sync(`${base}/*.png`)
        .map(f => getFileInformation(f, POSTS_DIR))
        .forEach(attachment => {
            attachments.set(attachment.filename, attachment);
    });

The service is a standard Express server route that serves an image.

Add images to sapper export

It seems as if we should be done, but I found out that sapper export only follows <a> links and not the <img href='my image'/> references.

This required some code changes to Sapper export to also follow href attributes and a new mode to read files as images are in binary.

I should probably make a pull request for this, but currently the code is hacky.

Result

I'm pretty happy with the result, but I do miss the following nice things from Hugo:

  • Scaffolding
  • It watches the .md files, Sapper does not (so manual recompile)

Ruben van der Zwaan

Hi, I'm Ruben. I'm a data scientist / engineer from the Netherlands. I run my own company Datasprong.