Clientseitige Abhängigkeiten in JavaScript verwalten

Von Marc Löhe / @bfncs

Struktur

  1. Status Quo
  2. CommonJS Module
  3. AMD (Require.js)
  4. Browserify
  5. ES6 Module

Status Quo








                    

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.

In größerem Projekten

Das eine oder andere Script

Spaghetti!

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

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



                    

ES6 modules

mymodule.js


export class q {
    constructor() {
        console.log('this is an es6 class!');
    }
}
                    

mymodule.js



                    

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?

Links

Bilder:

Vielen Dank!

… und hier in Ruhe nachlesen:
https://github.com/boundaryfunctions/fedeps