« Home

Using make in frontend projects

Summary: Using make can greatly simplify the maintainance of your project. If any command requires more than a few words to type, consider adding it to a Makefile.

It’s common to use package.json scripts to run commands in node-based projects, for example, npm run dev.

Recently, I came across several articles discussing the use of make and decided to give it a try.

As a result, I’ve started using make in all of my frontend projects, and I really like it. Here’s a simplified example of a Makefile for one of my work projects:

devpod: # start a devcontainer and ssh into it
	devpod up .
	devpod ssh lmst
dev: # run in a dev mode
	npx vite
type:
	npx tsc --noEmit
size:
	npx vite build
	npx size-limit
test:
	npx tap

I think it’s better than scripts in a package.json file, because of:

For development I use devcontainers + devpod setup, and connecting to a devcontainer is pretty much to type:

devpod up .
devpod ssh projname

Now I’m just typing make devpod, and after a few seconds I’m inside a devcontainer.

In a team, this approach will work only if each of team’s members uses a platform with a make available. On linux and Mac, it’s already included. On a Windows system you need to install make by yourself.

A small tutorial on how to start using “make”

It’s pretty easy to get started with make -just create a Makefile in the project’s root directory.

Makefile consists of targets. For example, in the make build command, build is a target.

Let’s add a type target, so we’ll be able to use make type to run typescript checks in our project:

type:
	npx tsc --noEmit

Nice! To build our project, we’ll use build target. Typically, we want to check types and then build the project. For this, we can use dependencies:

type:
	npx tsc --noEmit

build: type
	npx vite build

Now, if we run make build, actually these commands will be executed:

npx tsc --noEmit
npx vite build

Variables

Some commands differ by just one flag, such as running typescript type checks mentioned above either single time or in watch mode:

npx tsc --noEmit
npx tsc --noEmit --watch

We can duplicate these commands:

type:
	npx tsc --noEmit
type-watch:
	npx tsc --noEmit --watch

Or we can use variables:

type:
        npx tsc --noEmit $(WATCH)

type-watch: WATCH = --watch
type-watch: type

Here, the type-watch target just sets a WATCH variable, and re-uses type target.

A “docs” Pitfall

If you run a target and there’s a file or directory with the same name exists, make will think that this target is up to date and won’t do anything!

A classic example here is a docs target - most probably you would like to run make docs to generate the documentation, but if your documentation is placed in the docs directory, you’ll see this message:

make: 'docs' target is up to date.

To always run the specified target in such scenarios, we need to use a special .PHONY target:

.PHONY: docs

docs:
	npx vitepress