I have been using Makefile for minifying CSS and JavaScript for some time, and I really like how simple you can utilize Makefile to perform tasks if you have it written well.

Normally, you would want to run non-minified JavaScript while in development and minified script in production.

You can view all files in this Gist.

1   Naming & structure

For non-minified file file, it’s named as file.dev.js and its minified version is named as file.min.js. There are the sources to be symbolic linked from file.js, for which is linked that depends on whether you are developing or deploying.

So, the HTML should always use file.js, not *.dev.js or *.min.js. This way, it’s much easier to switch without needing to touch HTML files.

Normally, I would lay files like:


/Makefile
/css/Makefile
/css/foo.dev.css
/css/bar.dev.css
/js/Makefile
/js/foo.dev.js
/js/bar.dev.js

But in this post, I would have to flatten them out because I am using Gist to store these example files and I don’t think Gist can support files in directories:


/Makefile
/Makefile.CSS
/foo.dev.css
/bar.dev.css
/Makefile.JS
/foo.dev.js
/bar.dev.js

Using foo.dev.css as example, the minified file and symbolic link file would look like:


/foo.min.css
/foo.css -> foo.min.css

or, if in development:


/foo.css -> foo.dev.css

2   The root Makefile

The root Makefile is only used for carry target onto the Makefiles for JS and CSS, Makefile.CSS and Makefile.JS:


dev min clean:
make -f Makefile.CSS $@
make -f Makefile.JS $@

There are three targets: dev, min, and clean. I don’t think I need to explain what they mean. The recipe is simply invoking the two Makefiles for doing actual work with the target name $@.

3   Makefiles for JS and CSS

I will only show and explain Makefile.JS in this post, they are almost identical. This is the Makefile.JS:


TARGETS = foo bar
TARGETS_ALL = $(TARGETS) foobar

CLOSURE_URI = http://closure-compiler.appspot.com/compile

all: min

foobar.%.js: $(addsuffix .%.js,$(TARGETS))
cat $^ > $@

%.min.js: %.dev.js
curl --data output_info=compiled_code --data-urlencode js_code@$< $(CLOSURE_URI) > $@

dev: $(addsuffix .dev.js,$(TARGETS_ALL)) dev_symlink
min: $(addsuffix .min.js,$(TARGETS_ALL)) min_symlink

dev_symlink: $(addsuffix .dev,$(TARGETS_ALL))
min_symlink: $(addsuffix .min,$(TARGETS_ALL))

%.dev %.min:
@if [ "`readlink $*.js`" != "$@.js" ]; then\
rm -f $*.js ;\
ln -v -s $@.js $*.js ;\
fi

clean:
rm -f $(addsuffix .js,$(TARGETS_ALL))
rm -f $(addsuffix .min.js,$(TARGETS_ALL))
rm -f foobar.dev.js

.PHONY: all clean dev_symlink min_symlink

The TARGETS stores the names of scripts without .dev.js, they are the scripts which need to be minified. In this case, they are foo bar.

The next TARGETS_ALL are foo bar foobar, where foobar is the concatenated file of foo and bar. I want to add the concatenation for this example because reducing files is a good way to speed up page loading a little bit.

4   make dev

Firstly, take a look at dev target:


dev: $(addsuffix .dev.js,$(TARGETS_ALL)) dev_symlink

The prerequisites are expanded to foo.dev.js bar.dev.js foobar.dev.js dev_symlink. The first two are the development or non-minified files, third one matchs foobar.%.js target, and last one is a phony dev_symlink target.

4.1   foobar.%.js target

The third prerequisite foobar.dev.js of dev target matches foobar.%.js recipe:


foobar.%.js: $(addsuffix .%.js,$(TARGETS))
cat $^ > $@

Its prerequisites are foo.dev.js bar.dev.js, which is also $^ in the recipe, and will be concatenated by cat command. $@ is the target, that is foobar.dev.js. Therefore the recipe would look like performing:


cat foo.dev.js bar.dev.js > foobar.dev.js

This recipe is also used for min target.

5   make min

The min is almost the same as dev target. The only difference is the target for minifying files:


%.min.js: %.dev.js
curl --data output_info=compiled_code --data-urlencode js_code@$< $(CLOSURE_URI) > $@

This is the recipe to minify files. You can see the command is running a HTTP request instead of a local command, because I don’t want to have Java on my computer, so I’ve found a way to minify files with remote services.

6   make clean

The clean is for removing generated or symbolic link files:


clean:
rm -f $(addsuffix .js,$(TARGETS_ALL))
rm -f $(addsuffix .min.js,$(TARGETS_ALL))
rm -f foobar.dev.js

7   Thoughts

I know I am not very good at writing Makefile, they may contain some pitfalls that I haven’t fallen into. If you have any suggestions or corrections, feel free to do so in the comments.