Use Make with javascript

Javascript build tools

During my frontend time, I have seen some of tools to build frontend projects. Grunt, Gulp and so on. However, these tools often make me get in the way for its verbose configuration and long dependent plugins.

Why make

Actually, most of the tasks we need to build can be run using existing executables in the shell. Sass, Uglify, Unit test and so on have awesome executables already, what we need is just an easy way to run our commands to build projects without extra tooling and plugins. And Make comes to rescue.

What and how

GUN Make is a tool which controls the generation of executables and other non-sources files of a program from the program's source files.

Though make is perceived as being only for C projects, it is a general-purpose build tool that can be used on any kinds of projects. And in the case of a frontend build, make can help us construct dependency graph and execute some commands directly.

At begin, we need a file called Makefile. A Makefile consists of rules that has ite own specific purpose. Each of these rules has:

  • a target, it can be a file or just a name
  • prerequisites, an optional set of files the target depends on
  • commands

And the syntax for a target is:

target: prerequisites
    commands

A Web project Makefile

Next, we will build a simple web project that uses Compass, jade. The contents of our source files are not important, what we concern is what we need to do with the files to make them ready to run.

At first, we should create package.json file to install the dependencies for this project, then run npm install. We could also achieve it in make.

{
  "dependencies": {
    "jade":    "1.9.2",
    "uglify-js":     "~2.4.0"
  }
}
NODE_MODULES = node_modules

install: package.json
    @npm install

Next, we may need to compile all jade templates which under the templates directory to the single file. Here is the sample.

template_js := templates/*.js
template_source := templates/*.jade

$(template_js): $(template_source)
    jade --client --no-debug $^

In first two lines, we use variables to define the source of our template, and in the recipe, we use jade --client command to render the source template file. $^ is a special make variable, which is a list of all the dependencies, separated with spaces. Next time, we need to concatenate our js file including runtime.js and jquery and minify it.

locals = js/locals.js
app_bundle := build/app.js
lib := vendor/jquery.js \
        node_modules/jade/runtime.js
uglifyjs := ./node_modules/uglify-js/bin/uglifyjs

$(app_bundle): $(lib) $(template_js) $(locals)
    $(uglifyjs) -cmo $@ $^

The locals.js is something about locals object and the $@ refers to the target. Besides, we can also compile our sass code.

scss_file := sass/*.scss
%.css: %.scss
    compass compile $(scss_file)

At last, we may add a .PHONY target, which is not really the name of a file, and tell the make not to find this file. What's more, when you run make without a target, the first entry is the one to run, usually named all by convention.

all: install $(app_bundle)
.PHONY: all

The whole Makefile looks like this:

uglifyjs := ./node_modules/uglify-js/bin/uglifyjs

app_bundle := build/app.js

locals = js/locals.js
template_js := templates/*.js
template_source := templates/*.jade
lib := vendor/jquery.js \
        node_modules/jade/runtime.js

scss_file := sass/*.scss
node_modules = node_modules

all: install $(app_bundle)

$(template_js): $(template_source)
    jade --client --no-debug $^

$(app_bundle): $(lib) $(template_js) $(locals)
    @$(uglifyjs) -cmo $@ $^

%.css: %.scss
    compass compile $(scss_file)

install: package.json
    @npm install

clean: 
    @rm -rf $(node_modules) $(template_js) $(app_bundle) $(scss_file)

.PHONY: all clean

Now we can create a web page to see the result.

<!DOCTYPE HTML>
<html>
    <head></head>
    <body>
        <script src="./build/app.js"></script>      
    </body>
</html>

Reference