Monday, May 30, 2016

Lazy inter-module communication with require() and webpack

Motivation

CommonJS' modules can be loaded on demand with require.ensure(...). Example:
require.ensure(["module-a"], function(require) {
    var a = require("module-a");
    // module-a can be used now ...
});
The well-known module bundler Webpack supports this syntax for loading modules on demand. Such lazy loaded module(s) define a split point. Split points split the codebase into "chunks" (separate JavaScript files) which are loaded on demand. This feature allows to keep the initial download small and only download the code when it's requested.

Webpack performs a static analysis of the code at the project build time. Dynamic information is only available at runtime and not available at build time. You have to define your modules as constants. This leads to the problem. The following code will not work:
var modA = "module-a";
require.ensure([modA], function(require) {
    var a = require("module-a");
    // module-a can be used now ...
});
Webpack says something like: Error: Module not found. A dependency to an entry point is not allowed.

Theoretically you can use require.context, but this needs some parts of module's path as constants too. Now, imagine you develop a modular UI library and would like to provide some callbacks for certain events (event handlers). Users of your library use it as a third-party library. They want to register their custom callback functions from their own modules. And they want to be called lazy, on demand, when an event occurs. You have many users and your library doesn't know about custom modules of course. That means, you can not hard-code module's path and function's name a priori. What to do?

Solution

This POC on GitHub demonstrates how to use truly dynamic require with webpack. It simulates third-party and own modules. The project structure:
index.html          // some HTML template from the third-party UI library 
external-modules    // third-party library with CommonJS modules
own-modules         // some library with CommonJS modules which uses the third-party library
common-modules      // simple utility modules
The modules in external-modules don't know about own-modules. Modules in the own-modules import modules from the external-modules. The most important webpack loader we use in this POC is bundle loader for webpack. This loader creates a special function with required module which can be executed lazily. The required module gets loaded when the function is executed.

The HTML template in the POC, backed by the third-party (external) modules, look like as follows (only short snippet):
<button data-init="button" data-onclick='["ownModules.button", "alertSync"]'>
    Try sync. call (ok)
</button>
<p></p>
<button data-init="button" data-onclick='["ownModules.button", "alertAsyncValid"]'>
    Try async. call (ok)
</button>
<p></p>
<button data-init="button" data-onclick='["ownModules.button", "alertAsyncInvalid"]'>
    Try async. call (error)
</button>
The full code can be found in index.xhtml. The callback functions are defined as values of data-* attributes. Example: data-onclick='["ownModules.button", "alertSync"]'. Here, the function alertSync from the namespace ownModules.button wants to be called when the button is clicked. The namespace is a simple JavaScript object defined in the global window scope.

The module's functions in the folder own-modules returns Promises. Promises allow asynchronous callbacks. In the POC, the NPM module promise light is used. If you have jQuery in your web app, you can use jQuery Deferred Object as well because it provides the same functionality. The sample implementation of the button.js looks like as
var Promise = require('promise-light');

module.exports = {
    alertSync: function (element) {
        document.getElementById('output').innerHTML = 'Clicked button. Sync. call is OK';
        return Promise.resolve('OK');
    },
    
    alertAsyncValid: function (element) {
        return new Promise(function setup(resolve, reject) {
            setTimeout(function () {
                document.getElementById('output').innerHTML = 'Clicked button. Async. call is OK';
                resolve('OK');
            }, 1000);
        });
    },
    
    alertAsyncInvalid: function (element) {
        return new Promise(function setup(resolve, reject) {
            setTimeout(function () {
                document.getElementById('output').innerHTML = 'Clicked button. Async. call has ERROR';
                reject('ERROR');
            }, 1000);
        });
    }
};
The external modules should be bootstrapped by the own modules. The bootstrapping occurs in the file entry.js which acts as an entry point in the webpack configuration.
var objectifier = require("./../common-modules/objectifier");
var bootstrap = require("./../external-modules/bootstrap");

document.addEventListener("DOMContentLoaded", function (event) {    
    // initialize external modules
    bootstrap.bootstrap();
    
    // lazy load own modules (s. https://github.com/webpack/bundle-loader)
    var lazyLoadedButton = require("bundle?lazy!./button");
    var lazyLoadedCheckbox = require("bundle?lazy!./checkbox");
    
    // and put them under namespaces ownModules.button and ownModules.checkbox resp. 
    objectifier.set('ownModules.button', lazyLoadedButton);
    objectifier.set('ownModules.checkbox', lazyLoadedCheckbox);
});
As you can see, the bundle loader is applied to the own modules. The returned functions are saved under arbitrary namespaces in order to be executed later in external modules. Note: the objectifier is just a CommonJS module porting of the beautiful getter and setter implementation for nested objects. The button.js in the external-modules registers an onlick event handler for all HTML elements having the data-init="button" attribute.
var lazyloader = require("./lazyloader");

module.exports = {
    init: function () {
        var elements = document.querySelectorAll('[data-init="button"]');
        for (var i = 0; i < elements.length; i++) {
            (function () {
                var element = elements[i];

                // onlick event handler
                var onclick = function () {
                    // so something module specific ...

                    // execute function defined via data-onclick attribute if it's defined,
                    // e.g. data-onclick='["ownModules.button", "alertSync"]'
                    lazyloader.execute(element, 'data-onclick',
                        function resolved(value) {
                            alert(value);
                        }, function rejected(error) {
                            alert(error);
                        });
                };

                element.addEventListener("click", onclick);
            })();
        }

        return elements;
    },

    doSomething: function () {
        // ...
    }
};
The core logic for the event handling is encapsulated in the lazyloader.js.
/**
 Loads modules specified via data-* attributes and executes the specified function.
 HTML-Example:

 <button data-init="button" data-onclick='["ownModules.button", "alertSync"]'>
    ...
 </button>

 The function ownModules.button.alertSync(button) will be executed.
 */

var objectifier = require("./../common-modules/objectifier");

module.exports = {
    /**
     * Executes the specified synchronous or asynchronous function from the specified module
     * and invokes resolved or rejected callbacks respectively.
     * The function should return a promise.
     * 
     * @param element HTML element where data-* is defined
     * @param data value of data-* as string
     * @param resolvedCallback callback which gets executed when the returned promise is fullfilled
     * @param rejectedCallback callback which gets executed when the returned promise is rejected
     */
    execute: function (element, data, resolvedCallback, rejectedCallback) {
        var strData = element.getAttribute(data);
        if (strData) {
            var objData = JSON.parse(strData);
            var module = objData[0];
            var func = objData[1];

            if (module && objectifier.exists(module) && func) {
                // module and function exist ==> load the module
                objectifier.get(module)(function (module) {
                    // execute the specified function from the module.
                    // return value is a promise (see https://www.npmjs.com/package/promise-light)
                    module[func](element).then(
                        function resolved(value) {
                            resolvedCallback(value);
                        },
                        function rejected(error) {
                            rejectedCallback(error);
                        });
                });
            }
        }
    }
};
It executes the specified function from the specified module. The caller receives a promise. If the promise gets resolved, the first function in .then(...) is executed. If the promise gets rejected, the second function in .then(...) is executed. The synchronous or asynchronous function from the own modules can control whether it resolves or rejects the promise. The caller can decide then what it should do in both cases. For instance, assume you got an accordion widget which belongs to a third-party library. One interesting case could be the validation of the content of an open accordion tab when the tab's header is clicked by the user. A custom function could validate the tab content on such click. In case of a successful validation, the clicked tab can be closed. Otherwise, the tab should stay open.

Screens

The initial load doesn't fetch chunks from the own modules.


Click on a button triggers the load of the first chunk.


Click on a checkbox triggers the load of the second chunk.


Sunday, May 15, 2016

NPM module Browser-Sync in Java / Web projects

Browser-Sync is a handy Node.js based NPM module which can be used for a faster web development. Browser-Sync synchronizes file changes and interactions across many devices. The most important feature is the live reloading. We can use the Browser-Sync in Java / Web projects too. Cagatay Civici created a great video tutorial how to use this module with the PrimeFaces showcase. The PrimeFaces showcase has a built-in Jetty server which looks to the source folder src/main/webapp as the web context root. After the Browser-Sync installation via the Node.js package manager NPM
 
npm install -g browser-sync
 
we have to start the Jetty server for the PrimeFaces showcase at http://localhost:8080/showcase. Afther that we can use this URL as proxy for a built-in server included in the Browser-Sync. The Browser-Sync should listen to changes under src/main/webapp
 
browser-sync start --proxy "http://localhost:8080/showcase" --files "src/main/webapp/**/*"
 
As result, a default browser will be started at http://localhost:3000/showcase with the PrimeFaces showcase. The port 3000 is the default port for the Browser-Sync.

This approach works well until you have made changes in Java files. Java files are not web resources under src/main/webapp. In Maven projects they located under src/main/java. That means, changes in Java files will not be recognized. The solution is exploded WAR. An exploded WAR is a directory where the web application gets deployed from. Every application server can deploy an exploded WAR. For Maven projects, this directory is normally target/webapp. The Maven WAR plugin has the goal war:exploded too. If you have an IDE, you can configure your web application as an exploded WAR. I have blogged about Hot Deployment with IntelliJ IDEA a couple of years ago. In IntelliJ, you can automatically copy changed files (CSS, JS, HTML resources and compiled Java files) to the directory for the exploded WAR.


Now, if you refresh the browser manually, you will see the changes in Java classes too. But we want to do this better. We want to use the highly praised live reloading! To achieve this goal, set files to be watched as follows
 
browser-sync start --proxy "http://localhost:8080/showcase" --files "target/classes/**/*.class, target/webapp/**/*"
 
The output looks like
[BS] Proxying: http://localhost:8080
[BS] Access URLs:
 ---------------------------------------------------------------------
       Local: http://localhost:3000/showcase
    External: http://192.168.178.27:3000/showcase
 ---------------------------------------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.178.27:3001
 ---------------------------------------------------------------------
Now, I can do any changes in all important files and see something like in the console
 
[BS] Watching files...
[BS] File changed: target\webapp\META-INF\MANIFEST.MF
[BS] File changed: target\webapp\WEB-INF\classes\some\showcase\bean\SomeBean.class
[BS] File changed: target\webapp\views\someView.xhtml
[BS] File changed: target\webapp\META-INF\MANIFEST.MF
The browser page gets updated by the Browser-Sync automatically (which uses WebSockets by the way). If you have trouble with your IDE, you can use Gulp to rescue! Here my idea for a gulpfile.js (Gulp 4).
var browsersync = require('browser-sync').create();

// init Browser-Sync
gulp.task('browser-sync', function() {
    browsersync.init({
        proxy: "http://localhost:8080/showcase"
    });
});

// compile changed Java files by Maven "mvn compile"
// compiled classes will be transfered to target/classes automatically
gulp.task('java', function () {
    // use 'spawn' to execute command using Node.js
    var spawn = require('child_process').spawn;

    // set the working directory to project root where gulpfile.js exists
    process.chdir(__dirname);

    // run "mvn compile"
    var child = spawn('mvn', ['compile']);

    // print output
    child.stdout.on('data', function(data) {
        if (data) {
            console.log(data.toString());
        }
    });
});

// copy changes from src/main/webapp to target/webapp 
gulp.task('webapp', function () {
    return gulp.src('src/main/webapp/**/*', {since: gulp.lastRun('webapp')})
        .pipe(gulp.dest('target/webapp'));
});

// watch files for changes
gulp.task('watch', function () {
    gulp.watch('src/main/java/**/*.java', gulp.series('java'));
    gulp.watch('src/main/webapp/**/*', gulp.series('webapp'));
    gulp.watch(['target/classes/**/*.class', 'target/webapp/**/*'], browsersync.reload);
});

// default task
gulp.task('default', gulp.series('browser-sync', 'watch'));
This file should be placed in the project root folder. Now, you are able to execute the command (Gulp should be installed of course)
 
gulp
 
and enjoy the live reloading! Please consider, the Gulp java task. Maven only compiles changed files. It works very fast! Without changes there are nothing to be compiled - the output of the mvn compile looks like:
 
[INFO] Nothing to compile - all classes are up to date
 
If we make a change in one Java file, the output looks like:
 
[INFO] Compiling 1 source file to <path>\showcase\target\classes
 
Note: The server should run in reloadable mode. E.g. Tomcat has a reloadable option in context.xml. Set it to true to force monitoring classes in /WEB-INF/classes/ and /WEB-INF/lib for changes and automatically reload the web application if a change is detected. JBoss has this feature automatically if you start it in the debug mode.

I can also imagine some complex Gulp tasks, such as compiling Java classes in dependent JAR files, build JARs and copy them to the WEB-INF/lib folder of the exploded WAR.