Make For Asset Management

In this article I want to look at the make utility. Yes, that make utility.

$ man make

MAKE(1)           LOCAL USER COMMANDS           MAKE(1)

NAME
       make - GNU make utility to maintain groups of programs

Can this humble utility simplify a web developer's life managing a complex system of front end assets1?

In the past, I've used tools like Grunt, Gulp, Ant, and PHP's own Assetic to do this. Nothing felt right. Complex configurations, new scripts, new dependencies. There must be a better way.

Digression Warning

If anything is known about me, it is this: I'm lazy (proof). Don't make me do any extra work I don't have to do. Seriously. That previous sentence was only one word, that's how lazy I am. To me, laziness is an ideal. It's an ideal I can believe in, a rare gem awash in an ocean of ruthless pragmatism. But I digress.

Digression Averted, Make To The Rescue

Today, I want to talk about make. I want to talk about how make allows me to get work done faster so I can spend more time thinking about interesting problems, and less time building the plumbing for my web applications.

And, let's face it, plumbing carries shit. The less time I spend working on plumbing, the less time I have shit on my hands.

A Brief, Incomplete, Mostly Wrong History of Make

Make was originally introduced by Dr. Stuart Feldman of Bell Labs, in April 1976. Prior to creating make, Dr. Feldman was on the original team that created Unix. Afterward, he helped author the first Fortran 77 compiler. It is almost impossible to overstate the impact that Dr. Feldman has had on software development.

Prior to widespread adoption of make, software was distributed with OS-specific "make" and "install" scripts. Make allowed multiple targets with commands to be written uniformly into one standardized build script. And this elegant solution is still one of the most widely distributed tools ever almost 40 years later.

Great. How Does This Relate To My Job?

The essence of make is to define repeatable transformations from a source to a target.

Ok. So what does that mean?

Transformations are shell commands, grouped into logical tasks by task name.

The source and target are filesystem objects. So what we have is a collection of shell commands, organized into logical groups of tasks, which define transformations in the filesystem.

Anatomy of a Makefile for the Web

By convention, a makefile's first target is a default catch-all, usually all:

all: clean build

More on this below.

Install

A web makefile is going to have different targets depending on things like libraries and the project structure. If dependencies are managed by Bower (and assuming bower is already configured and working), an install target like the one below works:

install:
    bower install

I usually have a few extra steps: convert a JS library from CommonJS to RequireJS, and create new distributable directories (note the buildDir macro2):

buildDir = /var/www/public

install:
    bower install \
    r.js -convert $(buildDir)/js/react-dropzone/ $(buildDir)/js/react-dropzone/ \
    mkdir -p $(buildDir)/js $(buildDir)/css \

Clean And Build

The build step will compile CSS and javascript. For the example below, we're compiling LESS to CSS and JSX to JS. As the name implies, clean removes files generated during build.

assetsDir = /var/www/assets
buildDir = /var/www/public

build:
    cp -r $(assetsDir)/dist/* $(buildDir)/js \
    cp -r $(assetsDir)/dist/font-awesome/fonts $(buildDir)/fonts
    jsx --harmony --extension jsx --es6module $(assetsDir)/jsx $(buildDir)/js \
    lessc $(assetsDir)/less/master.less $(buildDir)/css/master.css

clean:
    rm $(buildDir)

Watching For Changes

Adding a --watch flag to the jsx command allows for automatic rebuild of JS resources when changed. Let's create a target for it.

watch:
    jsx --harmony --extension jsx --es6module --watch $(assetsDir)/jsx $(buildDir)/js

Putting It All Together

The makefile below contains two macros: assetsDir, buildDir, and four targets: install, build, watch, clean.

assetsDir = /var/www/assets
buildDir = /var/www/public

install:
    bower install \
    r.js -convert $(assetsDir)/dist/react-dropzone/ $(assetsDir)/dist/react-dropzone/ \
    mkdir $(buildDir)/js $(buildDir)/css

build:
    cp -r $(assetsDir)/dist/* $(buildDir)/js \
    cp -r $(assetsDir)/dist/font-awesome/fonts $(buildDir)/fonts \
    jsx --harmony --extension jsx --es6module $(assetsDir)/jsx $(buildDir)/js \
    lessc $(assetsDir)/less/master.less $(buildDir)/css/master.css

watch:
    jsx --harmony --extension jsx --es6module --watch $(assetsDir)/jsx $(buildDir)/js

clean:
    rm $(buildDir)

1. By assets, I'm referring to: javascript, preprocessed css, fonts, third party dependencies, and anything else that will eventually become accessible to the public. 2. Macros are variables local to the makefile.

Tags: make, frontend

Prototypal Inheritance In PHP

Generally, programming languages follow one of two types of inheritance patterns. These patterns describe how objects are defined, and how properties and methods are composed between them.

Classical Inheritance

Likely the most familiar pattern is classical inheritance, which is how PHP and many other languages are designed. Briefly, classical inheritance involves keywords like classes (abstract, final), objects, interfaces, and methods with special usage, like constructors and destructors. Classes extend other classes (single or multiple inheritance -- PHP implements single inheritance).

<?php

class vehicle {
    protected $engine = null;
    protected $milesTravelled = 0;
    protected $doorCount = 0;
}

class truck extends vehicle {
    // $engine, $milesTravelled, and $doorCount are inherited from vehicle
    protected $doorCount = 2;
    protected $hasTailHitch = true;
}

class motorcycle extends vehicle {
    // $engine, $milesTravelled, and $doorCount are inherited from vehicle
    protected $doorCount = 0;
}

$vehicle = new vehicle();
$truck = new truck();
$motorcycle = new motorcycle();

Prototypal Inheritance

The lesser known and used of the two patterns is prototypal, which is how Javascript handles inheritance, for example. Instead of classes and interfaces, there are only objects. Objects are dynamically assigned properties and methods, and new objects (children) inherit the properties and methods of the parent object, through the parent's prototype. The prototype is a reference to a set of properties and methods, and because it is a reference any changes are reflected across child objects sharing the same prototype.

<?php

$vehicle = new stdClass();
$vehicle->engine = null;
$vehicle->milesTravelled = 0;
$vehicle->doorCount = 0;

$truck = new $vehicle(); // doesn't work
$truck->doorCount = 2;
$truck->hasTailHitch = true;

$motorcycle = new $vehicle(); // doesn't work
$motorcycle->doorCount = 0;

Whoa, wait a minute. There isn't any structure to this code. This is a very dynamic and powerful concept that radically simplifies things. The only problem now is that the lines where we instantiate our children (truck and motorcycle) will not actually return the right results, since PHP is class-based and all (a runtime error will probably trigger).

Making Invocation Work

As a dynamic language, PHP is well suited to the protypal pattern, and has native support for assigning new properties (and methods as of 5.3+) to objects at run time, so most of the hard work has already been done for us. The only missing piece is native syntax for the invocation lines that create child objects. So what does it take to make those lines work? With a slight syntax tweak, turns out not much, actually:

<?php

public function __invoke() {
    return new self($this);
}

/**
 * In a hypothetical class, defining this magic method will allow the object to be invoked,
 * so instead of:
 *
 * $truck = new $vehicle();
 *
 * The syntax becomes:
 *
 * $truck = $vehicle();
 */

If we create a class and define its magic __invoke() method to return a new instance of itself, our object invocation syntax now works. In order to implement prototypal inheritance more completely, a little more work is involved, however, a well-formatted solution is still less than 100 LOC.