3. Struktur und Konfiguration
Der hauptsächliche Nutznießer der Laufzeitumgebung DNX ist derzeit ASP.NET. Außer dem Namen hat dies nur noch wenig mit dem bisherigen ASP.NET zu tun, auch wenn einige Konzepte übernommen wurden. Es ist einfacher “das neue ASP.NET Core” zu lernen, wenn man erstmal möglichst viel von der alten Welt ausblendet. Zuerst werden einige fundamentale Konzepte vorgestellt, die unbedingt notwendig sind, um den Rest des Buches zu verstehen.
3.1 Die Projektstruktur
Die Projektstruktur für vNext-Projekte ist anders als bei bisherigen ASP.NET-Projekten. Die Projektstruktur entsteht durch die Standardvorlage in Visual Studio mit dem Namen Web Application. Statt weniger komplexer Dateien gibt es jetzt mehr Dateien, diese sind jedoch kompakter und spezialisierter.
Ein Projekt erstellen
Um Sie zu erreichen, gehen Sie wie üblich über die Programmiersprache, beispielsweise C# auf den Ordner Web und dann auf ASP.NET Core Templates – Web Application. Die Auswahl des Frameworks 4.6 auf der vorherigen Seite spielt für die Wahl der DNX-Laufzeitumgebung hier keine Rolle.
Wie bei den vorherigen Projektformen kann die Authentifizierungsform gewählt werden. Die Auswahl entspricht den bisherigen Möglichkeiten:
- Keine Authentifizierung
- Authentifizierung mit Benutzerkonten – dies nutzt OAuth und erlaubt sowohl eine lokale Datenbank als auch Authentifizierungsprovider
- Work and School basiert auf einem bestehenden Active Directory, lokal oder in der Azure Cloud
- Windows Authentifizierung nutzt NTLM im lokalen Intranet
Für erste Experimente empfiehlt es sich, auf die Authentifizierung zu verzichten und diese Funktionen erst später hinzuzufügen. Das Projekt erscheint sonst auf den ersten Blick unnütz komplex.
Nach der Auswahl entsteht die Struktur. Alle benötigten Pakete werden nun heruntergeladen – bis sich das Projekt nutzen lässt kann je nach Internet-Verbindung einige Zeit vergehen. Solange dieser Vorgang läuft, steht Restoring Packages im Solution Explorer.
Folgende Bausteine liefert diese Projektvorlage:
- Der Ordner Properties: Enthält die Eigenschaften des Projekts in der Datei launchSettings.json
- Der Ordner References: Die Referenzierung der Laufzeitumgebungen DNX 4.6.1 und .NET Core 1.0 (wenn beide benutzt werden)
- Der Ordner Dependencies: Die Abhängigkeiten der Client-Bausteine bzw. der JavaScript-Umgebung. Standardmäßig verweist dieser Teil auf die Repositories Bower und npm (Node Package Manager).
- Der Ordner wwwroot: Der Stammordner des Zielprojekts, also quasi die Abbildung der Produktionsumgebung. Hier sind die final benutzten Client-Skripte, Bilder, CSS-Dateien usw. enthalten. Ergänzt wird die Umgebung durch die .NET-Assemblies, die im übergeordneten Projekt erzeugt werden. Der Gulp-Task kopiert aus den JavaScript-Quelldateien die im Client tatsächlich benutzten Versionen nach wwwroot bzw. einen der Unterordner.
- Der Ordner Controllers: Die Controller für die Web- bzw. API-Umgebung. Eine Unterscheidung zwischen API-Controller und der Verarbeitung von Razor-Views gibt es nicht mehr. Die neue Basisklasse
Controllerkann beides. - Der Ordner Views: Wird mit Razor-Views gearbeitet, liegen diese wie bisher hier.
- Die Dateien:
- appsettings.json: Allgemeine Einstellungen zur Applikation.
- gulpfile.js: Die Steuerdatei für den clientseitigen Erstellungsvorgang, basierend auf JavaScript-Skripten.
- project.json: Die Abhängigkeiten, die durch Zugriff auf Nuget aufgelöst werden.
- Project_Readme.html: Eine statische Info-Datei. Diese können Sie bedenkenlos löschen.
- Startup.cs: Steuerung des Startverhaltens des Projekts. Dies ersetzt die Aufrufe in der früher benutzten global.asax. Insbesondere werden hier die durch Dependency Injection nun dynamisch linkbaren Module vereinbart.
Die scheinbare Flut von neuen Dateitypen soll dazu dienen, die Aufgabe jeder einzelnen Datei zu beschränken. So ist der Zweck und damit der Umfang jeder Datei reduziert und damit die konkrete Aufgabe einfacher.
Auf der Ebene der Solution gibt es noch die Datei global.json und den Ordner artifacts. In global.json wird das SDK (Zielplattform) festgelegt. Kompilierte Assemblies sind in artifacts zu finden.
Die Client-Repositories
Der Umgang mit speziellen Client-Repositories ist ein Bruch mit der Fokussierung auf Nuget. Nuget ist nur noch für .NET und CoreFX zuständig. Es ist zwar absehbar, dass viele Client-Pakete (also solche, die auf JavaScript und CSS basieren), weiter über Nuget gepflegt werden. Die Ausrichtung der JavaScript-Szene auf Bowser & Co. führt aber dazu, dass kleinere Projekte entweder überhaupt nicht mehr oder mit Verzögerung bereitgestellt werden.
Bower
Bower liefert clientseitige Projekte wie beispielsweise jQuery oder AngularJS. Bower selbst verwaltet nur Pakete und deren Abhängigkeiten, die eigentlichen Codes werden jedoch von Github abgerufen. Es werden also in den meisten Fällen Quellprojekte geliefert. Statt einer Datei udn eventuell noch der minimierten Version kann dies dazu führen, dass Dutzende JavaScript-Dateien auf der Festplatte landen oder auch mal statt CSS die Präkompiler-Versionen in der Sprache LESS oder SASS auftauchen.
Sie müssen also, wenn Sie mit aktuellen Paketen arbeiten wollen, hier die entsprechenden Werkzeuge einsetzen, um mit diesen Quellpaketen umgehen zu können. Dazu dient der Task Runner – eine auf node.js basierende Ausführungsumgebung für entwicklerseitiges JavaScript. Der JavaScript-Code wird dabei nicht im Browser, sondern auf der Kommandozeile ausgeführt. Dies schließt volle Rechte beim Zugriff auf das Dateisystem mit ein.
NPM
Derartige entwicklerseitige Vorgänge basieren auf auch Paketen. Node.js bringt neben der Laufzeitumgebung für JavaScript auch eine eigene Paketverwaltung mit den Node Package Manager npm. Damit lassen sich Werkzeuge beschaffen, die man bei der Entwicklung einsetzen kann, unter anderem:
- Kopieren von Dateien
- Ordner aufräumen
- Minimieren von JavaScript- und CSS-Dateien
- Übersetzen von TypeScript nach JavaScript
- Übersetzen von LESS oder SASS nach CSS
- Unit Tests für JavaScript
Die ausführende Umgebung ist node.js, als Steuerungsinstrument wird jedoch ein darauf aufbauendes Werkzeug benutzt: Gulp. In Visual Studio gibt es die Funktion Task Runner, die Gulp-Skripte ausführen kann.
Konfigurationen
ASP.NET Core 1.0 kann mehrere Frameworks adressieren, damit verschiedene Laufzeitumgebungen bedient werden können. Standardmäßig wird das normale .NET-Framework benutzt. Alternativ kann .NET Core benutzt werden. .NET Core kommt in Frage, wenn das Hosting später auf Linuy erfolgen soll. Wenn ältere Bausteine integriert werden sollen, wird diese Option meist nicht zur Verfügung stehen, da Referenzen auf das .NET-Framework existieren. Die zur Verfügung stehenden Abhängigkeiten finden Sie in der Projektstruktur unter References. Die Einstellung selbst erfolgt in der Datei project.json unter dem Abschnitt targets.
Die Einstellungen selbst müssen nicht zwingend in den Dateien vorgenommen werden. Wie üblich unterstützt Visual Studio die Einrichtung durch zahlreiche grafische Dialoge. Die Grundkonfiguration ist im Kontextmenü des Projekst unter Properties (Alt-Enter) verfügbar.
Die Einstellungen zum Erstellen des Projekts (Build) umfassen folgende Optionen:
- Produce outputs on build: Wird diese Option aktiviert, werden die Pakete und Assemblies erstellt und im Ordner artifacts abgelegt. Assemblies entstehen für jedes aktivierte Framework inklusive der üblichen PDB-Dateien (in der Debug-Konfiguration) und der XML-Dokumentationsdateien. Außerdem wird eine Paket-Datei für Nuget mit der Erweiterung .nupkg erstellt und die Beschreibung der App aus der project.json kopiert.
- Compile TypeScript on build: TypeScript-Dateien (mit der Erweiterung *.ts) werden in JavaScript kompiliert. Die Datei wird unmittelbar neben die Quelle platziert.
Im Debug-Tab finden Sie Optionen zum Einstellen des Hosts, der zum Debuggen benutzt wird. Die Optionen lassen sich unter Profilen zusammenstellen und verwalten. Standardmäßig stehen zwei Profile zur Verfügung:
- IIS Express
- web
Mit web ist hier das Kommando gemeint, dass den Kestrel-Webserver startet.
Mit dem IIS Express können Sie Startadresse, Port, SSL und Authentifizierungsverfahren wählen. Bei Kestrel kann nur die Startadresse festgelegt werden. Beide Profile erlauben die Auswahl der Laufzeitumgebung, des Frameworks und der Architektur.
Die Datei project.json
Die Datei project.json ist neu und konfiguriert das Projekt zum Erstellungszeitpunkt. Hier werden die serverseitige Abhängigkeiten festgelegt und die elementaren Pfade. Die wichtigsten Optionen sind:
- “version”: Version des Projekts im Semver-Format
- “authors”: Array mit den Autoren
- “description”: Beschreibungstext
- “dependencies”: Objekt mit den Abhängigkeiten (“Name”: “Version”)
- “frameworks”: Unterstützte Frameworks (dnx461 und netstandard)
- “scripts”: Array von Kommandos (auf der Kommandzeile), die automatisch ausgeführt werden:
- “prebuild”: Vor dem Erstellen des Codes
- “postbuild”: Nach dem Erstellen des Codes
- “prepack”: Vor dem Erstellen des Pakets
- “postpack”: Nach dem Erstellen des Pakets
- “prerestore”: Vor dem Wiederherstellen von Paketen
- “postrestore”: Nach dem Wiederherstellen von Paketen
- “compilationOptions”: Einstellungen für den Erstellungsvorgang, die an den Compiler weitergegeben werden:
- “define”: Array mit Konstanten, die als Compiler-Symbole benutzt werden
- “allowUnsafe”:
trueoderfalse - “warningsAsErrors” :
trueoderfalse
- “configurations”: Objekt mit Debug- und Release-Einstellungen
- “webroot”: Der Ordner für die Client-Dateien (standardmäßig wwwroot)
- “exclude” und “publishExclude”: Array mit Dateien (mit Platzhaltern), die nicht beachtet bzw. nicht mit veröffentlicht werden.
1 {
2 "version": "1.0.0-*",
3 "compilationOptions": {
4 "emitEntryPoint": true
5 },
6
7 "dependencies": {
8 "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
9 "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
10 "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
11 "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final",
12 "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
13 "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
14 "Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final",
15 "Microsoft.Extensions.Configuration.FileProviderExtensions" : "1.0.0-r\
16 c1-final",
17 "Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
18 "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
19 "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
20 "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
21 "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc1-final"
22 },
23
24 "commands": {
25 "web": "Microsoft.AspNet.Server.Kestrel"
26 },
27
28 "frameworks": {
29 "dnx461": { },
30 "netstandard1.0": { }
31 },
32
33 "exclude": [
34 "wwwroot",
35 "node_modules"
36 ],
37 "publishExclude": [
38 "**.user",
39 "**.vspscc"
40 ],
41 "scripts": {
42 "prepublish": [ "npm install", "bower install", "gulp clean", "gulp mi\
43 n" ]
44 }
45 }
Die Datei global.json
Die Datei global.json konfiguriert die Solution als Ganzes.
1 {
2 "projects": [ "src", "test" ],
3 "sdk": {
4 "version": "1.0.0-rc2-final"
5 }
6 }
The projects property designates which folders contain source code for the solution. By default the project structure places source files in a src folder, allowing build artifacts to be placed in a sibling folder, making it easier to exclude such things from source control.
../_images/solution-files.png The sdk property specifies the version of the DNX (.Net Execution Environment) that Visual Studio will use when opening the solution. It’s set here, rather than in project.json, to avoid scenarios where different projects within a solution are targeting different versions of the SDK.
wwwroot
Der Ordner wwwroot hat eine ganz zentrale Funktion: Er trennt die Projektstruktur während der Entwicklung von der Struktur der finalen Website. Bisher war dies zusammengefasst und das Ergebnis nicht immer optimal für den Entwickler oder den Webserver. wwwroot ist quasi eine Unterstruktur, die die Website abbildet. Während das Routing von ASP.NET MVC einige Probleme bei der Pfadzuordnung lösen konnte, waren statische Dateien immer genau dort abzulegen, wo die Pfade der Webseiten es verlangten. Bei komplexeren Applikationen, wie einer Single Page App mit AngularJS, herrschte dann schnell Chaos, weil die Übersicht im Projekt verloren ging.
Darüberhinaus waren projektspzifische Dateien (wie web.config oder global.asax) Teil der Distribution. Sie mussten daher explizit geschützt werden. Im Grunde ist dies jedoch eine unnütze Maßnahme. Besser wäre es, solche Dateien überhaupt nicht zu verteilen. Dies wird durch wwwroot erreicht.
Clientseitige Dateistrukturen sind eine weitere Fehlerquelle. Neben den mittels bower abgerufenen Quelldateien sind es auch eigene “Hilfsdateien”, die nicht verteilt werden sollten, beim Entwickeln aber notwendig sind. Dazu gehören Codes in TypeScript, LESS oder SASS. Hier werden im Client nur die resultierenden Produkte in JavaScript und CSS benötigt. Nicht mit verteilt werden auch Unit-Tests in JavaScript und Dateien, die mit Debug-Code angereichert sind.
Die Entwicklung der serverseitigen Elemente, beispielsweise der API-Controller in C#, findet ohnehin nicht in wwwroot statt. Unterliegen Sie jedoch nicht der Versuchung, der Einfachheit halber JavaScript direkt dort zu platzieren. Gehen Sie stattdessen folgendermaßen vor:
- Erstellen Sie im Hauptprojekt einen Ordner Client
- Platzieren Sie die Skripte (TypeScript/JavaScript) passend zu Struktur der Controller und Views
- Erstellen Sie passende Unit-Tests
- Erweitern Sie das gulpfile.js (oder gruntfile, falls Grunt benutzt wird) um passende Aufgaben:
- Minimieren der Datei
- Zusammenfassen (bundle)
- kopieren der Dateien an die passende Stelle nach wwwroot
3.2 Startverhalten konfigurieren
Das Startverhalten wird in der Datei Startup.cs beschrieben. Der Konstruktor ist der Startpunkt der Applikation. Dem Konstruktor werden die Umgebungen übergeben, sodass im Code drauf Bezug genommen werden kann:
1 public Startup(IHostingEnvironment env)
2 {
3 var builder = new ConfigurationBuilder()
4 .SetBasePath(appEnv.ApplicationBasePath)
5 .AddJsonFile("appsettings.json")
6 .AddEnvironmentVariables();
7 Configuration = builder.Build();
8 }
9
10 public IConfigurationRoot Configuration { get; set; }
Zwei weitere Methoden werden implizit (durch die Laufzeit) benutzt: ConfigureServices zum Festlegen der intern verfügbaren Dienste und Configure, um diese Dienste zu konfigurieren.
1 public void ConfigureServices(IServiceCollection services)
2 {
3 services.AddMvc();
4 }
Im Beispiel wurde der Dienst “Mvc” hinzugefügt. Dafür gibt es eine explizite Methode, da es sich um einen Standarddienst handelt. Auf diesen Aufruf stützt sich die Konfiguration der Routen, im folgenden Abschnitt ab Zeile 21:
1 public void Configure(IApplicationBuilder app,
2 IHostingEnvironment env,
3 ILoggerFactory loggerFactory)
4 {
5 loggerFactory.AddConsole(Configuration.GetSection("Logging"));
6 loggerFactory.AddDebug();
7
8 if (env.IsDevelopment())
9 {
10 app.UseBrowserLink();
11 app.UseDeveloperExceptionPage();
12 }
13 else
14 {
15 app.UseExceptionHandler("/Home/Error");
16 }
17
18 app.UseIISPlatformHandler();
19
20 app.UseStaticFiles();
21
22 app.UseMvc(routes =>
23 {
24 routes.MapRoute(
25 name: "default",
26 template: "{controller=Home}/{action=Index}/{id?}");
Alles was zur Verarbeitung von Anforderungen notwendig ist, ist an dieser einen Stelle zusammengefasst. Weitere Konfigurations-Dateien oder web-config-Einstellungen sind nicht erforderlich. Applikationsspezifische Einstellungen – also der private Teil – sind in der Datei appsettings.json zu finden.
Konfiguration der Applikation
Eigene Konfigurationen wurden bislang als XML abgelegt. Diese Aufgabe übernimmt jetzt die Datei appsettings.json. Der Name der Datei ist frei wählbar, die Einstellung erfolgt in der Startumgebung Startup.cs (Zeile 3, appsettings.json ist der Standard):
1 public Startup(IHostingEnvironment env)
2 {
3 var builder = new ConfigurationBuilder()
4 .AddJsonFile("appsettings.json")
5 .AddEnvironmentVariables();
6 Configuration = builder.Build();
7 }
Hier ein Beispiel für eine appsettings.json-Datei:
1 {
2 "AppSettings": {
3 "HomeTitle": "Hallo ASP.NET Core"
4 },
5 "Logging": {
6 "IncludeScopes": false,
7 "LogLevel": {
8 "Default": "Verbose",
9 "System": "Information",
10 "Microsoft": "Information"
11 }
12 },
13 "Data": {
14 "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnetCo\
15 re-Intro"
16 },
17
18 "Entity": {
19 "ApplicationDbContext": {
20 "ConnectionString": "Data:DefaultConnection:ConnectionString"
21 }
22 }
23 }
Gegenüber den bisherigen “AppSettings” können hier nicht nur Schlüssel-/Werte-Paare platziert werden, sondern auch komplexe Objekte. Objekte können sich auch gegenseitig referenzieren, wie ín Zeile 18 zu sehen ist.
Um die Datei zu lesen wird eine Schnittstelle IConfiguration injiziert. Die Implementierung Configuration liefert die Elemente über Indexer. Diese steht in der Startup.cs standardmäßig zur Verfügung. Ergänzen Sie zum Zugriff den Code in Zeile 4:
1 public void ConfigureServices(IServiceCollection services)
2 {
3 services.AddMvc();
4 services.AddInstance(typeof (IConfiguration), Configuration);
5 }
Im Code (dies wird meist ein Controller sein), wird dann auf die Schnittstelle IConfiguration verwiesen:
1 using Microsoft.Extensions.Configuration;
2
3 public class HomeController : Controller
4 {
5 private readonly IConfiguration _config;
6
7 public HomeController(IConfiguration config){
8 _config = config;
9 }
10
11 public IActionResult About()
12 {
13 string appName = _config["AppSettings:HomeTitle"];
14 ViewData["Message"] = "Titel: " + appName;
15 return View();
16 }
17
18 public IActionResult Index(){
19 return View();
20 }
21 }
3.3 Bundling und Optimierung
Wie bereits kurz gezeigt, gibt es für all diese Verfahren ein komplett auf JavaScript basierendes Ökosystem. Die Grundlage ist Node und damit der Zugriff über den Node Package Manager npm.
In MVC6 liefert Microsoft keine eigene Werkzeuge mehr, sondern erweitert Visual Studio um Funktionen, die dieses JavaScript-Ökosystem nutzen. Standardmäßig wird Gulp eingesetzt.
Grunt oder Gulp?
Grunt ist nach eigener Darstellung auf der Website ein „JavaScript based Task Runner“. Er dient der Automation von Entwicklungsvorgängen. Bei Grunt geht Konfiguration vor Programmierung. Ausgangspunkt ist das Gruntfile, mit dem die Aufgaben automatisiert werden. Grunt ist Plug-In-orientiert, d.h. es werden für viele typische Vorgänge fertige Module angeboten, die in das Gruntfile eingebunden werden.
Gulp ist ein Workflow-basiertes System. Es setzt auf das Streaming-Modul von node auf. Abgewickelt werden auch hier Aufgaben. Die elegante Umsetzung verspricht weniger Konfiguration und mehr Flexibilität. Hier geht Programmierung vor Konfiguration. Auch für Gulp gibt es fertige Module, die Standardaufgaben erledigen.
Die Unterschiede
Es geht hier um Konfiguration versus Programmierung. Grunt gibt einen klaren Weg vor – Konfiguration von fertigen Aufgaben. Gulp ist sehr offen – primitive eingebauten Aufgaben, Mini-Aufgaben aus der Community und ein flexibler Weg, diese zu kombinieren.
Grunt lebt also vor allem von seinem Repository an Modulen. Diese sind via npm von Github beschaffbar und die Liste ist beeindruckend lang. Gulp ist jünger und hat weniger zu bieten, benötigt aber auch weniger, weil sich durch wenige Code-Elemente vieles in JavaScript erledigen lässt, wofür man in Grunt erst ein Plug-In bauen müsste.
Das Gruntfile nutzt ein JSON-Objekt zur Konfiguration und ist sehr primitiv aufgebaut:
1 grunt.initConfig({
2 clean: {
3 src: ['build/app.js', 'build/vendor.js']
4 },
5 copy: {
6 files: [{
7 src: 'build/app.js',
8 dest: 'build/dist/app.js'
9 }]
10 },
11 concat: {
12 'build/app.js': ['build/vendors.js', 'build/app.js']
13 }
14 // ... other task configurations ...
15 });
16 grunt.registerTask('build', ['clean', 'bower', 'browserify', 'concat', 'co\
17 py']);
Am Ende dieses Skripts organisiert der ‚build‘-Task die zuvor konfigurierten Elemente. Jeder Konfigurationsschritt ist unabhängig von anderen. Die Aufgaben werden sequenziell ausgeführt. Da in den meisten Fällen mit Dateien operiert wird, ist bei jeder Aufgabe eine Quelle und ein Ziel anzugeben. Grunt parst diese Angaben und führt die Kopiervorgänge dann aus.
Gulp ist dagegen reines JavaScript:
1 var gulp = require('gulp');
2 var sass = require('gulp-sass');
3 var minifyCss = require('gulp-minify-css');
4 var rename = require('gulp-rename');
5
6 //declare the task
7 gulp.task('sass', function(done) {
8 gulp.src('./scss/ionic.app.scss')
9 .pipe(sass())
10 .pipe(gulp.dest('./www/css/'))
11 .pipe(minifyCss({
12 keepSpecialComments: 0
13 }))
14 .pipe(rename({ extname: '.min.css' }))
15 .pipe(gulp.dest('./www/css/'))
16 .on('end', done);
17 });
Hier werden die Dateioperationen zusammengefasst und dann über das Node-Modul Streams ausgeführt. Die Quellangabe wird an alle folgenden Module weitergereicht und dies ermöglicht sehr kompakte Angaben, auch wenn die Aufgaben komplexer werden. Im Hintergrund nutzt Gulp die Bibliothek Vinyl, eine Art virtuelles Dateisystem.
Arbeit mit dem Dateisystem
Beide Werkzeuge nutzen Node und dessen Module zum Arbeiten mit dem Dateisystem. Das ist bei Grunt weitgehend ‚fs‘, bei Gulp eher ’stream‘. Grunt schreibt viele Dateien während des Prozesses und ist strikt dateiorientiert. Streams sind dagegen meist im Speicher und deshalb erfolgen viele Prozesse bei Gulp „in memory“. Nun sind viele Projekte eher klein und der Vorteil bei der Geschwindigkeit kaum messbar. Wer ein Projekt nach Tagen der Arbeit für die Auslieferung fertig macht wird den Unterschied zwischen 400ms und 800ms kaum bemerken (In der Praxis kann das aber auch mal 25ms zu 1200ms sein).
Community
Grunt ist länger am Markt als Gulp und hat eine deutlich größere Community. Allerdings ist die Zunahme bei Gulp erheblich und ein Ausgleich absehbar. Je nach Stärke der nächsten Versionen könnte Grunt den Vorsprung halten oder Gulp könnte auch überholen. Grunt ist derzeit Version 0.4.5 (Stable 02/2016) bzw. 1.0.0 (Dev 02/2016). Die Versionierung ist eher die typische, sehr zurückhaltend und vorsichtige Strategie wie bei vielen Unix-Projekten. Gulp ist dagegen sehr aggressiv und derzeit bereits bei 3.9.1 (Stand 02/2016).
Dokumentation
Die Website und API-Dokumentation zeigt mehr Reife, Umfang und Qualität bei Grunt. Gulp ist eher rudimentär und Quellcode lesen ist an der Tagesordnung. Während es endlose Artikel und Blogs zu beiden gibt, ist eine zentrale Website gerade für Einsteiger ein wichtiger Punkt. Bei Gulp ist davon nicht viel zu sehen.
In Grunt ist schneller konfiguriert – wenige fertige Aufgaben und alles steht bereit. Gulp setzt statt dessen auf verkettete JavaScript-Callbacks. Die Verkettung macht den Code aber auch kompakt und direkt. Grunt-Plug-Ins erscheinen mehr auf ihre Aufgabe zugeschnitten und konkreter an das Buildsystem Grunt angepasst. Gulp ist dagegen clever programmiert und spürbar schneller sowie sehr viel flexibler bei komplexeren Aufgaben. Plug-Ins für Gulp sind eher Node-Stream-Module als explizite Gulp-Plug-Ins. Sie erscheinen weniger stark auf ihren Zweck zugeschnitten als bei Grunt.
Optimierung mit Gulp
Gulp bietet eine ganze Reihe von node-Modulen, die für die Optimierung benutzt werden können. Ein miminales Set besteht aus drei Bereichen:
- Minimieren
- Zusammenfassen
- Löschen und Kopieren
Betrachten Sie zuerst folgendes package.js:
1 {
2 "name": "ASP.NET",
3 "version": "0.0.0",
4 "devDependencies": {
5 "gulp": "3.8.11",
6 "gulp-concat": "2.5.2",
7 "gulp-cssmin": "0.1.7",
8 "gulp-uglify": "1.2.0",
9 "rimraf": "2.2.8"
10 }
11 }
Wenn Sie dies so in Visual Studio eingeben, bzw. die vorhandene Datei anpassen, lädt Visual Studio die Module herunter und stellt sie lokal im versteckten Ordner node_modules bereit. Die benutzten Module sind:
- gulp: Der Task Runner Gulp selbst
- gulp-concat: Ein Modul zum Verbinden von Dateien
- gulp-cssmin: Ein Modul zum Minimieren von CSS-Dateien
- gulp-uglify: Ein Modul zum Minimieren von JS-Dateien
- rimraf: Ein Modul zum Löschen und kopieren (rimraf ist ein Wortspiel auf “rm” und “rf”, den unter Linux benutzten Kommandzeilenwerkzeugen zum Erzeugen, Umbenennen und zum Löschen von Ordnern).
In gulpfile.js werden die Pakete nun aktiviert. Praktisch sind die Konstruktoraufruf auf die JavaScript-Klassen:
1 var gulp = require("gulp"),
2 minifycss = require("gulp-cssmin"),
3 concat = require("gulp-concat");
Im Skript kann nun auf die Funktionen zugegriffen werden. Gulp nutzt die Stream-Funktionen von node und kann deshalb Dateien sehr schnell asynchron und damit parallel lesen und schreiben. Zum Minimieren des CSS wird eine Kombination aus rimraf zum Entfernen der alten Version und gulp-cssmin zum Erzeugen der neuen Version benutzt:
1 gulp.task("clean:css", function (cb) {
2 rimraf(paths.concatCssDest, cb);
3 });
4
5 gulp.task("min:css", function () {
6 return gulp.src([paths.css, "!" + paths.minCss])
7 .pipe(concat(paths.concatCssDest))
8 .pipe(cssmin())
9 .pipe(gulp.dest("."));
10 });
Der Bequemlichkeit halber lassen sich die Aufgaben zusammenfassen:
1 gulp.task("min", ["min:js", "min:css"]);
Nun kann im Task Runner der Aufruf erfolgen. Alternativ kann der Auslöser auch an eines der Build-Ereignisse (Clean, Before, After) oder beim Öffnen des Projekts gehängt werden.