Gradle for NPM Users

Daniel shares a Gradle tutorial for those more familiar with npm as a build tool.

Gradle for npm users

Recently a project I was working on changed its build pipeline to use Gradle with Jenkins. Before that we had just been using npm scripts to compile and serve assets, and deploy code. There is already a wealth of information on how to use npm as a build tool, the many uses of npm scripts, and how they just run tasks in the shell., but I was uncertain if any of that knowledge could be transferred over to using Gradle, a historically Java based build tool. I’m not a Java developer and I haven’t touched the JVM since college, so I wasn’t sure what to think about a tool whose first testimonial read: “We do truly feel that Gradle is the best build system for the JVM…”

Regardless of my comfort level with Java or Gradle’s own domain specific language, our build was changing, and I needed to quickly understand this new tool.

Since I am very familiar with the way npm works, I used that knowledge to find a few of the parallels to npm that I found useful when beginning to understand Gradle’s process—most critically, init, dependencies, scripts, and run.

init

For any npm project you begin, you have the npm init command to help bootstrap the application and help direct you down the “happy path.” When you start or migrate your application to using Gradle you can use the exact same type of command, gradle init.

Gradle can be used for many different types of projects (Scala, Java, Groovy, and more). It has a --type flag that allows us to specify what type of application we are building. As JavaScript developers, we will almost always leave this option as its default, --type basic which generates the following files:

  • build.gradle
    • This file is like our generated package.json. It contains some boilerplate code in Gradle’s DSL that outlines how to setup a build.
  • gradle/
    • This directory is where we will create our Gradle build files, but for now just contains a generic wrapper to allow Gradle to run. You’ll find settings and properties you can modify very similar to how NPM has a .npmrc.
  • gradlew
    • This executable allows other users who do not have gradle installed to run our build tasks. It’s similar to creating a runtime version of npm that users could run without having to have it installed on their machines.
  • gradlew.bat
    • This is similar to the gradlew but for Windows machines
  • settings.gradle
    • This file is used to specify any projects you want to include in your build if your build system had to build multiple projects or repositories.

This initial setup will help configure the expected file structure and executables for you to begin creating Gradle files, tasks, and running your project!

dependencies

As we continue creating build tools in Gradle, we will need certain dependencies to create specific types of tasks to build assets, deploy to third party services such as Heroku, or notify a Github Pull Request that a certain task has been accomplished. When using npm, we have the install command that allows us to use npm’s registry to download and install any other libraries we need. However, for Gradle, it isn’t quite as straightforward.

In order to add a dependency for a Gradle task to use, we need to first tell Gradle of the dependency in the build.gradle file. If you were using CloudFoundry to deploy code or create PR Review apps, you would change your file to look something akin to this:

gradle
buildscript {
    dependencies {
      classpath 'org.cloudfoundry:cf-gradle-plugin:1.1.3'
    }
}

Here we see that we are defining a specific dependency through a Java syntax specifying where the package lives. In this case, it lives in the org library as cloudfoundry and we are grabbing the cf-gradle-plugin at version 1.1.3. With the dependency specified, we can then use it in whatever Gradle task we want!

One thing to note is that there is a repositories object defined in your buildscript. That repositories object acts in the same way as adding or changing the npm registry that you configure. Some dependencies are located in different repositories, so before you try to add one make sure that it can be located in one of the repository you have listed. If it isn’t, read up on how to use other external repositories in Gradle’s documentation.

Using dependencies

By themselves, dependencies are useless and just a bunch of weight. What you care about is using the dependencies you specify to achieve a goal. In npm, you will have just referenced the specific package in a npm script such as:

"build:js:watch" : "webpack --config=webpack.config.js -w"

In Gradle, you will use the dependencies by putting them into the runtime through the apply function. If we were to take the CloudFoundry dependency you added above, we can begin to use it by adding apply plugin: 'cloudfoundry' to the build.gradle file or the separate Gradle task file you are working in. Once the plugin has been applied, you can use the instance similar to how you would treat a Node Module through an import or require.

scripts

When using npm, there exists a "scripts" object that defines a key value pair definition of tasks you can execute. For Gradle, we use files that implement the desired effect, similar to if you had an npm script call out to execute a .js file that hides the internal implementation. Let’s take a quick look at an example gradle file to help break down some basics that you may have used when creating npm scripts.

def nodeVersion = '8.0.0'

def configureNode {
    node {
      version = nodeVersion
      download = true
      nodeModulesDir = file("${projectDir}")
    }
}

task nsp(type: NpmTask, dependsOn: 'npmInstall') {
    configureNode
    args = ['run', 'nsp']
}

task npmInstall(type: NpmTask) {
    configureNode
    inputs.file('package.json')
    outputs.upToDateWhen { file('node_modules').exists() }
    npmCommand = ['install']
}

Let’s break this down by code block.

First, def is used to define a variable called nodeVersion and store a string.

Next, def is defining a function called configureNode that can be called anywhere in its visible context. You can abstract out any replicated logic into a function definition using def to help keep similar pieces for each task. Notice that def is best read as “defines” similar to how you can have a let or const in JavaScript.

After our def, we have our first task called nsp. It specifies its type as a NpmTask to help Gradle understand that this task uses npm and not another type of task related to other pieces of the build. This task dependsOn another task called npmInstall that we will see defined right after this task. Once it’s dependent task has been run, this task will run the command nsp in a shell instance running the Node Security Project.

Lastly, we have our npmInstall task that runs a npm install from the input package.json file.

run

npm’s run command runs the given command in the shell and allows us to use npm beyond just a dependency management tool. Gradle’s equivalent comes in its own CLI. This is the easiest of all of the transpositions. Given you have installed the CLI all you have to do is run gradle yourtasknamehere and Gradle will run the given task. It’s really that simple!

Finish.

Gradle is a unique tool in that it allows you to specify build pipelines that can replace some of npm’s tasks, but can also easily integrate into using npm scripts directly so that the transition is not difficult. Hopefully this overview gave you a quick glimpse into Gradle as a tool and how to begin to grok more complex build systems that are using it.

For more in-depth guides like how to run Webpack with Gradle, check out Gradle’s Guide section on their website.