Browser…​unplugged
Showcases of modern Browser technologies

20. August 2017

Shortcodes in Form von Custom-Tags in Markdown

Eckige Klammern sind die meistgenutzte Alternative zu Tag-Formaten. Zuerst wurden sie in BBCode populär, dann fanden sie ihren Weg in WordPress, um damit Widgets zu beschreiben. Mein neues node.js-Plugin markdown-it-shortcode-tag  nutzt stattdessen spitze Klammern. Warum von der eingeführten Praxis abweichen?

Markdown  ist eine Sprache, um Text in einem Format zu schreiben, das automatisiert in ansprechend formatiertes HTML konvertiert werden kann, jedoch als einfacher Text lesbar bleibt. Aber es gibt eine lange Reihe von Anwendungsfällen, in denen technische Anweisungen mit lesbarem Inhalt vermischt werden müssen. Verständlicherweise ist das standardisierte Markdown sehr zurückhaltend gegenüber solchen Instruktionen und unterstützt nur sehr wenige, wie Links und Bildeinfügungen.

Sobald man den Punkt erreicht, wo man entscheidet dass dieses Standardformat nicht mehr ausreicht und Erweiterungen unvermeidlich werden, stellt sich die Frage nach dem Format. Möglicherweise soll ein Widget beschrieben werden, sagen wir ein an den rechten Rand geschobenes Audio-Steuerelement:

<media type="audio" src="/assets/soundbit.mp3" side="right">

Es ließe sich argumentieren, dass die Verwendung von eckigen Klammern ein bekanntes Format ist, also warum mit spitzen Klammern davon abweichen?

Zum einen ist der Einsatz nicht wirklich gleich. Keiner der Anwendungsfälle von eckigen Klammern in Markdown beinhaltet die Übergabe von Attributen. Deshalb ist das Signal: dies liegt außerhalb des Umfangs von Markdown, also hat es ein besonderes Format.

Ich lege den Schwerpunkt jedoch auf einen anderen Aspekt. Wenn man schon die Grundannahme durchbricht, dass Markdown lesbarer Text ist, dann sollte wenigstens die gerenderte Ausgabe ablenkungsfrei sein. Wenn Text mit einem Shortcode von einem Standard-Renderer verarbeitet wird, sollte die Darstellung entsprechend dem Paradigma der graceful degradation erfolgen.

Eine Syntax mit eckigen Klammern würde dazu führen, dass in einem standardkoncormen Rendering der Shortcode unverändert ausgegeben würde. Bei der Verwendung von spitzen Klammern interpretieren sowohl der Renderer als auch der anzeigende Browser den Shortcode als HTML5-Custom Tag. Ist dieser unbekannt, wird er einfach nicht angezeigt. Nach meiner Meinung ist das das viel bessere Ergebnis,

🔗 Verwendung auf dieser Seite

Ich mag markdown-it  mit seiner Unterstützung von Plugins. Allerdings verwendet Harp , der Static Site Generator, den ich ausgewählt habe um diese Seite zu bauen, marked . Und das ist überhaupt keine Hilfe, wenn man versucht es zu erweitern. Um diese Webseite zu erzeugen, habe ich deshalb den Renderer in der terraform -Engine, die Harp zugrunde liegt, ausgetauscht . Danach konnte ich einige Shortcodes mit meinem Plugin implementieren.

Harp definiert lokale Variablen, die die Template-Preprozessoren Pug , vormals Jade, und EJS  benutzen können. Der Inhalt von Templates kann an jeder Stelle in anderen Templates als sogenannte Partials  eingefügt werden.

Mit meinen Modifikationen können beide Mechanismen auch in Markdown eingesetzt werden. Ein Shortcode verlinkt ein Partial-Template (hier eine Datei media.jade, die ein an den linken Rand geschobenes Bild einfügt):

<partial src="partials/media" medium="avatar" side="left" width="300px">

Der andere gibt den Inhalt einer lokalen Variable (title) aus:

<local title>

Die Definition der Shortcodes wurde dem terraform Markdown-Renderer  so hinzugefügt:

// lade Markdown-Parser
var md = require('markdown-it')({ html: true, linkify: true });

// definiere Shortcodes
const shortcodes = {
  partial: { // fügt ein Template ein
    render: function (attrs, env) {
      const path = attrs.src;
      if (!path) throw new TerraformError(/*...*/);
      delete attrs.src;

      // Funktion env.partial() wird von terraform bereitgestellt und
      // gibt das gerenderte Template zurück. Attribute werden dem
      // Template als lokale Variablen übergeben.
      return env.partial(path, attrs) || '';
    }
  },

  local: { // gibt eine Variable aus
    inline: true,
    render: function (attrs, env) {
      // ermittle den ersten Attribut-Namen
      const name = Object.getOwnPropertyNames(attrs)[0];
      if (!name || locals[name] !== true) throw new TerraformError(/*...*/);

      // Hole den Inhalt der Variable von env
      return env[name];
    }
  }
};

// Aktiviere das Plugin
md.use(require('markdown-it-shortcode-tag'), shortcodes);

// terraform-Interface
module.exports = function(fileContents, options){

  return {
    compile: function(){
      return function (locals) {
        // rufe markdown-it auf und übergebe die lokale Umgebung
        return md.render(fileContents.toString(), locals);
      };
    }, //...
  };

};