The Dojo Toolkit represents one of the most powerful and feature-rich JavaScript libraries to-date. Apart from its core functionality such as DOM manipulation (i.e. Dojo namespace) it provides developers with a vast range of widgets (Dijits, i.e. Dojo Widgets), lots of incubator code (DojoX) and numerous Utils classes. (More info can be found here: E-Learning Standards – Critical and Practical Perspectives.)
When writing more complex client side applications you will soon realize that the overall loading time increases by a factor that significantly reduces the usability and responsiveness. Thus, it would be nice to have a build script that compresses relevant JavaScript source files into optimized code that you then can include in your frontend HTML files. Additionally, you will most definitely want to be able to create different releases of your code in a simple yet consistent way. That’s were Dojo’s build system comes in handy!
Dojo Build Layer System
Before explaining the bits and pieces needed for this build script you need to understand Dojo’s build layer system. Each layer represents a list of files that should be put together to produce a compressed and optimized version, much like a C include header file. Below you find an examplary layer file for a Login module:
dojo.provide("layerLogin.js"); dojo.require("dojo.i18n"); dojo.require("custom.Login");
Line 1 declares the layer file and must match the actual file name. Following the first line are all files included in this layer. For instance, dojo.i18n represents Dojo’s internationalization (i18n) functionality and is required by this layer. Furthermore, custom.Login on Line 3 represents a file named Login.js that resides in the custom namespace and contains the Login module’s core code:
/js/custom/Login.js
The source of Login.js:
dojo.require('dijit.form.Form'); dojo.require('dijit.form.Button'); dojo.require('dijit.form.TextBox'); dojo.provide('custom.Login'); dojo.declare('custom.Login', null, { /** * Default constructor */ constructor: function() { this.init(); }, /** * Initializes Login module. */ init : function() { dijit.byId('loginUser').focus(); } });
As you can see Login represents a Dojo “class”. Although you can use plain JavaScript code in any of the included layer files Dojo classes provide you with the power of encapsulating module specific code (as you are used to when writing object oriented code). You can find more information on Dojo’s classes here: Classy JavaScript with dojo.declare
Build Layer Profile
As you might have guessed correctly we could have multiple layer files in our Dojo based application. That’s why it is good to think of layer files as modules. Consequently, the next step would be to combine all modules into a configuration profile that Dojo then can use to compress into one optimized JavaScript file. Let’s have a look at an example profile file:
dependencies = { layers: [ { // one of the stock layers. It builds a "roll up" for // dijit.dijit which includes most of the infrastructure needed to // build widgets in a single file. We explicitly ignore the string // stuff via the previous exclude layer. name: "../dijit/dijit.js", // what the module's name will be, i.e., what gets generated // for dojo.provide(<name here>); resourceName: "dijit.dijit", // modules *not* to include code for layerDependencies: ["string.discard"], // modules to use as the "source" for this layer dependencies: ["dijit.dijit"] }, { name: "../custom/layerLogin.js", dependencies: ["custom.layerLogin"], copyrightFile: "../../../../COPYRIGHT" }, { name: "../custom/layerMain.js", dependencies: ["custom.layerMain"], copyrightFile: "../../../../COPYRIGHT" }], prefixes: [ ["dijit", "../dijit"], ["dojox", "../dojox"], // include COPYRIGHT ["custom", "../custom", "../../../../COPYRIGHT"] ] }
The dependencies variable at the beginning is a JSON object holding all required layers of this profile. Additionally, it defines the core files (stock layers) needed to run Dojo afterwards.
The Build Script
Now that you have an overview of the structure of a Dojo build profile let’s have a look at the actual build script. Below you find a bash script that incorporates all required steps for generating optimized JavaScript source code based on a custom build profile using Dojo’s build layer:
#!/bin/sh # # Script for deploying the "release" version of the currently set JS layers via the profile file. # It will create (or overwrite) the release folder in the JS_LIB/custom/ directory, # i.e. JS_LIB/custom/release/RELEASE_VERSION # # @author Matthias Kerstner <matthias@kerstner.at> # #profile file should reside inside current directory PROFILE_FILE=custom.profile.js CWD="$(cygpath -aw $(pwd))" #UNIX: $(pwd) #(absolute) path to PHP, or simply 'php' if it is in the PATH PHP_PATH="/cygdrive/c/xampp/php/php.exe" #UNIX: "/opt/lampp/bin/php" #release number should be read from config/base.php PHP_REQUIRE="require('$(cygpath -aw $(pwd)/../src/config/base.php)')" #UNIX: "require('$(pwd)/../src/config/base.php');" #get release version from config file RELEASE_VERSION=`$PHP_PATH -r "$PHP_REQUIRE; echo MY_VERSION;"` #determine release path RELEASE_PATH="$(cygpath -aw $(pwd)/../src/web/js/release/)" #UNIX: $(pwd)/../src/web/js/release/ # remove any previous releases if [ -d $RELEASE_PATH ]; then echo "removing existing releases..." rm -r $RELEASE_PATH fi # create release directory again mkdir $RELEASE_PATH # path to dojo buildscript DOJO_BUILDSCRIPT_PATH=$(pwd)/../src/web/js/util/buildscripts/ echo switching to buildscript path $DOJO_BUILDSCRIPT_PATH #change to buildscripts folder as required by dojo's build.sh cd $DOJO_BUILDSCRIPT_PATH #call dojo's build script with profile specified above #UNIX: build.sh #WINDOWS: build.bat exec ./build.bat profileFile=../../profile/"$PROFILE_FILE" \ action=release cssOptimize=comments optimize=shrinkSafe releaseName=$RELEASE_VERSION #change to lib/js folder cd $(cygpath -aw $(pwd)/../../) #UNIX: $(pwd)/../../
This script basically does the following:
- Defines which profile file to use (line 11)
- Determines the current working directory (line 13)
- Sets path to PHP executable to read release version from base.php (line 17, 21 and 25)
- Sets release path based on release version (line 28)
- Removes any previous releases and creates release folder (line 32)
- Sets path to the build script (line 41)
- Executes build script from within its folder (line 51)
- Changes back to lib/js folder (line 55)
In order for this script to run your project needs to have a certain folder structure. I have created a reference setup which you can download from GitHub: Dojo Build Script.
Note: The script should be executed from within the scripts folder. Don’t panic if the scripts runs for a while, there are plenty of files that need to be processed during the building process. After all, you get a highly compressed layer file afterwards, right?
Feel free to customize the script to your needs 🙂