Efficiently Calling JS Bundlers in a Makefile

It is an open secret, that I prefer make over all those newfangled build tools, that first need a ton of configuration to get off the ground. A simple rule to add autoprefixer goodies to a CSS file is exactly two lines long:

out.css: in.css
./node_modules/.bin/postcss "$<" --use autoprefixer --output "$@"

This little rule uses GNU make Automatic Variables, which allows us to copy-paste it or use it in a pattern rule to address a bunch of CSS files at once.

Given, that the make command is in your path, updating out.css means typing

make

and you’re done. It will also check timestamps and only rebuild the file, if in.css changed.

So far, so good. However, sometimes we have a single command, that issues several files at once, which depend from several others. For example, if you use Webpack or Rollup to handle your CSS and JS files, this will ring true immediately for you.

How do we tell make then, that it should call webpack, if any of the source files changed, and that it should call it exactly once for all of our destination files?

There is a good answer on StackOverflow, that details the process. In a nutshell, we will leverage a virtual intermediate file for this. I.e., we tell make to build a file, that won’t ever exist, and then depend our output files from it. We go even a step farther and tell make, that this file won’t ever exist.

Such a Makefile will look like this:

dist/output_1.js \
dist/output_2.js \
dist/output_3.js \
dist/output_4.js: intermediate-build-step

.INTERMEDIATE: intermediate-build-step
intermediate-build-step: src/input_1.js src/input_2.js
mkdir -p dist
webpack

In lines 1 to 4 we tell make, that our four output files depend on the file intermediate-build-step. In line 6, we tell make, that this file is virtual, or “intermediate” in make lingo. This means, make knows, that the file won’t exist. It won’t look for it, when it compares timestamps in order to determine, if the output files should be made.

Line 7 handles our dependencies. List all input dependencies for all output files here. You can also do that in a separate rule:

dist/output_3.js: src/additional_requirement.js

In line 8, we create the output folder, just to be sure. If it already exists, the -p flag ensures, that make will continue. Finally, in line 9 we call Webpack.

The wonderful thing about this solution is, that you can call make over and over again, and it will only ever run Webpack, if any of the input files changed.