Apr 22, 2016

Using Bundled Webpack Instance with Gradle

In this post I’ll be explaining how to use React, Spring Boot, and Webpack to package all the javascript files together. As always, feel free to follow along or you can cheat and get the final source here. If you want to follow along, clone this and you can work through this post to match the other one. I’m going to assume some knowledge of node.js and npm but I’ll also go into some of the details that would help a Java developer turned full stack developer with some nice to have background information. I’ll be using React for this but you can apply the things learned here with any js framework you like. First a little background for some new frontend developers:

As a Java developer, you’re most likely familiar with Maven or the Gradle build system. These are systems which manage the dependencies for you. This makes it possible to say I depend on sprint-boot-starter-web JAR and the build system will find the 20+ dependencies needed and add it to your classpath. Well node.js is a program that run javascript files, much like Java, and npm (Node Package Manager) is a package manager that’s written in javascript and thus we need node installed too. Just like how Maven has a pom.xml, gradle the build.gradle file, npm has a file for dependency management and configuration:

package.json

{
  "name": "webpack-gradle",
  "version": "0.0.1",
  "description": "Example project using webpack and gradle",
  "scripts": {},
  "devDependencies": {},
  "dependencies": {}
}

The devDependencies are things needed for development/building while the dependencies section is for items needed for the application itself. You can create an empty file like the one above using:

npm init

You can use npm to install dependencies with the following commands:

npn install -save react
npm install -save-dev webpack

Now that node.js/npm is explained, let me explain the need for webpack and what is it. Webpack is one of the new ways to compile/transform and package your javascript files. Since this is the javascript world, expect something newer to come along within 4 hours though. Before webpack you would use something like grunt to compile your files and bower to handle the dependencies. If you’re coming from a Java background and you’re trying to learn node/node libraries and use it with your application you may see things like while searching the web:

// CommonJS
var lodash = require('lodash');
console.log(lodash.isNumber('sdf'));
 
// AMD Style: 
define(["lodash"], function (lodash) {
  console.log(lodash.isNumber('sdf'));
});
 
// ES6:
import lodash from 'lodash'
console.log(lodash.isNumber('sdf'));

CommonJS is used in a lot of node.js projects and is really simple to use. The problem is that by default it won’t work in a browser! Browsers have no concept of modules or require statements, at least not in the the ES5 standard (different for ES6 though). The second one called Asynchronous module definition (AMD), can be used by a browser with things like RequireJS. This is great but it’s a pain to get this working. While trying to learn about node, the fact that they always use a node server to do the magic for them is not helpful for a Java developer trying to get working via a normal application server. Insert webpack. Webpack doesn’t care what style to use, it will transform the javascript to make it useable for a browser for components that were written with CommonJS, are expecting an AMD loader like requirejs, or which use ES6 syntax. It performs a lot of functions via the use of plugins including creating giant chucks for everything in one bundles, multiple bundles, and transcompiling ES6 code to something current browsers can understand. It also handles dependencies for you. In the the old days (literally a few days ago), you would use Bower for dependency management. Bower is a build system that’s really popular. What you normally would do is install all your dependencies in the package.json file and also in a bower.json file for the files you really need. Webpack just uses the package.json file and you define entry points (e.g., index.jsx, admin.jsx, etc) and it will do the digging itself to find the dependencies you actually need based upon import and require statements so you get rid of one configuration file at least. You could have one entry point or help secure your application to make is so that only logged in people get to a bundle that’s applicable to them.

Ok. Background information is now done. We want to start a new Spring application. We’ll create a simple Spring Boot MVC app and reuse the same starter project from the previous post. Again, you can do a clone from here if you want to start at the point I am. Using npm, we’ll want to install a few items and save them to packson.json so that they are actually downloaded in the future. We’ll want Webpack to package our project, babel to “transcompile” ES6 to ES5 since browsers don’t support ES6 yet, and then of course: React. Run the following commands:

npm install -save-dev babel babel-core babel-preset-es2015 babel-preset-react webpack
npm install -save react

Webpack uses loaders to load different types of files like css, less, etc. Even though we won’t be using all these, lets go ahead and load them too since you’re most likely going to.

npm install -save-dev babel-loader css-loader file-loader less less-loader style-loader url-loader

Now we also want to put in some options for babel here since it reads it to tell it we’re using es2015 (another name for ES6). All said and done, the packson.json should look like this:

package.json

{
  "name": "webpack-gradle",
  "version": "0.0.1",
  "description": "Sample application using react with webpack and Spring Boot",
  "scripts": {},
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "devDependencies": {
    "babel": "^6.5.2",
    "babel-core": "^6.21.0",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "css-loader": "^0.23.1",
    "file-loader": "^0.8.5",
    "less": "^2.6.1",
    "less-loader": "^2.2.3",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.15"
  },
  "dependencies": {
    "react": "^15.0.1",
    "react-dom": "^15.0.1"
  }
}

Ok, now we have npm knowing about all the items we need but we need to configure Webpack now. Webpack comes with its own configuration file. You can do a LOT of things with webpack such as creating multiple “chunks”, uglify, and compressing. For this tutorial we’re going to create one giant chunk and turn on source maps so we can debug our code in the browser. By default this doesn’t take out whitespace or anything but instead concats them all together and adds in its special sauce to get everything working. You can create a prod profile that does this though. Create a webpack.config.js file at the project root with the following contents:

webpack.config.js

var path = require('path');
var webpack = require('webpack')
var ROOT = path.resolve(__dirname, 'src/main/webapp');
var SRC = path.resolve(ROOT, 'javascript');
var DEST = path.resolve(__dirname, 'src/main/webapp/dist');
 
module.exports = {
  devtool: 'source-map',
  entry: {
    app: SRC + '/index.jsx',
  },
  resolve: {
    root: [
      path.resolve(ROOT, 'javascript'),
      path.resolve(ROOT, 'css')
    ],
    extensions: ['', '.js', '.jsx']
  },
  output: {
    path: DEST,
    filename: 'bundle.js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,  // Notice the regex here. We're matching on js and jsx files.
        loaders: ['babel-loader?presets[]=es2015&presets[]=react'],
        include: SRC
      },
 
      {test: /\.css$/, loader: 'style-loader!css-loader'},
      {test: /\.less$/, loader: 'style!css!less'},
 
      // Needed for the css-loader when [bootstrap-webpack](https://github.com/bline/bootstrap-webpack)
      // loads bootstrap's css.
      {test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff'},
      {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream'},
      {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file'},
      {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml'}
    ]
  }
};

OK, we’re almost there! Now we need hook this into gradle. Again, we’re using a project with gradle which defines its own npm and node version. This is useful for this part. We want webpack to be run before Sprint Boot starts. This actually is easier than it sounds! Open up the build.gradle file and put in the following items:

Neat huh? We’re using the Webpack installed at the project level to run before startup. If you start the application you’ll notice that it starts but at the every start it complains that src/main/webapp/javascript/index.jsx doesn’t exist. We’ll change that in a minute but you may have noticed that the file extension is jsx. We’ll be using JSX for React. If this is your first exposure to it you may screaming blasphemy since it contains XML syntax inside of javascript. This takes a few days to get over but you will get over it. You don’t have to use the syntax but the readability will go down and the code you have to write will be a lot more if you want to use “normal” javascript. You can use the js file extension if you want. It doesn’t matter since we’re using babel to convert the file for us via the Webpack config. I prefer to use .jsx for React files and .js extension for pure javascript as a pure contention. You’re free to chose whatever you want. Now, lets get started on our first page.

Create an index.html with the following contents:

src/main/webapp/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>A Test Page</title>
</head>
<body>
<div id="content"></div>
<script src="dist/bundle.js"></script>
</body>
</html>

Webpack will create a bundle.js file and put it in the dist directory, so we always just need to reference that. Let’s create a simple page which shows some tables and seat numbers and if they are open or reserved to help show React’s component based rendering and also show off webpack using ES6 imports and requires in one. Create a css file:

webapp/css/general.css

.seat {
  display: inline-block;
  width: 40px;
  text-align: center;
}
 
.seat.open{
  background-color: green;
}
 
.seat.reserved{
  background-color: red;
}
 
.table{
  margin-bottom: 10px;
}
 
.table-title{
  font-weight: bold;
}

Now let’s create our make entry point (index.jsx):

webapp/javascript/index.jsx

Right off the bat you’ll notice we’re using imports statements instead of require statements. You’ll also notice we’re importing a Table component we haven’t created yet but that it’s defined as a file structure path. This is because it’s not a project dependency in the package.json file. You’ll also see that I’m using an import statement for a CSS file. Why? Well doing this will allow the CSS file to be compressed and put into the bundle.js file as well and I don’t need to reference the object via a variable. I can do:

var $ = require('jquery');
// or
import * as jquery from 'jquery';

if I wanted to and I could use jQuery like you’re probably used to. You could include the CSS file manually in the index.html if wanted and you can include the file only when you need it (such as in the seat.jsx). I can see the case for doing that for large projects but for “generic” css files used by all files, I’d just toss it in the entry point so you don’t have to change tons of require statements if you move files around later. You’ll also see that I’m using Table like it’s an HTML element. This is the power of JSX. Let’s define our other files then too:

webapp/javascript/table.jsx

webapp/javascript/seat.jsx

You’ll notice some “let {xxxx} = this.props” syntax here. The props has what was passed in as attributes and the ES6 {xxx} syntax is just short form for:

let number = this.props.number;
let open = this.props.open;

except you can grab inner map objects with the null safety checks added for you.

Starting up the Spring application now works and it shows the contents for you. You can change the javascript files and Webpack compiles them again upon startup. Having to restart the application all the time just for javascript changes is no fun but luckily Webpack is able to monitor our changes and create a new bundle.js file for us. The problem is that Webpack is buried in our node_modules directory and also you need to pass it some arguments. This is where creating a script at the root of the project comes into play. Let’s create one:

watchJS.sh

#!/usr/bin/env bash
 
# This is a simple helper script used to start the Webpack watch process
# so that javascript files can be automatically reloaded when they are saved.
# The version of node must match what's in gradle.
 
NODE_VERSION="5.8.0"
NODE_LOC="./.gradle/nodejs/node-v${NODE_VERSION}-darwin-x64/bin/node"
 
chmod u+x ${NODE_LOC}
${NODE_LOC} ./node_modules/.bin/webpack --watch --display-error-details

Now I just run the follow an a terminal:

# Only need to chmod the first time you ever use file
chmod u+x watchJS.sh
 
./watchJS.sh

and the code is automatically recompiled when I change something but I do need to hit refresh in the browser. And we’re done! We now have Webpack working through gradle which compiles React using ES6 features and we don’t even need to install node, npm, or Webpack globally. Oh, and the build server need absolutely nothing globally installed and just needs internet connection (or locally hosted artifatory). Your build is now isolated from old (or new) globally installed node, npm, and Webpack installs!

This is a simple application and if you’re really going to use React you should look at react-router if you want to create a simple page application like angular and spend a few days learning about redux to store your application state outside of the React components themselves.

Hopefully this helps get you started with using Webpack with gradle and you learned something you can apply to your project.

About the Author

Object Partners profile.

One thought on “Using Bundled Webpack Instance with Gradle

  1. shawn wang says:

    Great article! We also enjoyed webjar a lot when using Spring boot. We are able to consolidate all client and server libs using maven or gradle.

    1. Jeff Torson says:

      Thanks Shawn. I agree that webjars are the way to go once you get large enough. For small applications, I like everything to be bundled together or for the situation I was in where node/npm wasn’t allowed to be globally installed on the build server.

  2. Scott Hickey says:

    Did you look at https://github.com/nebula-plugins/gradle-webpack-plugin? I was wondering about the pros/cons of your approach vs the Netflix plugin, thanks.

    1. Jeff Torson says:

      Hi Scott. I looked at it originally too but if you take a look at the only issue it has, you’ll see it a deal breaker unless you build and host it yourself internally. For whatever reason, they didn’t actually release the plugin. The two plugins do about the same but I like com.moowork.gradle one since I can run any script and not just webpack. You could, for example, run gulp if you wanted from gradle with the moowork one. I didn’t need to call other scripts (yet) for my small projects but at the end of the day, one plugin worked and the other one failed right away at building…so I went with the one that worked 🙂

  3. johny says:

    good article but do you know how to not pack javascript source files into *.war file? I wish to have only resulting minified file there

    1. Jeff Torson says:

      Hi Johny. There’s a few ways to do this. I’ve seen projects have a master gradle file (like a parent pom) with a frontend and backend folder/module. I like this approach if you’re looking to keep things in the same project. To keep things simple, all you would really need to move the source out of /src/main/webapp to someplace else. I created a branch where I just moved the javascript and CSS to /frontend and updated the webpack to point to this. Take a look at the no_bundled_source branch to see what I’m talking about. This will still have the source map in there so you’ll need to play with webpack in order to create development and production build profiles.

  4. greg says:

    thanks for the article. i was wondering why you choose to put the static files in src/main/webapp instead of src/main/resources/static? in the webpack config file I could see the src/main/resources/static lines commented out. what are the advantages?

    1. Jeff Torson says:

      I did this as fun application to play with. When things were in static, the files were cached by Spring Boot and were never read from disk again. This is fine for prod but for development, that wasn’t something I wanted since I was constantly making changes and didn’t want to redeploy. Another solution would be to extract the javascript project into a different project and use Express/Webpack Dev Server and proxy the connection (e.g., host Webpack/Express on 9090 and proxy /api -> localhost:8080 and point your browser to http://localhost:9090).

  5. Adrian Aguirre says:

    Hi Jeff,

    I was wondering if you could help me out with an issue I am having with this run through. My questions are specific to the watchJS.sh, and code that is being added to the build.gradle file.

    FAILURE: Build failed with an exception.

    * Where:
    Build file ‘/Users/squirm/Desktop/engineering/gradle/gradlenpm-master/build.gradle’ line: 7

    * What went wrong:
    A problem occurred evaluating root project ‘gradlenpm-master’.
    > Could not get unknown property ‘processResources’ for root project ‘gradlenpm-master’ of type org.gradle.api.Pro
    ject.

    * Try:
    Run with –stacktrace option to get the stack trace. Run with –info or –debug option to get more log output.

    BUILD FAILED

    —————————————————————————————————

    watchJS.sh ——-

    ERROR:

    $ ./watchJS.sh
    chmod: –watch: No such file or directory
    chmod: –display-error-details: No such file or directory

  6. Adrian Aguirre says:

    I was able to fix my issue with the build.gradle file. Still looking into the watch script

    1. Jeff Torson says:

      Hi Adrian. Sorry I missed the email about this comment. I played with this tonight and was having issues starting from scratch myself. I needed babel-core in the package.json but I think your error is something else. I got push a commit for this though. Did you just clone from the repo then? If so, you’ll need to do a “sh gradlew build” to get your project setup first. This will download node and npm locally to the project. If that doesn’t work, post the top part of your build.gradle file since it appears something isn’t right. Also, please go a git pull to get the latest package.json file if you cloned the repo.

    2. Elajia says:

      Adrian,

      I was following the tutorial and get the same problem. How did you fix it?

      1. Jeff Torson says:

        Can you give me some more details on your issue? If you do a clone of the project, you’ll need to at least do a ./graldew build in order for the node_modules to be downloaded and initialized. A gradlew bootRun will start the server and also initialize the directory if it hasn’t been done yet..

  7. Harris says:

    Thanks for the helpful article!

    I’m actually trying to integrate my angular webpack with Gradle! I used the same plugin and task you had, only changed Node and npm version (although it doesn’t seem to be the problem since i tried using the same version you have and get the same failure message)

    My build failed at webpack task!

    Execution failed for task ‘:webpack’.
    > Process ‘command ‘/**/the-app/.gradle/nodejs/node-v6.11.0-darwin-x64/bin/node” finished with non-zero exit value 255

    and another question what is the purpose of this line in build.gradle:
    processResources.dependsOn ‘webpack’

    Thanks

    1. Jeff Torson says:

      Hi Harris,
      I tried this again tonight and it worked for me but only if I used the package.json in the githib repo . When I tried following the directions, it installed Webpack 3 vs Webpack 1 and I got an error about the config not being valid anymore. Webpack 2 was still in beta when I wrote this, so I’ll take a look by this weekend to see what changes I’ll needed since it now 2 version off.

      The processResources.dependsOn ‘webpack’ line was added so that npmInstall was kicked off. Looking at the script line, I don’t think bootRun actually starts Webpack like it implies. I’ll dig in deeper by this weekend. It’s been a while and I’ll refresh page and repo to try to stay current.

  8. Harris says:

    Thank you! it would be great if you update it as it’s very useful!

    1. Jeff Torson says:

      Was a little busy this weekend. The fix for webpack 3 was pretty simple: root got replaced with modules. Check out the webpack 3 branch here. It’s basically the same except I updated the node and npm version too. Branch compare here.

  9. Faisal Julaidan says:

    very nice article.

    But why when i move all the files from webapp folder to resources folder everything break up even though i did modify all the paths in webpack.config

Leave a Reply to johny Cancel reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]