Makefiles for Frontend

An alternative to npm package scripts

Dave Honneffer
Jan 27, 2020 · 4 min read

While npm scripts are built-in and work well for a variety of situations, there are times when another task runner might make more sense.

My goal is not to suggest you always use Makefiles instead of npm scripts — but rather show possible advantages and provide an introduction. Doesn’t hurt to have one more tool in the toolbox, right?


  • Makefiles can easily include documentation, or even self-document
  • Makefiles support inheritance — this means you can easily share a common set of commands across projects in your team — we even distribute our commands in an npm module!
  • Make can help consolidate any shell scripts you’re using
  • You can use npm-scripts and Make together, these aren’t mutually exclusive approaches
  • Make includes a dry run flag (), which is extremely helpful when creating more complex scripts
lots of old hammers — because Make is ‘one more tool in the toolbox’
lots of old hammers — because Make is ‘one more tool in the toolbox’

Quick intro

In this section we’ll do some comparisons between NPM scripts and what they’d look like when translated to Make. We’ll also quickly review the structure of a Make rule.

Basic rules

"scripts": {
"build": "rollup -c"

Here we have a typical npm script (in our ) to run Rollup. We could run this with or .

npx rollup -c

This same command would look like this 👆 in Make (in a file named ), and can be run with .

We do need to use a task-runner (either or ) to actually run our commands, but things still work the same outside of that small change. [1]

Breaking it down

A single script in Make is called a rule, and rules have the following form:

target: prereq1 prereq2 prereqN
  • Note that the indentation for commands — must be a tab. [2]
  • A target can have as many commands as needed, they are all run in a separate shell. [3]
  • Prereqs are often another target, but can be used to set up variables for your rule. Examples of these are both shown below.

Running multiple rules

To run multiple commands with npm package scripts, you might do something like this.

"scripts": {
"build": "npm run build:js && npm run build:css",
"build:js": "rollup -c",
"build:css": "sass main.scss dist/main.css"

The syntax for this in Make is shown below — note Make will automatically short-circuit any failures, so if the command fails, won’t run; the same behavior we’re getting above via our .

build: js cssjs:
npx rollup -c
npx sass main.scss dist/main.css

Recipe-level variables

These can be used by Make, but not by any programs we run. They’re useful in cases where we’d just be adding another flag to an already existing command — as we often do when running in development vs. production environments.

In the example below:

  • running will first set the variable to , then will run the target as a prereq (meaning of course it will run the and tasks) — this means the task would be run as
  • running would just run and tasks, meaning won’t be set to anything — thus the task would be run as
  • we can use in our Make recipes, but Rollup and Sass won’t know about as an environment variable
dev: WATCH = --watch
dev: build
build: js cssjs:
npx rollup -c $(WATCH)
npx sass $(WATCH) main.scss ./dist/main.css

If we do want our recipes to have the environment variable set, that’s an easy fix! We just add like so:

prod: export NODE_ENV = production
prod: build

An example Makefile

You can also view a gist for this example instead.

# this tells Make to run 'make help' if the user runs 'make'
# without this, Make would use the first target as the default
# here we have a simple way of outputting documentation
# the @-sign tells Make to not output the command before running it
@echo 'Available commands:'
@echo -e 'dev \t\t — \t run the development environment
@echo -e 'prod \t\t — \t build for production

# setting a command up so we can run yarn before any other command
# this way our dependencies will always be up to date

# running 'make dev' will first run yarn
# then start Rollup and Sass in watch mode
dev: WATCH = --watch
dev: yarn build
# running 'make prod' will first run yarn
# then will clean up our old css build
# then will run Rollup and Sass for a production build
prod: export NODE_ENV = production
prod: yarn clean build

# the '-' before rm below allows that command to fail
# even if this fails, make will continue running
# this is a contrived example
-rm ./dist/main.css
build: js css
npx rollup -c $(WATCH)
npx sass $(WATCH) main.scss ./dist/main.css



[1] — We can directly run the files as well if this is preferred.

[2] — It actually doesn’t have to be a tab, but you should leave the Make-default alone. If you really want to change this, you can look into the variable.

[3] — The variable can work around this, but is generally advised against.

[*] — Yes, we should have set up

Photo by Sucrebrut on Unsplash Blog – Product, Design, and Tech

Posts from the people building the world’s greatest…