Entwicklungs-Workflow Angular, Typescript und Less mit Gulp

TL;DR;

Live-Bearbeitung von CSS, JavaScript und HTML ohne IDE mit Gulp am Beispiel von Angular, Typescript und Less. Jedoch ist der Workflow auch mit anderen Framekworks und Tools einsetzbar. Auch VanillaJS wird unterstützt.

Repository: https://github.com/DerAlbertSamples/AngularLessGulp

IDEs, Visual Studio und Frontend Development

Schnelles Feedback beim Entwickeln haben wir doch alle gerne, leider ist die Web-Frontend Entwicklung ein Punkt der in Visual Studio nur sehr langsam Feedback geben kann. Man muss immer die Anwendung neu starten.

Entwickelt man Single Page Applications für den Browser mit Visual Studio so hat man seit Version 2013 BrowserLink und Web Essentials. Damit ist es möglich das CSS zur Laufzeit im Browser auszutauschen und auch die Seite neu laden zu lassen. Das gute daran ist dass auch automatisch erkannt wird wenn man am C# Code was geändert hat und dann neu kompiliert wird.

Die Nachteile sind aus meiner Sicht das neue oder geänderte Javascript und CSS Dateien nicht berücksichtigt werden, man kann man mit Asp.Net Optimization die Razor-Templates so befüttern dass dies automatisch passiert. Jedoch muss man dazu die Anwendung neu starten. Daran muss man jedoch denken das kein .NET Code ist und dies kann auch noch "ewig" dauern. Insgesamt eine Verbesserung aber nicht wirklich gut.

Microsoft kann mit Visual Studio nur dem "schnellen" Markt des Frontend-Tooling hinterher entwickeln, aber auch mit Updates alle 3 Monate kommen sie einfach nicht hinterher. Viele Probleme die man hat sind im marktüblichen Frontend-Tooling auch schon gelöst. Mit Marküblichen Tooling meine ich auf NodeJS basierende Tools die über grunt oder gulp zusammengestöpselt werden.

Visual Studio 2015 wird nun eine Möglichkeit geben grunt- und gulp-taks in Visual Studio zu verwenden. So in der Art wie es WebStorm schon länger bietet. So kann man diese einfach aus Visual Studio ausrufen und auch MSBuild Schritten zuordnen.

Jedoch ist gar keine IDE oder ähnliches Notwendig um trotzdem einen komfortablen Workflow bei der Frontend Entwicklung zu haben. Einen dieser Wege beschreibe ich jetzt hier. Diese können dann auch über IDEs wie Visual Studio 2015 und WebStorm genutzt werden. Für Visual Studio 2013 gibt es eine Extension welches dies auch ermöglicht. WebStorm und Visual Studio bieten ja gerade im Bereich Javascript gutes Tooling welches Notepad z.B. nicht wirklich liefert und man auch nicht mit ein paar Task nachbilden kann.

Folgendes wird hier erklärt

  • Umwandlung von Typescript nach Javascript
  • automatische Annotationen für Angular Dependency Injection
  • Umwandlung von LESS nach CSS
  • Automatisches einbinden von Javascript und CSS, so dass gelöschte und neue Dateien dem HTML bekannt gemacht werden
  • Automatisch neu Laden der Seite wenn sich HTML und Javascript geändert hat
  • On-The-Fly austauschen von CSS Dateien wenn sich darin etwas geändert hat

Mit diesen paar Tasks, basierend auf NodeJS und Gulp als Runner, bekommt man einen sehr guten Workflow für die Frontend Entwicklung hin, welcher komplett unabhängig von der Plattform ist (läuft z.B. Windows, OS X und Linux), aus IDEs verwendet werden kann und auch mit anderen Build-Tasks kombinierbar ist.

Was hier nicht gezeigt wird ist das Deployment mit Minifzierung der Dateien und auch die Server-Seite wird komplett ignoriert. Es wird hier mit Plain HTML gearbeitet und nicht mit Razor-Templates oder anderes. Auch nicht das die Aufrufen von MSBuild wenn sich C#-Dateien geändert haben. Dies ist dann möglicherweise Teil von weiteren Blogposts.

Das ganze Sample ist auf Github verfügbar.
https://github.com/DerAlbertSamples/AngularLessGulp

Die verwendeten Werkzeuge

Grundlage von allem ist NodeJS, dieses muss zuerst installiert sein.

Nun werden ein paar Node Module gebraucht die wir global installieren.

npm install -g tsc bower gulp

Dies installiert den Typescript Compiler, Bower und Gulp so dass diese beim angemeldeten Benutzer im globalen Pfad liegen. Diese werden im Benutzer-Ordner abgelegt.

Nun sucht man sich einen schönen Ordner wo das Projekt schon drin liegt oder man legt sich einen neuen an.

Möchte man nun die weiteren Abhänigkeiten die wir brauchen Projekt-Spezifisch speichern so das man dies sehr einfach wieder herstellen kann müssen wir Konfiguration/Beschreibungs Dateien für npm und bower anlegen. Die package.json und bower.json

npm init
...
bower init

Dabei wird man nach allerlei Daten gefragt, diese kann man wie gewünscht ausfüllen. Im Beispiel-Repository sind diese vorhanden, wenn man sich das heruntergeladen hat kann man die verwendeten abhänigkeiten aus dem Netz herunterladen

npm install
bower install

Alle hier verwendeten node module kann man auch mit

npm install --save-dev browser-sync del gulp gulp-ignore gulp-inject gulp-less gulp-ng-annotate gulp-sync gulp-typescript gulp-util main-bower-files

installieren. --save-dev gibt an dass Abhänigkeiten in der package.json hinterlegt werden damit man diese mit npm install einfach installieren kann.

Die Bower Komponenten installiert man manuel auf ähnlichem weg.

bower install --save angular angular-route bootstrap

Damit lädt man sich die aktuellen Version der angegeben Kompononenten und speichert sie in der bower.json.

Struktur des Beispiels

source/app/*
source/assets/*
source/css/*
source/index.html

source ist der Ordner in dem sich die Site befindet, source/app beinhaltet die Angular App die entwickelt wird. source/assets da liegen alle 3rd Party Client Komponenten. source/css beinhaltet die Site spezifischen Style-Sheets.

Das Gulpfile

Die gulpfile.js ist Datei in der man die Gulp-Task hinterlegt und schreibt. Dies ist eine einfach Javascript Datei mit NodeJS ausführt und mit gulp gestartet wird. Die Node Module die man verwendet muss man einfach mit require() einbinden.

Hier die gesammelten Werke die bei den Task verwendet werden.

var gulp = require('gulp');
var path = require('path');
var less = require('gulp-less');
var typescript = require('gulp-typescript');
var inject = require('gulp-inject');
var mainBowerFiles = require('main-bower-files');
var browserSync = require('browser-sync');
var ngAnnotate = require('gulp-ng-annotate');
var sync = require('gulp-sync')(gulp);
var del = require('del');
var util = require('gulp-util');

Des Weiteren braucht man bei den Tasks diverse Parameter immer wieder und diese hinterlegt man am einfachsten als Objekt im gulpfile.js.

var config = {
	'site' : './source',
	'assets' : './source/assets',
	'app' : './source/app/',	
	'injectFiles' : './source/index.html',
	'bowerComponents': './bower_components',

	'typescript' : {
		removeComments: false,
		target: 'ES5',
		noExternalResolve: false,
		noImplicitAny: false
	},
	'typings' : './typings'
}

Diese werde ich erklären wenn es sie verwendet werde.

Die Gulp-Tasks

Gulp Task sind generell darauf ausgelegt Dateien zu manipulieren. Es wird eine oder mehrere Dateien eingelesen und wieder geschrieben. Dazwischen hängt die pipe die die Dateien transformiert, umbenennt, filtert oder auch nichts mit macht.

gulp.task('taskName', ['otherTask'], function() {
	return gulp.src('quelldatei')
           .pipe(gulp.dest('ausgabedatei'));
}

Damit wird der Task taskName definiert. Hier das einfach kopieren einer Datei.

Dieser kann dann über die Shell mit

gulp taskName

ausgeführt werden, damit wird dann zuerst otherTask ausgeführt und erst wenn dieser, bzw. alle in dem Array angebenen Tasks fertig sind dann wird die function() ausgeführt und macht ihre Magie.

Prinzipiell werden mit Gulp alle Tasks erstmal asynchron ausgeführt. Synchronität muss man anfordern. So würden alle abhängigen Tasks zusammen ausgeführt werden und auf deren Fertigstellung gewartet bis der eigentliche Task ausgeführt wird.

Man muss keinen abhänigen Task angeben, dann lässt man das String-Array einfach weg.

copy-assets

Mit Bower bekommmt man die benötigten komponenten so gut wie im kompletten Quelltext, da diese aus dem jeweiligen Git-Repository abgerufen und abgelegt werden.

Jedoch möchte ich daraus nur bestimmte Dateien in meinem Projekt habe und kopiere mir diese in meinen Site Ordner. Der Task dazu ist sehr einfach.

gulp.task('copy-assets', function() {
	return gulp.src(mainBowerFiles(), { 'base': config.bowerComponents })
	.pipe(gulp.dest(config.assets));
});

Jede Bower Komponent hat sollte in ihrem eigenen bower.json hinterlegt haben was die Haupt Dateien sind. Diese können mit
dem Gulp-Module main-bower-files als Quelldateien angeben werden. So dass nur dies in den config.assets Ordner kopiert werden. Da wir hier den Gulp-Stream zurückgeben kann Gulp feststellen wann der Task zuende ist (dies brauchen wir bei abhänigkeiten).

Jedoch habe ich in diesem Fall ein kleines Problem. Bootstrap ist relativ großzügig und ich habe nicht nur die fertigen CSS Dateien, und die minifizierten Varianten drin sondern auch noch die Less-Quellen davon. Auch zieht Bootstrap auch noch automatisch jQuery mit rein. Dies kann praktisch sein, möchte ich in diesem fall jedoch nicht. Ich möchte nur die bootstrap.css, bootstrap-theme.css und die Schriften für die Glyphicons.

Nun kann ich in meiner bower.json die vordefinierten main-bower-files überschreiben und damit beinflussen was kopiert wird.

Ich definiere dort entsprechende anpassungen

{
  "overrides" : {
    "jquery" : {
      "main" : ".ignore"
    },
    "bootstrap" : {
      "main" : [ 
     	 "dist/css/bootstrap.css",
     	 "dist/css/bootstrap-theme.css",
     	 "dist/fonts/*"
         ]
    }
  }
}

Da kann man einzelne Datein und Datei-Patterns angeben, als einzelner String oder als Array.

Bei jQuery gebe in .ignore an, diese Datei existiert nicht, ist aber die einzige die haben will, also wird keine Datei kopiert.

Bei Bootstrap geben ich die gewünschte CSS Dateien an und das die kompletten Schriften kopieren werden sollen.

Für Angular gebe ich hier keine abweichungen an. Nach dem Task sind mein Asset Ordner so aus.

source/assets/angular/angular.js
source/assets/angular-route/angular-route.js
source/assets/bootstrap/dist/css/bootstrap.css
source/assets/bootstrap/dist/css/bootstrap-theme.css
source/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
source/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
source/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
source/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.woff

Damit kann ich nun arbeiten.

clean-assets

Assets sich während der Entwicklungszeit keine stabile Einheit, sie ändern sich, neue kommmen hinzu oder es fallen weg. Dann muss man seinen Assets Ordner von Zeit zu Zeit mal aufräumen. Mit dem node module del kann man einfach Dateien löschen.

gulp.task('clean-assets', function(cb) {
	del([path.join(config.assets,"**/*")], cb);
})

Die Besonderheit hier ist dass del kein Gulp spezifisches Modul ist und keine Streams kennt. Ist für das Löschen auch nicht notwendig. Es soll einfach löschen, nicht weiter manipulieren.

Jedoch kann nun Gulp mangels Stream nicht erkennen wann der eigentliche Task, der ja asynchron gestartet wird, mit seiner Arbeit zu Ende ist. Dafür reicht Gulp der Task-Function einen Callback rein den man beim Ende der Arbeit aufrufen muss. del kann ich praktischerweise auch den Callback direkt weiterreichen. So das Gulp entsprechend erkennen kann wenn der Task fertig ist.

Dies ist dann relevant wenn man copy-assets so erweitert dass vor dem kopieren erstmal der Assets Ordner gelöscht wird.

gulp.task('copy-assets', ['clean-assets'], function() {
 // ...
});

Ohne den Callback würde noch beim löschen kopiert werden und sich die beiden Tasks in die Query kommen.

compile-ts

Die Anwendung wird in diesem Beispiel in Typescript geschrieben also muss das Typescript für den Browser in Javascript umgewandelt werden. Dabei hilft gulp-typescript.

Die Struktur der Beispiel-Anwendung sieht so aus.

app/common/directives/buttons.ts
app/common/directives.ts
app/modules/home/views/home.html
app/modules/home/home.ts
app/app.ts
index.html

Nun soll alles was an TypeScript im app-Ordner ist in JavaScript umgewandelt werden.

var tsProject = typescript.createProject(config.typescript);

gulp.task('compile-ts', function() {

	var typescriptFiles = [
		path.join(config.app, "**/*.ts"),
		path.join(config.typings, "**/*.ts")
		];

	return gulp.src(typescriptFiles)
	.pipe(typescript(tsProject))
	.pipe(gulp.dest(config.app));
});

Es wird ein Typescript Projekt erstellt, mit der Konfiguration die im config.typescript definiert ist.

{
    'typescript' : {
		removeComments: false,
		target: 'ES5',
		noExternalResolve: false,
		noImplicitAny: false
	}
}

Dies entspricht den Parametern mit denen der Typescript Compiler gefüttert wird.

In typescriptFiles werden die Dateien definiert die kompiliert werden sollen. In config.app liegt die geschriebene Anwendung und in config.typings liegen die Typescript definitionen von Angular ind jQuery. Wie diese da hinkommen können, wird weiter unter geklärt.

Mit gulp.src() werden nun alle Dateien eingelesen auf die die definierten Pattern passen und in die Pipe geschickt. Dort werden sie mit gulp-typescript in JavaScript umgewandelt und mit gulp.dest() wieder gespeichert.

Angular DI Annotationen mit ng-annotate

Jetzt ist es hier eine Angular Anwendung und bei Angular gibt es Dependency Injection auf Basis von Parameter-Namen. Dies ist gut und schlecht.

Schlecht ist es bei der Minifizierung von JavaScript, da werden üblicherweise Parameternamen auch gekürzt um Platz zu sparen. Gut dass es überhaupt DI ermöglicht.

Damit Angular trotzdem funktionfähig ist muss man den Angular Code mit Annotationen versehen damit die DI von Angular die abhängikeiten erkennt.

// Beispiel Annotationen
var homeController = function(theDependency) {
}
homeController.$inject=['theDependency'];

// oder

var homeController = ['theDependency', function(theDependency) {
}];

Problem ist, die Abhänigkeiten müssen bei den Annotationen immer mitgepflegt werden. Dies ist nervig und fehleranfällig.

Mit ng-annotate gibt es ein Node Module welches das Javascript parst und den üblichen verdächten Anweisungen automatisch Annotationen erstellt. Nun kann man dies mit gulp-ng-annotate einfach in die Pipeline einklinken und schon wird in "einem" Schritt aus Typescript Javascript mit Annotationen für Angular, immer aktuell, immer frisch.

gulp.task('compile-ts', function() {

	var typescriptFiles = [
		path.join(config.app, "**/*.ts"),
		path.join(config.typings, "**/*.ts")
		];

	return gulp.src(typescriptFiles)
	.pipe(typescript(tsProject))
	.pipe(ngAnnotate())
	.pipe(gulp.dest(config.app));
});

watch-ts

Möchte man nun während der Entwicklung nicht jedesmal gulp compile-ts aufrufen wenn sich eine Typescript Datei ändert oder hinzukommt. So kennt Gulp dafür das Konzept der Watcher. Man kann einfach auf Änderungen im Datei-System reagieren.

gulp.task('watch-ts', ['compile-ts'], function() {
	gulp.watch("**/*.ts", 
        { cwd : config.app, read:false, debounceDelay: 50 }, ['compile-ts']);	
});

gulp.watch() überwacht Datei mit dem angegeben Pattern und führt bei Änderungen den angegebenen Task aus.

Der zweite Parameter ist ein Parameter Objekt.

cwd ist das current working directory, wird dies nicht angegeben so wird nur auf Änderungen an Dateien reagiert, nicht auf das Löschen oder Hinzufügen von Dateien.

read gibt an ob die Dateien auch in die Pipe eingelesen werden soll, oder ob nur die Metadaten durchgereicht werden. Der Inhalt ist in diesem Fall für uns nicht relevant also lesen wir die Dateien nicht sein.

debounceDelay ist dafür dass nicht bei mehreren Events hintereinander die Dateien in die Pipe geschickt werden.

Ruft man gulp watch-ts auf so wird nun jede Änderung an den Typescript Dateien nach dem Speichern kompiliert. Auch neue Dateien werden erfasst. Jedoch wenn man eine Datei umbenennt oder löscht hat man die alten Javascript Dateien noch rumliegen.

Um da auch drauf zu reagieren, kann man sich bei dem Watcher an Events hängen. In diesem Falle das change-Event, da prüft man nun ob eine Datei gelöscht worden ist und löscht die dazu gehörigen erzeugten Dateien.

gulp.task('watch-ts', ['compile-ts'], function() {
	var watcher = gulp.watch("**/*.ts", 
            { cwd : config.app, read:false, debounceDelay: 50 }, ['compile-ts']);
	watcher.on('change', function (e) {
		if (e.type == "deleted") {
			var file = util.replaceExtension(e.path, ".*");
			del(file);
		}
	});
});

Mit gulp-util kann ich z.B. einfach die Extension der Datei ändern, ich nehme hier .* auch anderen Dateien die auf der Quelldatei basieren gelöscht werden.

compile-less und watch-less

Analog zum compilieren von Typescript verfahre ich auch beim Less Dateien. Deshalb hier die Tasks ohne weitere Erklärungen für gulp-less.

gulp.task('compile-less', function() {
	return gulp.src(path.join(config.site, "**/*.less"), {base: config.site})
	.pipe(less())
	.pipe(gulp.dest(config.site));
});

gulp.task('watch-less', ['compile-less'], function() {
	var watcher = gulp.watch("**/*.less", 
		{ cwd : path.join(config.site, "css"), read: false, debounceDelay: 50 }, ['compile-less']);
	watcher.on('change', function (e) {
		if (e.type == "deleted") {
			var file = util.replaceExtension(e.path, ".*");
			del(file);
		}
	});
});

inject-files

JavaScript und CSS Dateien sind abhänigkeiten einer HTML Seite die im HTML deklariert werden müssen. Dies ist bei sich ändernden oder vielen Dateien doch ein gewisses Maß an Aufwand die HTML Datei mit den ganzen JavaScript und CSS Dateien auf dem aktuellen Stand zu halten. Für .NET gibt es von Microsoft entsprechendes Tooling, dies verlangt aber den Neustart der Anwendung und muss auch gepflegt werden.

Mit gulp-inject kann man nun, unter anderem, auf Basis der Dateien die sich in einer Pipe befinden anderen Dateien ändern und so z.B. diese Abhänigkeiten durch einsetzen. Ein einfaches Beispiel ist CSS und JavaScript in HTML Dateien.

Ziel ist es nun die Assets und eigenen Dateien in der richtigen Reihenfolge in die HTML Datei zu schreiben.

Dazu muss ich in der HTML Datei festlegen wo etwas hinkommt.
Dies geschieht über Kommentare, da gibt man an welche Gruppe und welcher Dateitype eingesetzt werden soll.

Hier gibt es die Gruppen assets und app sowie die Dateitypen css und js.

<html ng-app="MyApp" ng-strict-di>
<head>
	<!-- assets:css -->
    <!-- endinject -->
    <!-- app:css -->
    <!-- endinject -->
</head>
	<!-- assets:js -->
    <!-- endinject -->
    <!-- app:js -->
    <!-- endinject -->
</body>
</html>

Zwischen den Kommentaren werden die <link> und <source> Tags entsprechend eingesetzt bzw. ersetzt.

Der Gulp Task dafür ist schon ein wenig komplexer.

gulp.task('inject-files', function() {
	var assetsFiles = [
		path.join(config.assets, "angular/*"),
		path.join(config.assets, "bootstrap/dist/css/bootstrap.css"),
		path.join(config.assets, "**/*"),
	];

	var appFiles = [
		path.join(config.app, "common/**/*"),
		path.join(config.app, "modules/**/*"),
		path.join(config.app, "*"),
		path.join(config.site, "css/*")
	];

	var appStream = gulp.src(appFiles, { 'base' : config.site});
	var assetsStream = gulp.src(assetsFiles, { 'base' : config.site});

	return gulp.src(config.injectFiles)
	.pipe(inject(assetsStream, { name: 'assets', ignorePath: config.site.substring(2), relative:false}))
	.pipe(inject(appStream, { name: 'app', ignorePath: config.site.substring(2), relative:false}))
	.pipe(gulp.dest(config.site));
});

Es werden zwei Arrays für die jeweiligen Gruppen an Dateien definiert. Diese werden normal über jeweils einen Stream mit gulp.src() eingelesen. Diese Streams muss man gulp-inject übergeben. Die Reihenfolge in der man hier die Datein angibt ist die Reihenfolge in der sie auch im HTML stehen werden.

Nun kommt die eigentliche Anpassung der HTML Datei, man eröffnet eine neue Pipe mit den Dateien mit man ändern möchte und ruft inject() für jeden Quellstream auf. Dort kann man über die Parameter noch beinflussen wie der eigentliche Dateiname im dem Tag auszieht und ein wenig transformieren bei Bedarf.

Hier bennenen wir nur die Gruppe der Dateien und geben an das ein paar Zeichen am Anfang ignoriert werden sollen, und die Pfade absolute im Quelltext stehen sollen.

watch-inject-files

Natürlich ist es super alle Dateien in die HTML Datei bringen zu können die eine Site braucht. Aber wird wollen es ja weiter automatisieren. Mit watch-ts und watch-less werden ja automatisch Javascript und CSS Datein erzeugt. Der nächste Schritt ist nun wenn Javascript sowie CSS Dateien hinzukommen oder gelöscht werden dass die HTML Datei auch direkt angepasst wird.

gulp.task('watch-inject-files', ['inject-files'], function () {
    var files = ["**/*.js", "**/*.css"];
    var watcher = gulp.watch(files, {cwd: config.site, read: false});
    var timeout;
    watcher.on('change', function (e) {
        if (e.type == "deleted" || e.type == "added") {
            if (timeout) {
                clearTimeout(timeout);
            }
            timeout = setTimeout(function () {
                timeout = undefined;
                gulp.start('inject-files');
            }, 300);
        }
    });
});

Wir überwachen hier alle Javascript und CSS Dateien (nicht die Typescript und Less Dateien) und sobald eine Datei gelöscht oder hinzugefügt wird schreiben wir die HTML Datei neu.

Über setTimeout() puffern wir ab wenn sich viele Datei gleichzeitig ändern. Der Einfachheit halber lasse ich immer alle Typescript- und Less-Dateien übersetzen, somit werden viel Events in kurzer Zeit gefeuert. Da hilft das debounceDelay beim watcher nicht weiter, da es immer unterschiedliche Dateien sind.

browser-sync

Jetzt haben wir die komplette Erzeugung des Javascript, CSS und das Anpassen der HTML Datei automatisiert. Nun müssen wir immer noch im Browser F5 drücken um die Seite neu zuladen.

Mit browser-sync gibt es ein schönes Node Module was automatisch die Seite im Browser neu lädt wenn sich etwas geändert hat. Und dies in allen Browsern die gerade damit verbunden sind. Ein Browser Plugin ist dazu nicht notwendig. So ähnlich wie BrowserLink was Visual Studio bietet.

gulp.task('browser-sync', function () {
    browserSync({
        server: {
            baseDir: config.site
        },
        files: [
	        "**/*.js",
	        "**/*.css",
	        "**/*.html"
        ],
        watchOptions: {
            debounceDelay: 100,
            cwd: config.site
        },
        logFileChanges: false
    });
});

Hier macht browser-sync selbst einen kleinen Webserver auf der die Dateien aus dem baseDir hostet und ausliefert. Als Standard wird auf http://localhost:3000 gehostet, dies ist jedoch anpassbar.

Auch ist es möglich eine vorhandene Site die z.B. im IIS Express gehostet ist anzugeben, dann arbeitet browser-sync als Proxy und schleift die gehosteten Dateien durch.

Dies muss gemacht werden weil in der ausgelieferte Seite von Browser-Sync ein wenig JavaScript einbettet werden muss damit die Seite mit Browser-Sync ohne Plugin kommunizieren kann.

Bei Änderungen am JavaScript und HTML wird die Seite komplett neu geladen, ändert man nur CSS Datein werden diese zur Laufzeit im Browser ausgetauscht ohne neu zuladen.

Der Standard-Browser wird von Browser-Sync gestartet. Wenn man möchte kann man jedoch einen oder mehrere spezielle Browser angegeben.

live-edit

Damit nun alles in Gulp mit einem Task ausgeführt wird müssen wir noch einen Task haben der alles miteinander verbindet. Und da z.B. vor dem inject-files erstmal alle Javascript und CSS Datein auf dem aktuellen Stand sein müssen. Müssen wir die Synchronität in Gulp sicherstellen. Dies geht mit gulp-sync damit kann man Build-Tasks Synchron aufrufen.

gulp.task('live-edit',
    sync.sync(
        [
            ['watch-ts', 'watch-less'],
            'watch-inject-files',
            'browser-sync'
        ], "live-edit"
    )
);

Der Aufruf von gulp-sync ersetzt das Array der Task-Abhänigkeiten. Jeder Eintrag in dem übergenen Array wird nacheinander abgearbeitet. Stehen mehrere Task in einem Eintrag (wie bei watch-less und watch-ts) so werden diese zwar asynchron gestartet, aber watch-inject-files wird erst aufgerufen wenn beide fertig sind. Abschließen wir browser-sync gestartet und im Browser angezeigt.

TypeScript Definitions Dateien

Im Ordner typings finden sich im Beispiel Typescript Definitionen von Angular und jQuery, diese kommen von Definitely Typed, und zwar wurden diese mit dem relativ neuen node module tsd heruntergeladen.

Es gibt die Definitionen auch auf Bower, NPM oder Nuget, nur oft nicht alle oder aktuell.

npm install -g tsd

Zur installation als Node Module.

Die Verwendung ist nicht ganz so einfach wie Bower oder NPM, aber machbar ;)

tsd init
tsd query angular --action install --save
tsd query angular-route --action install --save
tsd query jquery --action install --save

Mit tsd init wird eine tsd.json Datei angelegt, dort kann man z.B. festlegen wie die Definitionen abgelegt werden.

Und nun?

Was kann verbessert werden? Was habt Ihr für Tasks oder Tools die eurer Frontend Leben vereinfachen? Rein damit in die Kommentare.