1. Web Design
  2. HTML/CSS
  3. HTML

Erstellen Sie Ihren eigenen Yeoman-Generator

Die Generatoren von Yeoman bieten der Plattform Flexibilität und ermöglichen es Ihnen, dasselbe Tool für jede Art von Projekt, an dem Sie arbeiten, JavaScript oder auf andere Weise, wiederzuverwenden. Bevor ich die enorme Bibliothek von über 400 von der Community bereitgestellten Generatoren erwähne. Manchmal haben Sie jedoch ein bestimmtes Setup, das Sie gerne in Ihren eigenen Projekten verwenden möchten. In solchen Situationen ist es sinnvoll, die gesamte Boilerplate in Ihren eigenen Generator zu abstrahieren.
Scroll to top

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

Die Generatoren von Yeoman bieten der Plattform Flexibilität und ermöglichen es Ihnen, dasselbe Tool für jede Art von Projekt, an dem Sie arbeiten, JavaScript oder auf andere Weise, wiederzuverwenden. Bevor ich die enorme Bibliothek von über 400 von der Community bereitgestellten Generatoren erwähne. Manchmal haben Sie jedoch ein bestimmtes Setup, das Sie gerne in Ihren eigenen Projekten verwenden möchten. In solchen Situationen ist es sinnvoll, die gesamte Boilerplate in Ihren eigenen Generator zu abstrahieren.

In diesem Artikel werden wir einen Yeoman-Generator erstellen, mit dem wir zeilenweise eine einzelne Seite erstellen können. Dies ist ein recht einfaches Beispiel, aber es ermöglicht uns, viele der interessanteren Funktionen zu behandeln, die Yeoman bietet.

Einrichten

Ich gehe davon aus, dass Sie Node.js Setup haben. Wenn nicht, können Sie die Installation von hier aus abrufen. Außerdem müssen wir Yeoman sowie den Generator für die Erstellung von Generatoren installiert haben. Sie können dies über den folgenden Befehl an npm erreichen:

1
npm install -g yo generator-generator

Schließlich müssen wir das eigentliche Projekt erstellen. Navigieren Sie also zu dem Ordner Ihrer Wahl und führen Sie Folgendes aus:

1
yo generator

Dadurch wird der Generator gestartet und Sie erhalten einige Fragen wie den Projektnamen und Ihr GitHub-Konto. In diesem Artikel werde ich meinen Generator auf one page benennen.

Die Dateistruktur

Lassen Sie sich nicht von der großen Anzahl von Dateien beunruhigen, die durch den Befehl generiert werden. Alles wird in einem Moment Sinn machen.

File Tree for Yeoman Generator

Die ersten paar Dateien sind Dotfiles für verschiedene Dinge wie Git und Travis CI. Wir haben eine package.json-Datei für den Generator selbst, eine readme-Datei und einen Ordner für Tests. Außerdem erhält jeder Befehl in unserem Generator einen Ordner mit einer index.js-Datei und einen Ordner für Vorlagen.

Die Datei index.js muss das eigentliche Generatorobjekt exportieren, das von Yeoman ausgeführt wird. Ich werde alles im eigentlichen Generator löschen, damit wir von vorne anfangen können. So sollte die Datei danach aussehen:

1
'use strict';
2
var util = require('util');
3
var path = require('path');
4
var yeoman = require('yeoman-generator');
5
var chalk = require('chalk');
6
7
var OnepageGenerator = yeoman.generators.Base.extend({
8
9
});
10
11
module.exports = OnepageGenerator;

Ein Yeoman-Generator kann sich aus zwei verschiedenen vorgefertigten Optionen erstrecken: dem Base-Generator, von dem Sie sehen, dass dieser erbt, oder dem NamedBase-Generator, der tatsächlich dasselbe ist, außer dass er einen einzelnen Parameter hinzufügt, den der Benutzer festlegen kann, wenn er sie festlegt Rufen Sie Ihren Generator an. Es spielt keine Rolle, für welche Sie sich entscheiden, Sie können Parameter jederzeit manuell hinzufügen, wenn Sie mehr benötigen.

Alles, was dieser Code tut, ist, dieses Generatorobjekt zu erstellen und es zu exportieren. Yeoman ruft das exportierte Objekt tatsächlich ab und führt es aus. Die Art und Weise, wie es ausgeführt wird, besteht darin, zuerst die Konstruktormethode aufzurufen, um das Objekt einzurichten. Anschließend werden alle Methoden, die Sie für dieses Objekt erstellt haben (in der Reihenfolge, in der Sie sie erstellt haben), einzeln ausgeführt. Dies bedeutet, dass es nicht wirklich wichtig ist, wie Sie die Funktionen nennen, und es gibt Ihnen die Flexibilität, ihnen Dinge zu nennen, die für Sie sinnvoll sind.

Das andere, was Sie wissen sollten, ist, dass Yeoman seine eigenen Funktionen für den Umgang mit dem Dateisystem hat, die Ihnen wirklich bei der Suche nach Pfaden helfen. Alle Funktionen, die Sie normalerweise verwenden würden, wie mkdir, read, write, copy usw., wurden bereitgestellt, aber Yeoman verwendet das Vorlagenverzeichnis im Ordner dieses Befehls als Pfad zum Lesen von Daten und den Ordner, in dem der Benutzer Ihren Generator ausführt Stammpfad für die Ausgabe von Dateien. Dies bedeutet, dass Sie beim Arbeiten mit Dateien nicht einmal über die vollständigen Pfade nachdenken müssen. Sie können lediglich copy ausführen, und yeoman übernimmt die beiden unterschiedlichen Speicherorte für Sie.

Input bekommen

Mit Yeoman können Sie Ihrem Generator Fragen hinzufügen, damit Sie Eingaben vom Benutzer erhalten und das Verhalten entsprechend anpassen können. Wir werden zwei Fragen in unserem Generator haben. Der erste ist der Name des Projekts und der zweite ist, ob ein Dummy-Abschnitt als Beispiel eingefügt werden soll oder nicht.

Um dies zu erreichen, fügen wir eine Funktion hinzu, die den Benutzer zur Eingabe dieser beiden Informationen auffordert und die Ergebnisse dann auf unserem Objekt selbst speichert:

1
var OnepageGenerator = yeoman.generators.Base.extend({
2
    promptUser: function() {
3
        var done = this.async();
4
5
        // have Yeoman greet the user

6
        console.log(this.yeoman);
7
8
        var prompts = [{
9
            name: 'appName',
10
            message: 'What is your app\'s name ?'
11
        },{
12
            type: 'confirm',
13
            name: 'addDemoSection',
14
            message: 'Would you like to generate a demo section ?',
15
            default: true
16
        }];
17
18
        this.prompt(prompts, function (props) {
19
            this.appName = props.appName;
20
            this.addDemoSection = props.addDemoSection;
21
  
22
            done();
23
        }.bind(this));
24
    }
25
});

Die erste Zeile innerhalb der Funktion legt eine done-Variable aus der async-Methode des Objekts fest. Yeoman versucht, Ihre Methoden in der Reihenfolge auszuführen, in der sie definiert sind. Wenn Sie jedoch einen asynchronen Code ausführen, wird die Funktion beendet, bevor die eigentliche Arbeit abgeschlossen ist, und Yeoman startet die nächste Funktion vorzeitig. Um dies zu umgehen, können Sie die async-Methode aufrufen, die einen Rückruf zurückgibt. Yeoman wartet dann mit der nächsten Funktion, bis dieser Rückruf ausgeführt wird. Dies wird am Ende nach Aufforderung durch den Benutzer angezeigt.

Die nächste Zeile, in der wir nur yeoman protokollieren, ist das Yeoman-Logo, das Sie gesehen haben, als wir den Yeoman-Generator kurz zuvor betrieben haben. Als nächstes haben wir eine Liste von Eingabeaufforderungen definiert. Jede Eingabeaufforderung hat einen Typ, einen Namen und eine Nachricht. Wenn kein Typ angegeben wurde, wird standardmäßig "input" für die Standardtexteingabe verwendet. Es gibt viele coole Eingabetypen wie Listen, Kontrollkästchen und Passwörter. Die vollständige Liste finden Sie hier. In unserer Anwendung verwenden wir eine Texteingabe und einen Bestätigungstyp (Ja/Nein).

Wenn das Fragenfeld fertig ist, können wir es zusammen mit einer Rückruffunktion an die prompt-Methode übergeben. Der erste Parameter der Rückruffunktion ist die Liste der vom Benutzer zurückerhaltenen Antworten. Wir hängen diese Eigenschaften dann an unser Hauptobjekt an und rufen die Methode done auf, um mit der nächsten Funktion fortzufahren.

Gerüst Unsere Anwendung

Unsere Anwendung hat das äußere Skelett, das die Kopfzeile, das Menü, die Fußzeile und etwas CSS enthält, und dann haben wir den inneren Inhalt, der zusammen mit einer benutzerdefinierten CSS-Datei pro Abschnitt in ein eigenes Verzeichnis verschoben wird. Auf diese Weise können Sie globale Eigenschaften in der Hauptdatei festlegen und jede Zeile für sich anpassen.

Beginnen wir mit der Header-Datei. Erstellen Sie einfach eine neue Datei im Vorlagenverzeichnis mit dem Namen _header.html mit den folgenden Angaben:

1
<!DOCTYPE HTML>
2
<html>
3
<head>
4
    <meta charset="utf-8">
5
    <title><%= site_name %></title>
6
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.12.0/css/semantic.min.css">
7
    <link rel="stylesheet" href="css/main.css">
8
</head>
9
<body>

Die Dateinamen müssen nicht mit einem Unterstrich beginnen, es wird jedoch empfohlen, den Namen vom endgültigen Ausgabenamen zu unterscheiden, damit Sie erkennen können, um welchen es sich handelt. Sie können auch sehen, dass ich neben der Aufnahme unserer CSS-Hauptdatei auch die Semantic UI-Bibliothek als Raster für Zeilen und für das Menü einbinde (ganz zu schweigen vom großartigen Styling).

Eine andere Sache, die Sie vielleicht bemerkt haben, ist, dass ich einen Platzhalter im EJS-Stil für den Titel verwendet habe, der zur Laufzeit ausgefüllt wird. Yeoman verwendet Underscore-Vorlagen (also 10 Striche). Als solche können Sie Ihre Dateien wie folgt erstellen und die Daten werden zur Laufzeit generiert.

Als nächstes erstellen wir die Fußzeile (fügen Sie diese in eine Datei mit dem Namen _footer.html im Vorlagenordner ein):

1
    </body>
2
</html>

Es schließt nur das HTML-Dokument für uns, da wir kein JavaScript in unserer Anwendung haben. Die letzte HTML-Datei, die für das äußere Gerüst benötigt wird, ist das Menü. Wir werden die tatsächlichen Links zur Laufzeit generieren, aber wir können den äußeren Container in eine Vorlagendatei namens _menu.html schreiben:

1
<div class="ui fixed inverted menu">
2
</div>

Es ist ein einfaches Div mit einigen Klassen, die von Semantic UI bereitgestellt werden. Wir werden die tatsächlichen Links basierend auf den generierten Abschnitten später abrufen. Als nächstes erstellen wir die Datei _main.css:

1
body{
2
    margin: 0;
3
    font-family: "Open Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
4
}

Nur ein Code, der bei der Menüpositionierung und beim Ändern der Schriftart hilft. Hier können Sie jedoch beliebige Standardstile für das gesamte Dokument festlegen. Jetzt müssen wir Vorlagen für die einzelnen Abschnitte und die zugehörigen CSS-Dateien erstellen. Dies sind ziemlich einfache Dateien, da sie für den Benutzer zum Anpassen ziemlich leer bleiben. Fügen Sie daher in _section.html Folgendes hinzu:

1
<div id="<%= id %>" class="ui grid">
2
    <div class="sixteen column">
3
        <h1><%= content %></h1>
4
    </div>
5
</div>

Nur ein äußeres Div mit einer eindeutigen ID, die wir basierend auf dem Namen des Abschnitts und einer Klasse für das semantische Rastersystem generieren. Dann habe ich innen ein H1-Tag hinzugefügt, das wiederum nur den Namen des Abschnitts enthält. Als Nächstes erstellen wir die CSS-Datei. Erstellen Sie daher eine Datei mit dem Namen section.css mit den folgenden Angaben:

1
#<%= id %>{
2
    background: #FFFFFF;
3
    color: #000;
4
}

Dies ist eher ein Platzhalter mit derselben ID wie der äußere Container, aber es ist üblich, den Hintergrund jeder Zeile zu ändern, um den Inhalt zu trennen. Deshalb habe ich diese Eigenschaft der Einfachheit halber hinzugefügt. Jetzt erstellen wir zur Vervollständigung eine Datei mit dem Namen _gruntfile.js. Wir werden sie jedoch später füllen und die bereitgestellte Datei _package.json ausfüllen:

1
{
2
    "name": "appname",
3
    "version": "0.0.0",
4
    "devDependencies": {
5
        "grunt": "~0.4.2",
6
        "grunt-contrib-connect": "~0.6.0",
7
        "grunt-contrib-concat": "~0.3.0",
8
        "grunt-contrib-cssmin": "~0.7.0"
9
    }
10
}

Es ist ein bisschen verderblich, was wir später tun werden, aber es sind die Abhängigkeiten, die wir für unsere Grunt-Aufgaben benötigen.

Wenn alle diese Dateien fertig sind, fügen wir die Methoden hinzu, um ein neues Projekt zu erstellen. Zurück in unserer Datei index.js fügen wir direkt nach der promptUser-Methode eine Funktion hinzu, um alle Ordner zu erstellen, die wir benötigen:

1
scaffoldFolders: function(){
2
    this.mkdir("app");
3
    this.mkdir("app/css");
4
    this.mkdir("app/sections");
5
    this.mkdir("build");
6
},

Ich fügte einen app-Ordner hinzu und darin zwei weitere Verzeichnisse: css und sections. Hier würde der Endbenutzer dort eine Anwendung erstellen. Ich habe auch einen build-Ordner erstellt, in dem die verschiedenen Abschnitte und CSS-Dateien zusammen kompiliert und in eine einzige Datei integriert werden.

Als nächstes fügen wir eine weitere Methode hinzu, um einige unserer Vorlagen zu kopieren:

1
copyMainFiles: function(){
2
    this.copy("_footer.html", "app/footer.html");
3
    this.copy("_gruntfile.js", "Gruntfile.js");
4
    this.copy("_package.json", "package.json");
5
    this.copy("_main.css", "app/css/main.css");    
6
7
    var context = { 
8
        site_name: this.appName 
9
    };
10
11
    this.template("_header.html", "app/header.html", context);
12
},

Hier verwenden wir zwei neue Methoden, copy und template, die in ihrer Funktion ziemlich ähnlich sind. copy nimmt die Datei aus dem Vorlagenverzeichnis und verschiebt sie unter Verwendung der angegebenen Pfade in den Ausgabeordner. Die template macht dasselbe, außer dass sie vor dem Schreiben in den Ausgabeordner die Vorlagenfunktion von Underscore zusammen mit dem Kontext durchläuft, um die Platzhalter auszufüllen.

Ich habe das Menü noch nicht kopiert, da wir dafür die Links generieren müssen, aber wir können die Links erst wirklich generieren, wenn wir den Demo-Bereich hinzufügen. Die nächste Methode, die wir erstellen, fügt den Demo-Abschnitt hinzu:

1
generateDemoSection: function(){
2
    if (this.addDemoSection) {
3
        var context = {
4
            content: "Demo Section",
5
            id: this._.classify("Demo Section")
6
        }
7
  
8
        var fileBase = Date.now() + "_" + this._.underscored("Demo Section");
9
        var htmlFile = "app/sections/" + fileBase + ".html";
10
        var cssFile  = "app/css/" + fileBase + ".css"; 
11
  
12
        this.template("_section.html", htmlFile, context);
13
        this.template("_section.css", cssFile, context);
14
    }
15
},

Eine weitere Funktion, mit der Sie möglicherweise nicht vertraut sind, ist die classify-Funktion, die Ihnen von Underscore Strings zur Verfügung gestellt wird. Was es tut, ist, dass es eine Zeichenfolge benötigt und eine "Klassen" -Version davon erstellt. Es entfernt Dinge wie Leerzeichen und erstellt eine Version mit Kamelhülle, die für Dinge wie HTML-Klassen und IDs geeignet ist. underscored macht das Gleiche, außer dass sie anstelle von Kamelhüllen Schlangenhüllen tragen. Abgesehen davon ist alles, was wir in der vorherigen Funktion getan haben. Das einzige andere erwähnenswerte ist, dass wir einen Zeitstempel vorab ausstellen, um die Dateien eindeutig zu halten, aber auch um sie zu bestellen. Wenn wir die Dateien laden, sind sie alphabetisch sortiert, sodass die Zeit als Präfix sie in Ordnung hält.

Die letzte Datei, die kopiert werden muss, ist die Datei menu.html. Dazu müssen wir jedoch die Links generieren. Das Problem ist, dass wir zu diesem Zeitpunkt nicht wirklich wissen, welche Dateien zuvor generiert wurden oder ob der Benutzer Abschnitte manuell hinzugefügt hat. Um das Menü aufzubauen, müssen wir aus dem Ausgabepfad lesen und nach dem Lesen der dort vorhandenen Abschnitte die Menüverknüpfungen erstellen. Es gibt eine Handvoll neuer Funktionen, die wir noch nicht verwendet haben, daher werden wir sie Zeile für Zeile durchgehen:

1
generateMenu: function(){
2
    var menu = this.read("_menu.html");
3
4
    var t = '<a><%= name %></a>';
5
    var files = this.expand("app/sections/*.html");
6
7
    for (var i = 0; i < files.length; i++) {
8
        var name = this._.chain(files[i]).strRight("_").strLeftBack(".html").humanize().value();
9
  
10
        var context = {
11
            name: name,
12
            id: this._.classify(name)
13
        };
14
  
15
        var link = this.engine(t, context);
16
        menu = this.append(menu, "div.menu", link);
17
    }
18
19
    this.write("app/menu.html", menu);
20
},

In der ersten Zeile wird der äußere HTML-Code des Menüs eingelesen, und dann habe ich eine Vorlagenzeichenfolge erstellt, die wir für jeden Link verwenden können. Danach habe ich die expand-Funktion verwendet, die einen Ressourcenpfad relativ zu dem Ordner akzeptiert, in dem der Generator aufgerufen wurde (der Ausgabepfad), und ein Array aller Dateien zurückgegeben, die dem angegebenen Muster entsprechen. In unserem Fall wird dies zurückgegeben alle Abschnitt HTML-Dateien.

Dann durchlaufen wir die Liste der Dateien und entfernen für jede Datei den Zeitstempel und die Dateierweiterung mit Unterstrichen, sodass nur der Name der Datei übrig bleibt. Bei der humanize-Methode werden Dinge mit Kamelhülle oder Zeichen wie Unterstriche und Bindestriche in Leerzeichen umgewandelt, sodass Sie eine von Menschen lesbare Zeichenfolge erhalten. Es wird auch der erste Buchstabe der Zeichenfolge großgeschrieben, was für unser Menü hervorragend geeignet ist. Wenn der Name getrennt ist, erstellen wir ein Kontextobjekt zusammen mit der zuvor verwendeten ID, sodass die Links uns tatsächlich zu den richtigen Abschnitten führen.

Jetzt haben wir zwei neue Funktionen, die wir noch nicht gesehen haben: engine und append. Die engine verwendet eine Vorlagenzeichenfolge als ersten Parameter und ein Kontextobjekt als zweiten und führt sie durch die Vorlagen-Engine und gibt die Ergebnisse zurück. append akzeptiert eine HTML-Zeichenfolge als ersten Parameter, einen Selektor als zweiten und etwas, das als dritter Parameter eingefügt werden soll. Es wird der bereitgestellte Inhalt am Ende aller Elemente eingefügt, die dem Selektor entsprechen. Hier fügen wir den Link zum Ende des menu div hinzu.

Zu guter Letzt haben wir die write-Methode, mit der unsere berechnete HTML-Zeichenfolge in die Datei geschrieben wird. Um hier fertig zu werden, fügen wir eine Methode zum Ausführen von npm hinzu:

1
runNpm: function(){
2
    var done = this.async();
3
    this.npmInstall("", function(){
4
        console.log("\nEverything Setup !!!\n");
5
        done();
6
    });
7
}

Der erste Parameter für npmInstall sind die Pfade, aber Sie können dieses Feld einfach leer lassen. Außerdem drucke ich am Ende nur eine Nachricht aus, um dem Benutzer mitzuteilen, dass die App bereit ist.

Kurz gesagt, wenn unser Generator läuft, fragt er den Benutzer nach dem Projektnamen und ob ein Demo-Abschnitt eingefügt werden soll oder nicht. Danach wird die Ordnerstruktur erstellt und alle für unser Projekt benötigten Dateien kopiert. Sobald dies erledigt ist, wird npm ausgeführt, um die von uns definierten Abhängigkeiten zu installieren, und es wird die Nachricht angezeigt, die wir gerade eingegeben haben.

Dies ist so ziemlich alles, was der Hauptgenerator tun muss, aber ohne die Grunt-Aufgaben ist es nicht so nützlich. Derzeit handelt es sich nur um eine Reihe separater Dateien. Um die Abschnitte jedoch ordnungsgemäß zu entwickeln, benötigen wir eine Möglichkeit, sie als einzelne Datei in der Vorschau anzuzeigen, und wir müssen auch in der Lage sein, die Ergebnisse zu erstellen. Öffnen Sie also _gruntfile.js aus dem Vorlagenverzeichnis und legen Sie los:

1
module.exports = function(grunt) {
2
  grunt.initConfig({
3
    //task config

4
  });
5
6
  grunt.loadNpmTasks('grunt-contrib-connect');
7
  grunt.loadNpmTasks('grunt-contrib-cssmin');
8
  grunt.loadNpmTasks('grunt-contrib-concat');
9
10
  grunt.registerTask('serve', ['connect']);
11
  grunt.registerTask('build', ['concat', 'cssmin']);
12
  grunt.registerTask('default', ['build']);
13
};

Bisher ist dies nur das Standard-Boilerplate, wir müssen eine Funktion exportieren und darin die drei Abhängigkeiten einfügen, die wir der Datei package.json hinzugefügt haben, und dann registrieren wir die Aufgaben. Wir werden eine serve-Aufgabe haben, die sich wie ein Server verhält und es uns ermöglicht, die einzelnen Dateien zusammen anzuzeigen, während wir unsere Site entwickeln. Dann haben wir den build-Befehl, der alle HTML- und CSS-Elemente in unserer App für die Bereitstellung zusammen kombiniert. Ich habe auch die letzte Zeile hinzugefügt. Wenn Sie also nur selbst grunt, wird davon ausgegangen, dass Sie erstellen möchten.

Beginnen wir nun mit der Konfiguration des build-Befehls, da dieser viel kürzer ist:

1
concat: {
2
    dist: {
3
        src: ["app/header.html", "app/menu.html", "app/sections/*.html", "app/footer.html"],
4
        dest: "build/index.html"
5
    }
6
},
7
cssmin: {
8
    css: {
9
        files: {
10
            "build/css/main.css": ["app/css/*.css"]
11
        }
12
    }
13
}

Für den HTML-Code werden nur alle HTML-Dateien in der Reihenfolge verkettet, die im Build-Ordner unter dem Namen index.html angegeben ist. Mit dem CSS möchten wir es auch minimieren, also verwenden wir das cssmin-Plugin und geben an, dass wir eine Datei namens main.css im Ordner build/css ausgeben möchten und dass sie die minimierten Versionen aller enthalten soll die CSS-Dateien in unserem Projekt.

Server verbinden

Als nächstes müssen wir die Konfiguration für den Connect-Server hinzufügen. Connect bietet eine Menge großartiger Middleware zum Bereitstellen statischer Dateien oder Verzeichnisse. Hier müssen jedoch zuerst alle Dateien kombiniert werden, sodass einige benutzerdefinierte Handler erstellt werden müssen. Lassen Sie uns zunächst die Basiskonfiguration einfügen in:

1
connect: {
2
    server: {
3
        options: {
4
            keepalive: true,
5
            open: true,
6
            middleware: function(){
7
                //custom handlers here

8
            }
9
        }
10
    }
11
},

Die keepalive-Option weist Grunt an, den Server am Laufen zu halten. Die open-Option weist Grunt an, die URL in Ihrem Browser automatisch zu öffnen, wenn Sie den Server starten (dies ist jedoch eher eine persönliche Präferenz), und die middleware-Funktion soll ein Array von zurückgeben Middleware-Handler zur Verarbeitung der Anforderungen.

In unserer Anwendung müssen grundsätzlich zwei Ressourcen verarbeitet werden, die Stammressource (unsere "index.html"). In diesem Fall müssen wir alle unsere HTML-Dateien kombinieren und zurückgeben und dann die "main.css"-Ressource in In diesem Fall möchten wir alle CSS-Dateien zusammen zurückgeben. Was alles andere betrifft, können wir einfach einen 404 zurückgeben.

Beginnen wir also damit, ein Array für die Middleware zu erstellen und das erste hinzuzufügen (dies gehört zur Funktion der middleware-Eigenschaft, in der ich den Kommentar platziert habe):

1
  var middleware = [];
2
  
3
  middleware.push(function(req, res, next) {
4
      if (req.url !== "/") return next();
5
      
6
      res.setHeader("Content-type", "text/html");
7
      var html = grunt.file.read("app/header.html");
8
      html += grunt.file.read("app/menu.html");
9
  
10
      var files = grunt.file.expand("app/sections/*.html");
11
              
12
      for (var i = 0; i < files.length; i++) {
13
          html += grunt.file.read(files[i]);
14
      }
15
   
16
      html += grunt.file.read("app/footer.html");
17
      res.end(html);
18
  });

Wir sind von Yeomans API zu Grunts gewechselt, aber zum Glück sind die Befehlsnamen fast identisch. Wir verwenden immer noch read, um Dateien zu lesen und zu expand, um die Liste der Dateien zu erhalten. Außerdem setzen wir nur den Inhaltstyp und geben die verkettete Version aller HTML-Dateien zurück. Wenn Sie mit einem Middleware-basierten Server nicht vertraut sind, wird er im Grunde genommen den Middleware-Stack durchlaufen, bis irgendwo auf der Linie eine Funktion die Anforderung verarbeiten kann.

In der ersten Zeile prüfen wir, ob es sich bei der Anforderung um die Root-URL handelt. Wenn dies der Fall ist, bearbeiten wir die Anforderung. Andernfalls rufen wir next() auf, wodurch die Anforderung in der Zeile an die nächste Middleware weitergeleitet wird.

Als nächstes müssen wir die Anfrage an /css/main.css übergeben. Wenn Sie sich erinnern, haben wir diese in der Kopfzeile eingerichtet:

1
  middleware.push(function(req, res, next){
2
      if (req.url !== "/css/main.css") return next();
3
      
4
      res.setHeader("Content-type", "text/css");
5
      var css = "";
6
  
7
      var files = grunt.file.expand("app/css/*.css");
8
      for (var i = 0; i < files.length; i++) {
9
           css += grunt.file.read(files[i]);
10
      }
11
  
12
      res.end(css);
13
  });

Grundsätzlich ist dies die gleiche Funktion wie zuvor, mit Ausnahme der CSS-Dateien. Zu guter Letzt werde ich eine 404-Nachricht hinzufügen und den Middleware-Stack zurückgeben:

1
  middleware.push(function(req, res){
2
      res.statusCode = 404;
3
      res.end("Not Found");
4
  });
5
  
6
  return middleware;

Das heißt, wenn Anfragen von den beiden vorherigen Funktionen nicht bearbeitet werden, senden wir diese 404-Nachricht. Und das ist alles, was dazu gehört: Wir haben den Generator, der das Projekt erstellt, und die Grunt-Aufgaben, mit denen wir eine Vorschau unserer App anzeigen und erstellen können. Das letzte Thema, das ich kurz behandeln möchte, sind Untergeneratoren.

Untergeneratoren

Wir haben bereits den Hauptgenerator erstellt, aus dem unsere Anwendung besteht. Mit Yeoman können Sie jedoch so viele Untergeneratoren erstellen, wie Sie nach dem ersten Gerüst verwenden möchten. In unserer Anwendung benötigen wir einen, um neue Abschnitte zu generieren. Um loszulegen, können wir tatsächlich einen Subgenerator aus dem generator:generator, um die Datei zu erstellen. Führen Sie dazu einfach den folgenden Befehl in unserem onepage-Ordner aus:

1
yo generator:subgenerator section

Dadurch wird ein neuer Ordner mit dem Namen section mit einer index.js-Datei und einem template-Ordner erstellt, genau wie bei unserem Hauptgenerator(App-Generator). Lassen Sie uns den Generator wie beim letzten Mal leeren und Sie sollten Folgendes haben:

1
'use strict';
2
var util = require('util');
3
var yeoman = require('yeoman-generator');
4
5
var SectionGenerator = yeoman.generators.NamedBase.extend({
6
7
});
8
9
module.exports = SectionGenerator;

Möglicherweise stellen Sie auch fest, dass wir von der NamedBase aus die reguläre Base erweitern. Dies bedeutet, dass Sie beim Aufruf des Generators einen Namensparameter angeben müssen und dann mit this.name auf this.name zugreifen können. Dieser Code besteht im Wesentlichen aus den beiden Funktionen, die wir bereits im vorherigen Generator geschrieben haben: generateDemoSection und generateMenu, sodass wir diese beiden Funktionen hier einfach kopieren können. Ich werde den Namen des ersten durch etwas passenderes ersetzen:

1
generateSection: function(){
2
    var context = {
3
        content: this.name,
4
        id: this._.classify(this.name)
5
    }
6
    
7
    var fileBase = Date.now() + "_" + this._.underscored(this.name);
8
    var htmlFile = "app/sections/" + fileBase + ".html";
9
    var cssFile  = "app/css/" + fileBase + ".css"; 
10
    
11
    this.template("_section.html", htmlFile, context);
12
    this.template("_section.css", cssFile, context);
13
},
14
15
generateMenu: function(){
16
    var menu = this.read("_menu.html");
17
    
18
    var t = '<a><%= name %></a>';
19
    var files = this.expand("app/sections/*.html");
20
    
21
    for (var i = 0; i < files.length; i++) {
22
        var name = this._.chain(files[i]).strRight("_").strLeftBack(".html").humanize().value();
23
        
24
        var context = {
25
            name: name,
26
            id: this._.classify(name)
27
        };
28
        
29
        var link = this.engine(t, context);
30
        menu = this.append(menu, "div.menu", link);
31
    }
32
    
33
    this.write("app/menu.html", menu);
34
}

Der einzige Unterschied besteht darin, dass ich anstelle der direkten Eingabe des Namens die Eigenschaft this.name verwende. Die einzige andere Sache ist, dass wir die Vorlagendateien _sections.html, _section.css und _menu.html von app/templates nach section/templates verschieben müssen, da dort die Befehle template/read angezeigt werden.

Codeduplizierung

Das Problem ist jetzt die Codeduplizierung. Zurück in der Datei app/index.js können wir also nicht den gleichen Code wie der Subgenerator haben, sondern einfach den Subgenerator aufrufen und ihm das Argument name übergeben. Sie können generateMenu vollständig aus app/index.js entfernen, und wir werden generateDemoSection wie folgt ändern:

1
generateDemoSection: function() {
2
      if (this.addDemoSection) {
3
          var done = this.async();
4
          this.invoke("onepage:section", {args: ["Demo Section"]}, function(){
5
              done();
6
          });
7
      } else {
8
          this.write( "app/menu.html", "");
9
      }
10
},

Wenn der Benutzer den Demo-Abschnitt erstellen wollte, invoke wir den Subgenerator auf, der das erste Argument übergibt, das der Name ist. Wenn der Benutzer den Demo-Beitrag nicht haben wollte, müssen wir noch etwas für das Menü erstellen, da unsere Grunt-Aufgaben versuchen, es zu lesen. Im letzten Abschnitt schreiben wir einfach eine leere Zeichenfolge in die Menüdatei.

Unser Generator ist endlich fertig und wir können ihn jetzt testen.

Testen Sie es aus

Als erstes müssen wir unseren Generator auf Ihrem System installieren. Anstatt das Gem regelmäßig zu installieren, können wir den Ordner mit dem Befehl link einfach mit dem Gem-Pfad verknüpfen. Auf diese Weise können wir hier weiterhin Änderungen vornehmen, ohne es jedes Mal neu installieren zu müssen. Führen Sie im Projektverzeichnis einfach nom link aus.

Der letzte Schritt besteht darin, den Generator tatsächlich zu betreiben. Erstellen Sie einfach einen neuen Ordner und führen Sie auf Ihrer yo onepage unseren Hauptgenerator aus und installieren Sie die Abhängigkeiten über npm. Wir können dann unsere Grunt-Aufgaben verwenden, um die Seite anzuzeigen (grunt serve) oder mit unserem Generator weitere Abschnitte hinzufügen, indem wir so etwas wie yo onpage verwenden: yo onepage:section "Another section", und die neuen Dateien werden hinzugefügt.

Abschluss

In diesem Artikel haben wir viele der allgemeinen Funktionen behandelt, aber es gibt noch interessantere Optionen zum Auschecken. Wie Sie wahrscheinlich sehen können, ist beim Bau eines Generators ein wenig Boilerplate erforderlich, aber genau das ist der Punkt, an dem Sie alles einmal erledigen und dann in all Ihren Anwendungen verwenden können.

Ich hoffe, es hat Ihnen Spaß gemacht, diesen Artikel zu lesen. Wenn Sie Fragen oder Kommentare haben, können Sie diese gerne unten hinterlassen.