Using Make & Makefiles to Automate your Frontend Workflow
Make is a build automation tool that’s been used for decades to build software. Learn how you can leverage make to automate frontend web development

make is a Unix tool that’s been around since the mid 1970’s, and is still widely used today for automating software build processes.
make is the OG of build tools
The make tool is available for just about every platform you can imagine, and is installed with the XCode CLI Tools on the Mac, and WSL2 on Windows. Probably you have these installed already if you’re doing development.
This article describes how you can leverage make to automate your frontend web development via a standardized CLI API of your own design.
Link Why use make?
With all of the build tools and automated systems out there, why should we use make? A few reasons:
- It lets you define a simple, standardized CLI API your team can use across projects
- The verb-noun semantics of make target are easy to digest & remember
- You effectively get local command aliases for each project that are context and project aware
- The tooling under the hood is abstracted away, and can be swapped out at any time
- make is already installed on your development machines, and is designed to automate builds
So people on your team (or just you, if you’re a team of one) can just type make dev to spin up a local development environment, without having to know what happens under the hood to make that happen.
If you decide to use a different local dev environment, you can swap it out, and make dev will still “do the thing” to make it happen.
Link Real World Examples
As discussed in the An Annotated Docker Config for Frontend Web Development article, I use Docker for a local development environment, and as discussed in the An Annotated webpack 4 Config for Frontend Web Development article, I use webpack as a build system.
As such, it’s much nicer to be able to type, say, make dev than it is to type docker-compose up when I want to work on a project.
However the real key here is that no matter what machinery needs to be run under the hood, make dev spins up the development environment.
Abstracting away what does the thing from the command has many benefits
So here’s an example of some of the commands I have in my Makefile, and what they do:
- make dev — does whatever needs to be done to spin up the project’s local dev environment, so I can work on the project
- make build — does whatever needs to be done to build a project’s production ready resources for deployment
- make clean — does whatever needs to be done to rebuild the project environment from scratch
- make docs — for my plugins, does whatever needs to be done to build the documentation
There are more commands of course, but these are a few examples that show how easy it is to onboard someone onto a project.
For a real-world example, try spinning up the devMode.fm website locally!
Link How make works
When you run make via your CLI terminal, it might look something like this:

Running make
make looks for a plain text file named Makefile in the current directory. This file has any number of targets that look like this:

Anatomy of a Makefile rule
- Target — this is normally a file or directory that needs to be built.
- Prerequisites — other files or targets that need to be built before the target can be built.
- Recipe — preceded by a tab, a series of any number of shell commands that are executed to build the target
So in the above example when we type make build it will first do whatever is needed to build the target named up (which is a prerequisite), and then it’ll run the commands in the recipe to build the target named build.
make will rebuild a target when either the target file or directory doesn’t exist, or when any of its prerequisites have been modified and so are newer than the target.
This Makefile Cheatsheet may come in handy when learning how Makefiles work.

Anatomy of a Makefile preamble
You also can define and use variables in your Makefiles, the ?= conditional assignment operator used above assigns a default value if the variable isn’t set already.
This can be useful in assigning default values that can be overridden via shell environment variables.
Using make, we can get local aliases to run project-specific commands
We mentioned earlier that targets are normally files or directories, but we can use the special built-in target named .PHONY to specify that the target is just a list of commands that should always be run.
We leverage this to use our Makefile to define local aliases that run commands to do various things with our project.
So let’s have a look at a few examples.
Link Craft Scaffolding Makefile
This Makefile is one I use in my Craft CMS scaffolding. The CMS or framework in use doesn’t really matter, the applied principles are what is important.
CONTAINER?=$(shell basename $(CURDIR))_php_1
BUILDCHAIN?=$(shell basename $(CURDIR))_webpack_1
.PHONY: build clean composer dev npm pulldb restoredb up
build: up
docker exec -it ${BUILDCHAIN} npm run build
clean:
docker-compose down -v
docker-compose up --build
composer: up
docker exec -it ${CONTAINER} composer \
$(filter-out $@,$(MAKECMDGOALS))
craft: up
docker exec -it ${CONTAINER} php craft \
$(filter-out $@,$(MAKECMDGOALS))
dev: up
npm: up
docker exec -it ${BUILDCHAIN} npm \
$(filter-out $@,$(MAKECMDGOALS))
pulldb: up
cd scripts/ && ./docker_pull_db.sh
restoredb: up
cd scripts/ && ./docker_restore_db.sh \
$(filter-out $@,$(MAKECMDGOALS))
update:
docker-compose down
rm -f cms/composer.lock
rm -f buildchain/package-lock.json
docker-compose up
update-clean:
docker-compose down
rm -f cms/composer.lock
rm -rf cms/vendor/
rm -f buildchain/package-lock.json
rm -rf buildchain/node_modules/
docker-compose up
up:
if [ ! "$$(docker ps -q -f name=${CONTAINER})" ]; then \
docker-compose up; \
fi
%:
@:
# ref: https://stackoverflow.com/questions/6273608/how-to-pass-argument-to-makefile-from-command-line