loading...

Writing a static-site generator in an afternoon

xoryouyou profile image Till von Ahnen ・3 min read

Intro

I've always had problems with full blown blog systems whether they required to many resources or it took to long to implement simple features.
So I took it upon myself to create a simple static-page generator in a day with a few goals:

  • use pug.js for templating
  • be able to add meta-data
  • use browser-sync
  • optional syntax highlighting

Compiling HTML with pug.js

First off all lets create a folder called posts/ and in there create a new folder called first-post/

In this folder put a simple post.pug file with the following lines:

// - post.pug
html
  body
    h1 Hello World

Now we can write a little script called build.js which loops over all folders
in posts/ and compiles the post.pug in it.


const fs = require("fs");
const path = require("path");
const pug = require("pug");

let postDir = "./posts"
let distDir = "./dist"
let imgDir = "./img";
let assetDir = "./assets";

// get all posts
let posts = fs.readdirSync(postDir);
// for each post
for(let p of posts)
{
// compile the pug file
    let c = pug.compileFile(path.join(postDir,p,"/post.pug"),{pretty:true});
    let html = c();
    fs.writeFileSync(path.join(distDir,p+".html"),html);
}

// copy stuff over
fsExtra.copySync(imgDir, path.join(distDir,"img/"));
fsExtra.copySync(assetDir, path.join(distDir,"assets/"));


This creates ./dist/first-post.html which should only display a big "Hello World" in your browser.

Additional data

To add some additional data like a title and such lets put a data.json next to the post.pug file and put some content in it.

{
    "title":"Getting started",
    "description": "Some Description",
    "keywords":"just, a, few, keywords"
}

Thanks to pug we can simply pass some metadata to the rendering function and use it as variables in the template.

...
let {title, description, keywords} = require("../site/posts/"+p+"/data.json");
let html = c({title, description, keywords});
...

Now we can use h1 #{title} in the pug file and have our json data displayed here.

Use browser-sync

Since I don't always want to run the build manually and refresh the page in the browser I wanted to make use of browser-sync.

For this we first need to wrap the build script into a module by simply exporting it as a function like such:


module.exports = function()
{
  // build.js code here
}

now we can create a watch.js file which watches over all the pug and json files and when something changes it calls the build script and refreshes the browser.

const bs = require("browser-sync").create();
const build = require("./build");

function fn(event, file) 
{

    build();
    bs.reload();
}

bs.init({
    https: true,
    server: "./dist",
    files: [
        {
            match:"./site/**/*.pug",
            fn: fn
        },
        {
            match:"./site/**/*.json",
            fn: fn
        }
    ]
});

Now we can just hit CTRL-S on any of these files and the whole compile / refresh process runs on its own.

Additional: Custom filter for prism.js

Since I want to have the page as a complete static rendered bundle of html files and no javascript in the frontend at all.

So how do we get syntax highlighting from prism.js on the page you ask?

By simply writing a custom filter in pug.js which uses prism.js to render the html and then in the frontend we only need to include some css to style it.

So we create a new file called highlight.js which contains the following code:

var Prism = require("prismjs");
var loadLanguages = require("prismjs/components/");
loadLanguages(["javascript"]);

function highlight(text, options)
{
    let html = Prism.highlight(text, Prism.languages.javascript, "javascript");
    return html;
}

module.exports = highlight;

Now we need to tell pug to use our custom filter by adding it into the options json.


const highlight = require("./highlight");


// in our for loop add the filter to the compile step
let c = pug.compileFile(path.join(postDir,f,"/post.pug"),
{
  pretty:true,
  filters:
  {
    highlight: highlight
  }
});

Now for the final step in pug we can simply include files via a filter as such:

html
  head
    link(rel="stylesheet", href="assets/prism.css")
  body
    h1 Hello syntax highlighting
    include:highlight some-file.js

Conclusion

So now we got a basic setup to take it from here but all-in-all a good start for an afternoon project.

Some thinks for the next days would be:

  • create an index page
  • use some pug templates for posts to create fields from data.json
  • setup deployment for s3 or github-pages

Discussion

markdown guide
 

thanks for fantastic example of node pug custom filters