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.
Contents
1 Naming & structure
For non-minified file file, its 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, its 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 dont 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 dont 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.
4.2 dev_symlink and %.dev targets
dev_symlink is the last prerequisite of dev target. Its goal is to maintain the correct symbolic link in conjunction with %.dev target. There is no actual files like foo.dev, its only phony for matching %.dev target in order to make symbolic link:
dev_symlink: $(addsuffix .dev,$(TARGETS_ALL)) %.dev %.min: if [ "`readlink $*.js`" != "$.js" ]; then\ rm -f $*.js ;\ ln -v -s $.js $*.js ;\ fi
For example, using foo.dev, $* is foo and $ is foo.dev, the actual shell command would look like:
if [ "`readlink foo.js`" != "foo.dev.js" ]; then\ rm -f foo.js ;\ ln -v -s foo.dev.js foo.js ;\ fi
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 dont want to have Java on my computer, so Ive 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 havent fallen into. If you have any suggestions or corrections, feel free to do so in the comments.