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 set up 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 ofnpm
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 its 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 grow more complex build systems that are using it.
For more in-depth guides, check out Gradle’s Guide section on their website.
Sparkbox’s Development Capabilities Assessment
Struggle to deliver quality software sustainably for the business? Give your development organization research-backed direction on improving practices. Simply answer a few questions to generate a customized, confidential report addressing your challenges.