8 Möglichkeiten, um Ihr Grunt-Setup zu verbessern
Boden - das bei den net Awards 2014 im Mai als Open Source-Projekt des Jahres ausgezeichnet wurde - hat sich in unserer Branche schnell zu einem unverzichtbaren konfigurationsbasierten Befehlszeilentool für die Ausführung von Aufgaben entwickelt, die alle Arten von Anforderungen erfüllen können. Das BBC News-Entwicklungsteam verwendet Grunt täglich, um sicherzustellen, dass die bbc.co.uk/news Die Codebasis wird getestet, fusselig, formalisiert, optimiert und automatisiert.
Hier sprechen Mark McDonnell von der BBC und Tom Maslen von BBC News über acht Möglichkeiten, um Ihre Grunt-Einrichtung schnell, wartbar und skalierbar zu halten.
01. Halten Sie Ihr Gruntfile wartbar
Eines der größten Probleme für Entwickler, die mit Grunt arbeiten, ist, dass sich diese wunderbar leistungsstarke Konfigurationsdatei zu einem unhandlichen Monster entwickeln kann. Wie bei den meisten komplexen Tools können sich Probleme mit der Wartbarkeit in kurzer Zeit schnell ansammeln. Die Benutzer sind überwältigt davon, wie sie die Komplexität, mit der sie jetzt konfrontiert sind, am besten lösen können.
Der beste Weg, um dieses Problem anzugehen, besteht darin, so viel wie möglich zu vereinfachen. Wenn wir ein Blatt aus dem objektorientierten Entwurfshandbuch herausnehmen würden, würden wir wissen, dass unsere Konfigurationsdatei zu viel leistet und dass wir sie in Bestandteile zerlegen müssen, um unsere Gruntfile-Anforderungen leichter erweitern und verwalten zu können ( wenn wir zum Beispiel weitere Aufgaben und Konfigurationseinstellungen hinzufügen müssen).
Was wir tun müssen, ist die Struktur unseres Gruntfiles zu vereinfachen. Es gibt einige Möglichkeiten, dies zu tun, aber die meisten Lösungen, über die Sie lesen, beschränken sich auf verschiedene Implementierungen dieses allgemeinen Themas. Das Beispiel, das ich zeigen werde, ist der beste Weg, um die Größe und Komplexität Ihres Gruntfiles zu reduzieren.
In Ihrem Stammverzeichnis (wo Sie Ihre Grunt-Datei haben) erstellen Sie einen 'Grunt'-Ordner. In diesem Ordner befinden sich einzelne JavaScript-Dateien. Jede enthält eine andere Aufgabe, die Sie in Ihre Haupt-Grunt-Datei aufgenommen hätten.
Ihre Verzeichnisstruktur könnte ungefähr so aussehen ...
|— Gruntfile |— package.json |— grunt | – contrib-requirejs.js
Unser Gruntfile kann jetzt so einfach sein wie:
module.exports = function(grunt) { grunt.loadTasks('grunt'); };
Ist das nicht besser Es ist erwähnenswert, dass die Zeichenfolge 'grunt', die an die loadTasks-Methode übergeben wurde, nichts mit dem tatsächlichen Grunt-Objekt zu tun hat. Es bezieht sich auf den Namen des Ordners, den Sie erstellt haben.
Sie hätten den Ordner beliebig nennen können: 'omg-so-sexy', zum Beispiel im obigen Code grunt.loadTasks ('omg-so-sexy') . Mit jeder Aufgabe in einer eigenen Datei müssen wir die Aufgabe etwas anders definieren, als sie normalerweise in der Grunt-Datei hinzugefügt wird. contrib-requirejs.js sollte folgendermaßen strukturiert sein:
module.exports = function(grunt) { grunt.config('requirejs', { compile: { options: { baseUrl: './app', name: 'main', out: './app/release/main.js' } } }); grunt.loadNpmTasks('grunt-contrib-requirejs'); };
02. Behalte diese Konfiguration außerhalb deiner Konfiguration!
Eine weitere wichtige Technik, die wir verwenden können, besteht darin, bestimmte Konfigurationstypen außerhalb des Gruntfiles zu verschieben. Ein offensichtlicher Ort, an dem dies häufig vorkommt, ist der JSHint-Plug-In Dies kann sehr groß sein und daher viel Platz in Ihrem gesamten Gruntfile beanspruchen.
Glücklicherweise hat JSHint eine integrierte Lösung für dieses Problem. Lassen Sie uns einen Blick darauf werfen, wie wir damit unsere JSHint-Aufgabe bereinigen können.
Der erste Schritt, den wir unternehmen müssen, ist das Erstellen einer neuen Datei mit dem Namen .jshintrc und darin Ihre JSON-Konfiguration einfügen:
{ 'curly': true, ... } // This is an example, you should define more than one option!
Anschließend können Sie in Ihrer JSHint-Aufgabe (von der wir annehmen, dass sie jetzt sicher außerhalb der Grunt-Datei und in einer eigenen separaten Aufgabendatei liegt) den Speicherort der Konfigurationsdatei angeben:
jshint: { files: ['./app/**/*.js'], options: { jshintrc: './grunt/.jshintrc' } }
Der gleiche Ansatz kann auf alle Konfigurationsdaten angewendet werden. Es kommt also vor, dass die JSHint-Task mit dieser Funktionalität vorgefertigt wurde. Bei anderen vorgefertigten Tasks müssen Sie die Konfigurationsdatei möglicherweise dynamisch selbst mit laden Grunz-API .
03. Führen Sie Aufgaben nur aus, wenn eine Änderung aufgetreten ist
Wenn Sie noch nichts von der Grunt-Contrib-Watch-Aufgabe gehört haben, sollten Sie sich diese als Erstes ansehen, da dies ein Lebensretter ist, um sicherzustellen, dass Sie eine Aufgabe nur ausführen, wenn sich die dieser Aufgabe zugeordneten Dateien tatsächlich geändert haben.
Stellen Sie sich vor, Sie haben eine JavaScript-Testsuite. Sie möchten die Aufgabe nicht nach jedem Speichern einer Datei manuell ausführen müssen (insbesondere, wenn Sie TDD - testgetriebene Entwicklung) ausführen, da dies ein langsamer Workflow ist. Es wäre besser, wenn Sie einfach Ihre JavaScript-Datei speichern würden… und BOOM die entsprechenden Tests sind ausgeschaltet und laufen! Genau das macht diese Aufgabe.
Im Folgenden finden Sie ein einfaches Beispiel, das zeigt, wie Sie eine Skript-Unteraufgabe aus der Hauptüberwachungsaufgabe erstellen können, die alle Ihre JavaScript-Dateien überwacht. Wenn sich eine dieser Dateien geändert hat, wird die entsprechende jshint-Aufgabe ausgeführt wurde separat eingerichtet:
watch: { scripts: { files: ['app/**/*.js'], tasks: ['jshint'], options: { spawn: false, }, }, }
04. Führen Sie Aufgaben nur für Dateien aus, die sich tatsächlich geändert haben
Das einzige, was schneller ist als die Verwendung von grunt-contrib-watch zum Ausführen von Aufgaben, wenn sich eine Datei ändert, besteht darin, Aufgaben nur für diejenigen Dateien auszuführen, die sich seit der letzten Ausführung der Aufgabe tatsächlich geändert haben. Hier ist die grunzen-neuere Aufgabe ist praktisch. Sie definieren Ihre Aufgaben als normal und müssen lediglich den Namen der Aufgabe voranstellen, mit der Sie ausgeführt werden möchten neuer: .
Zum Beispiel:
grunt.initConfig({ jshint: { all: {src: 'src/**/*.js'} } }); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-newer'); grunt.registerTask('lint', ['newer:jshint:all']);
Jetzt wenn du rennst Grunzfussel Die jshint-Task wird nur für Dateien ausgeführt, die sich seit dem letzten Mal geändert haben jshint Aufgabe wurde ausgeführt.
Wenn Sie also die Aufgabe ausführen und dann eine einzelne JavaScript-Datei bearbeiten, wird diese einzelne Datei beim Speichern dieser Datei nur fusselig, da die Grunzen-neuer Die Task weiß, dass keine anderen Dateien erneut für JSHint ausgeführt werden müssen.
05. Erstellen Sie einen Standard-Startpunkt für die Grunt-Einrichtung für alle Projekte
Grunt hat eine eingebaute Funktion namens grunzen-init . Sie können eine Vorlagenprojektstruktur definieren, die beim Starten eines neuen Projekts dynamisch mit konfigurierbaren Werten versehen wird.
Es ist ein Befehlszeilentool, das von einer JSON-Datei konfiguriert wird. Sie legen Fragen in der JSON-Datei fest, diese werden in der Befehlszeile beantwortet und die Werte werden an die Projektvorlage übergeben.
Stellen Sie sich beispielsweise vor, Sie entwickeln eine große Anzahl von Node.js-Modulen, die Sie in NPM (Node Package Manager) veröffentlichen. Anstatt immer wieder dieselbe Ordnerstruktur und Dokumentation README-Dateien erstellen zu müssen (aber nur kleinere Details wie den Namen der Bibliothek zu ändern), können Sie eine Vorlage erstellen, die grunzen-init kann nutzen, um alles automatisch für Sie einzurichten.
06. Verstehe, was jede Aufgabe tut
Die größte Kritik an Grunt ist, dass es langsam ist. Während Grunt in der Tat einige suboptimale Entwurfsentscheidungen enthält (obwohl diejenigen, die für die kommende Version von Grunt v1.0 aktiv angesprochen werden), wird ein mit Aufgaben überladenes Grunt-Setup offensichtlich langsam ablaufen.
Als Beispiel haben wir kürzlich an einem Projekt gearbeitet, für das wir eine 90-KB-Datendatei hinzugefügt hatten D3.js (ein beliebtes Datenvisualisierungstool) zum Kompilieren in eine interaktive Karte. Diese Datendatei führte dazu, dass unser Grunt-Build mehr als zwei Minuten brauchte, um eine verkettete JS-Datei zu rendern ( grunt-contrib-requirejs ). Es ist keine großartige Erfahrung, zwischen den Speichern so lange warten zu müssen.
Der Build dauerte so lange, weil grunt-contrib-requirejs eine JavaScript-Quellkarte für die verkettete Datei erstellte, eine vergebliche Aufgabe für eine Datendatei mit Tausenden von Punkten. Durch das Blacklisting der Datendatei wurde der Build auf wenige Sekunden zurückgesetzt.
Führen Sie Aufgaben parallel aus
Eine gute Möglichkeit, die Laufzeit von Grunt zu beschleunigen, besteht darin, Aufgaben parallel auszuführen. Zwei sehr beliebte Aufgaben können Ihnen dabei helfen?
- grunzen-parallel
- grunzen gleichzeitig
Um ehrlich zu sein, gibt es nicht viel zu wählen - wir würden uns leicht zum Grunzen gleichzeitig neigen, weil:
- Die API ist etwas einfacher
- Das Projekt-Chatter auf GitHub ist neuer (es macht keinen Spaß, sich auf tote Projekte zu verlassen).
- Es ist von Sindre Sorhus gemacht !!!
Unabhängig davon, für welche Sie sich entscheiden (wählen Sie grunt-parallel, wenn Sie auch benutzerdefinierte - nicht grunzende - Aufgaben ausführen möchten), sollten Sie sie zusammen mit der verwenden Zeitgrunzen Plug-In, ein fantastisches Tool, das Ihnen sagt, wie lange die Ausführung jeder Aufgabe dauert.
Sie haben dieses Zitat wahrscheinlich schon einmal gehört, aber es ist wahr. Bevor Sie mit der Mikrooptimierung aller Teile Ihres Gruntfiles beginnen, sollten Sie zunächst messen, wie lange es dauert, bis der Build in seiner aktuellen Form ausgeführt wird. Analysieren Sie anschließend nach jedem Refactoring die Leistung des Builds, um sicherzustellen, dass Sie keine Regression eingeführt haben.
Zum Beispiel haben wir kürzlich das Plug-In für das gleichzeitige Grunzen in unser Grunt-Setup aufgenommen. Es hat die Verarbeitung von zwei Unteraufgaben mit RequireJS beschleunigt, aber tatsächlich die Erstellungszeit für unsere Sass-Aufgaben verlängert. Dies lag daran, dass die beiden Unteraufgaben in Sass mit 0,8 und 0,2 Sekunden ausgeführt wurden.
Wenn Sie sie Seite an Seite mit der 0,5-Sekunden-Strafe laufen lassen, eine zweite Instanz von Grunt hochzufahren, erhöht sich die Zeit auf 1,3 Sekunden! Dies liegt daran, dass das parallele Ausführen von zwei Aufgaben Kosten verursacht: Normalerweise ca. 0,5 Sekunden, die Zeit, die zum Hochfahren einer weiteren Instanz von Grunt benötigt wird.
08. Aufgaben bedingt laden
Grunt lädt alle Aufgaben, die Sie der Grunt-Datei hinzufügen, in den Speicher, unabhängig davon, ob sie verwendet werden sollen oder nicht. Bei kleinen Grunt-Setups ist dies kein Problem. Wenn Sie jedoch mehr Aufgaben zu Ihrem Setup hinzufügen, dauert es länger, bis Grunt alles hochfährt, bevor die von Ihnen angeforderte Aufgabe ausgeführt wird. Dies kann besonders schmerzhaft sein, wenn Sie eine Aufgabe haben, die von etwas besonders Schwerem abhängt, wie z. B. GraphicMagick, dessen Laden in den Speicher fünf Sekunden dauern kann.
Seien wir also etwas trickreich und richten Sie die Grunt-Konfiguration auf eine ganz bestimmte Art und Weise ein, um dieses Problem zu umgehen. Wir können Aufgaben innerhalb der Konfiguration definieren, deren einzige Rolle darin besteht, andere Aufgaben wie folgt zu definieren und auszuführen:
module.exports = function (grunt) { grunt.registerTask('images', [], function () { grunt.config('responsive_images', { main: { ... } }); grunt.loadNpmTasks('grunt-responsive-images'); grunt.task.run('responsive_images'); }); };
Die Aufgabe Bilder wird jedes Mal in den Speicher geladen, wenn Grunt ausgeführt wird, die darin enthaltenen Unteraufgaben jedoch nicht. Diese werden nur geladen und ausgeführt, wenn Sie ausgeführt werden Grunzbilder . Dadurch wird die Hochlaufzeit, die Grunt benötigt, um eine Aufgabe ausführen zu können, massiv verkürzt. Der einzige Nachteil besteht darin, dass Sie jetzt über Unterschichten von Aufgaben verfügen. Daher müssen Sie die Aufgabennamen angeben, die die darin ausgeführten Aufgaben beschreiben oder mit ihnen verwechseln könnten.
Fazit
Wir hoffen, Sie fanden diesen Artikel hilfreich. Letztendlich ist es jedoch am besten, zu verstehen, was Sie tun, um Ihr Grunt-Setup wartbar, schnell und skalierbar zu halten.
Lesen Sie weiter über Grunt; Folgen Sie Vordenkern wie Ich bin Altman - der Schöpfer von Grunt, Sindre Sorhus - Node.js Superstar und Addy Osmani - Workflow-Enthusiasten sowie @gruntjs für die neuesten Nachrichten über das Projekt. Die besten Handwerker werden zu Experten für den Umgang mit ihren Werkzeugen.
Wörter: Mark McDonnell und Tom Maslen
Mark McDonnell ist Möchtegern-Programmierung polyglot und Tom Maslen ist Experte für JavaScript, HTML und CSS. Dieser Artikel erschien ursprünglich in Netzmagazin Ausgabe 256.