Spring and Node.js

Keine Kommentare

Spring is a very powerful Java framework to build strong backends. And with Spring Boot it puts back the fun. But there is a big weakness regarding frontend technologies, as it is not designed for that. In this domain young techniques like Node.js and NPM support the newest and hottest stuff.
This post shows a way how to combine Spring Boot and Node.js in one project and get use of powerful tools like Grunt, Bower, Karma, and PhantomJS. The full sourcecode is available on Github.

Maven is King

To combine the Java and the Javascript world we will use Maven. If you prefer Gradle more, this is no problem. The used Maven plugins should work there too.

Maven Node.js Plugin

There exist some Maven plugins to have control on Node.js. In this post I chosed frontend-maven-plugin as it does the job very good and is easy to configure.
In the first step the plugin downloads the operating system specific Node.js distribution and stores it in the local project folder. You can adjust the Node.js and NPM version by parameters. If you are curios why I'm using some older versions - it's because I got problems with the node-sass plugin with the latest one.

<execution>
  <id>install-node-npm</id>
  <goals>
    <goal>install-node-and-npm</goal>
  </goals>
  <configuration>
    <nodeVersion>v0.10.32</nodeVersion>
    <npmVersion>2.1.8</npmVersion>
  </configuration>
</execution>

After installing Node.js and NPM we advice the plugin to install all NPM packages defined in package.json in the next step. NPM is a dependency manager - like a little Maven for Node.js.

<execution>
  <id>npm-install-modules</id>
  <goals>
    <goal>npm</goal>
  </goals>
</execution>

One of the packages defined in this project is Grunt - a task runner for Node.js. There we will define all steps to prepare our frontend resources. As Grunt works with cascading tasks we will configure it to start the dist task which will include all other neccessary tasks. If you prefer Gulp, there is a possibility to start it by the Maven plugin too.

<execution>
  <id>grunt-dist</id>
  <goals>
    <goal>grunt</goal>
  </goals>
  <configuration>
    <arguments>dist</arguments>
  </configuration>
</execution>

But Node.js is not only good to prepare frontend resources like CSS and JS files. It offers methods to execute tests for your Javascript files. The tests get executed by Karma and guess what - there is a nice Maven goal defined in the plugin too. We just have to define a configuration file which will be described later.

<execution>
  <id>karma-test</id>
  <phase>test</phase>
  <goals>
    <goal>karma</goal>
  </goals>
  <configuration>
    <karmaConfPath>src/test/webapp/WEB-INF/javascript/karma.conf.ci.js</karmaConfPath>
  </configuration>
</execution>

Spring Boot

As described before this is a very easy step. It just needs to add the spring-boot-starter-parent POM and a spring-boot-web-starter. As we want to use the Thymeleaf template engine, we will use the spring-boot-starter-thymeleaf starter.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.2.2.RELEASE</version>
</parent>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
</dependencies>

As we want to be able to start the project by executing mvn spring-boot:run, we have to add the frontend resources from Node.js by extending the spring-boot-maven-plugin configuration. There we add the ${project.build.directory}/grunt folder as resource. Later we will instrument Grunt to put the compiled resources there.

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
      <configuration>
        <classifier>exec</classifier>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <folders>
      <folder>${project.build.directory}/grunt</folder>
    </folders>
  </configuration>
</plugin>

Maven WAR plugin

The Spring Boot command is only one possibility to launch our application. Another way is to create an Uber-WAR by mvn package. Also knowing nothing about our generated frontend resources, we have to instrument the maven-war-plugin to copy them into the new container too.

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <failOnMissingWebXml>false</failOnMissingWebXml>
    <packagingExcludes>WEB-INF/lib/tomcat-*.jar</packagingExcludes>
    <webResources>
      <resource>
        <directory>${project.build.directory}/grunt/public</directory>
        <targetPath>public</targetPath>
      </resource>
    </webResources>
  </configuration>
</plugin>

Short Java Code

As this post is more about Node.js and not Spring, we will just create the Spring application class and define a controller to display a template.

package cnagel.spring_node;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@SpringBootApplication
@Controller
public class Application {

 public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
 }
 
 @RequestMapping("/")
 public String index() {
  return "index";
 }

}

Our template looks very basic and just includes the styles.css and scripts.js resource. The ThymeLeaf template engine helps us to get the correct path for them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Spring-Node.js</title>
<link th:href="@{/styles.css}" rel="stylesheet" type="text/css" />
</head>
<body>
  <div class="container">
    <h1>Spring-Node.js</h1>
  </div>
  <script type="text/javascript" th:src="@{/scripts.js}"></script>
</body>
</html>

Node.js Modules and Configuration

The Java part is finished - let's have a look on Node.js and supported tools. We will start with NPM and Bower, both managing dependencies. Further Grunt to coordinate all tasks, processing the resources and test the JavaScript code.

NPM

NPM is used to manage our Node.js package dependencies. This is done by the package.json file, which simply defines our needed libraries in the JSON format and adds some additional information about our project.

{
  "name": "spring-node.js",
  "description": "A Spring Node.js project",
  "version": "0.0.1",
  "repository": "https://github.com/cnagel/spring-node.js.git",
  "devDependencies": {
    "bower": "~1.3.12",
    "grunt": "~0.4.5",
    "grunt-bower-install-simple": "1.1.0",
    "grunt-cli": "~0.1.13",
    "grunt-concurrent": "~1.0.0",
    "grunt-contrib-uglify": "~0.7.0",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-karma": "~0.10.1",
    "grunt-sass": "~0.18.0",
    "karma": "~0.12.31",
    "karma-jasmine": "~0.3.5",
    "karma-phantomjs-launcher": "~0.1.4"
  }
}

Nothing special in there, if you want more information about the used packages have a look on the specific NPM or GitHub sites.

Bower

Bower is a dependency manager for frontend resources. We will use it to embed Bootstrap as CSS framework, furhter Modernizr and jQuery as Javascript examples.

{
  "name": "spring-node.js",
  "version": "0.0.1",
  "dependencies": {
    "bootstrap-sass": "twbs/bootstrap-sass#~3.3.3",
    "modernizr": "2.8.3",
    "jquery": "2.1.3"
  },
  "devDependencies": {
  }
}

But no fear, this is a frontend resource agnostic tutorial, you can choose any other CSS or Javascript framework supported by Bower. I just want to give an overview how to use the manager. We will later see how to get them into our compiled code.

Stylesheets and Javascript Resources

While Bower defines our frontend resource libraries, we now want to write our custom CSS and Javascript code. As CSS alone has some disadvantages we will use SASS. And as a good coder we will add some tests to our JavaScript code.

Sass

There exist several ways to embedd multiple SASS files into one, and I will choose the @import way. There we have to define all included SASS files manually. In this case it will be the Bootstrap library which comes with all the CSS sugar we love. Further we add a new CSS directive setting the color of our headline to the brand primary color defined by a Bootstrap variable.
Tip: Before including Bootstrap, you can pre define any Bootstrap SASS variable!

@import 'bootstrap';
body h1 { color: $brand-primary; }

Javascript

In this example we will just have one very basic Javascript files. It defines a sayHello method, excecutes it if the DOM is loaded and prints the output to the console.

function sayHello(name) {
  return "Hello " + name + "!";
}

$(function() {
  console.log(sayHello("to everyone"));
});

Test configuration

To execute Javascript tests, we will use the three following techniques:

As Karma gets executed by the Grunt task runner, we just need to define a Karma configuration file. Following code shows how to set PhantomJS and Jasmine for the tests. Furhter we have to define the locations of jQuery, our Javascript code and the tests.

module.exports = function(config) {
  config.set({
    basePath: '../../../../..',
    frameworks: ['jasmine'],
    files: [
      '.bowercomponents/jquery/dist/jquery.js',
      'src/main/webapp/WEB-INF/javascript/*.js',
      'src/test/webapp/WEB-INF/javascript/*.js'
    ],
    exclude: ['src/test/webapp/WEB-INF/javascript/karma.conf*.js'],
    reporters: ['progress'],
    port: 9876,
    logLevel: config.LOG_INFO,
    browsers: ['PhantomJS'],
    singleRun: false,
    autoWatch: true,
    plugins: [
      'karma-jasmine',
      'karma-phantomjs-launcher'
    ]
  });
};

The Karma configuration is used for development and will always execute the tests if there are changes on the source files. To execute the test only once and terminate the testing engine we will use a second config file, which embeds this one and overrides the autoWatch settings. his one will be used by Maven.

var baseConfig = require('./karma.conf.js');
module.exports = function(config){
    baseConfig(config);
    config.set({
        singleRun: true,
        autoWatch: false
    });
};

Javascript Tests

In parallel to our Javascript source code, the tests wil be short and easy too. Doing this in a more behaviour driven fashion we write them in Jasmine syntax.

describe('The sayHello function', function(){
  it('should greet with name', function(){
    expect(sayHello("World")).toBe("Hello World!");
  });
});

Grunt

After defining our resources, code, and tests, it needs a task runner to get it all together. As configured in the pom.xml we will use Grunt and it just needs an additional file to define our tasks. The file starts by defining help variables for neccessary code and resource directories. Further we have to import used plugins provided by the NPM package manager. Then comes the tricky part, where the plugins get configured and we have to provide the correct paths and parameters.

        <!-- Bower -->
        "bower-install-simple" : {
          options : {
            directory : config.bower,
          },
          dist : {
            options : {
              production : true,
            },
          },
        },
        <!-- Karma -->
        karma : {
          unit : {
            configFile : '<%= config.js_unit %>/karma.conf.js'
          },
          integration : {
            configFile : '<%= config.js_integration %>/karma.conf.js'
          },
        },
        <!-- SASS -->
        sass : {
          dist : {
            options : {
              outputStyle : 'compressed',
              includePaths : [ '<%= config.bower %>/bootstrap-sass/assets/stylesheets/' ],
              sourceMap : true
            },
            files : {
              '<%= config.dest %>/styles.css' : '<%= config.sass %>/app.scss'
            }
          }
        },
        <!-- JS uglify -->
        uglify : {
          dist : {
            options : {
              sourceMap : true,
            },
            files : {
              '<%= config.dest %>/scripts.js' : [
                  '<%= config.bower %>/modernizr/modernizr.js',
                  '<%= config.bower %>/jquery/dist/jquery.js',
                  '<%= config.bower %>/bootstrap-sass/assets/javascripts/bootstrap.js',
                  '<%= config.js %>/**/*.js', ]
            }
          }
        }

Get it run

By execution the mvn test goal, all frontend resources get collected, merged into the corresponding file and the Javascript tests executed. You will see a Karma error message if the test fails and Maven will stop too.

While writing your CSS or JS code, there exists also the opportunity to recompile and test the code each time you make changes. Therefore you have to start Grunt wih the default task: node_modules/grunt-cli/bin/grunt.

Keine Kommentare :

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.