In diesem Beitrag wird die Architektur der hybriden App aus dem letzten Beitrag durch den Einsatz des Frameworks RequireJS verbessert. Da wir das Projekt schon auf dem Module-Pattern aufgebaut hatten, sind nur noch wenige Modifikationen notwendig, um dynamisches Laden der Module zu ermöglichen.
Dieses dynamische Laden kann zu erheblichen Performance-Verbesserungen führen. Außerdem lassen sich Abhängigkeiten zwischen den Modulen mit dem Framework require.js leichter verwalten. Auch wird durch den Einsatz von r.js (Ein NodeJS-Modul) eine Optimierung des Source-Codes möglich gemacht (Minification). Dieser Punkt ist aber nicht mehr Gegenstand dieses Artikels.
Configuration
Zunächst legen wir im Ordner js unseres Projekts eine weitere Source-Datei config.js an:
requirejs.config({ "baseUrl": "js/libs", "paths": { "app": "../app", "mod": "../mod" } // ,"shim": { // "jquery.alpha": ["jquery"], // "jquery.beta": ["jquery"] // } }); console.log('config.js');
Damit definieren wir unsere Projektstruktur. Als Basis-Url wird der Ordner definiert, in dem sich unsere fremden Frameworks, wie jQuery oder UnderscoreJS befinden. Dies hat den Vorteil, dass wir bei Abhängigkeiten nur den Namen der Skripte in libs, z.B. jquery (für jquery.js), angeben müssen, um auf diese Ressourcen zugreifen zu können.
Desweiteren werden Pfade angelegt und zwar relativ zur Basis-Url, so dass wir für Modul-Abhängigkeiten auf diese Definitionen zurückgreifen können. So kann man beispielsweise auf das Modul util.js im Ordner mod mittels ['mod/utils'] in der Modul-Definition verweisen.
Ausgeklammert wurde die shim-Deklaration, weil die jquery-Plugins jquery.alpha und jquery.beta hier nicht benötigt werden. Sie dienen nur als Vorlage für zukünftige Erweiterungen. Über Deklarationen in diesem Bereich können Abhängigkeiten von Ressourcen definiert werden, so dass bei Laden eine festgelegte Reihenfolge eingehalten wird.
index.html
Die index.html-Datei können wir jetzt wie folgt vereinfachen:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="format-detection" content="telephone=no" /> <meta name="msapplication-tap-highlight" content="no" /> <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 --> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" /> <link rel="stylesheet" type="text/css" href="css/index.css" /> <title>Hello World</title> </head> <body> <div class="app"> <h1>Apache Cordova</h1> <canvas width="27" height="27" id="spinner"></canvas> <div id="deviceready" class="blink"> <p class="event listening">Connecting to Device</p> <p class="event received">Device is Ready</p> <p class="event status"></p> <p class="takephoto"><button>Take a photo</button></p> </div> </div> <script type="text/javascript" src="cordova.js"></script> <script type="text/javascript" src="js/libs/require.js"></script> <script> require(['js/config'], function() { // Load the main app module to start the app requirejs(["app/main"], function(main) { // require(['jquery', 'mod/util', 'mod/debug', 'app/spin', 'app/cam']); main.initialize(); }); }); </script> </body> </html>
Man erkennt, dass die meisten Module nicht mehr in index.html definiert werden. Dort wird nur noch das require.js- und das cordova.js-Framework definiert.
Anpassung der Module
In Folgenden wird nicht mehr der komplette Source-Code veröffentlicht, sondern nur noch Modul-Kopf und -Ende, um das Prinzip stärker hervorzuheben. Grundlage sind sämtliche Module, wie sie im vorherigen Kapitel beschrieben wurden.
Modul app/main.js
define(["mod/util", "mod/debug", "app/spin", "app/cam", "jquery"/*, "require" /*, "jquery.alpha", "jquery.beta"*/], function(util, debug, spin, cam, $) { // ... source code ... }); console.log('main.js');
Modul app/spin.js
define(function() { // ... source code ... }); console.log('spin.js');
Modul app/cam.js
define(["jquery"], function($) { // ... source code ... }); console.log('cam.js');
Modul mod/util.js
define(function() { // ... source code ... }); console.log('util.js');
Modul mod/debug.js
define(["./util"], function(util) { // ... source code ... }); console.log('debug.js');
Ergebnis ist das gleiche Verhalten der App, wie im vorhergehenden Artikel beschrieben.
Fazit
Der Einsatz von require.js erfordert nur wenig Entwicklungsaufwand, wenn die Module schon nach dem Prinzip des Modul-Pattern vorliegen.
Es ist eine config.js-Datei anzulegen. Dort werden die Pfade zu den Ressourcen festgelegt.
In den Modulen selbst werden die Abhängigkeiten definiert, die request.js dann verwendet, um die Module dynamisch unter Berücksichtigung der Abhängigkeiten zu laden. Andere Mechanismen sind möglich, werden aber hier nicht diskutiert.
Insgesamt steht damit ein leistungsfähiges Architekturprinzip als Vorlage zur Verfügung, um Apps in weiteren Projekten zu entwickeln.