Schritt für Schritt zum SystemJS-Profi

SystemJS Beginner’s Guide
Kommentare

Über die Verwendung eines Modulsystems für JavaScript muss man mittlerweile nicht mehr diskutieren – die Frage ist eher, auf welches Modulsystem setzt man, damit die Applikation auch zukunftssicher ist? Eine mögliche Variante ist das ECMAScript-Modulsystem mit einer Kombination aus SystemJS und JSPM. Genau damit werden wir uns jetzt beschäftigen.

Wer etwas auf sich hält, der setzt ein Modulsystem in seiner JavaScript-Applikation ein. Bis auf die Tatsachen, dass es mittlerweile jeder macht, warum sollten Sie sich die Arbeit machen und in Ihre Applikation ein Modulsystem einführen oder bei neuen Applikationen auf ein Modulsystem setzen?

Die Erklärung ist ganz einfach: Kein Entwickler lädt gerne die Script-Dateien seiner Applikation manuell. Zu einfach ist es, hier Dateien zu vergessen, was nur zu ärgerlicher Fehlersuche führt. Noch schlimmer wird die Situation, wenn verschiedene Skripte aufeinander aufbauen und Sie diese Abhängigkeiten von Hand auflösen müssen. Und genau in solchen und in noch vielen weiteren Situationen kann Ihnen ein Modulsystem helfen.

Nun ist es ja in JavaScript bekanntlich immer so, dass es nicht nur eine etablierte Lösung gibt, sondern mindestens eine Handvoll. Warum sollte es bei Modulsystemen anders sein? Glücklicherweise ist die Auswahl an Modulsystemen noch recht überschaubar.

SystemJS – Schnelleinstieg

Wenig Zeit? Hier geht es schnell zu den einzelnen Themengebieten:

InstallationJSPMAnwendungen bauenTestsIntegration

SystemJS

Bis vor einiger Zeit konkurrierten vor allem das CommonJS- und das AMD-Modulsystem um die Vorherrschaft in der JavaScript-Welt. Nachdem es sich über Jahre hinweg herausgestellt hat, dass ein Modulsystem eine durchaus sinnvolle Errungenschaft ist, wurde ein Entwurf in den Sprachstandard aufgenommen.

Der Vorteil hierbei ist ganz klar die native Unterstützung der Browser. Das Problem ist, dass noch kein Browser die JavaScript-Module unterstützt, und dass es sich beim Standard weder um die etablierten AMD- noch um die CommonJS-Module handelt, sondern eine abweichende Syntax festgelegt wurde.

Modulsystem Syntax Implementierung
AMD require, define RequireJS
CommonJS require, module.exports Browserify
ES 6 Modules import, export SystemJS
Die verschiedenen Modulsysteme auf einen Blick

Wollen Sie nun also Ihre Applikation modularisieren, bleibt Ihnen nur wieder der Griff zum Modulloader Ihres Vertrauens. Und hier kommt SystemJS ins Spiel.

Die Idee hinter dieser Bibliothek ist es, Ihnen ein Polyfill zur Verfügung zu stellen, mit dem Sie modulare Applikationen erstellen können. SystemJS ist dabei recht flexibel und akzeptiert nicht nur die ECMAScript-Modulsyntax, sondern unterstützt auch AMD- und CommonJS-Module. Das bedeutet, dass Sie, egal welches Modulsystem Sie einsetzen möchten, mit SystemJS nichts falsch machen.

Installation

Installiert wird SystemJS nach bester JavaScript-Manier über einen der vielen Paketmanager. Ein npm install systemjs führt genauso zum Ergebnis wie bower install systemjs oder jspm install npm:systemjs. Ziel dieser Aktion ist es, den Quellcode von SystemJS auf Ihr System zu laden, was Sie natürlich auch von Hand über Ihren Browser erledigen können.

Die beste Kombination ist SystemJS und JSPM, da diese beiden Werkzeuge nahtlos zusammenarbeiten. Das bedeutet, dass Sie die mit JSPM installierten Pakete direkt mit SystemJS laden können. Aber dazu später mehr.

Haben Sie die system.js-Datei gespeichert, können Sie den nächsten Schritt unternehmen und Ihre Applikation entwickeln. Wollen Sie die ECMAScript-Modulsyntax verwenden, müssen Sie zusätzlich dazu noch einen Transpiler wie Traceur, Babel oder TypeScript installieren. Diesen speichern Sie entweder im Basispfad Ihrer Applikation, den Sie mit der Eigenschaft baseURL angeben, oder Sie erstellen ein manuelles Mapping mit der map-Eigenschaft der Konfiguration.

<script src="node_modules/systemjs/dist/system.js"></script>
<script>
  System.config({
    baseURL: './app',
    transpiler: 'babel',
    map: {
        babel: '../../systemjs/node_modules/babel-core/lib/api/browser.js'
    }
  });
  System.import('main.js')
</script>

Nach diesem initialen Setup geht es nun daran, Module zu formulieren und sie zu einer vollwertigen Applikation zusammenzufügen.

Für Module gilt: pro Datei ein Modul. Ein Modul ist eine abgeschlossene Einheit, die ein API nach außen exportiert. Sie sollten darauf achten, dass Sie die API immer gut sichtbar und immer an derselben Stelle in einem Modul definieren. Das export-Schlüsselwort legt dabei ein solches API fest.

Grundsätzlich können Sie alles exportieren; ob es sich dabei um primitive Werte, Funktionen, Objekte oder Klassen handelt, spielt keine Rolle. Für eine bessere Wartbarkeit Ihrer Applikation sollten Sie jedoch darauf achten, dass Sie die unterschiedlichen Typen nicht zu sehr mischen und sich im Idealfall auf Klassen und Objekte beschränken.

function myModule() {
  console.log('Hello Module');
}
export {
  myModule: myModule
};

Ein einmal definiertes Modul lässt sich mit dem import-Schlüsselwort einbinden. Dabei geben Sie an, über welche Variable Sie auf die Inhalte zugreifen möchten und wo das Modul liegt. Um den Rest kümmert sich dann SystemJS.

Eine normale Applikation ist so aufgebaut, dass es eine zentrale Datei gibt, die für die Initialisierung der Applikation sorgt und die übrigen Module einbindet. Für das Basismodul gelten besondere Regeln, so dass hier Seiteneffekte erlaubt sind. Ansonsten sollten Sie darauf achten, dass ein Modul bei der Einbindung möglichst wenig, wenn nicht gar keine Seiteneffekte hat. Sie sind nur sehr schwer zu kontrollieren und noch schwieriger zu testen.

import myModule from 'module';
myModule();

JSPM

Sowohl JSPM als auch die ECMAScript-Modulsyntax von SystemJS basieren auf dem ES6 module loader. Das ist auch einer der Gründe, warum die beiden Werkzeuge so gut zusammenarbeiten.

JSPM dient bei der Entwicklung von Anwendungen als Paketmanager. Mit ihm können Sie alle Bibliotheken installieren, die Sie für die Entwicklung und den Betrieb Ihrer Applikation benötigen. Als Paketquellen dienen vor allem NPM und GitHub, was allerdings den Großteil der üblichen Bibliotheken abdeckt.

Die Verwendung von JSPM erleichtert Ihnen vor allem auch die Arbeit mit SystemJS, da der Paketmanager auch hier bei der Auflösung der Abhängigkeiten ganze Arbeit leistet. Beginnen Sie die Entwicklung Ihrer Applikation mit dem Kommando jspm install npm:systemjs, wird nicht nur SystemJS mit all seinen Abhängigkeiten installiert, sondern auch noch eine grundlegende Struktur für Ihr Projekt geschaffen.

JSPM erstellt eine package.json-Datei, in der Sie alle wichtigen Eckdaten Ihrer Applikation dokumentieren und beispielsweise die Abhängigkeiten verwalten können. JSPM legt zu diesem Zweck eine eigene Sektion mit dem Namen „jspm“ an. Zusätzlich zur package.json erhalten Sie eine Datei mit dem Namen config.js, in der die Konfiguration für SystemJS abgelegt wird. Hier finden Sie alle Einstellungen, die Sie zu Beginn noch direkt in der index.html Ihrer Applikation vorgenommen haben.

JSPM übernimmt automatisch das Mapping zu den installierten Paketen für Sie, sodass Sie sich um nichts kümmern müssen und direkt mit der Implementierung starten können. Als Vorbereitung für den Build Ihrer Applikation, also dem Zusammenfassen aller Dateien zu einer großen, optimierten Datei, sollten Sie dafür sorgen, dass Sie möglichst alle Bestandteile sauber in eigene Dateien ablegen und komplett auf inline-JavaScript verzichten. Die Datei app/main.js wird so zum zentralen Einstiegspunkt Ihrer Applikation. Mit der Anweisung System.import steigen Sie in Ihre Applikation ein und laden weitere Module. In allen weiteren Dateien können Sie dann direkt mit import und export arbeiten. Am Ende dieses ersten Schrittes laden Sie neben Ihrer Einstiegsdatei nur noch die Konfiguration und SystemJS selbst. Den Rest übernimmt das Modulsystem für Sie.

System.import('./app/user.js').then(function (data) {
  var john = new data.User('John', 'Doe');
  console.log(john.getFullName());
});

Bauen

Für die Entwicklung ist die Auslieferung zahlreicher Dateien ein praktikabler, wenn nicht sogar der beste Ansatz. Das Debugging der Applikation ist in diesem Zustand wesentlich einfacher als mit einer zusammengefassten und minifizierten Datei. Auch wenn die Unterstützung von Source Maps die Arbeit erheblich erleichtern, besteht immer noch die Hürde, dass bei jeder Änderung im Quellcode die Applikation neu gebaut werden muss. Dieser Prozess dauert in den meisten Fällen nur wenige Sekunden, erhöht allerdings den Aufwand und die Fehleranfälligkeit im Entwicklungsprozess. Aus diesem Grund sollten Sie für die Entwicklung den Originalzustand Ihrer Applikation verwenden und für das Release dann die gebaute Version der Applikation.

Haben Sie, wie in unserem Fall, alle Komponenten sauber strukturiert, ist das Bauen der Applikation kein großes Problem. Entsprechende Plugins gibt es für alle gängigen Build-Tools wie Gulp oder Grunt. Für dieses Beispiel verwenden Sie Grunt; die grundsätzliche Vorgehensweise ist allerdings auch in Gulp ähnlich. Sie teilen dem Build-System mit, wo es die Konfiguration findet, ob der Quellcode optimiert werden soll und wohin die Dateien geschrieben werden sollen.

Für einen erfolgreichen Build mit Grunt müssen Sie zunächst das Paket grunt-cli global und grunt lokal installieren. Zusätzlich benötigen Sie für den SystemJS-Build dann nur noch das Plugin grunt-systemjs-builder. Nach der Installation konfigurieren Sie den Build; eine entsprechendes Gruntfile.js sieht folgendermaßen aus:

module.exports = function(grunt) {

  grunt.loadNpmTasks('grunt-systemjs-builder');
  grunt.loadNpmTasks('grunt-contrib-concat');

  grunt.initConfig({
    systemjs: {
      options: {
        baseURL: "./",
        transpiler: "babel",
        configFile: "./config.js",
        minify: true,
        build: {
          mangle: true
        }
      },
      dist: {
        files: [{
          "src": "./app/main.js",
          "dest": "./app/app.min.js"
        }]
      }
    },
    concat: {
      files: {
        src: ['jspm_packages/system.js','app/app.min.js'],
        dest: 'dist/app.min.js'
      }
    }
  });

  grunt.registerTask('default', ['systemjs', 'concat']);
};

Den SystemJS-Build führen Sie dann mit dem Kommando grunt systemjs aus. Damit erhalten Sie die Datei app.min.js im /dist/-Verzeichnis, die Ihre gesamte Applikation exklusive SystemJS enthält. Das bedeutet, dass Sie noch eine Anpassung in Ihrer index.html vornehmen müssen, bevor Sie Ihren Build testen können. In dieser Datei entfernen Sie die Referenzen auf den Startpunkt Ihrer Applikation und die Konfiguration und fügen stattdessen einen Verweis auf die gebaute Datei ein. Und schon können Sie Ihre Applikation verwenden. Öffnen Sie den Netzwerk-Tab Ihrer Entwicklertools, werden Sie feststellen, dass erheblich weniger Anfragen an den Server gesendet werden.

Damit kennen Sie aber bei weitem noch nicht alle Features von SystemJS. Ein weiterer, sehr wichtiger Aspekt ist das Testen von Applikationen. Und auch das ist ohne weitere Probleme mit SystemJS möglich.

Tests

Grundsätzlich sind Sie frei in der Wahl Ihres Test-Frameworks. Doch hat sich mittlerweile die Kombination aus Karma und Jasmine zu einem de-facto-Standard in der JavaScript-Welt entwickelt. Daher beschränken wir uns hier auf ein Beispiel mit Karma und Jasmine. Mit wenigen Anpassungen ist es aber auch möglich, andere Test-Frameworks wie beispielsweise Mocha zu verwenden.

Eines der vorrangigen Ziele beim Testen von JavaScript-Applikationen ist es, den Quellcode nicht anpassen zu müssen, wenn man ihn testen möchte. Aus diesem Grund gibt es für den Test Runner Karma ein Plugin, das dafür sorgt, dass die ECMAScript-Module Ihrer Applikation korrekt geladen werden, ohne dass Sie entweder den kompilierten Code testen oder erst umständlich Wrapper-Funktionalität schaffen müssen. Das Plugin, um das es hier geht, trägt den Namen karma-systemjs. Es verwendet eine ähnliche Struktur wie sie auch bei der normalen Applikationsentwicklung zum Einsatz kommt.

Aktuell gibt es noch einige Inkompatibilitäten zwischen karma-systemjs und der neuesten Version von Babel, sodass es bei der Verwendung dieses Moduls zu Problemen kommen kann. Hierfür gibt es allerdings schon ein Bugticket, an dem gearbeitet wird.

Beim Einsatz von karma-systemjs erweitern Sie Ihre Karma-Konfiguration um einen SystemJS-spezifischen Teil. Er sorgt dafür, dass die Konfigurationsdatei geladen wird und die Dateien, die Sie für Ihre Tests benötigen, korrekt ausgeliefert werden. Außerdem können Sie Teile der SystemJS-Konfiguration überschreiben und sie so an die Testanforderungen anpassen. Das wird beispielsweise nötig, wenn Sie Mock-Bibliotheken wie angular-mocks laden möchten.

API Summit 2017

Moderne Web APIs mit Node.js – volle Power im Backend

mit Manuel Rauber (Thinktecture), Sven Kölpin (Open Knowledge)

API First mit Swagger & Co.

mit Thilo Frotscher (Freiberufler)

Bei der Verwendung von karma-systemjs ist es wichtig, dass Sie zusätzlich zum systemjs-Konfigurationsabschnitt „systemjs“ auch als Framework in die Karma-Konfiguration aufnehmen. Dann steht aber Ihren Tests mit SystemJS nichts mehr im Weg und Sie können dort auch import-Statements verwenden und damit Ihre Dateien laden.

SystemJS-Integration

Nun ist es häufig so, dass Sie entweder schon eine bestehende Applikation haben, in die Sie SystemJS integrieren möchten, oder aber Sie müssen Bibliotheken nutzen, die noch kein Modulsystem verwenden. Ein Beispiel hierfür sind zahlreiche jQuery-Plugins. Zu diesem Zweck gibt es die sogenannten „Shims“ in SystemJS.

In Ihrer SystemJS-Konfiguration können Sie in der Sektion „meta“ ein Objekt angeben, dessen Schlüssel der Name der Datei ist, die geladen werden soll. Der Wert ist ein Objekt, das unter anderem die Eigenschaft deps akzeptiert, über die Sie Abhängigkeiten auf andere Module angeben können. So können Sie einen Abhängigkeitsbaum spezifizieren, obwohl die angegebenen Dateien den Modulstandard nicht erfüllen.

Zusätzlich zu den deps können Sie außerdem globale Variablen spezifizieren, die das Modul benötigt, und zugleich festlegen, wie sie erfüllt werden sollen.

System.config({
  baseURL: "/systemjs/",
  defaultJSExtensions: true,
  transpiler: "babel",
  babelOptions: {
    "optional": [
      "runtime",
      "optimisation.modules.system"
    ]
  },
  meta: {
    "MyLib": {
      deps: ["vendor/jquery"],
      globals: {
        "jQuery": "vendor/jquery"
      },
      exports: "ML",
      format: "global"
    }
  }
});

Fazit

Über die Verwendung eines Modulsystems für JavaScript muss man mittlerweile nicht mehr diskutieren. Die Frage ist eher, auf welches Modulsystem setzt man, damit die Applikation auch zukunftssicher ist. Eine mögliche Variante ist das ECMAScript-Modulsystem mit einer Kombination aus SystemJS und JSPM. Diese Bibliotheken nehmen Ihnen sehr viel Arbeit ab und verfügen mittlerweile über eine sehr aktive Community, die auch bei der Lösung von Problemen helfen kann.

Aufmacherbild: Wooden building blocks von Shutterstock / Urheberrecht: Timof

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -