HTML5 ist Webstandard – die wichtigsten Fakten

Gestern war es soweit: das W3C hat HTML5 den Recommendation-Status verliehen, womit es offizieller, fertiger Webstandard ist. Viele wichtige Leute finden das total toll und es wabert eine bunte Mischung aus Schulterklopfen und zynischen Kommentaren durch das Netz. Ich habe mich mal an einer neutralen Zusammenfassung der wichtigsten Fakten versucht:

Was ist jetzt überhaupt Webstandard?
Das, was nun Standard ist um vom W3C und allen anderen als „HTML5“ bezeichnet wird, ist die fünfte Version der Hypertext Markup Language – gewissermaßen HTML 5.0. Das Dokument beschreibt alle offiziell gültigen HTML-Elemente sowie diverse Details rund um Browser-APIs. Es handelt sich dabei nicht um die Gesamtheit des Technologie-Universums, das gerne auch gerne als „HTML5“ bezeichnet wird. Die Web-Plattform besteht aus vielen Einzelteilen, von denen einige dem neuen Standard entspringen, viele andere jedoch nicht. Der Umfang des Standards ist der große blaue Kreis in der folgenden Grafik, der Rest ist das erweiterte (teilweise noch nicht fertig standardisierte) HTML5-Universum:
Grafik, die das Verhältnis von diversen Webtechnologie-Spezifikationen zueinander illustriert
Man sieht deutlich: da ist noch einiges an weiteren Webstandards zu verabschieden.
Was gibt es Neues?
In dem neuen Standard gibt es diverse frische Browserfeatures, die heutzutage allerdings nicht mehr ganz so frisch erscheinen. Audio- und Video-Elemente, Application Cache, Canvas und so manche JavaScript-API sind dem geneigten Web-Nerd schon länger bekannt. Wichtiger dürfte sein, dass die neuen Spezifikationen mit einem ganz anderen Niveau an Präzision und Detailliertheit aufwarten, als es bei vorherigen HTML-Specs der Fall war. Im HTML5-Dokument findet man alles, nur keine schwammigen Formulierungen mit Interpretationsspielraum – ein großer Gewinn für alle Spezifikations-Leser und eine Messlatte für zukünftige Spezifikationen.
Hat HTML5 was gebracht?
Die Idee hinter HTML5 ist, den Browser von einem dummen Dokumentbetrachter in eine konkurrenzfähige Applikationsplattform zu verwandeln. Das hat recht gut funktioniert, denn im Prinzip gibt es nur wenig, das der Browser nicht leisten kann. Entsprechend viele Webapps werden heutzutage entwickelt, was ich persönlich vor allem an der Anzahl der Java- und C#-Entwickler in meinen JavaScript-Kursen messe. Im Mobile-Sektor sieht die Sache noch anders aus. Diverse technische Puzzleteile fehlen noch und auch bei den Webentwicklern mangelt es oftmals noch an Verständnis für mobilespezifische Probleme – die wenigsten machen sich z.B. Gedanken darum, wie schnell ihre Webapp oder Webseite einen Smarthone-Akku leersaugt. Ich gehe aber davon aus, dass sich all diese Probleme mit der Zeit lösen lassen und dass sich die Webplattform auch außerhalb von Hybrid-Apps auf dem Mobile-Sektor etablieren wird.
Was ändert sich jetzt durch die Standardisierung?
Nichts. Die meisten Features, die jetzt offiziellen Status genießen, funktionieren schon länger in modernen Webbrowsern. Einige andere Features sind auch am Tag nach der Standardisierung noch reine Papiertiger. Daran ändert der offizielle Status der Spezifikationen nichts.
Sollte die Standardisierung nicht erst 2022 passieren?
Der damalige Editor der Spezifikationen, Ian Hickson, hatte mal 2022 als mögliches Jahr der Standardisierung von HTML5 in den Raum geworfen. Hintergrund dieser Aussage war, dass es seiner Einschätzung nach mindestens so lange dauern würde, bis die HTML5-Testsuite vollständig und wirklich jedes Feature sicher in mindestens zwei Browsern gelandet ist. Stand heute fehlen auch in der Tat noch viele Tests und so manches Feature gibt es in keinem Browser. Aber das W3C hat sich trotzdem entschieden, den Standard-Stempel schon mal zu zücken.
Wie geht es mit W3C und WHATWG weiter?
Keine der beiden Arbeitsgruppen, die mehr oder minder nebeneinanderher an der Webplattform basteln, scheint vor der unmittelbaren Auflösung zu stehen. Zwar wird immer mal wieder verstärkt mit diversen Säbeln gerasselt (so wirft die WHATWG dem W3C regelmäßig vor, es würde Spezifikationen plagiieren und für alle Arten von Chaos sorgen), aber es haben mehrere Jahre lang mehrere Spezifikationen und beide Arbeitsgruppen nebeneinander existiert, ohne dass die Welt untergegangen wäre. Ob die Unstimmigkeiten über eine neue API innerhalb einer Arbeitsgruppe oder zwischen Arbeitsgruppen ausgetragen werden, macht in Endergebnis vermutlich keinen zu großen Unterschied. Am Ende liegt die Wahrheit eh im Browser und dort dann relativ eindeutig.
Was kommt als nächstes?
Von Seiten des W3C gibt es eine Spezifikation für HTML 5.1 und die WHATWG-Version des HTML-Standards geht schon länger über den Umfang von HTML 5.0 hinaus. Insofern herrscht an neuen Features kein Mangel. Hinzu kommen allerlei andere interessante Neuheiten in der Webplattform wie z.B. Service Workers, der gesamter Themenkomplex rund um Web Components und natürlich auch ECMAScript 6. All diese Neuheiten entstehen außerhalb des Zuständigkeitsbereichs eines möglichen neuen HTML-Standards statt, da dieser sich eben nur um HTML und die dazugehörige Browser-Infrastruktur kümmert.

Zusammengefasst: Eigentlich ist nichts passiert. Nur ein Teil des ganzen großen HTML5-Zirkus hat jetzt einen offizielleren Status als zuvor – aber im alten Internet Explorer funktioniert es trotzdem noch nicht. Also schön zurücklehnen und weitermachen und wie bisher!

Fragen zu HTML5 und Co beantwortet 17- Semantik für Pull Quotes, File-Inputs, Shared Workers aus Blobs, Flexbox

Umzugsbedingt lag das Blog ein paar Wochen brach, aber zum Glück nicht die Inbox. So haben sich wieder einige Fragen zu HTML5, CSS3 und JS (bzw. DOM) angesammelt, die ich mir Vergnügen beantwortet habe. Allerdings ist die Inbox jetzt auch schon wieder leer. Wenn euch also eine Frage zum Themenkomplex „futuristisches Frontend“ auf dem Herzen liegt, zögert nicht mir eine E-Mail oder einen Tweet zukommen zu lassen.

Welches semantische Element für Pull Quotes?

Wie sollte man Pull Quotes mit Zitaten aus dem umgebenden Text semantisch auszeichnen?

Hier ist <aside> das Mittel der Wahl, garniert mit ein ARIA-Extras. Konkurrenz-Element Nummer 1, <blockquote>, scheidet allein schon aus inhaltlichen Gründen aus. Zitat Spezifikationen:

The blockquote element represents a section that is quoted from another source.

Hingegen heißt es beim <aside>-Element ausdrücklich:

The [aside] element can be used for typographical effects like pull quotes or sidebars [...]

Da es sich bei einer solchen Pull Quote um einen reinen typografischen Effekt handelt, kann man über den ergänzenden Einsatz des Attributs aria-hidden auf dem <aside>-Element nachdenken. Zwar ist dieses Attribut eigentlich dafür gedacht, unsichtbare Elemente vor Screenreadern zu verbergen, aber da sich bei Pull Quotes um völlig redundante Informationen handelt, möchte sie sich ziemlich sicher niemand vorlesen lassen. Die korrekte Lösung für Pull Quotes heißt also <aside aria-hidden="true">.

Input-Value von File-Inputs setzen?

Ich habe ein spezielles Problem. Gibt es eine Möglichkeit mittels JavaScript den value eines Inputs vom Typ File zu setzen? Eigentlich ist das ja verboten, ich würde aber gerne, wenn ein Nutzer keine Datei ausgewählt hat, automatisch eine Datei vom Webserver laden lassen. Bekommt man das irgendwie hin?

Da gibt es leider keinen geheimen Trick. Das Setzen des value von File-Inputs mit etwas anderem als einem leeren String (um das Feld zu löschen) wird immer mit einem InvalidStateError quittiert; so wollen es die Spezifikationen.

Ohne Workaround geht es nicht. Man müsste also bei serverseitiger Verarbeitung nachsehen, ob eine Datei mitgeschickt wurde und andernfalls darauf reagieren. Bei clientseitiger Verarbeitung könnte man die Alternativ-Datei per XHR laden und für den Fall vorhalten, dass das Formular ohne ausgewählte Datei abgeschickt wird. Mit XHR2 ist das zum Glück ganz einfach, denn da gibt es das responseType-Attribut mit "blob" als möglichem Wert:

function handleFile(file){
  console.log(file)
}

document.querySelector('form').onsubmit = function(evt){
  evt.preventDefault();
  var fileField = document.querySelector('[name=file]');
  // Keine Datei gewählt? Default per XHR holen!
  if(fileField.files.length === 0){
    var req = new XMLHttpRequest();
    req.open('GET', 'formtest.html');
    req.responseType = 'blob';
    req.onload = function(evt){
      var blob = evt.target.response;
      handleFile(blob);
    };
    req.send();
  }
  // Gewählte Datei verarbeiten
  else {
    handleFile(fileField.files.item(0))
  }
};

Das ist keine sehr schöne Lösung, aber auch keine Katastrophe. Mit FormData könnte man einfach das ohnehin anfallende Formular-Datenset modifizieren und eine nicht ausgewählt Datei ergänzen, aber mit der Browserunterstützung der dafür nötigen Teile von FormData sieht es nicht besonders gut aus.

Shared Worker aus Strings oder Blobs erstellen

Kann man Shared Worker aus Strings bzw. Blobs erstellen?

Möglich ist alles! Damit sich alle Shared Worker in den gleichen Prozess einklinken, müssen sie mit dem gleichen Namen und der gleichen Quell-URL erstellt werden. Das Problem hierbei: nimmt man in zwei Webseiten das gleiche Ausgangsmaterial (also einen String JS-Code) werden hieraus zwei unterschiedliche Blob-Objekte mit unterschiedlichen Blob-URLs. Es muss also einen Weg geben, allen Webseiten eine kanonische Blob-URL mitzuteilen. Gelingt das, ist der Shared Worker selbst kein Problem, denn sowohl Worker selbst als auch Blob-URLs unterliegen der normalen Same Origin Policy (außer im IE). Also frisch ans Werk!

Zunächst brauchen wir Code für einen Shared Worker, um das Konzept zu testen. Dieser Schnipsel (nur stilecht als JS-Multiline-String) sendet als Worker allen verbundenen Webseiten jede Sekunde eine Zahl zwischen 23 und 42, so dass alle Seiten jeweils schön synchronisiert sind:

var workerCode = "var clients = [];\
self.onconnect = function(evt){\
  var client = evt.ports[0];\
  client.postMessage(42);\
  clients.push(client);\
};\
setInterval(function(){\
  var x = Math.floor(Math.random() * (42 - 23 + 1)) + 23;\
  clients.forEach(function(client){\
    client.postMessage(x);\
  });\
}, 1000);";

Die Webseiten sollen diesen Wert in einem DOM-Element anzeigen. Dazu dient die folgende Handler-Funktion:

function workerHandler(evt){
  var x = evt.data;
  document.querySelector('#ShowX').innerHTML = x;
}

Um die kanonische Blob-URL zu kommunizieren könnte man das Storage-Event oder eine der HTML5-Kommunikations-APIs nutzen. Für unser Beispiel nehmen wir Cross-Document-Messaging und um den Versuchsaufbau einfach zu halten, lassen wir die Webseiten einander mit window.open() öffnen. Um zu entscheiden, ob eine Webseite selbst eine Blob-URL erstellen oder auf eine Nachricht des Eltern-Elements soll, prüfen wir window.opener. Falls die Webseite die Pionierrolle einnehmen soll, erstellt sie Blob und URL selbst, ansonsten wird auf eine Cross-Document-Message gewartet:

var workerUrl;

// Bootstrap-Szenario: erstes Fenster, Blob, URL und Worker
// selbst aus dem Code erstellen
if(!window.opener){
  var workerBlob = new Blob([workerCode], {
    type: 'text/javascript'
  });
  workerUrl = window.URL.createObjectURL(workerBlob);
  var worker = new SharedWorker(workerUrl, 'SyncWorker');
  worker.port.onmessage = workerHandler;
  document.querySelector('button').removeAttribute('disabled');
}

// Externe-Worker-Quelle-Szenario: Popup-Fenster, Worker-URL
// vom Eltern-Fenster erwarten
else {
  window.onmessage = function(evt){
    workerUrl = evt.data;
    var worker = new SharedWorker(workerUrl, 'SyncWorker');
    worker.port.onmessage = workerHandler;
    document.querySelector('button').removeAttribute('disabled');
  };
}

Nun fehlt nur noch der Button für neue Fenster und schon sind wir fertig:

// Neues Fenster öffnen, Worker-URL per Message übergeben
document.querySelector('button').onclick = function(){
  var newWindow = window.open(window.location.href);
  newWindow.onload = function(){
    newWindow.postMessage(workerUrl, '*');
  };
};

Das Beispiel funktioniert zwar, ist aber naturgemäß etwas fragil. Sobald eine Seiten-Instanz nicht über den Button auf einer anderen Seite geöffnet wird, bricht alles zusammen; die Seite hält sich irrtümlich für die erste, erstellt eine eigene Blob-URL und scheitert mit der Erstellung des Workers, da bereits einer mit gleichem Namen existiert. Ein sinnvolleres Vorgehen könnte wie folgt aussehen:

  1. Grundsätzlich eine eigene Blob-URL erstellen
  2. In einem Try-Catch-Block versuchen mit der eigenen URL einen neuen Shared Worker zu eröffnen
    1. Im Erfolgsfall Blob-URL in Local Storage ablegen; die Blob URL ist jetzt kanonisch, da noch kein anderer Worker mit diesem Namen aktiv ist
    2. Im Falle einer Exception muss es bereits einen laufenden Prozess mit kanonischer URL geben und diese muss im Storage liegen. URL aus Local Storage auslesen und nochmal versuchen den Worker zu erstellen

Diese Variante sei dem geneigten Leser als Hausaufgabe überlassen.

Wie tiefgreifend wirkt Flexbox (und warum)?

Ich habe in letzter Zeit viel zu Flexbox gelesen. Meine Frage ist, wie sich die Flexboxeinstellungen des Elternelements auswirken. Gelten diese für alle Kindelemente? Oder nur auf für nächstgelegene Kindelement?

Flexbox-Einstellungen wirken sich immer nur auf die direkten Kindelemente aus. Das irritiert Flexbox-Newcomer manchmal – man würde vielleicht erwarten, dass CSS-Eigenschaften gefälligst durch alle Kindelemente weitergegeben werden sollten. Allerdings ist (technisch gesehen) Flexbox nichts weiter als ein zusätzlicher Layout-Modus. Flexbox steht damit komplett gleichberechtigt neben den altbekannten Block-, Inline-, Table- und Positioned-Layoutmodi (letzterer betrifft position:absolute etc.). Macht man sich das klar, wundert man sich nicht mehr, denn bei allen Layout-Modi regeln Elternelemente die Anordnung ihrer direkten Nachfahren, aber nicht aller Nachfahren. Und das ist auch gut so! Man stelle sich nur vor, wie nutzlos position:absolute wäre, wenn diese Eigenschaft an alle Kindelemente eines Elements vererbt würde …

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Ansonsten kann man mich natürlich auch als Erklärbär zu sich kommen lassen.

Erklärbär-Termine für November und Dezember 2014

Noch eben schnell zum Jahresende auf den neusten Stand in Sachen Frontend kommen? Meine traditionellen Münchner HTML5- und CSS3-Termine bieten auch in diesem Quartal die Gelegenheit hierzu.

  • 8. - 10. Dezember in München: HTML5-Schulung bei der Open Source School. Mein bewährtes drei­tä­gi­ges HTML5-Standardprogramm stattet die Teilnehmer im Druckbetankungsverfahren mit so gut wie allem aus, was man zu HTML5 wissen muss. Von semantischem Markup bis hin zu Canvas-Frameworks ist alles dabei. Einen großer Praxisanteil mit überschaubaren Arbeitsgruppen stehen auf dem Plan und ein Exemplar des HTML5-Buchs gibt es obendrein.
  • 11. und 12. Dezember in München: CSS3 bei der Open Source School. Mein bewährtes zweitä­gi­ges CSS3-Standardprogramm katapultiert die Teilnehmer in das CSS3-Zeitalter, in dem Webfonts, Animationen und Farbverläufe fließen. Geboten wird auch hier ein großer Praxisanteil, kleine Arbeitsgruppen und mindestens ein CSS3-E-Book gibt es als Bonus.

Termine unpassend, Orte alle zu weit weg und Programme nicht genehm? Ich komme auch gerne mit einem maßgeschneiderten Talk oder Workshop vorbei – mich kann man mieten!

Erfahrungsbericht: Ein Workflow für ECMAScript 6

In den letzten Wochen habe ich an einem größeren JavaScript-Projekt herumgeschraubt, das ich versuchsweise mit so viel ECMAScript 6 wie möglich bestritten habe. Es ging mir einseits darum, mit dem neuen JavaScript-Standard echte, praktische Erfahrung zu sammeln und andererseits einen möglichest runden Workflow zu erarbeiten. Während ersteres sehr gut funktioniert hat (mein Gehirn leidet mittlerweile, wenn es ES5 schreiben muss) war letzteres sehr mühsam und tendenziell auch eher so mittel-erfolgreich. Wobei das auch daran liegen mag, dass ich neben ES6 auch Gulp und Browserify zu Felde geführt habe und mir vielleicht nur diese spezielle Technologie-Cocktail so auf den Magen schlägt.

Die folgenden Zeilen sollen kurz berichten, mit welchen Tools ich meinen ES6-Code für aktuelle Browser aufbereite, welche Alternativen es gibt und warum ich mich für welche Tool-Kombination entschieden habe. Ich würde jedem, der ein ähnliches Experiment vorhat, dringend empfehlen selbst ein wenig die Transpiler-Landschaft zu erkunden, denn die verschiedenen Tools unterscheiden sich durchaus recht stark.

Warum überhaupt schon ES6?

ECMAScript 6 ist noch kein in Stein gemeißelter, fertiger Standard, aber wenn man die Entwicklung verfolgt hat, ist klar, dass inhaltlich eigentlich alles gesagt worden ist. Neue Features werden bereits für ECMAScript 7 verplant und an Version 6 ändert sich schon länger nichts gravierendes mehr. Vieles in ES6 ist nicht wirklich neu, sondern fällt eher in die Kategorie „syntaktischer Zucker“. Destructuring ist das beste Beispiel:

// ES6-Code
var arr = [23, 42]
var [a, b] = arr;

// ES5-Entsprechung
var arr = [23, 42];
var a = arr[0],
    b = arr[1];

Auch Klassen sind nicht mehr als eine andere für Constructorfunktionen nebst Prototypen-Setup und selbst Generators lassen sich im Prinzip mit heutigem JavaScript abbilden. Und natürlich gibt es Tools, die ES6-Code nach ES5-Code übersetzen, so dass man heute schon große Teile von des neuen Standards nutzen kann, wenn man sich nur ein wenig anstrengt.

Den Sinn hinter dieser Übung sehe ich vor allem in der Gewöhnung an all die neuen Werkzeuge. Zwar sind ES6-Klassen im Prinzip exakt das gleiche wie Constructorfunktionen, aber in der Benutzung eben doch nicht ganz. Arrow Functions sind normale Funktionen mit lexikalischem this, sehen aber im Code aus wie von einem anderen Stern. Alte Bekannte wie var und function verschwinden ggf. völlig aus JavaScript-Code und zumindest bei mir dauert der Prozess, der Generators in meinen aktiven JavaScript-Wortschatz überführt, noch immer an. Es ist also alles in ES6 ziemlich anders und man muss sich dran gewöhnen.

Allerdings reden wir hier immer noch über experimentelle Technologie und ist kein völliger Selbstläufer, sich hierfür einen Workflow einzurichten. Ich bin zu einem für mich brauchbaren Ergebnis gekommen, aber andere haben sich komplett andere Workflows zusammengestrickt. Es ist also keinesfalls gesagt, dass die folgenden Zeilen die beste Lösung enthalten.

Was lässt sich heute mit ES6-Transpilern erreichen?

Es lassen sich sehr große Teile von ES6 nach ES5 übersetzen und es steht eine breite Palette an ES6-Transpilern zur Auswahl. Ein Tool, das mich komplett begeistert, habe ich nicht gefunden. Bei jedem Kandidaten gibt es an irgendwelchen Stellen Probleme. Die einen sind mir persönlich zu mächtig, die anderen können mir zu wenig und die nächsten unterstützen keine Source Maps. Hinzu kommt, dass so mancher Transpiler zwar gut funktioniert, die dazugehörigen Grunt/Gulp-Plugins aber defekt sind und die Autoren sich nicht für Pull Requests interessieren.

Viel von ECMAScript 6 ist einfach nur neue Syntax und entsprechend unproblatisch für Transpiler. Einige etwas kniffligere ES6-Features lassen sich zwar im Prinzip auch umsetzen, waren mir bei meinen Versuchen aber nicht geheuer. Features wie z.B. let oder Symbols erfordern von Transpilern sehr fortgeschrittene Trickserei, was nicht sonderlich lesbaren Quelltext produziert und sich auch nicht besonders angenehm anfühlt. Grundsätzlich gilt aber: wenn man nur will, kommt man mit ES6-Transpilern sehr weit und die fortgeschritteneren Tools (Traceur, es6-transpiler) bieten auch die Möglichkeit dazu.

Neben den unterstützten Features ist der Hauptunterschied zwischen den verschiedenen ES6-Transpilern ganz klar die Philosophie. Bei gleichem ES6-Input produzieren sie sehr unterschiedliche ES5-Ergebnisse, die zwar das gleiche Machen, aber einfach sehr unterschiedlich aussehen. Als Beispiel können wir uns mal diesen simplen ES6-Schnipsel ansehen:

class Foo {
  constructor(){
    this.bar = 42;
  }
  baz(){
    var someFn = () => this.bar; 
    return someFn();
  }
}

Die unterschiedlichen Transpiler schaffen es, hieraus wirklich sehr unterschiedliche Ergebnisse zu produzieren.

Transpiler-Tools im Vergleich

Ernsthaft ausprobiert habe ich drei Tools: SweetJS-Makros für ES6, Traceur und ES6-Transpiler. Letzteres ist in meinem Workflow, ergänzt durch Regenerator, im Rahmen einen-Gulp-Tasks das Mittel der Wahl. Das heißt nicht, dass alles damit toll wäre, sondern nur, dass diese Kombination der beste Kompromiss aus Extra-Arbeit und Gewinn durch neue Features darstellt. Aber alle drei Tools haben so ihr Vorzüge.

SweetJS bietet Makros für JavaScript und das Modul es6-macros enthält mit Destructuring, Klassen und Arrow Functions nur wenige neue Features. Diese werden aber ausgesprochen stressfrei umgesetzt; man bindet einfach das Modul in seinen Grunt/Gulp-Task für SweetJS ein und schon funktioniert es anstandslos. Fummelige Konfiguration, halbgare Features oder irgendwelchen anderen Stress gibt es nicht; dafür gibt es aber nicht wahnsinnig viele neue Features. Die Beispiel-ES6-Klasse sieht mit SweetJS kompiliert wie folgt aus:

function Foo(){
  this.bar = 42;
}
Foo.prototype.baz = function baz(){
  var someFn = function (__fa_args){
      return this.bar;
    }.bind(this, typeof arguments !== 'undefined' ? arguments : undefined);
  return someFn();
};

Das ist nicht zu 100% ES6-konform (Klassen sollten eigentlich im Strict Mode sein), aber dafür extrem übersichtlich! SweetJS allgemein und vor allem die ES6-Makros sind sehr zu empfehlen, wenn man sich erst noch an den Transpiler-Gedanken gewöhnen muss. Die Tools machen nicht so wahnsinnig viel, sind leicht zu benutzen und der erzeugte Code ist übersichtlich.

Der Traceur-Compiler ist das genaue Gegenstück zu den schmalen Sweet-Makros. Traceur kann fast alles (u.A. Generators, Module und Promises), was man dem Tool auch anmerkt. Man kann sich einen Wolf konfigurieren, manches funktioniert eher mittelgut und Traceur-JavaScript funktioniert nicht ohne eine Runtime, die man vor dem Rest-Script einbauen muss. Die Runtime wird im generierten Code auch reichlich eingesetzt, so dass man aus Traceur-Output nicht immer schlau wird. Man sehe sich nur an, was Traceur aus unserer Beispielklasse fabriziert:

$traceurRuntime.ModuleStore.getAnonymousModule(function() {
  "use strict";
  var Foo = function Foo(){
    this.magicValue = 42;
  };
  ($traceurRuntime.createClass)(Foo, {
    bar: function(){
      var $__108 = this;
      var someFn = (function(){
        return $__108.magicValue;
      });
      return someFn();
    }}, {});
  return {};
});

Traceur scheint mir am ehesten ein Tool zum Experimentieren um des Experimentierens willen zu sein. Wenn man ein ES6-Programm auch zu benutzen gedenkt, weiß ich nicht, ob man immer die Runtime mitschleppen und die generierten Enigmas debuggen möchte. Als schnelle ES6-Demo ist allerdings kaum etwas besser geeignet, als die Traceur-Repl.

Irgendwo zwischen diesen beiden Extremen bewegt sich es6-transpiler. Es gibt nicht ganz so viele Features wie bei Traceur (z.B. fehlen Generators), dafür wird aber auch keine Runtime benötigt. Sehr schön ist, dass der Sourcecode nach dem Transpiling wirklich nur an den Stellen geändert ist, an denen neue ES6-Features verwendet wurden. Allerdings ist das auch nötig, denn als einziges Tool auf der Liste produziert es6-transpiler keine Source Maps.

var Foo = (function(){
  "use strict";
  var PRS$0 = (function(o, t){
    o["__proto__"] = {
      "a": t
    };
    return o["a"] === t;
  })({}, {});
  var DP$0 = Object.defineProperty;
  var GOPD$0 = Object.getOwnPropertyDescriptor;
  var MIXIN$0 = function(t, s){
    for(var p in s){
      if(s.hasOwnProperty(p)){
        DP$0(t, p, GOPD$0(s, p));
      }
    }
    return t;
  };
  var proto$0 = {};

  function Foo(){
    this.bar = 42;
  }
  DP$0(Foo, "prototype", {
    "configurable": false,
    "enumerable": false,
    "writable": false
  });
  proto$0.baz = function(){
    var this$0 = this;
    var someFn = function(){
      return this$0.bar
    };
    return someFn();
  };
  MIXIN$0(Foo.prototype, proto$0);
  proto$0 = void 0;
  return Foo;
})();

Das ist zwar viel Code, aber dafür auch recht klarer Code. Es gibt auch hier eine „Runtime“, die allerdings direkt im generierten Code zu finden ist. Das Rätselraten hält sich also Grenzen. Normalerweile ist der Runtime-Teil des generierten Codes so untergebacht (alles in einer Zeile), dass Zeilennummern von Quell- und Generiertem Code 1:1 aufeinanderpassen. Debuggen ist also trotz fehlender bzw. eingeschränkter Source Maps recht erträglich.

Regenerator aus dem Hause Facebook passt nicht ganz in die Reihe, da es sich nur ein einziges ES6-Feature kümmert: Generators. Das macht es auch auf recht angenehme und nachvollziehbare Weise (Talk zum Thema) und kann daher gut mit den SweetJS-ES6-Makros oder es6-transpiler kombiniert werden. Regenerator braucht eine eigene Mini-Runtime.

Mein aktueller Workflow

Für mein ES6-Projekt verwende ich Gulp, es6-transpiler und Regenerator. Der ES6-Task in meiner Gulpfile stellt sich aktuell wie folgt dar:

gulp.task('client-compile', function(){
  return browserify('foo.js', {
      debug: true
    })
    .transform(regeneratorify)
    .transform('es6-browserify')
    .require(regenerator.runtime.dev)
    .bundle()
    .on('error', function(err){
      gutil.log(gutil.colors.red('Browserify error:'), err.message);
      this.end();
    })
    .pipe(source('foo.dist.js'))
    .pipe(gulp.dest('./www'))
    .on('error', gutil.log);
});

Grundlage des Transpilier-Prozesses ist Browserify. Da bis vor kurzem noch nicht mal feststand, wie ES6-Module überhaupt aussehen sollen (mittlerweile ist das geklärt) und ich im Rahmen meines letzten Projekts durch RequireJS nachhaltig traumatisiert wurde, wollte ich mal einem Konkurrenz-Modulsystem eine Chance geben. Browserify nimmt normalerweise nur in NodeJS lauffähige CommonJS-Module und macht sie fit den Browser. Dabei wird grundsätzlich die gesamte Modulstruktur in eine einzige Datei kompiliert, was ja heutzutage aus Performance-Sicht ganz wünschenswert ist. Außerdem erlaubt es Browserify, Transformationsfunktionen über der Browserifizierung den Code zu jagen. An dieser Stelle lassen sich die ES6-Transpiler wunderbar einbauen.

Mit es6-browserify existiert bereits eine brauchbare Browserify-ES6-Transpiler-Transformationsfunktion. Diese kann einfach anhand ihres Modulnames via transform('es6-browserify') eingebunden werden. Da es allerdings in es6-transpiler keine Generator-Unterstützung gibt, muss vorher noch Regenerator den Code bearbeiten. Weil ich hierfür keine brauchbare Browserify-Transformationsfunktion gefunden habe, habe ich kurzerhand meine eigene gebaut:

function regeneratorify(file){
  var data = "";
  var stream = through(write, end);
  function write(buf){
    data += buf;
  }
  function end(){
    var rdata = regenerator(data);
    stream.queue(rdata);
    stream.queue(null);
  }
  return stream;
}

Die Einbindung der Funktion erfolgt anhand des Funktionsnamens via .transform(regeneratorify). Zu guter Letzt braucht Regenerator noch seine (kleine) Runtime. Diese wird mittels require(regenerator.runtime.dev) eingebunden, wobei regenerator das normale, mit require() geladene Regenerator-Modul ist.

Der Rest des Codes ist eigentlich nur Zeug, das Browserify und Gulp (zwei sehr eigenwillige Projekte) miteinander zu verbinden sucht. Es war ausgesprochen frustrierend die diversen teilweise sehr halbgaren und/oder kaputten Gulp- und Browserify-Plugins durchzuprobieren, bis ich schließlich zu dem hier beschriebenen Ergebnis gekommen bin.

Die Erfahrungen mit dem Workflow und ES6

Ist erst mal der Buildprozess eingerichtet und alles automatisiert, lebt es sich in der ES6-Welt ganz angenehm. Es gibt viele schöne Features, an die man sich aber auch erst mal gewöhnen muss … wenn man sie denn wirklich alle einsetzen möchte. Und ich glaube nicht, dass das der Fall ist.

Ich würde noch nicht empfehlen, das nächste anstehende JS-Projekt voll auf ES6 auszurichten. Rechnet man die Fummelei mit den Tools, den Lernaufwand und die bei größeren Programmen doch irgendwann spürbare Kompilierzeit von 2-3 Sekunden zusammen, so lohnt es sich in der Gesamtabrechnung einfach noch nicht, es sei denn man hat sehr spezielle Dinge vor. Manche Probleme und Programmierstile können von einigen ES6-Features durchaus profitieren. Generators machen Sequenzen zum Kinderspiel und wenn man viel funktional programmiert lohnen sich Arrow Functions durchaus. Man schreibt (a, b) => a +b einfach so sehr viel lieber als function(a, b){ return a +b; } und dem Code wird durch die fehlenden Schweifklammern sehr viel Grundrauschen entzogen.

Die Features, die meiner Erfahrung nach einen erheblichen Mehrwert bieten (Arrow Functions, Generators) sind jene, die man mit eher kleinen Tools (SweetJS-Makros, Regenerator) zum Einsatz bringen kann. Deshalb ist mein Eindruck: wenn heute schon ES6, dann lieber gezielt Einzel-Feature-Tools hernehmen, als sich den Stress eines vollständigeren Transpilers aufzubürden. Es gilt als auch für den neuen JavaScript-Standard die gute alte 80/20-Regel: 80% des Gewinns werden mit 20% des Einsatzes erzielt. Und mehr als 20% Einsatz sollte man sich im heutigen ES6-Ökosystem meiner Meinung nach nicht zumuten.

Folgt mir!

Kauft mein Zeug!

Cover der HTML5-DVD
Infos auf html5-dvd.de, kaufen bei Amazon

Cover des HTML5-Buchs
Infos auf html5-buch.de, kaufen bei Amazon

Cover des CSS3-Buchs
Infos auf css3-buch.de, kaufen bei Amazon