Home

Awesome

<div><img src="./icon.png" style="border-radius: 8px" height="64"></div>

Hostic

Yet another static web site builder

There are plenty static web site generators around, but many of them think for you or try to make use of a specific framework on all costs. Hostic in contrary was built from the ground to use the optimal tools for the task while keeping the process pleasant.

Some features:

Getting Started

Start a project like this:

npm init hostic <site-name>
cd <site-name>
npm i
npm start

To build the static version into the dist folder:

npm run build

To preview and develop your site:

npm start

Open your browser at http://localhost:8080. The browser will reload after any change you saved to your project.

Site

Creating a new site starts with an entry file usually site/index.js. This one only export one default function that takes a path argument:

export default function (path) {
  let site = new Site(path)
  // Define your site's pages here
  return site
}

The path in out example is site from site/index.js. This is required because we cannot use __dirname due to implementation details.

site.html("/sample", (ctx) => {
  ctx.body = <div>Hello World</div>
})

This will create an HTML file with content <div>Hello World</div>. It uses JSX to describe the content. HTML files will automatically reload when the content or a referenced asset changes. <html>, <head>, <body> and everything es needed will be added automatically.

As you noted the Context ctx contains data and can receive new data as well for other Middleware (see below). It is important to put the result into ctx.body.

Middleware

Hostic makes use of the middleware programming pattern as known from Koa.js or Express.js. Complexity and extensibility can quite elegantly be managed this way.

A middleware is written as easy such a simple function:

;async (ctx, next) => {
  // Do something before nested middlewares are executed
  await next()
  // Do something after bested middlewares were executed
}

You can register the middleware like this:

site.use(myMiddlewareFunction)

It can also be added per page, like this:

site.html("/", template, async (ctx) => {
  ctx.body = <div>My content for the template</div>
})

Context

The context that is passed to Middleware is important. The most important property of it is body. It holds the content of the page. For HTML and XML usually in form of a virtual DOM. But it can also be used to pass properties to other Middlewares, like lang for language or title for the page title.

Plugins

A special kind of Middleware is the Plugin. It is basically the same, but it can have some attributes to better define its place in the process chain. You add them like this:

import { plugin } from "hostic"

// ...

app.use(plugin.tidy())

This is the most simple variant of a plugin:

export function example(opt = {}) {
  return {
    name: 'example',
    priority: 0.80,
    middleware: async (ctx, next) => {
      // ...
      await next()
      // ..
    })
  })

The priority tells when the plugin should be executed. The higher the value the earlier it executes. Imagine it like they are nested like this:

jsx {
  links {
    html {
   		// your middleware
    }
  }
}

These are the priorities of plugins bundled with Hostic. The ones with stars are activated by default.

PriorityPluginDescription
0.99*jsxProvides JSX Functionality
0.98tidyMakes HTML pretty
0.90...User slot for top level plugins
0.80locale, *linksApply translations and adjust links and media to be absolute
0.70...User slot for plugins that require translations
0.60disqus, matomo, cookieConsent, youtubeServices
0.55metaSEO funcitionality
0.50*htmlMakes sure body and head are correct, otherwise adds them
...User slot for templates etc. Default priority is 0

More details:

matomo

Adds tracking code for Matomo to pages to allow better insights about your visitors. The code is respecting "don't track me" settings and also has an entry point for users to opt out.

youtube

Use the original code from YouTube to embed a video and this plugin will replace it by a lazy loading alternative. This helps speeding up page load while at the same time improving privacy for the visitor.

cookieConsent

Displays an information about the use of cookies, required by European law.

disqus

Privacy conforming integration of Disqus service.

locale

Translate:

Translations can be provided as simple objects like:

{
  "Translate this": "Übersetze das"
}

Virtual DOM (zeed-dom)

This DOM abstraction for HTML and XML content is not designed for speed like in UI frameworks. Its goal is to help doing post process tasks on the content with familiar API. You can e.g. use CSS selectors to retrieve elements like root.querySelectorAll('img[src]') and then manipulate like element.setAttribute('src', src + '?ref=example'). Some special additions help to work on nodes like document.handle('h1,h2,h3', e => e.classList.add('header')).

Learn more at github.com/holtwick/zeed-dom.

Static Files

Serving static files:

Markdown

As for most static site generators Markdown is a welcome format for content. The first part describes properties in YAML and the second part the textual content:

---
title: Example
lang: en
---

# Example of a Page

Lorem ipsum

(more details to be added)

Lazy Loading and Multiple Passes

Site creation and serving contents are two separate steps. In the first step paths and their contents descriptions are registered to a site manager. In the second step the content is dynamically generated on demand.

A benefit from this separation is, that the content registration can have multipe passes, for example you can first register all pages and then in a second pass modify them. As an example in a multi language website it is possible to first register all pages and then connect the alternate pages. An example:

site.routes.values().forEach((page) => {
  if (page.path.startsWith("/en/")) {
    page.meta.alt = {
      de: "/de/" + page.path.substr(4),
      "*": "/" + page.path.substr(4), // Redirection based on
    }
  }
})

Another step is done for assets. If a HTML page has references to images, CSS, JS etc. it can add these references on the fly. That increases speed and offers more flexibility. The build process for the static pages is therefore run twice, because in the first step new references to assets might have been added.

Apache

By convention the .html suffix is dropped i.e. the url /a/b.html will become /a/b. To support this on Apache add a .htaccess file with the following lines:

RewriteEngine on
RewriteRule ^([^.]+[^/])$ $1.html [PT]

Configuration

The top level of configuration are environment variables. You can set them in your build environment or note them down into .env or .env.local files. The later one is intended to be excluded from Git repositories in case you need to set sensitive information.

Available settings are:

Performance

Fast previews and build processes are a great thing to have if you are working with a tool like this. Hostic tries to achieve this by doing the following:

Be aware of ...

Why another web site builder?

Read about it in my blog.

I don't know... there are plenty of good tools around. But I stumbled into creating this one and then got fascinated by esbuild, vite, the own virtual DOM and other details that where interesting to implement. For non geeky people it might be easier to start with something from the shelf like 11ty.

License

Hostic is free and can be modified and forked. But the conditions of the EUPL (European Union Public License 1.2) need to be respected, which are similar to ones of the GPL. In particular modifications need to be free as well and made available to the public.

For different licensing options please contact license@holtwick.de.

Get a quick overview of the license at Choose an open source license. This license is available in the languages of the European Community.

Author

My name is Dirk Holtwick. I'm an independent software developer located in Germany. Learn more at hotlwick.de.