Vor kurzem war ich an der Erstellung eines internen Systems für die Erstellung von HTML-Prototypen beteiligt, die als Referenzpunkt für ein E-Commerce-CMS namens Hybris dienen sollten. Es begann einfach, mit PHP. Als das Team wuchs, wuchs auch die Codebasis, was die Dinge schließlich unordentlich machte und mir schlaflose Nächte bereitete.
Wir haben anfangs PHP verwendet, weil
- Includes. PHP
include()s erlauben die Erstellung wiederverwendbarer Code-Teile. Zum Beispiel: Header, Footer und Komponenten. - Vertrautheit. Kandidaten wurden mit Vorkenntnissen in PHP & Content-Management-Systemen wie WordPress rekrutiert.
- Logik. Wie
if () {} else {}. Leute konnten logische Features nutzen oder sich entscheiden, reines HTML zu schreiben, was Personen mit unterschiedlichen Fähigkeiten entgegenkam. - Einfachheit. Einfach genug, um unsere Reise zu ermöglichen.
Von Anfang an strebten wir nach einem effektiven komponentisierten System. Wir suchten nach Inspiration aus Systemen wie Atomic Design und wie Templating von CMSs wie WordPress und Magento erreicht wird.
Design-Patterns mit PHP
Wir entwickelten eine Methode, um Code-Schnipsel aufzuteilen. Wir nannten sie
- Templates
- Komponenten
- Fragments
Templates waren Webseiten, die auf einer Boilerplate-PHP-Datei basierten. Sie sahen so aus
<?php
require_once('../../_assets/classes/Prototype.class.php');
$pageTitle = 'Boilerplate template | HTML';
Prototype::getComponent('shared/head');
?>
<body>
<?php Prototype::getComponent('shared/simple-header'); ?>
<div id="main-content" class="container-outer">
<div class="container-set-width">
<div class="container-main">
<!-- ADD YOUR STUFF HERE -->
</div>
</div>
</div>
<?php Prototype::getComponent('shared/footer'); ?>
<?php Prototype::getComponent('shared/scripts'); ?>
</body>
</html>
Components waren Dinge wie Formulare, Karussells und größere wiederverwendbare Code-Blöcke. Idealerweise waren Komponenten in sich geschlossen und konnten überall platziert werden, ohne sich Sorgen machen zu müssen, dass die CSS-Vererbung das Erscheinungsbild unbeabsichtigt beeinträchtigt.
Fragments waren alles, was kleiner als eine Komponente war, wie ein Produktbild oder ein einzelnes Produkt inklusive Preis und Produkttitel.
Was PHP fehlte
Das Hauptmanko unseres PHP-Prototypensystems war die Template-Vererbung. Dies führte zu einer unerwünschten Vergrößerung der Codebasis. Es gab Dinge, die nicht mehr gebraucht wurden, und viele doppelte Dateien. Es war auch eine unangenehme Erfahrung, Dinge zu suchen und zu finden.
PHP erfüllte anfangs seinen Zweck, aber schließlich ersetzten wir es durch Nunjucks. Nunjucks ermöglichte eine andere Art von System.
Gründe, warum wir von PHP zu Nunjucks gewechselt sind
- Template-Vererbung. Reduziert Code-Duplizierung und ermöglicht uns, weniger Code zu pflegen.
- Funktionalität. Nunjucks überzeugte andere ausprobierte Template-Sprachen (wie Handlebars) aufgrund integrierter Funktionen wie Includes, Filtern und Makros.
- Dokumentation. Die Dokumentation ist *sehr* nützlich. Sie hilft, Nunjucks leicht zu verstehen und liefert funktionierende Beispiele.
- Ergebnisse. Konnte als HTML mit Grunt kompiliert werden. Dies mit PHP zu tun, erwies sich als problematisch.
- Logik. Wir konnten weiterhin Personen mit unterschiedlichen Fähigkeiten entgegenkommen, da man weiterhin logischen Code oder reines HTML schreiben kann.
- Einfachheit.
Design-Patterns mit Nunjucks
- Layouts
- Templates
- Komponenten
- Partials
- Macros
Verglichen mit der vorherigen Liste mit PHP werden Sie feststellen, dass hier Layouts und Makros neu sind. Partials ist ein anderes Wort für Fragments.
Layouts sind Boilerplate-HTML-Dateien, ähnlich wie Templates, aber dieses Mal wird eine einzige Datei als Basis verwendet, anstatt jedes Mal eine Kopie. Wie? Durch Template-Vererbung. Um die Nunjucks-Dokumentation zu zitieren:
Template-Vererbung ist eine Möglichkeit, die Wiederverwendung von Templates zu erleichtern. Beim Schreiben eines Templates können Sie „Blöcke“ definieren, die von Kind-Templates überschrieben werden können. Die Vererbungskette kann beliebig lang sein.
Hier ist ein Beispiel für eine Nunjucks-Layoutdatei
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ page_title }}</title>
<meta name="description" content="{{ page_description }}">
<link rel="stylesheet" href="{{ base_path }}css/style.css">
<meta name="viewport" content="width=device-width, initial scale=1">
</head>
<body class="{{ body_classes }}">
<article>
<nav id="main-nav" class="container-fluid navbar navbar-dark bg-inverse {{ main_nav_classes }}">
<div class="container">
{% block nav %}
{% include "components/nav.njk" %}
{% endblock %}
</div>
</nav>
<header id="main-header" class="container-fluid {{ main_header_classes }}">
<div class="container">
{% block header %}
{% include "components/header.njk" %}
{% endblock %}
</div>
</header>
<section id="main-section" class="container-fluid {{ main_section_classes }}">
<div class="container">
{% block main %}
<!-- This block (and any other block) can be replaced using template inheritance, or leave the default content in -->
<h2>This `<h2>` tag appears by default, yet can be replaced</h2>
<p class="lead">
This `<p>` tag is also a default but can be replaced.
</p>
{% endblock %}
</div>
</section>
<footer id="main-footer" class="container-fluid m-t-3 {{ main_footer_classes }}">
<div class="container">
{% block footer %}
{% include "components/footer.njk" %}
{% endblock %}
</div>
</footer>
</article>
{% block footer_scripts %}
{% include "partials/footer-scripts.njk" %}
{% endblock %}
</body>
</html>
Nunjucks und Grunt passen zum Projekt
Unsere Projektanforderungen sind spezifisch. Sie verlangen bestimmte Dinge. Zum Beispiel ist eine der Geschäftsanforderungen, dass wir HTML-Dateien für die Implementierung in Java-Template-Dateien an Offshore-Entwickler *übergeben*.
Als wir noch PHP verwendeten, nutzten wir ein paar Grunt-Plugins: grunt-php-2-html & grunt-prettify, um diesen Prozess zu automatisieren. Die Struktur des Projekts konnte jedoch oft zu Problemen mit dem Variablenbereich führen. Dies führte dazu, dass einzelne Komponenten-HTML-Dateien mit eingebetteten PHP-Fehlern kompiliert wurden. Nicht ideal! Dann öffnet man die Datei und entfernt den Fehler manuell, und dann ist er nicht mehr automatisiert, oder man verbringt Zeit damit, herauszufinden, warum der Variablenbereich völlig durcheinander ist, anstatt mit seiner Arbeit voranzukommen. *Es ist ein Prozess, der nach der Implementierung wenig Gedanken erfordern sollte*.
Glücklicherweise bedeutet die Verwendung von grunt-nunjucks-2-html, dass alle Dateien von Anfang an in HTML kompiliert werden. Um das Frontend überhaupt sehen zu können, muss man eine `.html`-Datei aufrufen. Wir verwenden Nunjucks im Wesentlichen als Static-Site-Generator. Die `.njk`-Dateien werden ignoriert und nur zum Kompilieren von HTML-Dateien wie Komponenten zum Übergeben oder vollständigen Webseiten zum Anzeigen verwendet. Die Sprache und die Ausgabe passen viel besser zu den Projektanforderungen als das alte PHP-System.
Nunjucks zur Gewährleistung der Code-Konsistenz verwenden
Es gibt noch einen anderen Aspekt unseres Projekts, der nichts mit Nunjucks zu tun hat, aber alles mit Design-Patterns.
Wir verwenden das Bootstrap-Framework (genau v3.3.4). Bei der Verwendung eines Frameworks wie Bootstrap in einem Team kann es zu Problemen mit der Codequalität kommen. Sollte es nicht geben, aber manchmal werden Konventionen nicht immer befolgt, richtig? Bootstrap ist eine großartige Ressource für bestimmte Anwendungsfälle, *aber nicht jeder Anwendungsfall ist gleich*. Die Dokumentation ist ausgezeichnet, es gibt viele Beispiele, aber manchmal kann man in Schwierigkeiten geraten, wenn man mit anderen zusammenarbeitet und das Framework von verschiedenen Personen unterschiedlich verwendet wird.
Wenn Sie dies vermeiden möchten, können Sie eine weitere großartige Funktion von Nunjucks verwenden: das Macro. Um Nunjucks zu zitieren:
Macros ermöglichen es Ihnen, wiederverwendbare Inhaltsblöcke zu definieren. Es ist ähnlich einer Funktion in einer Programmiersprache.
Hier ist ein Beispiel für ein Nunjucks-Makro
{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}" value="{{ value | escape }}" />
</div>
{% endmacro %}
Jetzt ist field als normale Funktion aufrufbar.
{{ field('user') }}
{{ field('pass', type='password') }}
Wir können einige der wiederverwendbaren Teile von Bootstrap nehmen und sie in wiederverwendbare, konfigurierbare Code-Blöcke verpacken, die wir im Folgenden als Makros bezeichnen.
Hier ist ein Beispiel für ein Bootstrap-Karten-Makro
{% macro cardMacro (image, title, text, btnText="Button") %}
<div class="card">
<img class="card-img-top img-fluid" src="{{ image }}" alt="{{ title }}">
<div class="card-block">
<h4 class="card-title">{{ title }}</h4>
<p class="card-text">{{ text }}</p>
<a href="#" class="btn btn-primary">{{ btnText }}</a>
</div>
</div>
{% endmacro %}
Der Code ist jedes Mal, wenn er verwendet wird, exakt derselbe. Es ist das Äquivalent eines Sass/Less-Mixins.
Ein Vorteil der Verwendung von Makros auf diese Weise ist, dass der Code unabhängig davon, wer ihn verwendet, immer konsistent sein wird. Dies ist ein schneller Gewinn für die Einhaltung von Standards. Ein weiterer Vorteil ist, dass Sie das Makro nur an einer Stelle aktualisieren müssen, wenn der Code später aktualisiert werden muss.
Komponentenorientierung von HTML für die Verwendung mit CSS-Frameworks.
Möchten Sie mehr erfahren? Wenn Sie bis hierher gekommen sind, haben Sie vielleicht eine ähnliche Anforderung und möchten mehr darüber erfahren, wie Sie Nunjucks und Grunt nutzen können.
Ich habe ein Repository namens: Bootstrap Patterns mit allem Code eingerichtet, der in diesem Artikel verwendet wird, sowie vielen weiteren Dingen, um Ihnen den Einstieg zu erleichtern. Ich habe hinzugefügt, was Sie benötigen, um die erforderlichen Pakete herunterzuladen. Da wir Grunt verwenden, müssen Sie Node auf Ihrem Rechner laufen haben. *Sie sollten Grundkenntnisse in der Verwendung eines Task-Runners und NPM haben*.
Was ist im Repository?
Funktionierende Beispiele dafür, wie Nunjucks, Grunt & ein CSS-Framework (in diesem Fall Bootstrap) kombiniert werden können. Es demonstriert, wie dieselben wiederverwendbaren Design-Patterns kombiniert werden können, um mehrere Variationen zu erzielen.
Betrachten wir jede der Design-Pattern-Ebenen
1) Layouts
Die Datei `layouts/layout.njk` besteht aus einer wiederverwendbaren Seitenstruktur. Ich habe alle üblichen Dinge berücksichtigt, die ich zum Erstellen von Webseiten mit Bootstrap benötige. Es gibt Variablen für Dinge wie Meta-Informationen, z. B.
<title>{{ page_title }}</title>
Variablenklassen wie
<body class="{{ body_classes }}">
Generische Bootstrap-Klassen, die für das Layout benötigt werden, wie z. B. .container und .container-fluid, die zu Elementen wie nav, header, section, footer hinzugefügt werden, zusammen mit Nunjucks-Variablen, Blöcken und Includes; um totale Flexibilität zu ermöglichen.
<header id="main-header" class="container-fluid {{ main_header_classes }}">
<div class="container">
{% block header %}
{% include "components/header.njk" %}
{% endblock %}
</div>
</header>
2) Komponenten
In der Datei: `components/nav.njk` werden die folgenden Nunjucks-Funktionen verwendet, um ein wiederverwendbares Muster zu erstellen
- Import
- For
- Filter
- Block
- Macro
{% import "macros/macro-search.njk" as macroSearch %}
<div class="nav navbar-nav">
{% for item in navItems %}
<a class="nav-item nav-link" href="{{ item.menu_item | lower | replace(" ", "") }}.html">{{ item.menu_item }}</a>
{% endfor %}
{% block navRight %}
{{ macroSearch.search() }}
{% endblock %}
</div>
Diese Datei demonstriert, wie mehrere Funktionen von Nunjucks kombiniert werden können. Ich habe ein Makro erstellt: `macros/macro-search.njk`, das wir in `components/nav.njk` importieren, da wir ein Suchformular in der Navigation hinzufügen möchten.
{% import "macros/macro-search.njk" as macroSearch %}
Wir durchlaufen einige JSON-Datenobjekte namens navItems, die zum Erstellen der Navigation verwendet werden, und verwenden dann einen Filter, um die URL klein zu schreiben und unerwünschte Leerzeichen zu entfernen, damit der Link gültig ist.
{% for item in navItems %}
<a class="nav-item nav-link" href="{{ item.menu_item | lower | replace(" ", "") }}.html">{{ item.menu_item }}</a>
{% endfor %}
3) Macros
Wir rufen das Makro für das Suchformular in Block-Tags auf, falls wir das Suchformular zu einem späteren Zeitpunkt nicht in dieser Komponente anzeigen möchten.
{% block navRight %}
{{ macroSearch.search() }}
{% endblock %}
4) Templates
In Templates erben wir Layouts wie folgt:
{% extends "layouts/layout.njk" %}
Dies ermöglicht es uns, dasselbe Layout viele Male zu erben, ohne den Code in weiteren Dateien neu schreiben zu müssen.
…Die Navigationskomponente wird dann in einem Block-Tag in der Layoutdatei aufgerufen.
<nav id="main-nav" class="container-fluid navbar navbar-dark bg-inverse {{ main_nav_classes }}">
<div class="container">
{% block nav %}
{% include "components/nav.njk" %}
{% endblock %}
</div>
</nav>
Ich umhülle die Navigation standardmäßig mit einem Block-Tag. Dieser Standard kann entweder beibehalten oder über ein Template geändert werden.
5) Partials
Die einzige Verwendung von Partials in diesem Beispiel waren einige Skript-Tags im Footer. Aber wie der Name schon sagt, ist es ein Teilcode… es könnte alles Kleine sein.
{% block footer_scripts %}
{% include "partials/footer-scripts.njk" %}
{% endblock %}
Endlich
Die Beispiele, die ich in diesem Artikel gebe, kratzen nur an der Oberfläche dessen, was erreicht werden kann. Es gibt weitere Beispiele im Repository, schauen Sie also bitte hinein und zögern Sie nicht, mir Ihre Gedanken mitzuteilen.
Ich möchte den Jungs im UI-Team bei N.Brown Group PLC, wo diese Ideen entstanden sind, ein großes Lob aussprechen, es hat Spaß gemacht!
Gibt es eine Möglichkeit, Standardwerte zu setzen, wie {{ page.title or “Standardtext” }}?
Laut der Dokumentation gibt es anscheinend zwei Möglichkeiten:
{{ foo | d('kein foo') }}{{ foo or 'kein foo' }}#2 basiert auf diesem Zitat aus der Dokumentation:
Gibt es einen Unterschied zwischen Nunjucks und Twig?
Nunjucks ist eine JS-Template-Engine, Twig ist eine PHP-Template-Engine. Sie scheinen einfach viele ähnliche Designentscheidungen zu teilen.
Nunjucks und Twig sind tatsächlich sehr ähnlich, was die Syntax angeht, aber Twig hat die zusätzlichen Vorteile von
(a) einer
include ... with-Syntax, die ich als äußerst nützlich empfunden habe, um Komponenten so einzurichten, dass sie leicht mit unterschiedlichen Daten sowohl in Pattern-Bibliotheken als auch in Haupt-Templates versorgt werden können.z.B.:
{% import "widget" with { title: "Beispiel-Widget" } %}(b) der Verfügbarkeit von PHP- und Node-Versionen, was sehr nützlich sein kann, z. B. um einer Website, die ursprünglich statisch war, einfach ein PHP-basiertes CMS hinzuzufügen, ohne die Templates ändern zu müssen (das ausgezeichnete Craft CMS verwendet beispielsweise Twig für Templating).
Aus den oben genannten Gründen habe ich tendenziell Twig gegenüber Nunjucks bevorzugt und empfehle Ihnen dringend, es sich anzusehen.
Ich habe gerade von Nunjucks gehört, daher weiß ich noch nicht viel darüber. Aber als JS-Templating-Engine, wird sie nicht unter denselben SEO-Problemen dynamischer Templating leiden wie alle anderen JS-Engines (einschließlich Angular, Mustache und alle anderen)?
Obwohl Google-Crawler JS bereits verstehen, tun es die meisten anderen Crawler nicht (besonders Facebook), was Benutzer von JS-Templating-Engines zwingt, Pre-Rendering-Magic für den Cache zu verwenden, um richtig gefunden zu werden. Nicht alles im Web ist eine App.
Dieser Artikel handelt von der Verwendung von Nunjucks zur Erstellung von HTML-Prototypen in Form von Seiten. In diesem Kontext gäbe es keine Probleme mit SEO-Auswirkungen.
Nunjucks fügt auch einige Dinge hinzu, die Twig nicht unterstützt, z. B. das
call-Tag. Wir verwenden Nunjucks täglich mit unserem eigenen Grunt-Plugin, dem grunt-nunjuckr. Alle unsere Frontend-Prototypen und statischen Seiten werden mit Nunjucks in einer Grunt-Aufgabe erstellt.Ich denke, Twig und Nunjucks sind beide großartige Sprachen für Templating. Wenn man sie beide richtig verwendet, kann man seine Templates von der Serverseite auf dem Frontend wiederverwenden. Das steht im Einklang mit progressive enhancement.