Node.js is not always a possibility. This is a JavaScript setup that works in an all-Java environment, and it shows that good JavaScript toolchains are possible even without Node.js.
Hopefully this will make your JavaScript development a whole lot easier when there's only Java all around.
This is an opinionated sample setup of a Java project which uses:
- Require.js for loading modules
- r.js for minified JavaScript in production
- Backbone.js for models, views and routers
- Hogan.js for Mustache templates or handlebars, both can be precompiled to JavaScript in production
- Jasmine for tests
- Sinon for test spies, stubs and mocks
- Saga for code coverage
- JSHint to detect problems and errors in the JavaScript code
Test coverage is useful for finding untested parts of a codebase. We
have included Saga, which is a
great Java-based test coverage tool for JavaScript. Saga generates test
coverage reports in both HTML and LCOV format. The latter is named
total-coverage.dat
and can be read by
Sonar (the file might need to be
renamed first).
Both files can be found in the target/coverage
folder after a build.
When developing the best way to run the tests is with:
mvn jasmine:bdd
The tests are also run during the build, just try it with:
mvn clean test
The code is minified by using the requirejs-maven-plugin. The plugin uses r.js with the buildconfig specified in buildconfig.js.
The minified code uses Almond instead of Require.js, as a full AMD loader is not needed for the minified code. Almond is also created by James Burke, the creator of Require.js, and is a minimal AMD API implementation with a minified and gzipped size of about 1 kilobyte.
The minified JavaScript code is put in the build
folder in the target.
To ensure that everything is working, open the index.html
in the
build
folder in the browser. We only need to include app.js
when the
code is minified, i.e.
<script type="text/javascript" src="app.js"></script>
This setup has a very simplified handling of the index file. It
basically just serves the JavaScript non-minified using Require.js when
the system property env
is set to development
, e.g. to see it up and
running:
$ mvn jetty:run -Denv=development
Otherwise it serves the minified JavaScript file, which includes timestamp for cache busting, which is put in when the file is packaged into a war, e.g. to see it up and running:
$ mvn jetty:run-war
To easily include templates we use a Require.js Hogan plugin and a RequireJS Handlebars Plugin. Each project would in most cases only use one template engine, so when you have chosen which one to use, just remove the other one.
Lets say we create the following Mustache file, foo.mustache
:
<div class="foo">
<h1>{{title}}</h1>
<ul>
{{#names}}
<li>{{.}}</li>
{{/names}}
</ul>
</div>
We can then load it using the hgn
command:
// this will load the "foo.mustache" file using the Hogan plugin
require(['hgn!foo'], function(foo) {
// the plugin returns the `render()` method of the `Hogan.Template`
var markup = foo({
title : 'Hello!',
names : ['world', 'foo bar', 'lorem ipsum']
});
console.log(markup);
});
During optimization the templates will be pre-compiled and stored as pure JavaScript for better performance.
This setup contains an example of an initial setup of a Backbone.js application, which solves some of the regular Backbone.js troubles people run into. It should be quite simple to remove Backbone, but keep the rest of the setup.
Most Backbone.js applications have memory leaks because of events. If you have code like this you might have problems:
var UserView = Backbone.View.extend({
initialize: function() {
this.model.on("change", this.render, this);
},
render: function() {
// ...
}
})
When you show and then remove this view from the DOM, it's easy to forget to remove the bound events on the model. And if you forget to do that it's quite likely that your views won't be garbage collected, which leads to memory leaks.
You find a potential solution in
eventBinder.js,
which is used by
destroy
in
view.js.
Now, instead of using on
, you would use bindTo
:
var UserView = Backbone.View.extend({
initialize: function() {
this.bindTo(model, "change", this.render, this);
},
render: function() {
// ...
}
})
When calling destroy
on the view, the event is automatically removed.
It is also important to recursively destroy subviews. This is
automatically done by destroy
when addSubView
is used on these
subviews, e.g.
renderUserDetails: function() {
var userDetailView = new UserDetailView({ el: el });
this.addSubView(userDetailView);
userDetailView.render();
}
There are many ways to solve views in Backbone. As already explained, we use Hogan.js with a Require.js plugin, which enable us to add templates as follows:
define(['base/view', 'hgn!modules/user/user'], function(View, userTemplate) {
var UserView = View.extend({
template: userTemplate,
render: function() {
this.renderTemplate();
}
});
return UserView;
});
You'll find renderTemplate
in
view.js.
The JSHint config can be found in
jshint-options.js.
You can find all the potential options in the
JSHint docs. The settings default to
breaking the build on any JSHint errors. If you want to be more lenient,
you can choose how many errors to allow via the JSHINT_MERCY
value.
This toolchain has been set up by @kjbekkelund and @hinderberg. However, we owe an enourmous amount of gratitude for the amazing work of the developers of the code used in this toolchain. You make the Java world far more bearable.
Unless otherwise noted, the code in this repo is in the public domain.