Writing a static-site generator in an afternoon
Tags: node, pug, webdev, static
Published: 2019-06-02 14:11:12

---
title: Writing a static-site generator in an afternoon
published: true
description: How to write a static-site generator for fun in an afternoon
tags: nodejs, pug, webdev, static 
---

### 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:
```jade
// - 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.

```js

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.

```json
{
	"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.


```js
...
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:

```js

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.

```js
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:

```js
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.

```js

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 <meta> fields from `data.json`
* setup deployment for s3 or github-pages