Beispielcode im Blog, der wirklich läuft
Bei einem Codeblock im Artikel weiß man nie, ob der Code tatsächlich läuft. Für Web Components robust schreiben wollte ich vierzehn Beispiele, die man im Browser ausprobieren kann. Der Code im Text sollte genau der sein, der dort läuft.
Den Code in den Artikel und in eine zweite Datei zu kopieren, geht schief, sobald man eine der beiden Stellen ändert. CodePen einzubetten geht, bringt aber einen Drittanbieter und einen <iframe> mit, und der Code im Artikel ist trotzdem eine Kopie.
Ich habe stattdessen eine Datei pro Beispiel. Eleventy macht daraus die lauffähige Seite und den Codeblock im Artikel, samt Link auf diese Seite.
Eine Datei pro Beispiel
Ein Beispiel ist eine .njk-Datei im Ordner examples/ des Beitrags. Im Front Matter stehen Titel, Anleitung und erwartete Ausgabe, darunter der Code, ohne <html> und <body>.
---
heading: What the console box mirrors
steps:
- Open the console and compare it with the "Console output" box.
expected:
- a normal line
---
<script>
console.log('a normal line');
</script>Eleventy rendert jede Datei in dem Ordner von selbst als Seite, Pagination oder eine Datenliste braucht es dafür nicht. Es muss nur wissen, dass das Beispielseiten sind und keine Blogposts. Das sagt eine Data-Datei im Ordner:
// examples/examples.11tydata.js
import { exampleData } from '../../../_11ty/examples.js';
export default exampleData({
base: '/11ty-runnable-examples/examples/',
article: '/11ty-runnable-examples/'
});exampleData() ist eine kleine Hilfsfunktion von mir. Sie setzt das Layout, leitet Permalink und Nummer aus dem Dateinamen ab und hält die Seiten aus Feeds, Sitemap und Suche heraus. Den Code ohne Front Matter legt sie als source ab, damit das Layout ihn unter dem Beispiel zeigen kann.
Das Layout ist ein normales Eleventy-Layout. Oben stehen Anleitung und erwartete Ausgabe, darunter ein Kasten, der console.log und Fehler mitschreibt, dann das Beispiel und zuletzt der Quelltext. Das Beispiel steckt ohne <iframe> direkt in der Seite und läuft mit deren CSS. Wer es davon abschotten will, nimmt <iframe srcdoc> und verzichtet auf das Layout. So sieht das Beispiel von oben aus:
<script>
console.log('a normal line');
console.log('several', 'values', 42, null);
setTimeout(() => {
throw new Error('an uncaught error, shown as well');
}, 0);
</script>Der Codeblock kommt per Shortcode
Im Text steht statt des Codes ein Aufruf:
{% example "01-mirror" %}Der Shortcode liest die Datei, macht einen Codeblock daraus und hängt den Link an:
eleventyConfig.addAsyncShortcode('example', async function (id, region, lang = 'html') {
const dir = path.join(path.dirname(this.page.inputPath), 'examples');
const code = readExample(path.join(dir, `${id}.njk`));
const block =
'```' + lang + '\n' + (region ? excerpt(code, region) : withoutMarkers(code)) + '\n```';
// base: die URL der Beispielseiten, aus der Data-Datei
return `${block}\n\n[Beispiel ausführen →](${base}${id}/)`;
});Er gibt Markdown zurück, den Rest übernimmt markdown-it, auch das Syntax-Highlighting. Fehlt ein Beispiel, bricht der Build ab. Ein Artikel mit totem Verweis soll gar nicht erst online gehen.
Nur einen Ausschnitt zeigen
Oft braucht der Artikel nur ein paar Zeilen, die Seite aber das ganze Beispiel. Dafür markiere ich eine Region im Beispiel:
<button id="count" type="button">Count</button>
<script>
let clicks = 0;
// #region handler
document.getElementById('count').addEventListener('click', () => {
clicks += 1;
console.log('clicks:', clicks);
});
// #endregion
</script>{% example "02-region", "handler", "js" %} zeigt im Artikel nur diese vier Zeilen. Die Seite führt weiterhin alles aus:
document.getElementById('count').addEventListener('click', () => {
clicks += 1;
console.log('clicks:', clicks);
});Die Marker sind Kommentare (//, /* */ oder <!-- -->) und tauchen im Ergebnis nicht auf. Ein Tippfehler im Regionsnamen bricht ebenfalls den Build ab, und der falsche Code landet nicht im Artikel.
Die Beispiele in Web Components robust schreiben laufen so. Die Links unter den Codeblöcken führen zu den Seiten, und der Code dort ist der aus dem Text.
Ein neuer Beitrag braucht dafür nur einen examples/-Ordner und eine Data-Datei. Vielleicht heißt das, dass ich jetzt mehr Tutorials schreibe. Die Mittel dafür habe ich jedenfalls bereit.