Many web applications are built today with distinctly different server-side and client-side technologies. In order to run these applications, both server-side and client-side sources must be built and packaged. Webpack has emerged as the tool of choice among JavaScript developers for bundling client-side assets.

Tradeoffs are inherent in any engineering decision, but using disjointed tools can slow down very capable development teams. Gradle provides mechanisms to integrate with almost any other toolchain, including Webpack and other JavaScript development tools. More information about managing Webpack with Gradle (including hot reloading) is covered in an in-depth topical guide (coming soon).

This guide will start you down a path that leverages the benefits of both Gradle and Webpack with minimal overhead.

What you’ll build

You will create a Gradle task that bundles web assets with Webpack in a way that leverages Gradle’s up-to-date checks and build cache.

What you’ll need

  • About

  • A text editor or IDE

  • The Java Development Kit (JDK), version 1.7 or higher

  • Node.js, version 8.0 or higher

  • A Gradle distribution, version 4.0 or better

Step 1: Create sample project and install dependencies

Setup a trivial web application by running these commands and copying sources to the files listed.

Step 1.1: Create sample project

This demo project is derived from Webpack’s Getting Started Guide. Learn more Webpack usage at https://webpack.js.org/guides/.
$ mkdir -p gradle-webpack-demo/app
$ cd gradle-webpack-demo
app/index.js
import _ from 'lodash';

function component () {
  var element = document.createElement('div');

  /* lodash is used here for bundling demonstration purposes */
  element.innerHTML = _.join(['Build', 'together;', 'not', 'alone'], ' ');

  return element;
}

document.body.appendChild(component());
index.html
<html>
  <head>
    <title>Gradle + Webpack Demo</title>
  </head>
  <body>
    <script src="build/js/bundle.js"></script> (1)
  </body>
</html>
1 Webpack-generated JS bundle will include index.js and lodash

Step 1.2: Install Webpack and lodash

$ npm init -y
$ npm install --save lodash@~4
$ npm install --save-dev webpack@~2

Once completed successfully, your project structure should look like this:

.
├── app
│   └── index.js
├── index.html
├── node_modules
│   └── lodash
│   └── webpack
├── package-lock.json
└── package.json
package-lock.json is present only for npm v5+, which is a recommended upgrade.

Step 2: Use an Exec Task

Command-line tools can be invoked through Gradle Exec tasks.

Custom Task Classes are highly recommended for any applications where configuration or reusability is desired; for example, to allow dev (debug with sourcemaps) and production variants.

Step 2.1: Create Gradle task

build.gradle
task webpack(type: Exec) { (1)
    commandLine "$projectDir/node_modules/.bin/webpack", "app/index.js", "$buildDir/js/bundle.js"
}
1 Declare an Exec task to invoke webpack
Executing the webpack task
$ gradle webpack

> Task :webpack
Hash: cebd0a554d64bf1868af
Version: webpack 2.6.1
Time: 406ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  544 kB       0  [emitted]  [big]  main
   [0] ./~/lodash/lodash.js 540 kB {0} [built]
   [1] ./app/index.js 269 bytes {0} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

You can now open index.html and see "Build together; not alone".

Step 3: Declare task inputs and outputs

You are now able to execute Webpack through Gradle. Let’s move on to only running Webpack when you need to, through Gradle’s up-to-date checks (AKA incremental build).

In order to take advantage of up-to-date checks, you must declare the inputs and outputs of your task. Change your task configuration in your Gradle build this way:

build.gradle
task webpack(type: Exec) {
    inputs.file("package-lock.json") (1)
    inputs.dir("app")
    // NOTE: Add inputs.file("webpack.config.js") for projects that have it
    outputs.dir("$buildDir/js")      (2)

    commandLine "$projectDir/node_modules/.bin/webpack", "app/index.js", "$buildDir/js/bundle.js"
}
1 Declare package-lock.json and everything under app/ as an input
2 Declare build/js as the output location

Execute gradle webpack to verify your configuration. We have changed the declared task inputs and outputs, so webpack will be run.

Now executing gradle webpack with no changes to web assets won’t unnecessarily invoke Webpack:

$ gradle webpack

BUILD SUCCESSFUL in 0s
1 actionable task: 1 up-to-date (1)
1 Gradle recognizes when JS sources haven’t been changed. webpack bundles are UP-TO-DATE and don’t need to be generated

Step 4: Leverage Gradle Build Cache

As of Gradle 4.0, Gradle can avoid work that has already been done on different VCS branches or by other machines via the Build Cache.

Suppose someone else pushed JS changes that were subsequently built by CI and shared in a remote build cache. If you have the same Webpack config and no JS changes, you can avoid re-bundling JS via Webpack and download the bundles straight from the build cache, thus saving your build’s bundling time.

Step 4.1: Make webpack task cacheable

build.gradle
task webpack(type: Exec) {
    inputs.file("package-lock.json").withPathSensitivity(PathSensitivity.RELATIVE) (1)
    inputs.dir("app").withPathSensitivity(PathSensitivity.RELATIVE)
    outputs.dir("$buildDir/js")
    outputs.cacheIf { true } (2)

    commandLine "$projectDir/node_modules/.bin/webpack", "app/index.js", "$buildDir/js/bundle.js"
}
1 Declare package-lock.json and app to be relocatable. Learn why this is important for caching.
2 Tell Gradle to always cache this task’s outputs if the build cache is enabled.

It is strongly advised to use a custom task class when writing cacheable tasks. An example of one for Webpack is provided in the Managing JavaScript topical guide (coming soon). Furthermore, you may want to declare property names for better diagnostics.

Learn more about best practices of using the Gradle Build Cache from the Build Cache topical guide.

Step 4.2: Run webpack to populate Gradle Build Cache

$ gradle webpack --build-cache (1)
Build cache is an incubating feature.
Using local directory build cache for the root build (location = ~/.gradle/caches/build-cache-1).

> Task :webpack
Hash: cebd0a554d64bf1868af
Version: webpack 2.6.1
Time: 411ms
····Asset    Size  Chunks                    Chunk Names
bundle.js  544 kB       0  [emitted]  [big]  main
···[0] ./~/lodash/lodash.js 540 kB {0} [built]
···[1] ./app/index.js 269 bytes {0} [built]
···[2] (webpack)/buildin/global.js 509 bytes {0} [built]
···[3] (webpack)/buildin/module.js 517 bytes {0} [built]


BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed
1 Enable Gradle Build Cache. Can also use org.gradle.cache=true in gradle.properties

Step 4.3: Make a small JavaScript change

Comment out a line or make some other trivial change.

app/index.js
-  element.innerHTML = _.join(['Build', 'together;', 'not', 'alone'], ' ');
+  // element.innerHTML = _.join(['Build', 'together;', 'not', 'alone'], ' ');

Step 4.4: Re-run webpack to bundle changes

$ gradle webpack --build-cache
Build cache is an incubating feature.
Using local directory build cache for the root build (location = ~/.gradle/caches/build-cache-1).

> Task :webpack
Hash: f86580c7ddca3e9d092a
Version: webpack 2.6.1
Time: 413ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  544 kB       0  [emitted]  [big]  main
   [0] ./~/lodash/lodash.js 540 kB {0} [built]
   [1] ./app/index.js 287 bytes {0} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

Step 4.5: "reset" changes

Uncomment to undo changes we made.

app/index.js
-  // element.innerHTML = _.join(['Build', 'together;', 'not', 'alone'], ' ');
+  element.innerHTML = _.join(['Build', 'together;', 'not', 'alone'], ' ');

Step 4.6: Resolve JS bundle from build cache

$ gradle --build-cache webpack
Build cache is an incubating feature.
Using local directory build cache for the root build (location = ~/.gradle/caches/build-cache-1).

BUILD SUCCESSFUL in 1s
1 actionable task: 1 from cache (1)
1 webpack was not executed. build/js/bundle.js was loaded from the build cache instead.

Even though you just made changes, re-bundling is not necessary. This same mechanism works well when switching git branches and other common development workflows.

Next Steps

Congratulations! You now have a Gradle task that executes Webpack, but only when web assets change. The benefits of using Gradle increase as your project grows.

Chances are, your needs are more complex. There are 2 next steps from here:

Happy bundling!

Help improve this guide

Have feedback or a question? Found a typo? Like all Gradle guides, help is just a GitHub issue away. Please add an issue or pull request to gradle-guides/running-webpack-with-gradle and we’ll get back to you.