Simple static site generator written in typescript
  • TypeScript 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-15 14:36:59 +02:00
.vscode Squash merge dev into main 2026-08-10 22:56:34 +02:00
src Allow full links in rel calls 2026-08-15 14:36:59 +02:00
.gitignore Squash merge dev into main 2026-08-10 22:56:34 +02:00
bun.lock Squash merge dev into main 2026-08-10 22:56:34 +02:00
package.json Squash merge dev into main 2026-08-10 22:56:34 +02:00
README.md Squash merge dev into main 2026-08-10 22:56:34 +02:00
tsconfig.json Squash merge dev into main 2026-08-10 22:56:34 +02:00

ssg

A static site generator made for my blog (eventually). It is relativly generic and can probably be used for your own static site generator needs. It works by providing a very basic post type and then calling template on it. Template wraps the file and uses js string interpolation to generate the final output data. This means it can do lots of complex things. There also is a very basic asset copy step, which moves static assets into the output directory.

Disclaimer: It does sadly currently use bun. This is something I want to remove in the future. Currently bun is used because it can execute ts without a external compiler step. Im open for pr's to move it away from that if someone wants to help me

Setup

  • Install bun
  • Setup a .env file with the relevant parameters (see: src/settings.ts)
  • Setup your templates, static assets, post.ts files
  • Run src/index.ts
  • Copy the output where you need it

GetPostEnumerator(posts, tagPosts)

Is expected to return either a post or null. When it returns a post it will simply template the post. This function will continue to be called until it returns null. The reasoning is, that it enables one post class to be used for several output files. One way this can be used is to create a page for each tag that is used by any blogpost. A default implementation is also provided, which simply returns the post once and then null.

Template accessible data

object description
post Information about the current post
arg Data that can be passed through states of the templating
arg.current Data about the current object in the content array
arg.time The timestamp, where the processing for this post started
posts An array of all posts
tagPosts A map of all posts with their tag being the key
read(file, arg)

Inserts the content of templateDir + file in the current location, while passing arg into the template, to be used by it

rel(path)

Get a path to path relative to the page root, regardless of which file/template you are currently in.

content(arg)

Helper function for blogposts. Loops through all of the objects in the content field of the post and calls their template. Also stores the rest of the object in arg.current

Example file

import { Post } from "../../../ssg/src/post";

export default class TagList extends Post {
    public override outputFile: string = "";
    public override baseTemplate: string = "postList.html";

    public override type = "other";

    public override title: string = "Alle Posts";

    private static counter = 0;
    public override GetPostEnumerator(posts: Post[], tagPosts: Map<string, Post[]>): Post | null {
        const keys: string[] = [];
        for(const k of tagPosts.keys()) {
            keys.push(k);
        }
        if(TagList.counter < keys.length) {
            const toReturn = new TagList();
            toReturn.title = keys[TagList.counter].toUpperCase();
            toReturn.outputFile = "tags/" + keys[TagList.counter] + ".html";

            for(const post of tagPosts.get(keys[TagList.counter])!.filter((p) => p.type != "other")) {
                toReturn.content.push({
                    type: "parts/postPreview.html",
                    content: post.title,
                    path: post.outputFile,
                    description: "description" in post ? post["description"] : "Default post description",
                });
            }

            TagList.counter += 1;
            return toReturn;
        }
        return null;
    }
}
<!DOCTYPE html>
<html>

<head>
    <title>${post.title}</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />

    <!--Stylesheets & Scripts-->
    <link rel="stylesheet" href="${rel('/styles/style.css')}" />
</head>

<body>
    ${read('header.html')}
    <div style="display:block">
        <div class="content">

            <h1>#${post.title}</h1>

            ${content(arg)}
        </div>
    </div>
    ${read('footer.html', arg)}
</body>

</html>