More or less these tools all depend on plugins to do even the simplest of tasks, from copying a file to creating an archive, you’ll need a plugin for it.
While, in theory this leads to great flexibility, in reality we are just duplicating the unix ecosystem. This also quickly leads to your project having a big bundle of development dependencies, and the tasks you are doing are just run-of-the-mill ordinary copying, building, bundling and minification.
Make is a pretty old tool, and for most developers coming from a native development background it is an old friend, but most web developers that don’t come from a computer science background don’t really seem to ever get introduced to it. However, that does not mean make is not a good tool, in-fact its far too underrated these days.
You have the entire unix ecosystem available to you, pipes, streams, utilities, it’s all there, most of the tools you’ll need to build are already on the system. If you are developing on a non-unix machine like Windows, then you should at least do yourself a favor and install a proper shell and a minimal bundle of tools like gow, perhaps a better terminal emulator to replace
cmd.exe as-well, like cmder.
Make works by defining rules in a file typically called
Makefile, no file extension, and do take note that capitalization matters. Rules are made up from targets and prerequisites, which can have shell commands executed upon them, which is usually referred to as the recipe.
In general a rule looks something like this:
targets: prerequisites recipe # Do take note, that is a tab, not spaces.
So with that knowledge, lets do a simple example. We want to copy a file from
input.txtwhich is the prerequisite to
output.txtwhich is the target. Our recipe will be a simple command line utility.
output.txt: input.txt cp $< $@
Lets move on to something more practical.
Compiling our Code
To do this we will need to define a rule that does a one to one conversion between the files in our src directory, to the transpiled files we want in our lib directory.
It’s all very similar to bash scripting,
$(VARIABLE) does variable expansion and
$(@D) is another automatic variable, it holds the directory part of the file name of the target.
Bundling our Code
Browserify, Webpack or just plain concatenation is also a rather standard thing to do if you are going to distribute your code for the browser environment. In order to do this we will need a rule that has a single target, takes all the sources files as prerequisites, feeding them through the bundler as the recipe.
# Like before, we will store our bundler in a variable BUNDLE = browserify # Along with its invocation flags. # This is an example of where using variables makes things easier # We will want to pass the same flags to both babel and # the babelify transform, since it is already a # variable there is no duplication. BUNDLEFLAGS = --transform babelify [$(JCFLAGS)] # Next we'll define our bundle filename DIST = dist.js # Finally we'll define the rule, again the goal is to pass all # prerequisites into the bundler and output it to the target. # We could have used the already compiled library files, # but generally this would yield a slightly bigger file # since compilers often generate helpers. $(DIST): $(SRC) $(BUNDLE) $(BUNDLEFLAGS) -o $@ $^
Optimizing our Code
Minification and dead code removal is another common build step. In this case we’ll just define a rule that takes the previously defined bundle target as a prerequisite and run it through uglify, we’ll do some simple substitution to get our minified bundle name based on the non-minified bundle name.
DIST_MIN = dist.min.js # Using uglifyjs as our minifier MIN = uglifyjs # No flags, just yet at least. MINFLAGS = # This rule will be really simple. # It's a single target with a single prerequisite passed through our optimizer $(DIST_MIN): $(DIST) $(MIN) $(MINFLAGS) $< -o $@
Cleaning our build
Make also has a special target called
.PHONY, the prerequisites of this special target are considered to be phony targets. Make will run the recipie of such a target unconditionally, regardless of whether a file with that name exists or what its last-modification time was, it’s useful in cases where we want to run a script like task regardless of what files have been generated, for example if we need to clean our build.
clean: rm $(LIB) rm $(DIST) rm $(DIST_MIN)
Make has a bit of a undeserved reputation for being hard to understand, it is not that different from shell scripting. In my opinion the syntax is clear and concise, you get free interop with everything, it has streaming, it has pipes, it has a big ecosystem called unix, so with that said. Viva la revolución, write a makefile today!
Feel free to hate on make at me via @caspervonb on Twitter
We often tell our peers, especially juniors that programming style is just a matter of personal preference, and i used to share this sentiment, but the more i think about it the more i realize that this is just bad advice, even tho the compiler might allow you to write braces or spaces in five different styles, that doesn’t mean you should. The language, in combination with the standard library always sets a precedence.
In this day and age, nearly everyone uses git. From designers to engineers we all depend on it. Despite git being a really good version control system, enevitably we do sometimes mess up and have to fix our configuration and/or working tree, or just plain forget how to do that one thing we rarely do.