Oh My js! Server side javascript for java developers?


Javascript tooling set was enriched a lot these last years: npm, bower, gulp, grunt, … but most of them work on nodejs and need a certain amount of knowledge before being able to get anything from it.

For a Java developer it can be complicated and tempting to do a plain JSP instead of embrassing javascript.

Making javascript development easy can only be done in the case of a framework with a lot of defaults cause by nature you need to develop in an exploded fashion (by module) fr the maintenance and aggregate them at the end for performances reasons (HTTP 2.x will maybe make it smoother but will likely not replace it completely).

If your project is before all a frontend project you need to go on js side but if your project is simple and more centered on the data or the server side we can probably find a compromise.

This was the origin of “Oh My js!” which is a small library I created to integrate the pipeline I often use for frontend development with Maven and the Java stack I use (TomEE if you doubt of it ;)).

First of all what needs do I want to cover and which ones I’ll ignore:

  • dependency management: ignored. I only want to handle runtime js dependencies and they are not that numerous in general so it can be done manually or worse case using webjars and a small groovy script for the optimization – will not be part of this post but can be another one if needed
  • build: yes and no. There are multiple parts of a build in javascript: the “big final aggregation” which aims to put all resources we can in a single file to make client loading faster and (optional) each module transpilation/rendering/compilation/… This is this last part we will target
  • test: java has a good tooling to do it but see next note for a more nuanced answer
  • packaging: not sure javascript has a real packaging model yet but java has so all is fine and secured
  • deployment: I build a war so maven/gradle are perfect

Of course I listed far more that what this post will cover but it was to show that the “blocking” part for a java developer is finally small enough to get some work to fill the gap.

Side note: frontend-maven-plugin is a great tool bringing to maven nodejs tooling (npm, bower, gulp, karma…). This however still needs to know these tools and just provide a “main-build” friendly solution so the initial cost can be important but it can worth it if you will need a lot of javascript.

For now Oh My js! library will provide two nice features:

  • es6 syntax transpilation: it means you can use es6 syntax (example after) and transpile/convert it to es5 (javascript) avoiding issues with browser not supporting yet es6 synatx
  • jade templating/rendering: writing template with jade avoids several html errors and makes it more readable in general

es6: the future of javascript

You can find a nice description of es6 features on babeljs website but here is an example:

import http from "util/http";
import Constants from "Constants";

export default {
    findById(id, onSuccess, onError) {
        http.get('api/user/' + id)
            .then(response => onSuccess(response.data), onError);
    }
};

This is just a small snippet and doesn’t show all features but you can already see the benefit of:

  • imports: will be converted to amd/umd/commonjs/systemjs imports after transpilation but makes the code more structured and less “array of string”
  • exports: es6 supports real modules allowing some encapsulation without any weird syntax (IIFE for instance)
  • arrow syntax: function can use the “=>” syntax making it closer to lambdas and generally lighter than the function () {}…
  • not really visible on this snippet but es6 handles the “this” as expected so no more need of creating a variable “self” to keep track of this in callbacks. This could even alone justify the migration!
  • Also not part of this snippet but es6 supports string literals meaning you can define a multi-line string which is very neat for a simple template (of a component for instance)
  • And much more as you can see on babel site

Jade: no more closing tag

Here too I will not replace jade website but a small sample to show you how a template looks like:

div.align-center.container
    form(id="loginForm", @submit.prevent="onSubmit")
        h2.text-center Login
        span(id="loginErrorMessages", style="color: red;") {{error}}
        div.form-group
            label(for="login") Username
            input(id="login", type="text", placeholder="Enter your username...", required, autofocus, v-model="formData.username").form-control
        div.form-group
            label(for="password") Password
            input(id="password", type="password", placeholder="Enter your password...", required, v-model="formData.password").form-control
        div.checkbox
            label
                input(type="checkbox", v-model="formData.rememberMe")
                span Remember Me
        button(id="loginSubmit", type="submit").btn.btn-lg.btn-primary.btn-block OK

This template is using bootstrap, vuejs (v-model, @submit) but the important part is the structure represents the rendered structure physically and you don’t need to close tags so it avoids a lot of errors and the <!– closing –> </div> comments polluting the html dom when used as main source.

So what is “Oh My js!”

Oh My js! provides an integration with some javascript utilities avoiding to dig into javascript stack (Gulp, npm, …​).

It generally provides 3 flavors of each integration:

  • service: simple java class with a String transform(String) method
  • runner: a main to let you "build" (transpile/render) your resources before optimizing the runtime. The options are passed using -- syntax. Ex: --option1 valueOfOption1. All runners share these options:
Name Description
source input base folder
target output base folder
include Pattern applied on the name of the file (not the path). If matching the file is transformed.
exclude Pattern applied on the name of the file (not the path). If matching the file is not transformed.
  • a servlet filter: allowing to have hot reloading during development with most of Servlet servers like Tomcat/TomEE/…​ Options are passed as filter init parameters or system properties when prefixed with the filter prefix (check dedicated doc). All filters share these parameters:
Name Description
active is the filter used (when associated with programmatic registration allows to deactivate it)
dev is in dev mode (ie files are checked for updates)
sources where are sources (default to ./webapp) for dev mode
cache where to cache generated file on the disk (default to null, ie no local disk caching)
excludes which request URI are ignored (Pattern)
includes which request URI are not ignored (Pattern)

Note: these filter delegates to get the underlying resource which means in the default case you need the defaultservlet (file resources) but you can use it with JSP generation or any other web technology.

Tip: com.github.rmannibucau.ohmyjs.servlet.DelegateFilterConfig allows to override easily the configuration as shown in samples and is provided with ohmyjs.jar.

BabelJS

Oh My js! integrates with babeljs babeljs to provide es6 (emacs2015) features.

Here are its 3 flavors:

com.github.rmannibucau.ohmyjs.service.BabelJsService

A simple java class wrapping babeljs es6 features:

boolean dev = false; // is the generation in dev mode
String moduleLoader = "amd"; // requirejs friendly
BabelJsService service = new BabelJsService(dev, moduleLoader)

String es6code="...";

System.out.println(service.transform(es6code));

Module value can be one of (see https://babeljs.io/docs/plugins/):

  • amd
  • commonjs
  • systemjs
  • umd

com.github.rmannibucau.ohmyjs.runner.BabelJsRunner

Additional options are:

Name Description
dev see service
module see service

The runner will take all .js and .es6 in the source folder and convert it to a .js in target folder.

com.github.rmannibucau.ohmyjs.servlet.BabelJsServerTranspiler

A servlet Filter handling babeljs integration. Additional configuration is:

Name Description
templates path to a folder where templates are relatively to the source path (default templates)
templateExtension template extension, default html
mapToEs6 should a .js be remapped to a .es6 resource (/foo.js will actually use /foo.es6)
module same as for service, defaul to amd

Programmatic registration example

@WebFilter("/js/app/*")
public class MyBabelJsFilter extends BabelJsServerTranspiler {
    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        // custom config
        final Map<String, String> overrides = new HashMap<>();
        final boolean isDev = "dev".equalsIgnoreCase(System.getProperty("my.environment"));
        overrides.put("dev", Boolean.toString(isDev));
        overrides.put("active", Boolean.toString(isDev));
        overrides.put("templates", "../template");
        overrides.put("templateExtension", "jade");
        overrides.put("sources", ofNullable(filterConfig.getServletContext().getRealPath("")).orElse("src/main/webapp"));
        overrides.put("module", "amd");
        overrides.put("mapToEs6", "true");
        overrides.put("cache", "target/cache");
        overrides.put("includes", ".*\\.js");

        // just delegate then
        super.init(new DelegateFilterConfig(filterConfig, overrides));
    }
}

This servlet will match incoming js request (includes) on /js/app/* and will map them on the corresponding es6 resource. In dev mode the jade templates named as the js file will be checked as well. If your naming convention between your template and js file is more complicated just override getAlternativeSourceFile(path) method to return the template file.

Web.xml registration

<filter>
  <filter-name>babeljs</filter-name>
  <filter-class>com.github.rmannibucau.ohmyjs.servlet.BabelJsServerTranspiler</filter-class>
  <init-param>
    <param-name>dev</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>active</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>templates</param-name>
    <param-value>../template</param-value>
  </init-param>
  <init-param>
    <param-name>templateExtension</param-name>
    <param-value>jade</param-value>
  </init-param>
  <init-param>
    <param-name>sources</param-name>
    <param-value>/opt/base/project/src/main/webapp</param-value>
  </init-param>
  <init-param>
    <param-name>mapToEs6</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>module</param-name>
    <param-value>amd</param-value>
  </init-param>
  <init-param>
    <param-name>cache</param-name>
    <param-value>/opt/base/project/target/cache</param-value>
  </init-param>
  <init-param>
    <param-name>includes</param-name>
    <param-value>.*\.js</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>babeljs</filter-name>
  <url-pattern>/js/app/*</url-pattern>
</filter-mapping>

Jade

Oh My js! integrates with jade.

Here are its 3 flavors:

com.github.rmannibucau.ohmyjs.service.JadeService

A simple java class wrapping jade standalone templating feature (without variables since it is designed to be used with a js framework like Angular 1/2, VueJS…​):

JadeService service = new JadeService()

String jadeTemplate="...";

System.out.println(service.transform(jadeTemplate));

com.github.rmannibucau.ohmyjs.runner.JadeRunner

The runner will take all .jade and .html in the source folder and convert it to a .html in target folder.

com.github.rmannibucau.ohmyjs.servlet.JadeServerRenderer

Specific configuration is:

Name Description
mapToJade should a .html be remapped to a .jade resource (/foo.jade will actually use /foo.html)

Programmatic registration example

@WebFilter("/js/app/template/*")
public class JadeSetup extends JadeServerRenderer {
    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        final Map<String, String> overrides = new HashMap<>();
        final boolean isDev = "dev".equalsIgnoreCase(System.getProperty("environment"));
        overrides.put("dev",  Boolean.toString(isDev));
        overrides.put("active",  Boolean.toString(isDev));
        overrides.put("sources", ofNullable(filterConfig.getServletContext().getRealPath("")).orElse("src/main/webapp"));
        overrides.put("mapToJade", "true");
        overrides.put("cache", "target/cache");
        overrides.put("includes", ".*\\.html");
        super.init(new DelegateFilterConfig(filterConfig, overrides));
    }
}

This servlet will match incoming html request (includes) on /js/app/template/* and will map them on the corresponding jade resource.

Web.xml registration

<filter>
  <filter-name>jade</filter-name>
  <filter-class>com.github.rmannibucau.ohmyjs.servlet.JadeServerRenderer</filter-class>
  <init-param>
    <param-name>dev</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>active</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>sources</param-name>
    <param-value>/opt/base/project/src/main/webapp</param-value>
  </init-param>
  <init-param>
    <param-name>mapToJade</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>cache</param-name>
    <param-value>/opt/base/project/target/cache</param-value>
  </init-param>
  <init-param>
    <param-name>includes</param-name>
    <param-value>.*\.html</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>jade</filter-name>
  <url-pattern>/js/app/template/*</url-pattern>
</filter-mapping>

Build tools

Runner are plain mains so it is easy to integrate them in a build. For instance for Maven you an use:

The project layout is:

.
`- src
   `- main
        `- webapp
              `- js
                  `- app
                      |- *.es6
                       `- template
                             `- *.jade

The build will render jade and es6 files in target/frontend using the same layout (js/app/…​).

<plugin> <!-- render jade and es6 files -->
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.4.0</version>
  <executions>
    <execution>
      <id>render-jade-templates</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>java</goal>
      </goals>
      <configuration>
        <mainClass>com.github.rmannibucau.ohmyjs.runner.JadeRunner</mainClass>
        <arguments>
          <argument>--source</argument>
          <argument>${project.basedir}/src/main/webapp/js/app/template</argument>
          <argument>--target</argument>
          <argument>${project.build.directory}/frontend/js/app/template</argument>
        </arguments>
      </configuration>
    </execution>
    <execution>
      <id>transpile-es6</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>java</goal>
      </goals>
      <configuration>
        <mainClass>com.github.rmannibucau.ohmyjs.runner.BabelJsRunner</mainClass>
        <arguments>
          <argument>--source</argument>
          <argument>${project.basedir}/src/main/webapp/js/app</argument>
          <argument>--target</argument>
          <argument>${project.build.directory}/frontend/js/app</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

Full Pipeline for a requirejs application

To optimize a requirejs application you can use these steps:

  • create the exploded war
  • remove the resources you don’t need (typicaly the one we’ll aggregate/uglify)
  • generate the .html/.js files with babel and jade runners
  • run r.js to optimize the application using this folder as root and linking external deps in the config
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.6</version>
  <executions>
    <execution>
      <id>prepare-war</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>exploded</goal>
      </goals>
    </execution>
    <execution>
      <id>default-war</id>
      <phase>package</phase>
      <goals>
        <goal>war</goal>
      </goals>
      <configuration> <!-- we dont want to overwrite already modified files -->
        <warSourceExcludes>**/*</warSourceExcludes>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>
<plugin> <!-- cleanup files which will not be delivered -->
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-clean-plugin</artifactId>
  <version>3.0.0</version>
  <executions>
    <execution>
      <id>remove-optimized-js</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>clean</goal>
      </goals>
      <configuration>
        <filesets>
          <fileset>
            <directory>${project.build.directory}/${project.build.finalName}/js/app</directory>
          </fileset>
          <fileset>
            <directory>${project.build.directory}/${project.build.finalName}/js/lib</directory>
            <excludes>
              <exclude>**/require.min.js</exclude>
              <!-- maybe some others depending yoru app -->
            </excludes>
          </fileset>
        </filesets>
        <excludeDefaultDirectories>true</excludeDefaultDirectories>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin> <!-- render jade and es6 files, Note: yes we could "hack" it and reuse the cache of tests, better to just regenerate it properly -->
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.4.0</version>
  <executions>
    <execution>
      <id>render-jade-templates</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>java</goal>
      </goals>
      <configuration>
        <mainClass>com.github.rmannibucau.ohmyjs.runner.JadeRunner</mainClass>
        <arguments>
          <argument>--source</argument>
          <argument>${project.basedir}/src/main/webapp/js/app/template</argument>
          <argument>--target</argument>
          <argument>${project.build.directory}/frontend/js/app/template</argument>
        </arguments>
      </configuration>
    </execution>
    <execution>
      <id>transpile-es6</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>java</goal>
      </goals>
      <configuration>
        <mainClass>com.github.rmannibucau.ohmyjs.runner.BabelJsRunner</mainClass>
        <arguments>
          <argument>--source</argument>
          <argument>${project.basedir}/src/main/webapp/js/app</argument>
          <argument>--target</argument>
          <argument>${project.build.directory}/frontend/js/app</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin> <!-- optimize requirejs app, needs a buildconfig.js in the root directory, depends your application -->
  <groupId>com.github.bringking</groupId>
  <artifactId>requirejs-maven-plugin</artifactId>
  <version>2.0.4</version>
  <executions>
    <execution>
      <id>r.js</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>optimize</goal>
      </goals>
      <configuration>
        <runner>nashorn</runner>
        <configFile>${project.basedir}/buildconfig.js</configFile>
        <filterConfig>true</filterConfig>
      </configuration>
    </execution>
  </executions>
</plugin>

The buildconfig.js file can look like:

({
    name: 'boot', // the main entry point of the application
    baseUrl: '${project.build.directory}/frontend/js/app', // we generated there with our runners
    out: '${project.build.directory}/${project.build.finalName}/js/app/app.min.js',
    optimize: 'uglify',
    paths: { // the dependencies/external libs linked in src/main/webapp directly cause we aggregate them in app.min.js
        'Vue': '../../../../src/main/webapp/js/lib/vue/vue.min',
        'VueRouter': '../../../../src/main/webapp/js/lib/vue/vue-router.min',
        'VueResource': '../../../../src/main/webapp/js/lib/vue/vue-resource.min',
        'boostrapNotify': '../../../../src/main/webapp/js/lib/bootstrap/bootstrap-notify.min',
        'bootstrap-datetimepicker': '../../../../src/main/webapp/js/lib/bootstrap/bootstrap-datetimepicker.min',
        'moment': '../../../../src/main/webapp/js/lib/moment/moment.min',
        'text': '../../../../src/main/webapp/js/lib/requirejs/text',
        // these deps are kepts like that
        'highlightjs': 'empty:',
        'jquery': 'empty:',
        'bootstrap': 'empty:',
        'ckeditor': 'empty:'
    },
    shim: { // dependencies as usual with requirejs
        'VueRouter': ['Vue'],
        'VueResource': ['Vue'],
        'bootstrap': ['jquery'],
        'boostrapNotify': ['bootstrap', 'jquery'],
        'bootstrap-datetimepicker': ['bootstrap', 'moment']
    }
})

Then you just need to modify a bit the main part of your application to use the bundle we just created. Personally I use a JSP to switch depending environment system property but any other way works (even a small groovy script linked with maven groovy plugin):

<% if (!"dev".equals(System.getProperty("environment", "prod"))) { /* load the bundle */ %>
require.config({
    baseUrl: 'js/app',
    paths: { // external libs not integrated to the bundle
        'jquery': '../../theme/startbootstrap-scrolling-nav-1.0.4/js/jquery',
        'bootstrap': '../../theme/startbootstrap-scrolling-nav-1.0.4/js/bootstrap.min',
        'ckeditor': '../lib/ckeditor/ckeditor',
        'highlightjs': '../lib/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack'
    },
    shim: {
        'VueRouter': ['Vue'],
        'VueResource': ['Vue'],
        'bootstrap': ['jquery'],
        'boostrapNotify': ['bootstrap', 'jquery'],
        'bootstrap-datetimepicker': ['bootstrap', 'moment']
    }
});
define('main', ['jquery', 'bootstrap', 'app.min'], function () {
    require(['boot']); // now our bundle is loaded we can require the actual boot module
});
require(['main']);
<% } else { /* dev/test */%>
require.config({
    baseUrl: 'js/app',
    paths: {
        'text': '../lib/requirejs/text',
        'Vue': '../lib/vue/vue.min',
        'VueRouter': '../lib/vue/vue-router.min',
        'VueResource': '../lib/vue/vue-resource.min',
        'jquery': '../../theme/startbootstrap-scrolling-nav-1.0.4/js/jquery',
        'bootstrap': '../../theme/startbootstrap-scrolling-nav-1.0.4/js/bootstrap.min',
        'boostrapNotify': '../lib/bootstrap/bootstrap-notify.min',
        'bootstrap-datetimepicker': '../lib/bootstrap/bootstrap-datetimepicker.min',
        'moment': '../lib/moment/moment.min',
        'ckeditor': '../lib/ckeditor/ckeditor',
        'highlightjs': '../lib/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack'
    },
    shim: {
        'VueRouter': ['Vue'],
        'VueResource': ['Vue'],
        'bootstrap': ['jquery'],
        'boostrapNotify': ['bootstrap', 'jquery'],
        'bootstrap-datetimepicker': ['bootstrap', 'moment']
    },
    waitSeconds: 0 // no timeout during tests
});
require(['bootstrap', 'test', 'boot']); // we can directly load the boot module since all modules are exploded (no bundle)
<% } %>

Notes

Babeljs and Jade integrations are using nashorn and therefore need Java >= 8 to work. The load of the original script is slow and it is recommended to not do it for each resource since the filters support hot reloading. The transformation is also synchronized so not production ready (ScriptEngine is not thread safe and creating one per thread would be slow in dev/test). Since resources are optimized after having been generated this is not an issue cause in production you will not do anything in these filters (you can use active = !dev when configuring your filter).

Also abusing of fast development tools like tomee-embedded-maven-plugin to have a simple “F5 solution” is highly recommended and only optimize the files when you are done.

Here is a potential configuration working with such a setup:

<plugin> <!-- dev server => mvn tomee-embedded:run -->
  <groupId>org.apache.tomee.maven</groupId>
  <artifactId>tomee-embedded-maven-plugin</artifactId>
  <version>${tomee.version}</version>
  <configuration>
    <context>/${project.artifactId}</context>
    <classpathAsWar>true</classpathAsWar>
    <containerProperties>
      <!-- we don't need a 100% EE server yet so switch off few things slowing down the boot or polluting logs -->
      <openejb.environment.default>false</openejb.environment.default>
      <tomee.skip-tld>false</tomee.skip-tld>
      <environment>dev</environment>
    </containerProperties>
  </configuration>
</plugin>

Then to check it works in optimized mode run mvn package and you can use tomee plugin (not embedded) to validate it:

<plugin> <!-- just to be able to test in not embedded mode before deployments => mvn tomee:run -->
  <groupId>org.apache.tomee.maven</groupId>
  <artifactId>tomee-maven-plugin</artifactId>
  <version>${tomee.version}</version>
  <configuration>
    <context>/${project.artifactId}</context>
  </configuration>
</plugin>

Available on central

If you are interested by this solution or want to give it a try it is available on central on coordinate com.github.rmannibucau:ohmyjs:0.0.1 (https://repo.maven.apache.org/maven2/com/github/rmannibucau/ohmyjs/0.0.1/) and sources are available on github: https://github.com/rmannibucau/ohmyjs

Advertisements

3 thoughts on “Oh My js! Server side javascript for java developers?

  1. ALBERT

    Hi, could you give us a simple example on how to automate precompiling jsx into js files upon every maven build ?

    Reply
      1. ALBERT

        I’ve been doing a simple app for manual transpiling jsx to js, which i pasted in http://pastebin.com/yRuJVnfR

        Currently this quick workaround works for me without maven, and later can be transformed into an ant task and be executed in maven.

        Thanks so much for your help and quick reply !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s