Einführung in Require.js

Das Problem

Wie binden wir normalerweise Scripte ein?

    <script src="js/jquery.js"></script>
    <script src="js/app.js"></script>
</body>
</html>

Das ist der Idealfall …

    <script src="js/jquery-ui.js"></script>
    <script src="js/jquery.js"></script>
    <script src="js/jquery.validate.min.js"></script>
    <script src="js/jquery.imageslider.js"></script>
    <script src="js/modul2.js"></script>
    <script src="js/modul1.js"></script>
    <script src="js/app.js"></script>
    <script src="js/myOwnFunctions.js"></script>
    <script src="js/init.js"></script>
</body>
</html>

… aber in der Regel sieht es doch eher so aus.

  • Websites werden immer komplexer
  • Komplexer Code ist schlechter zu warten
  • Komplexer Code ist schlechter zu testen
  • Verwaltung der Abhängigkeiten von Hand ist fehleranfällig

Kleiner Test:

Hat jemand den Fehler im Code bemerkt?

    <script src="js/jquery-ui.js"></script>
    <script src="js/jquery.js"></script>
    <script src="js/jquery.validate.min.js"></script>
    <script src="js/jquery.imageslider.js"></script>
    <script src="js/modul2.js"></script>
    <script src="js/modul1.js"></script>
    <script src="js/app.js"></script>
    <script src="js/myOwnFunctions.js"></script>
    <script src="js/init.js"></script>
</body>
</html>
Wir brauchen etwas, dass unseren Code übersichtlich hält und sich um die Verwaltung von Abhängigkeiten kümmert.

AMD

Asynchronous Module Definition

Was ist AMD?

  • Ein Standard für JavaScript-Module
  • Abhängigkeiten werden in jedem Modul definiert
  • Module werden asynchron geladen
  • Einfach zu verwenden

Vorteile

  • Kleine Module sind einfacher zu warten
    als riesige Code-Monster
  • Jedes Modul sollte nur eine Funktion erfüllen
  • Module sind als Einzelbausteine leichter zu testen

Require.js

Ein Loader für AMD-Module

requirejs.org

  • define() wird verwendet, um Module zu definieren
  • require() lädt diese Module asynchron und führt danach Code aus

define(): Module definieren


define('mymodule', ['module1', 'module2'], function( m1, m2 ) {
    return m1 + m2;
});
                    

define(
    'mymodule',             // Modulname (optional)
    ['module1', 'module2'], // Abhängigkeiten
    function( m1, m2 ) {}   // Code
);
                    
Module sollten immer eine Funktion/einen Constructor oder ein Objekt zurückgeben, aber sich nicht selbst initialisieren.

// Meh.
define(function() {
    var Foo = function() { … };
    return new Foo();
});

// Yay!
define(function() {
    var Foo = function() { … };
    return Foo;
});
                    

require(): Module asynchron laden


require(['jquery', 'myapp'], function($, myApp) {
    // Wird erst ausgeführt, wenn beide Module geladen sind
    myApp.init();
    $('body').append( myApp.html );
});
                    

jQuery und viele andere Bibliotheken bringen AMD-Unterstützung schon mit

Um alles zu laden wird nur noch ein
einzelner <script> Tag benötigt:
    <script src="js/require.js" data-main="js/main"></script>
</body>
</html>

Zwei Tipps zur Konfiguration

paths


require.config({
    paths: {
        // Core libraries
        'jquery'             : 'lib/jquery/jquery.min',
        'jquery-ui'          : 'lib/jquery-ui-1.10.3.custom.min',
        'underscore'         : 'vendor/lodash',
    }
});
                        
Bibliotheken am besten mit 'path' als Dependency angeben, z.B. 'jquery-ui'. Ändert sich die Version, muss der Pfad nur an einer Stelle geändert werden.

shim

Scripte verwenden, die kein AMD unterstützen:

require.config({
    shim : {
        'backbone': {

            // Abhängigkeiten
            deps: ['jquery', 'underscore']

            // Globales Objekt zur Rückgabe
            exports: 'Backbone'
        }
    }
});
                            

Buildprozess

r.js ist ein Kommandozeilentool,
dass die einzelnen Module zusammenfasst:

github.com/jrburke/r.js

Alternative: Require.js + Grunt = <3

github.com/gruntjs/grunt-contrib-requirejs

Demo-Time!

Coole Extras

Module abhängig von
der Umgebung laden

Development


// main.js
require.config({
    paths: { 'config' : 'config/app.development' }
});
                        

Build


// build.js / Gruntfile.js
{
    paths: { 'config' : 'config/app.production' }
}
                        

Lazy Loading von Modulen


var datePicker;
$('input.date').on('click', function() {
    if (!datePicker) {
        require(['jquery-datepicker'], function(pick) {
            datePicker = pick;
            pick.init();
        });
    } else {
        datePicker.init();
    }
});
                        

Plugins

… für Texte, JSON, Template-Engines, etc.

require(['json!package.json'], function (package) {
    console.log(package.name);
});
                        

Zusammenfassung

  • AMD-Module sind kompakt und in sich geschlossen
  • Jedes AMD-Modul definiert seine eigenen Abhängigkeiten
  • Require.js lädt Module und Abhängigkeiten in der richtigen Reihenfolge
  • Der Optimizer fasst Module in einer oder mehreren Dateien zusammen
  • Module können auch "bei Bedarf" nachgeladen werden
  • Verschafft Übersicht in komplexen Projekten

That's all folks!

Fragen?

Frederic Hemberger (@fhemberger)