Clientseitige Abhängigkeiten in JavaScript verwalten
Von Marc Löhe / @bfncs
Struktur
Status Quo
CommonJS Module
AMD (Require.js)
Browserify
ES6 Module
Status Quo
<script src="jQuery.js"></script>
<script src="myPreciousPlugin.js"></script>
<script src="anotherPlugin.js"></script>
<!-- etc... -->
<script src="oneMoreplugin.js"></script>
<script src="dom.js"></script>
Status Quo
(function () {
var $ = this.jQuery;
this.myExample = function () {
$.doSomething();
};
}());
Beispiel von requirejs.org
Status Quo
- Module werden definiert durch eine Factory-Funktion zur Abkopplung vom restlichen Code.
- Abhängigkeiten werden als globale Variablen referenziert, die bereits vorher irgendwie geladen sein müssen.
- Die Abhängigkeiten sind schwach defininiert: der Entwickler muss selber wissen, wann er welchen Bestandteil wie lädt.
Maintainability
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”
John F. Woods
Wie kann man JavaScript-Code im Browser effizient und übersichtlich modularisieren?
Common JS / Node.js Module
circle.js
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
app.js
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));
Beispiel aus der Node.js-Dokumentation
Common JS im Browser
Das in Node verwendete Modulformat wurde von der CommonJS-Gruppe standardisiert und lässt sich prinzipiell so auch im Browser implementieren.
Common JS im Browser
Der Standard berücksichtigt aber leider nicht die besonderen technischen Bedingungen im Browser:
- Module müssen über das Netzwerk geladen werden
- Das Laden der Module findet daher grundsätzlich asynchron statt.
- Es gibt keine browserübergreifende API, jede Webseite/-anwendung muss ihren eigenen Lademechanismus mitbringen.
Asynchronous module definition (AMD)
Mit dem AMD-Standard wurde versucht möglichst viele Vorteile von CJS-Modulen in die Welt der Browser zu bringen, in dem es die technischen Eigenheiten der Browser-Infrastruktur berücksichtigt.
AMD: API
-
define um Module zu definieren
-
require um Abhängigkeiten zu laden
AMD: define
define(
module_id /*optional*/,
[dependencies] /*optional*/,
definition function /*instantiate the module*/
);
Die module_id wird i.d.R. automatisch aus dem Pfad generiert und nicht explizit im Quelltext definiert (DRY).
AMD: define
define(
'myModule',
['foo', 'bar'],
function (foo, bar) {
return myModule = {
doStuff: function() {
console.log('Yay! Stuff');
}
};
});
AMD: require
require(
['foo', 'bar'],
function ( foo, bar ) {
// Modules foo and bar are now loaded and ready to be used
foo.doSomething();
});
AMD: Implementation
Der Standard AMD wird durch mehrere verschiedene JS-Loader-Bibliotheken implementiert:
require.js
index.html
<script data-main="js/app.js" src="js/require.js"></script>
js/app.js
requirejs.config({
baseUrl: 'js/lib',
paths: {
app: '../app'
}});
requirejs(
['jquery', 'canvas', 'app/sub'],
function ($, canvas, sub) {
// All modules are loaded and can be used now.
});
Pro
- Funktioniert asynchron im Browser
- Klare Definition der Abhängigkeiten
- Ermöglicht ein performantes asynchrones Nachladen
- Mehrere Module können in einer Datei definiert werden (daher ein CJS-Transportformat)
- Ermöglicht einfachen Austauch von Modulen durch Mockups für Unit-Testing
Contra
- Viele HTTP-Requests, typischerweise ein File pro Library: unperformant
- Viel Boilerplate-Code, insbesondere wenn mehrere Loader-Formate unterstützt werden sollen (jQuery+CJS+ASM)
- Asynchrones Laden ist im Prinzip eine tolle Sache, aber schadet hier oft mehr, als es nutzt
Browserify
Browserify lets you require('modules') in the browser by bundling up all of your dependencies.
Browserify: Installieren
npm install -g browserify
Ggfs. das Modul für das Buildsystem der Wahl mitinstallieren: grunt, gulp, etc.
Browserify: Beispiel
main.js
var unique = require('uniq');
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));
Modul mit installieren und kompilieren
npm install uniq
browserify main.js -o bundle.js
index.html
<script src="bundle.js"></script>
ES6 modules
mymodule.js
export class q {
constructor() {
console.log('this is an es6 class!');
}
}
mymodule.js
<script>
System.import('mymodule').then(function(m) {
new m.q();
});
</script>
Für Unentschlossene
SystemJS
Universal dynamic module loader - loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS. Works with both Traceur and Babel.
https://github.com/systemjs/systemjs
Jetzt ihr:
- Welche Techniken benutzt ihr?
- Welche Erfahrungen habt ihr gemacht?
- Was funktioniert besonders gut/schlecht?
- Vermisst ihr etwas?
Clientseitige Abhängigkeiten in JavaScript verwalten
Von Marc Löhe / @bfncs