Advertisement
  1. Code
  2. Web Development

Handlebars.js - ein Blick hinter den Kulissen

Scroll to top
Read Time: 11 min

() translation by (you can also view the original English article)

Handlebars haben mit ihrer Einführung in Frameworks wie Meteor und Ember.js an Popularität gewonnen, aber was passiert wirklich hinter den Kulissen dieser aufregenden Template-Engine?

In diesem Artikel werden wir uns eingehend mit dem zugrunde liegenden Prozess befassen, den Lenker durchlaufen, um Ihre Vorlagen zu kompilieren.

In diesem Artikel wird erwartet, dass Sie meine vorherige Einführung in Handlebars gelesen haben. Daher wird davon ausgegangen, dass Sie die Grundlagen zum Erstellen von Lenkervorlagen kennen.

Wenn Sie eine Handlebarsvorlage verwenden, wissen Sie wahrscheinlich, dass Sie zunächst die Quelle der Vorlage mithilfe von Handlebars.compile() in eine Funktion kompilieren und dann mit dieser Funktion den endgültigen HTML-Code generieren und Werte für Eigenschaften und Platzhalter übergeben.

Aber diese scheinbar einfache Kompilierungsfunktion macht tatsächlich einige Schritte hinter den Kulissen, und darum geht es in diesem Artikel wirklich. Werfen wir einen Blick auf eine kurze Aufschlüsselung des Prozesses:

  • Tokenisieren Sie die Quelle in Komponenten.
  • Verarbeiten Sie jedes Token zu einer Reihe von Operationen.
  • Konvertieren Sie den Prozessstapel in eine Funktion.
  • Führen Sie die Funktion mit dem Kontext und den Hilfsprogrammen aus, um HTML auszugeben.

Die Einrichtung

In diesem Artikel werden wir ein Tool zum Analysieren von Handlebarsvorlagen bei jedem dieser Schritte erstellen. Um die Ergebnisse auf dem Bildschirm ein wenig besser anzuzeigen, verwende ich den Syntax-Textmarker prism.js, der von der einzigen Lea Verou erstellt wurde. Laden Sie die minimierte Quelle herunter und denken Sie daran, JavaScript im Abschnitt Sprachen zu überprüfen.

Der nächste Schritt besteht darin, eine leere HTML-Datei zu erstellen und das muss Folgendes haben:

1
<!DOCTYPE HTML>
2
<html xmlns="http://www.w3.org/1999/html">
3
    <head>
4
        <title>Handlebars.js</title>
5
        <link rel="stylesheet" href="prism.css"></p>
6
7
        <script src="prism.js" data-manual></script>
8
        <script src="handlebars.js"></script>
9
    </head>
10
    <body>
11
        <div id="analysis">
12
            <div id="tokens"><h1>Tokens:</h1></div>
13
            <div id="operations"><h1>Operations:</h1></div>
14
            <div id="output"><h1>Output:</h1></div>
15
            <div id="function">
16
                <h1>Function:</h1>
17
                <pre><code class="language-javascript" id="source"></code></pre>
18
            </div>
19
        </div>
20
        <script id="dt" type="template/handlebars">
21
        </script>
22
23
        <script>
24
            //Code will go here

25
        </script>
26
    </body>
27
</html>

Es ist nur ein Boilerplate-Code, der Lenker und Prisma enthält und dann einige Divs für die verschiedenen Schritte einrichtet. Unten sehen Sie zwei Skriptblöcke: Der erste ist für die Vorlage und der zweite für unseren JS-Code.

Ich habe auch ein wenig CSS geschrieben, um alles ein bisschen besser zu arrangieren. Sie können es hinzufügen:

1
     
2
    body{
3
        margin: 0;
4
        padding: 0;
5
        font-family: "opensans", Arial, sans-serif;
6
        background: #F5F2F0;
7
        font-size: 13px;
8
    }
9
    #analysis {
10
        top: 0;
11
        left: 0;
12
        position: absolute;
13
        width: 100%;
14
        height: 100%;
15
        margin: 0;
16
        padding: 0;
17
    }
18
    #analysis div {
19
        width: 33.33%;
20
        height: 50%;
21
        float: left;
22
        padding: 10px 20px;
23
        box-sizing: border-box;
24
        overflow: auto;
25
    }
26
    #function {
27
        width: 100% !important;
28
    }

Weiter benötigen wir eine Vorlage. Beginnen wir also mit der einfachsten Vorlage, nur einem statischen Text:

1
<script id="dt" type="template/handlebars">
2
    Hello World!
3
</script>
4
5
<script>
6
    var src = document.getElementById("dt").innerHTML.trim();
7
8
    //Display Output

9
    var t = Handlebars.compile(src);
10
    document.getElementById("output").innerHTML += t();
11
</script>

Das Öffnen dieser Seite in Ihrem Browser sollte dazu führen, dass die Vorlage, wie erwartet, im Ausgabefeld angezeigt wird. Noch nichts anderes. Wir müssen jetzt den Code schreiben, um den Prozess in jeder der anderen drei Phasen zu analysieren.

Basic OutputBasic OutputBasic Output

Token

Der erste Schritt, den der Lenker an Ihrer Vorlage ausführt, besteht darin, die Quelle zu tokenisieren. Dies bedeutet, dass wir die Quelle in ihre einzelnen Komponenten aufteilen müssen, damit wir jedes Teil angemessen behandeln können. Wenn sich beispielsweise Text mit einem Platzhalter in der Mitte befindet, trennen die Lenker den Text, bevor der Platzhalter ihn in ein Token einfügt, und der Platzhalter selbst wird in einen anderen Token und zuletzt den gesamten Text nach dem Platzhalter eingefügt würde in einen dritten Token gelegt werden. Dies liegt daran, dass diese Teile beide die Reihenfolge der Vorlage beibehalten müssen, aber auch unterschiedlich verarbeitet werden müssen.

Dieser Vorgang wird mit der Funktion Handlebars.parse() ausgeführt. Sie erhalten ein Objekt zurück, das alle Segmente oder 'Anweisungen' enthält.

Um besser zu veranschaulichen, wovon ich spreche, erstellen wir eine Liste mit Absätzen für jeden der herausgenommenen Token:

1
    
2
    //Display Tokens

3
    var tokenizer = Handlebars.parse(src);
4
    var tokenStr = "";
5
    for (var i in tokenizer.statements) {
6
        var token = tokenizer.statements[i];
7
        tokenStr += "<p>" + (parseInt(i)+1) + ") ";
8
        switch (token.type) {
9
            case "content":
10
                tokenStr += "[string] - \"" + token.string + "\"";
11
                break;
12
            case "mustache":
13
                tokenStr += "[placeholder] - " + token.id.string;
14
                break;
15
            case "block":
16
                tokenStr += "[block] - " + token.mustache.id.string;
17
        }
18
    }
19
    document.getElementById("tokens").innerHTML += tokenStr;

Zunächst führen wir die Vorlagenquelle in Handlebars.parse aus, um die Liste der Token abzurufen. Anschließend durchlaufen wir alle einzelnen Komponenten und erstellen eine Reihe von lesbaren Zeichenfolgen, die auf dem Segmenttyp basieren. Klartext hat eine Art "content", den wir dann einfach in Anführungszeichen setzen können, um zu zeigen, was er bedeutet. Platzhalter haben eine Art "mustache", den wir dann zusammen mit ihrer "id" (Platzhaltername) anzeigen können. Und zu guter Letzt haben Blockhelfer eine Art "block", mit dem wir dann auch nur die interne "id" (Blockname) des Blocks anzeigen können.

Wenn Sie dies jetzt im Browser aktualisieren, sollte nur ein einzelnes 'string'-Token mit dem Text unserer Vorlage angezeigt werden.

Tokens!Tokens!Tokens!

Operationen

Sobald der Lenker über die Sammlung von Token verfügt, durchläuft er jeden einzelnen und "generiert" eine Liste vordefinierter Vorgänge, die ausgeführt werden müssen, damit die Vorlage kompiliert werden kann. Dieser Vorgang wird mit dem Objekt Handlebars.Compiler() ausgeführt, das das Token-Objekt aus Schritt 1 übergibt:

1
    
2
    //Display Operations

3
    var opSequence = new Handlebars.Compiler().compile(tokenizer, {});
4
    var opStr = "";
5
    for (var i in opSequence.opcodes) {
6
        var op = opSequence.opcodes[i];
7
        opStr += "<p>" + (parseInt(i)+1) + ") - " + op.opcode;
8
    }
9
    document.getElementById("operations").innerHTML += opStr;

Hier kompilieren wir die Token in der Operationssequenz, über die ich gesprochen habe, und dann durchlaufen wir jede einzelne und erstellen eine ähnliche Liste wie im ersten Schritt, außer dass wir hier nur den Opcode drucken müssen. Der Opcode ist der "name" der Operation oder der Funktion, der für jedes Element in der Sequenz ausgeführt werden muss.

Zurück im Browser sollte jetzt nur noch eine einzige Operation mit dem Namen "appendContent" angezeigt werden, mit der der Wert an den aktuellen "buffer" oder die "string of text" angehängt wird. Es gibt viele verschiedene Opcodes und ich glaube nicht, dass ich qualifiziert bin, einige davon zu erklären, aber eine schnelle Suche im Quellcode nach einem bestimmten Opcode zeigt Ihnen die Funktion, die für diesen ausgeführt wird.

Op CodesOp CodesOp Codes

Die Funktion

Die letzte Stufe besteht darin, die Liste der Opcodes zu nehmen und sie in eine Funktion umzuwandeln. Dazu wird die Liste der Operationen gelesen und der Code für jede Operation intelligent verkettet. Hier ist der Code, der erforderlich ist, um die Funktion für diesen Schritt aufzurufen:

1
    
2
    //Display Function

3
    var outputFunction = new Handlebars.JavaScriptCompiler().compile(opSequence, {}, undefined, true);
4
    document.getElementById("source").innerHTML = outputFunction.toString();
5
    Prism.highlightAll();

In der ersten Zeile wird der Compiler erstellt, der in der Operationssequenz übergeben wird, und diese Zeile gibt die endgültige Funktion zurück, die zum Generieren der Vorlage verwendet wird. Anschließend konvertieren wir die Funktion in einen String und weisen Prism an, die Syntax hervorzuheben.

Mit diesem endgültigen Code sollte Ihre Seite ungefähr so aussehen:

The FunctionThe FunctionThe Function

Diese Funktion ist unglaublich einfach, da es nur eine Operation gab, gibt sie nur die angegebene Zeichenfolge zurück. Lassen Sie uns nun einen Blick auf die Bearbeitung der Vorlage werfen und sehen, wie sich diese einzelnen einfachen Schritte zu einer sehr leistungsfähigen Abstraktion zusammenschließen.


Vorlagen untersuchen

Beginnen wir mit etwas Einfachem und ersetzen wir einfach das Wort 'World' durch einen Platzhalter. Ihre neue Vorlage sollte wie folgt aussehen:

1
    <script id="dt" type="template/handlebars">
2
        Hello {{name}}!
3
    </script>

Und vergessen Sie nicht, die Variable zu übergeben, damit die Ausgabe in Ordnung aussieht:

1
    //Display Output

2
    var t = Handlebars.compile(src);
3
    document.getElementById("output").innerHTML += t({name: "Gabriel"});

Wenn Sie dies ausführen, werden Sie feststellen, dass das Hinzufügen nur eines einfachen Platzhalters den Prozess erheblich verkompliziert.

Single PlaceholderSingle PlaceholderSingle Placeholder

Der komplizierte if/else-Abschnitt ist, weil er nicht weiß, ob der Platzhalter tatsächlich ein Platzhalter oder eine Hilfsmethode ist

Wenn Sie sich noch nicht sicher waren, was Token sind, sollten Sie jetzt eine bessere Idee haben. Wie Sie auf dem Bild sehen können, wurde der Platzhalter von den Zeichenfolgen getrennt und drei einzelne Komponenten erstellt.

Als nächstes gibt es im Operationsbereich einige Ergänzungen. Wenn Sie sich an etwas erinnern, verwendet Handlebars zur einfachen Ausgabe von Text die Operation 'appendContent', die Sie jetzt oben und unten in der Liste sehen können (sowohl für "Hello" als auch für "!"). Der Rest in der Mitte enthält alle Vorgänge, die zum Verarbeiten des Platzhalters und zum Anhängen des maskierten Inhalts erforderlich sind.

Schließlich wird im unteren Fenster diesmal nicht nur eine Zeichenfolge zurückgegeben, sondern eine Puffervariable erstellt und jeweils ein Token verarbeitet. Der komplizierte if / else-Abschnitt ist, weil er nicht weiß, ob der Platzhalter tatsächlich ein Platzhalter oder eine Hilfsmethode ist. Es wird also versucht festzustellen, ob eine Hilfsmethode mit dem angegebenen Namen vorhanden ist. In diesem Fall wird die Hilfsmethode aufgerufen und 'stack1' auf den Wert gesetzt. Wenn es sich um einen Platzhalter handelt, wird der Wert aus dem übergebenen Kontext (hier mit dem Namen 'depth0' bezeichnet) zugewiesen. Wenn eine Funktion übergeben wurde, wird das Ergebnis der Funktion in die Variable 'stack1' eingefügt. Sobald dies alles erledigt ist, entweicht es, wie wir es in den Operationen gesehen haben, und hängt es an den Puffer an.

Versuchen wir für unsere nächste Änderung einfach dieselbe Vorlage, außer diesmal, ohne den Ergebnissen zu entgehen (fügen Sie dazu eine weitere geschweifte Klammer "{{{name}}}" hinzu).

Wenn Sie die Seite aktualisieren, wird nun die Operation entfernt, um der Variablen zu entkommen, und stattdessen wird sie nur angehängt. Dies wird in die Funktion eingeblendet, die jetzt einfach überprüft, ob der Wert kein falscher Wert ist (außer 0), und dann hängt es an, ohne ihm zu entkommen.

Single Placeholder Non EscapedSingle Placeholder Non EscapedSingle Placeholder Non Escaped

Ich denke, Platzhalter sind ziemlich einfach. Schauen wir uns nun die Verwendung von Hilfsfunktionen an.


Hilfsfunktionen

Das hat keinen Sinn, komplizierter zu machen, als das sein muss. Erstellen wir einfach eine einfache Funktion, die das Duplikat einer übergebenen Zahl zurückgibt. Ersetzen Sie also die Vorlage und fügen Sie einen neuen Skriptblock für den Helfer hinzu (vor dem anderen Code)):

1
<script id="dt" type="template/handlebars">
2
    3 * 2 = {{{doubled 3}}}
3
</script>
4
5
<script>
6
    Handlebars.registerHelper("doubled", function(number){
7
        return number * 2;
8
    });
9
</script>

Ich habe beschlossen, es nicht zu umgehen, da es die endgültige Funktion etwas einfacher zu lesen macht, aber Sie können beide ausprobieren, wenn Sie möchten. Auf jeden Fall muss Folgendes bewirken:

Helper FunctionHelper FunctionHelper Function

Hier können Sie sehen, dass es weiß, dass es sich um einen Helfer handelt. Statt "invokeAmbiguous" zu sagen, heißt es jetzt "invokeHelper" und daher gibt es auch in der Funktion keinen if/else-Block mehr. Es stellt jedoch weiterhin sicher, dass der Helfer vorhanden ist, und versucht, für eine Funktion mit demselben Namen auf den Kontext zurückzugreifen, falls dies nicht der Fall ist.

Eine weitere erwähnenswerte Sache ist, dass Sie sehen können, wie die Parameter für Helfer direkt übergeben werden und wenn möglich, wenn die Funktion generiert wird (die Nummer 3 in der Doppelfunktion), tatsächlich fest codiert sind.

Das letzte Beispiel, das ich behandeln möchte, handelt von Blockhelfern.


Block Helfer

Mit Block-Helfern können Sie andere Token in eine Funktion einbinden, die ihren eigenen Kontext und ihre eigenen Optionen festlegen kann. Schauen wir uns ein Beispiel mit dem Standard-Blockhelfer 'if' an:

1
<script id="dt" type="template/handlebars">
2
    Hello
3
    {{#if name}}
4
        {{{name}}}
5
    {{else}}
6
        World!
7
    {{/if}}
8
</script>

Hier prüfen wir, ob "name" im aktuellen Kontext gesetzt ist. In diesem Fall wird er angezeigt, andernfalls geben wir "World!" Aus. Wenn Sie dies in unserem Analysegerät ausführen, werden nur zwei Token angezeigt, obwohl mehr vorhanden sind. Dies liegt daran, dass jeder Block als eigene 'Vorlage' ausgeführt wird, sodass alle darin enthaltenen Token (wie {{{name}}}) nicht Teil des äußeren Aufrufs sind und Sie ihn aus dem Knoten des Blocks selbst extrahieren müssen.

Wenn Sie sich außerdem die Funktion ansehen:

Block HelperBlock HelperBlock Helper

Sie können sehen, dass die Funktionen des Blockhelfers tatsächlich in die Funktion der Vorlage kompiliert werden. Es gibt zwei, weil eine die Hauptfunktion und die andere die Umkehrfunktion ist (wenn der Parameter nicht existiert oder falsch ist). Die Hauptfunktion: "program1" ist genau das, was wir vorher hatten, als wir nur Text und einen einzelnen Platzhalter hatten, denn wie ich bereits erwähnt habe, werden alle Blockhilfefunktionen genau wie eine reguläre Vorlage aufgebaut und behandelt. Sie werden dann durch den "if" -Helfer geführt, um die richtige Funktion zu erhalten, die dann an den äußeren Puffer angehängt wird.

Wie zuvor ist es erwähnenswert, dass der erste Parameter für einen Blockhelfer der Schlüssel selbst ist, während der Parameter 'this' auf den gesamten übergebenen Kontext gesetzt ist, was nützlich sein kann, wenn Sie Ihre eigenen Blockhelfer erstellen.


Abschluss

In diesem Artikel haben wir vielleicht keinen praktischen Blick darauf geworfen, wie man etwas im Lenker erreicht, aber ich hoffe, Sie haben ein besseres Verständnis dafür, was genau hinter den Kulissen vor sich geht, damit Sie mit diesem neuen Fund bessere Vorlagen und Helfer erstellen können Wissen.

Ich hoffe, Sie haben es genossen zu lesen, wie immer, wenn Sie Fragen haben, können Sie mich gerne auf Twitter (@GabrielManricks) oder im Nettuts+ IRC (#nettuts on freenode) kontaktieren.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.