Tuesday, April 9, 2013

Custom Dojo Builds Using Gradle

The Dojo Toolkit provides a build utility that will minify packages of javascript and css source files. While this powerful tool works very well, I have struggled with finding the right set of best practices for more complex projects that depend on other third party modules.

My primary build tool is Gradle and in the past I tried treating third party javascript sources as Maven artifacts. The build would download and unpack the artifacts from my local repository, and then proceed to perform the build. The unpacking of the artifacts involved a lot of disk I/O and was incredibly inefficient. The unpacked artifacts ended up in the build directory and every time, clean was run the source files were removed. This wasn't working and it became time to reevaluate.

The Source Repository

The solution I came up with was to externalize the third party javascript source from the project. The source repository is a place on the file system outside of the project that houses javascript source code. By keeping a centralized source repository, the build no longer needs to retrieve and unpack third party javascript sources. So instead of manually installing the sources into a local maven repository, the sources need to be installed into the source repository. And there are future plans to automate the retrieval of sources and installation into the source repository. An example of a source repository might look as follows:

C:\JS_SOURCE\
    dgrid-0.3.6
    dojo-release-1.7.3-src
    dojo-release-1.8.3-src
    put-selector-0.3.2
    xstyle-0.0.5
The Dojo Gradle Plugin

I have multiple projects and as I refactored, it became clear that had repeated my self and was doing so again. It was time to write a Gradle plugin to support what I was doing. The plugin can use Java or Node to perform the custom Dojo Toolkit build.

The plugin can be loaded right from the Maven central repository:

buildscript {
    repositories {
        mavenCentral()
    }
        
    dependencies {
        classpath 'com.evinceframework:evf-build-gradle:0.1.0'
    }
}

When using the plugin, a build profile (i.e. build.profile.js) is not required. The profile will be generated from what is specified in the Gradle build script. The build script will look similar to:


version = '0.1.0.BUILD-SNAPSHOT'

apply plugin: 'dojo'

dojo {
    dojoVersion='1.8.3'
    sourceRepository=javascriptRepo
    buildWithNode=project.hasProperty('useNode').toBoolean() 
        && project.useNode.toBoolean()

    profile.configure {
        // packages - dojo, dijit added automatically.
        pkg name: 'dojox'
        pkg name: 'evf-example'

        // layers
        layer name: 'evf-example/app', 
          includes: ['evf-example/CustomWidget']
    }
}

With this build script, a build can be executed with the following command

> gradle build -PjavascriptRepo=C:/JS_SOURCE

When a build executes, it will first install the source code from the project into the source repository.

C:\JS_SOURCE\my-webclient-0.1.0.BUILD-SNAPSHOT

The output of the build is put into the project's build directory but can be configured to be written elsewhere.

The source of the plugin is located https://github.com/cswing/evinceframework-build.

A working example project at https://github.com/cswing/evinceframework-build/tree/master/example.

Documentation can be found in the wiki.

Future of the plugin
  • Add the ability to install a specific version of the Dojo Toolkit SDK into the source repository.
  • Add the ability to install a maven artifact into the source repository.
  • Add the ability to install source from GitHub projects into the source repository.
  • Add the ability to install source from Google Code into the source repository.
  • Add the ability to remove a project from the source repository.
  • Add the ability for a project to define it's dependencies, and automatically install them into the source repository if not already installed.
  • Package the source as an artifact that can be published using the Maven plugin.

Monday, April 8, 2013

Base Mixin For Creating Widgets

The use of AMD became the standard way of writing custom widgets in v1.7 of the Dojo Toolkit. With the refactoring to AMD modules, a lot of utility functions that were located off of the root dojo module now need to be included in the require statement. As a result, I found myself writing the following code for each custom widgets I was creating:
define([
    "dojo/_base/config",
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/dom-attr",
    "dojo/dom-class",
    "dojo/dom-construct",
    "dojo/dom-style",
    "dojo/on",
    "dojo/Evented",
    "dijit/_Widget"
], function(config, declare, lang, domAttr, domClass, 
      domConstruct, domStyle, on, Evented, Widget){
    /* widget definition */
});
In an attempt to avoid always having to require these common modules, I have created a ComplexWidget mixin that will require these common modules and make them available as properties on the widget. This widget is released under the Apache License v2.0 and the latest code can be found here.
define([
 "dojo/_base/config",
 "dojo/_base/declare",
 "dojo/_base/kernel",
 "dojo/_base/lang",
 "dojo/dom-attr",
 "dojo/dom-class",
 "dojo/dom-construct",
 "dojo/dom-style",
 "dojo/on",
 "dojo/Evented",
 "dijit/_Widget"
], function(config, declare, kernel, lang, domAttr, domClass, 
      domConstruct, domStyle, on, Evented, Widget){
 
    return declare("evf/ComplexWidget", [Widget, Evented], {
  
        kernel: kernel,
  
        dojoConfig: config,
  
        lang: lang,
  
        domAttr: domAttr, 
  
        domClass: domClass, 
  
        domConstruct: domConstruct, 
  
        domStyle: domStyle,
  
        dojoOn: on,
  
        listen: function(target, type, listener, dontFix) {   
            var hndl = 
              on(target, type, this.hitch(listener), dontFix);
     
            this.own(hndl);
            return hndl;
        }, 
  
        hitch: function(fn) {
            return lang.hitch(this, fn);
        }
    });
});
Writing a custom widget looks like the following:
define([
    "dojo/_base/declare",
    "evf/CustomWidget"
], function(declare, ComplexWidget){

    return declare("myApp/MyWidget", [ComplexWidget], {        

        postCreate: function(){
            this.inherited(arguments);
            this.domClass.add(this.domNode, 'MyWidgetCssClass');
        }
    });
});

The result is easy access to commonly used modules without having to write a lot of boilerplate code.