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.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.