How to convert your Gruntfile to npm scripts
In this post we’re going to take a quick look at npm scripts, and the thought process behind writing them. We’ll do this by taking Mozilla’s Makerstrap project and its Gruntfile and convert it to npm scripts to show us the benefits of npm scripts as a frontend build tool.
I should note before starting that there are many use cases for Grunt or Gulp, and npm scripts may not cover all of them. While it’s likely npm scripts can reduce your Grunt or Gulp files greatly, this post’s goal isn’t to diminish the usefulness of those task running services.
The goal of this post is to introduce another way that arguably has some simplification benefits. I ❤️ what Gulp and Grunt have done for frontend workflows—npm scripts is simply another option that we should consider.
If you already have a good understanding of npm scripts you can skip this overview.
If you’re less confident with npm scripts, all you really need to know is that it allows you to add tasks to your package.json’s
scripts object. These tasks are run pretty much exactly like how you would run them through the command line.
What does a npm script look like?
start: node server.js is a example of a simple script that allows a node server to start when you type
There are 3 main things you should know about npm script naming:
1) built-in script naming
npm has a list of ready-to-use npm script property names such as
test. In fact the command you likely use daily,
npm install, is an npm script! You can run a script by running
$ npm <name of script>.
2) pre and post script naming
When naming your scripts, you can add
post to tell npm to run them before or after. So if you have a
start script, you could also write a
prestart script that will run something before the
start script runs.
3) custom script naming
If you’re like me, and want to be more 🐠expressive🐠, you can use your own script names. If for example we wanted to re-write that start command we could do so with
getTheFluxCapacitorFluxingDoc: node server.js.
To run our custom named script we’d then run
$ npm run getTheFluxCapacitorFluxingDoc, with
run telling npm to run a non-standard named script.
Running multiple scripts
To run multiple scripts we can use syntax like
npm run watch:* which looks for any
watch:<asubtask> and runs them. This allows us to break use a script strucutre that represent smaller individual tasks, and then run them altogether.
Another popular way of running scripts is with
npm-run-all module which allows us to use its
--parallel argument to run scripts in parallel.
Just like on the command line, npm scripts also allows us to use
&& syntax to add additional commands to a script.
TLDR: Why do I use npm scripts?
- if you’re already using npm, you don’t need to install anything. No extra task runner to install
- no added abstraction of learning the ins and outs of Gulp and Grunt (that isn’t to say there aren’t some benefits to their abstraction too)
- in my experience the code and time spent writing npm scripts is considerably less!
- no more plugins
We’re going to work through converting Makerstrap’s Gruntfile into npm scripts that will live in
package.json. This should hopefully show that simplifying your build process isn’t too painful. Let’s take quick look at the Gruntfile:
So what’s under there anyways? Under wear?
To start our process of writing npm scripts, let’s start by breaking the Gruntfile into it’s different pieces. There are 4 registered tasks at the bottom of the Gruntfile:
- default (works with just running
We’ll use this structure for writing our npm scripts.
These tasks would be typically run by running
grunt, and were used as a way to get the necessary server and files going for development. The task is made up of three pieces that are wrapped together in a
grunt.registerTask('default', ['less:development', 'shell:runServer', 'watch' ]);
Let’s first look at trying to rewrite the
less:development part of the task as an npm script.
In the Gruntfile we used
'demo/compiled/demo.dev.css': 'demo/less/demo.less' to compile
demo.dev.css and place it in a
We can use the great autoless module to help us compile our
.less file. While autoless does have a
watch feature we don’t need that here because we just want the compile feature.
autoless doesn’t let’s you specify a specific file, so we can just give it the
demo/less/ directory as a source, and
demo/compiled/ as the output point for the compiled
Here’s what the above Gruntfile code looks like as an npm script:
Our next ~task~ to recreate as a script is a watch task:
The above code watches a set of
.less files and runs the
less:development compile task when it sees a change.
We can use
autoless here again. We’ll also give it the general directory name:
Finally, we need to look at adding a short script to replace this command.
This Grunt task can be summed up very nicely like so:
"server": "node demo/server.js"
server is one of npm’s reserved properties.
For the stats task we’ll need to write a script that checks the filesize. We already have the
less:dev task completed above.
grunt.registerTask('stats', ['less:development', 'filesize']);
The above code outputs the following when
$ grunt stats is run:
It returns the size
makerstrap.min.css, it also tells us how long it took for the task to run.
For the purpose of this post, we’ll skip trying to output nice charts, and focus just on returning the file sizes.
I couldn’t find an alternative to the Grunt filesize plugin, but it’s a very simple feature to recreate. Just create a tiny
filesize.js module and use
fs.stat to return the filesize values.
Because npm scripts allows us to run a node command from the script, we can use
filesize.js like so to return the filesizes:
stats: node filesize.js makerstrap.complete.min.js && node filesize.js makerstrap.min.js
which outputs something very similar to the grunt plug-in.
We’ve already written a
filesize script, so we’ll just focus on
grunt.registerTask('build', ['less:build', 'filesize']);
The above code builds our Makerstrap app readying it for deployment.
demo/compiled/demo.css from their respective
- less/build/makerstrap.less -> dist/makerstrap.min.css
- less/build/makerstrap.complete.less -> dist/makerstrap.complete.min.css
- demo/less/demo.less -> demo/compiled/demo.css
To accomplish this, we can re-use our
less-dev task and combine it with another
autoless command to handle the two
build/ files. Altogether our npm script for the build task would look something like this:
npm run less-dev && autoless --source-map --no-watch less/build/ dist/
Now, we can write the script for the final Grunt task:
copy:latest creates a directory called
/dist and adds a build version of your app.
copy:version creates a directory called
/dist and adds a build version of Makerstrap’s css files.
We can use the great
cpx module to copy files to
/dist like so:
So if we take all of our newly written npm scripts, we should have something that looks like this:
For ease I’ve used the
npm-run-all module to be able to run the server and watch tasks in parallel when I run
All told, that wasn’t too painless, and should hopefully be easier to handle as your app and frontend pieces evolve.
Here’ the final version of Makerstrap done using npm scripts: github.com/pippinlee/makerstrap
- Substack on “npm run”
- Kate Hudson’s “advanced front-end automation with npm” talk
- npm’s great docs on “run-script”
- Please do be careful if you include the
getTheFluxCapacitorFluxingDoccommand in your npm script, I can’t tell you how often I’ve forgotten to include an
endscript to cool and shut it down and then ended up back at Alamos with Feynman.