Reading and rendering a post

So now I have a pretty straightforward method of creating blog post files. As you may have guessed, I'm relying on the filename structure to provide the post ordering. Why? Because I'm lazy. Although I'm recording the post creation date inside each file, I'd rather skip having to load all posts (at least the metadata) into memory and read the date field in order to sort them. I could generate some kind of index, but nah. And that's easy to add later. Databases are tricked out for this sort of thing, but I'm not using a database. Filenames it is!

So now I need a way to read the post files and use a template to render HTML output. The idea here is to:

  1. given a filepath, find and open that file
  2. read the metadata and contents of the file
  3. parse any Markdown in the contents
  4. return a Post object representing the post

Luckily there's a half-kabillion NPM libraries for parsing text. I chose the standard frontmatter library to parse the post into a JSON object, and markdown-it to convert Markdown content into HTML:

const frontmatter = require('frontmatter');
const markdown = require('markdown-it')({ html: true });
const fs = require('fs');
import { Post } from './types';
import { checkType } from './checkType';

export function getPostData(filename: string): Post {
    const postContents: string = fs.readFileSync(`./posts/${filename}`, { encoding: 'utf8' });
    const parsedContents: any = frontmatter(postContents);
    const post: Post = JSON.parse(JSON.stringify(parsedContents.data));
    
    checkType(post, 'Post');
    post.content = markdown.render(parsedContents.content);
    return post;
}

The frontmatter library returns the parsed file contents as a single JS object with two properties: data for the metadata header section, and content for the body content. I'm using the JSON.parse(JSON.stringify(...)) trick to create a deep copy of the post metadata JSON and assign it to a Post type constant.

A simple template

From here it should be pretty simple to pass the post object to a function that uses it to return some HTML. I'd like all my primary template functions to adhere to a particular interface, so I added the following type definition to src/types:

export type Template = (posts: Post | Post[], blog?: Blog) => string;

Which is to say: functions of type Template must support 1) a single blog Post OR an array of Posts as an argument, and 2) a optional Blog argument (our parsed blog config), and must return a string.

Sweet, let's prototype a simple template function that generates a bare-bones HTML page from a post:

import { Post, Template } from '../src/types';
import { sanitize } from '../src/';
const df = require('user-friendly-date-formatter');

function getDisplayDate(dateTime: string) {
    return df(new Date(dateTime), '%D %fM %YYYY');
}

export const htmlPostTemplate: Template = (post: Post): string => {
    const title = sanitize(post.title);
    return `<article>
        <h1><a href="${ post.guid }">${ title }</a></h1>
        <time datetime="${ post.date }">${ getDisplayDate(post.date) }</time>
            ${ post.content }
        </article>
        `;
}

It's almost like JavaScript template literals were made for this kind of thing!

Okay, next up we glue this all together.


Posted in: blogging, code, javascript, node

Previously: 20 years since Y2K

Next: Wrapping up the blog project