Komponentenorientierte Design-Patterns mit Nunjucks & Grunt

Avatar of Morgan feeney
Morgan feeney am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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

  1. Includes. PHP include()s erlauben die Erstellung wiederverwendbarer Code-Teile. Zum Beispiel: Header, Footer und Komponenten.
  2. Vertrautheit. Kandidaten wurden mit Vorkenntnissen in PHP & Content-Management-Systemen wie WordPress rekrutiert.
  3. Logik. Wie if () {} else {}. Leute konnten logische Features nutzen oder sich entscheiden, reines HTML zu schreiben, was Personen mit unterschiedlichen Fähigkeiten entgegenkam.
  4. 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

  1. Template-Vererbung. Reduziert Code-Duplizierung und ermöglicht uns, weniger Code zu pflegen.
  2. Funktionalität. Nunjucks überzeugte andere ausprobierte Template-Sprachen (wie Handlebars) aufgrund integrierter Funktionen wie Includes, Filtern und Makros.
  3. Dokumentation. Die Dokumentation ist *sehr* nützlich. Sie hilft, Nunjucks leicht zu verstehen und liefert funktionierende Beispiele.
  4. Ergebnisse. Konnte als HTML mit Grunt kompiliert werden. Dies mit PHP zu tun, erwies sich als problematisch.
  5. Logik. Wir konnten weiterhin Personen mit unterschiedlichen Fähigkeiten entgegenkommen, da man weiterhin logischen Code oder reines HTML schreiben kann.
  6. Einfachheit.

Design-Patterns mit Nunjucks

  1. Layouts
  2. Templates
  3. Komponenten
  4. Partials
  5. 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!