Monday, January 26, 2015

Vert.x with gulp.js

Vert.x is often put forward as a polyglot alternative to node.js that runs on the JVM. A read through the vert.x javascript docs indicates that javascript is a first-class language in vert.x, and both node.js and vert.x use an event-driven, non-blocking I/O programming model. But to what degree will a node programmer feel at home in writing a vert.x application?

In this blog post I will look at using gulp, a node.js build tool, to build a vert.x 2 module.

Platform Installation

Before proceeding, be sure to have both the vert.x and node.js platforms installed. Vert.x will provide the run-time for our application, and node.js will provide us with the build environment for our project. Refer to the vert.x install docs and the node and npm install docs for further details.

Project layout

The gulp.js build tool has us apply transformations to streams of our source code, and as such doesn’t dictate how we structure our source code within our project. The structure I chose is as follows:

.
    ├── gulpfile.js
    ├── node_modules
    │   └── ...
    ├── package.json
    ├── src
    │   ├── app.js
    │   ├── mod.json
    │   └── ...
    ├── tasks
    │   ├── vertx.gulp.js
    │   └── zip.gulp.js
    └── vertx_modules
        └── ...

The package.json file manages the npm dependencies for our gulp.js build, where those dependencies are stored in the node_modules folder. The gulpfile.js file is our gulp build file, and incorporates individual build tasks defined in the tasks folder. The src folder contains our the source for our vert.x module, and finally the vertx_modules folder contains the vert.x modules on which our application depends.

The gulp build

The gulp build file (gulpfile.js) is pretty straightforward; the top-level gulp file is used to configure the project, with individual tasks defined in separate files. These files are then included using the require statement.

gulpfile.js
process.env.VERTX_MODS = 'vertx_modules';
    
    var gulp = require('gulp');
    
    var opts = {
      module: {
        group: 'ca.bleathem',
        artifact: 'demo',
        version: '0.0.1'
      },
      paths: {
        src: 'src/**/*',
        dist: 'dist'
      }
    };
    
    opts.module.name = opts.module.group + '~'
                     + opts.module.artifact + '~'
                     + opts.module.version + '.zip';
    opts.paths.cp = 'src';
    
    require('./tasks/vertx.gulp.js')(gulp, opts);
    require('./tasks/zip.gulp.js')(gulp, opts);
    
    gulp.task('default', ['vertx']);

Notice the VERTX_MODS environment variable is set in the gulpfile. Using the build file to programtically set environment variable depending on the deployment target (production/development) can be a powerful technique. Here we set VERTX_MODS to store vertx modules in a folder paralleling the node.js modules.

The *.gulp.js build files containing the individual gulp task definitions are stored in the gulp sub folder.

...
    ├── tasks
    │   ├── vertx.gulp.js
    │   └── zip.gulp.js
    ...

Let’s explore these vertx and zip tasks further.

The vert.x gulp task

The gulp plugin guidelines recommend not creating a plugin for a task that can "be done easily with an existing node module". To this end, we’ll start by seeing how far we can by leveraging the abilities of node to spawn a child process. Below is a gulp task that runs the vert.x module that is our sample application:

vertx.gulp.js
var spawn = require('child_process').spawn
      , gutil = require('gulp-util');
    
    module.exports = function(gulp, opts) {
      gulp.task('vertx', [], function(done) {
        var child = spawn('vertx', ['runmod', opts.module.name, '-cp', opts.paths.cp ], {cwd: process.cwd()}),
            stdout = '',
            stderr = '';
    
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', function (data) {
            stdout += data;
            gutil.log(data.slice(0, data.length - 1));
        });
    
        child.stderr.setEncoding('utf8');
        child.stderr.on('data', function (data) {
            stderr += data;
            gutil.log(gutil.colors.red(data.slice(0, data.length - 1)));
            gutil.beep();
        });
    
        child.on('close', function(code) {
            gutil.log('Done with exit code', code);
            done();
        });
      });
    };

The bulk of the above listing deals with re-directing and formatting the output of the vert.x child process. The invocation of the spawn function is the interesting part, and is where we pass our arguments to the vert.x process. In our case we want to run the module that is our sample project, and we set the vert.x classpath to our source folder to allow for on-the-fly code changes.

Invoking the build via the command gulp vertx will start vert.x, running the module in our project.

The zip gulp task

The distribution format for vert.x is a wonderfully simple zip format. This makes it easy to use a the gulp-zip plugin to zip up the file and create a bundle for our module.

vertx.gulp.js
var zip = require('gulp-zip');
    
    module.exports = function(gulp, opts) {
      return gulp.task('zip', function() {
        return gulp.src(opts.paths.src)
          .pipe(zip(opts.module.name))
          .pipe(gulp.dest(opts.paths.dist));
      });
    };

The above source transformation is a trivial one. Those familiar with gulp will recognize we could easily add additional stream transformations here, eg. compiling coffescript, minifying client code, compiling sass etc.

On to vert.x 3

The above build works well for vert.x 2. However vert.x 3 is around the corner and introduces many changes. The changes relevant to our gulp build include:

  1. Vert.x 3 will do away with modules and flatten the classpath across verticals. This will directly affect how we structure our source code and invoke vert.x from our gulpfile.

  2. Vert.x 3 will also resolve packaged verticles from npm, which will align nicely with our npm-based build approach.

Stay tuned for a new post addressing a gulp.js build targeting vert.x 3.