Blog der Heimetli Software AG

Statische HTML-Files mit node.js zusammensetzen

Es gibt viele Varianten zum zusammensetzen von HTML, aber bisher keine mit JavaScript. In diesem Post sehen wir uns an was node.js in diesem Bereich zu bieten hat.

Es gibt sicher noch einige andere Ansätze, aber ich kann ganz einfach nicht alle ausprobieren...

Die Files für Header und Footer haben den gleichen Aufbau wie im letzten Post:

header.inc

  <header>
   <nav>
    <ul>
     <li><a href="index.html">Home</a></li>
     <li><a href="about.html">About</a></li>
    </ul>
   </nav>
  </header>

footer.inc

  <footer>
   <p>Text im Footer</p>
  </footer>

liquidjs

Liquid ist eine Templatesprache für HTML. Dazu gehört ein Tool namens liquidjs das auf node.js läuft. Es muss mit npm installiert werden.

Dieses Tool löst die Aufgabe einfach und glatt. In diesem Fall hat index.html folgenden Inhalt:

<!DOCTYPE html>
<html lang="de">
 <head>
  <meta charset="utf-8">
  <title>{{title}}</title>
 </head>
 <body>
{% include "header.inc" %}  <h1>index.html</h1>
  <p>Ein bisschen Text als Inhalt</p>
{% include "footer.inc" %} </body>
</html>

Das HTML hinter den include-Tags verhindert Leerzeilen nach den eingefügten Teilen. Die geschweiften Klammern markieren eine Variable. Die Klammern und die Variable werden beim Rendering durch deren Wert ersetzt. Die Variablen sind durch einem JSON-String auf der Kommandozeile definiert.

$ liquidjs '{"title":"Die Startseite"}' < index.html
<!DOCTYPE html>
<html lang="de">
 <head>
  <meta charset="utf-8">
  <title>Die Startseite</title>
 </head>
 <body>
 <header>
   <nav>
    <ul>
     <li><a href="index.html">Home</a></li>
     <li><a href="about.html">About</a></li>
    </ul>
   </nav>
  </header>
  <h1>index.html</h1>
  <p>Ein bisschen Text als Inhalt</p>
  <footer>
   <p>Text im Footer</p>
  </footer>
 </body>
</html>

node.js

Mit einem Script auf Node kann die Aufgabe ebenfalls gelöst werden:

const fs       = require( "fs" ) ;
const readline = require( "readline" ) ;
const stream   = fs.createReadStream( process.argv[2] ) ;
const regex    = /{% *include +"([-_.A-Za-z0-9]+)" *%}/

const rl = readline.createInterface({
  input: stream,
}) ;

rl.on( "line", line => {
   const matches = line.match( regex ) ;

   if( matches )
   {
      process.stdout.write( fs.readFileSync(matches[1],"utf8") ) ;
   }
   else
   {
      console.log( line ) ;
   }
}) ;

index.html sieht fast gleich aus wie bei der liquidjs-Version:

<!DOCTYPE html>
<html lang="de">
 <head>
  <meta charset="utf-8">
  <title>Die Startseite</title>
 </head>
 <body>
{% include "header.inc" %}
  <h1>index.html</h1>
  <p>Ein bisschen Text als Inhalt</p>
{% include "footer.inc" %}
 </body>
</html>

Die Expansion von Variablen funktioniert nicht mit diesem einfachen Programm, und deshalb steht der Text direkt im File. Dafür braucht es keine Vorkehrungen um Leerzeilen zu vermeiden. Der Input wird auf der Kommandozeile angegeben, und direkt vom Filesystem gelesen.

$ node combine.js index.html

eleventy

eleventy, oft auch 11ty geschrieben, ist ein Static Site Generator. Das Programm ist nicht so einfach zu handhaben wie die beiden anderen, dafür ist es viel mächtiger. Es hält sich per Default an vorgegebene Konventionen, aber die lassen sich bei Bedarf flexibel übersteuern. Wenn das nicht reicht kann man sogar eigene JavaScript-Erweiterungen schreiben, und von eleventy ausführen lassen.

Für userere einfache Aufgabe braucht es nichts besonderes und deshalb halten wir uns an die Konventionen:

  • Die Templates liegen im Directory _includes
  • Die erzeugten Seiten werden in _site geschrieben

Header und Footer haben genau den gleichen Inhalt wie oben gezeigt, aber die Extension ist .liquid und sie liegen im Directory _includes.

index.html ist in zwei Teile aufgeteilt. Im aktuellen Directory liegt index.md:

---
layout: index.liquid
title: Die Startseite
---
# index.html

Ein bisschen Text als Inhalt

Der erste Teil definiert wie das File gerendert werden soll, der zweite ist Markdown. Das Markdown wird in HTML übersetzt und ins Template eingesetzt.

Im Directory _includes liegt das zugehörige index.liquid:

<!DOCTYPE html>
<html lang="de">
 <head>
  <meta charset="utf-8">
  <title>{{title}}</title>
 </head>
 <body>
{% include header %}
{{ content }}
{% include footer %}
 </body>
</html>

Und jetzt wird es wieder ganz einfach

$ eleventy

erzeugt index.html im Directory _site.

Zugegeben, das ist Overkill für diese simple Seite. Für eine grössere Website lohnt sich der Aufwand weil viele HTML-Seiten den gleichen Aufbau haben.