A Simple Static Website Generator built with PHP
How this blog started: Using a php local server and shell utilities to generate a static website.8 August, 2019
Disclaimer
If you are looking for ways to build your own static website, I would suggest a good SSG like Hugo or Jekyll. This was a weekend project just to play around with this specific concept
I am also migrating to Hugo to get some of the good stuff like tags, comments and analytics
Introduction
It all started with a colleague encouraging me to start my own blog, mostly to write about vim-related stuff and anything else I have spent unusual amounts of time on.
So naturally, I couldn’t just start a wordpress docker container and be done with it.
Because of my unhealthy obsession with efficiency, I wanted to find the best way to create something simple, fast and with focus on the one thing in blogs that actually matters: content
Requirements
I knew I wanted a few things:
Writing environment
I want to write posts in vim (or maybe emacs orgmode in the future) because it’s a fast and distraction-free environment.
Also, markdown format seems the best for such a solution.
Pure HTML pages
A static HTML website is fast, portable and easy to deploy in many places online, a lot of them for free (see github/gitlab pages, amazon S3 etc)
Simplicity
Every time I wanted to add a new post, I wanted to be able to just fire up vim, write a new .md file, run a build command and WHABAM! there’s your a website
Inspiration
All this started from an article I came across while searching for a Static Website Generator.
In the article the author uses PHP itself to create an extremely simple static website generator.
The content is actually generated dynamically by PHP, BUT there is an extra step where the whole website is essentially crawled and stored in html format with a simple command: wget -r
But enough talk, lets get to the good stuff:
Templating
I decided to make my life easy and just use twig for templating.
templates/
├── base.html.twig
├── index.html.twig
└── post.html.twig
Twig lets you define blocks
that can be easily extended in “child” templates
<html>
<head>
<title>{% block title %}Just Another Tech Blog - Filippos Karailanidis{% endblock %}</title>
</head>
<body>
<header>
<big><a href="/">Just Another Tech Blog</a></big>
<small>Filippos Karailanidis</small>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
</footer>
</body>
</html>
The index
The index page should display all the posts located in the posts/
folder, in reverse chronological order.
Here comes the first question:
How do we keep the date of the post so we know how to order them?
The following approaches came to mind: filesystem date and metadata.
Filesystem date is easy and takes advantage of the date the file was created, but it is not a reliable solution since a simple copy-paste of the files or a new clone would wipe all the dates of the posts.
So I went for metadata
Each file has a header that looks like this
---
date: Fri 12 Jul 2019 06:21:09 PM EEST
---
Once the index is loaded, all the posts are parsed with the help of this library, kept into memory and sorted based on date.
<?php
$posts = [];
// Get All Posts
$files = glob("posts/*");
foreach( $files as $file ){
$slug = pathinfo($file, PATHINFO_FILENAME);
$contents = file_get_contents($file);
$posts[] = PostFactory::create($slug, $contents);
}
// Sort by date
usort($posts, function($a, $b) {
return $b->date() <=> $a->date();
});
Also, while we are here, why don’t we add a title and a summary to display as index:
---
title: A Simple Static Website Generator built with PHP
date: Fri 12 Jul 2019 06:21:09 PM EEST
summary: "How this blog started: Using a php local server and shell utilities to generate a static website."
---
The title will also be used in the <title>
tag
The template looks like this:
{% extends 'base.html.twig' %}
{% block content %}
{% for post in posts %}
<section>
<h2><a href="{{ post.slug }}">{{ post.title }}</a></h2>
<small>{{ post.date.format('d F Y') }}</small>
<p>{{ post.summary }}</p>
</section>
<hr />
{% endfor %}
{% endblock %}
The Post Page
Here things are much simpler. All we have to do is display Title, Summary and Content.
We use the parsedown library to parse Markdown to HTML and we are good to go!
{% extends 'base.html.twig' %}
{% block title %}
{{ post.title }} - filippos.dev
{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<p>{{ post.summary }}</p>
{{ post.content|raw }}
</article>
{% endblock %}
The Application Code
This part could be much smaller, but I just wanted to make it as structured as possible.
If you want to learn more about this approach to web programming, there is an excellent article series on the symfony website.
.
├── src
│ ├── Config.php # Configuration Object
│ ├── ConfigFactory.php # Configuration Object Factory
│ ├── Controller.php # Contains index logic and route actions
│ ├── PostFactory.php # Post Factory utilizing markdown library
│ ├── Post.php # Post Entity
│ └── Router.php # Simple routing based on top-level path
└── index.php # Dependency building and injecting
Scripting
Inside composer.json I included some useful scripts for building and deploying the website:
{
"scripts": {
"build": [
"rm -rf dist/*",
"wget -P dist/ -nH --html-extension -r localhost:8080"
],
"server": "php -S localhost:8080"
}
}
After running composer server
, you can see how your blog looks at http://localhost:8080
This way you don’t have to install an external web server.
Let’s break down the wget
command which is where all the magic happens
wget \
-P dist/ \ # Destination folder
-nH \ # noHostname option. Without this, files would be
# written to dist/localhost:8080/
--html-extension \ # add .html to files downloaded, this is important
-r \ # recursive: the flag that makes this whole thing possible
# this will make wget follow all routes it detects
# and download all of your pages
localhost:8080 # the path of your dev server.
# The server needs to be running when you run this command
Final Touches
All that’s left is some styling & css and the blog is ready!
Now to create a new post, we just add a new .md file with the correct structure and all the rest is taken care of.
To build your static website, just run
composer build
You can see the whole project code here