Website Builds Using Make

Posted Friday 26th February, 2010

In the interests of improving quality in production, of eliminating repetitive tasks, and of general development time saving, it’s often a good idea to automate some of the website build process. What do I mean by “website build process”? Put simply, the task of preparation and publication to production (your live, open-to-the-internet environment), from a development environment.

In many cases, this may only be a push from your local machine to your web server, via SCP (or worse, FTP). However, if you’re working with any level of intricacy—or if you’re working in a team environment—you will, no doubt, have experienced (amongst other things) the logistical nightmare of multiple CSS and JavaScript files: First building page specific style sheets, so you maintain a single HTTP request for each, and secondly compressing those files so that any unwanted elements are removed for production (e.g. comments, needless whitespace etc.). If you’re not sure why you’d want to do these things, I’d recommend having a look at Steve Souder’s “High Performance Web Sites: Essential Knowledge for Front-End Engineers”.

In this post, I’m going to look at how you can automate the CSS and JavaScript part of the build process using Make, a handy little program that is installed with the standard build tools on most *nix based systems.

What we want to achieve

In the future, we may wish the build to pull completed code from a source-control repository (i.e. Git or SVN) down to a staging environment, and the subsequent push from staging to production. We may also wish our build to handle the pushing of static assets—including images—to a CDN, like Amazon Cloudfront, or its platform, Amazon S3.

For the purposes of this article, we simply want to handle the generation of page-specific CSS and JavaScript static assets. This is by no means a complete build, but it’s as good a place to start as any. So let’s break down that process into steps, to better understand it:

  1. We want to amalgamate our various style sheets, or JavaScripts, into a single, page-specific sheet, so that we can reduce HTTP requests for the page. Imagine we’ve developed our pages to include CSS like so:
    /index.html
    <link rel="stylesheet" href="reset.css" type="text/css">
    <link rel="stylesheet" href="core.css" type="text/css">
    <link rel="stylesheet" href="top-articles.css" type="text/css">
    <link rel="stylesheet" href="tna-module.css" type="text/css">
    
    /article.html
    <link rel="stylesheet" href="reset.css" type="text/css">
    <link rel="stylesheet" href="core.css" type="text/css">
    <link rel="stylesheet" href="article-content.css" type="text/css">
    <link rel="stylesheet" href="tna-module.css" type="text/css">
    As you can see, both pages include several of the same style sheets. However, they also each have different sheets, which means we can’t really have One Big Style Sheet To Rule Them All™ (a “method” I’ve had to suffer so often in the past on large corporate projects) without including every style ever used across the entire site. That would mean downloading styles that could be entirely redundant in the current page. If we adopt a build process to combine style sheets for a page based on what is actually needed, we can generate page-specific sheets without having to maintain duplicated style rules (which would be the case if we maintained page-specific style sheets manually). This means we reduce our HTTP requests for CSS to a single link per page:
    /index.html
    <link rel="stylesheet" href="page-index.css" type="text/css">
    
    /article.html
    <link rel="stylesheet" href="page-article.css" type="text/css">
    This also rings true for JavaScript, which should be treated in exactly the same way.
  2. We’d like to compress our page-specific style sheets and external JavaScript using minification. In my case, I tend to use the YUI Compressor tool, since it handles both JavaScript and CSS. There’s really no reason why you couldn’t use separate tools for each; Google Closure Compiler, for example.
  3. The new page-specific files will require versioning so that the filenames change each time they are updated. This means we can adopt a far (or, at least, moderately far) future Expires HTTP header to better make use of caching. You can read more on this in the YDN performance best practices under ”Add an Expires or a Cache-Control Header”. There are several methods I could adopt for this:
    • Add a version number on the end of the filename:
      styles.1.0.css
      This means a certain degree of management in terms of major and minor releases, and often results in amusing version numbers like 1.132.
    • Add some form of timestamp to the filename:
      styles.20100221170134.css
      Generation of timestamps may not be consistent across servers, particularly if you’re working in a load balanced environment (i.e. the files are duplicated across front-end servers). Also, the filenames, and any related strings, are much longer.
    • Create a hash (MD5, SHA etc. of the updated file and use that, or a trimmed version of it, as the filename:
      6c7f5ed2.css
      This is a method I’ve only recently been introduced to, but one that clearly has its advantages. Firstly, hashing the file means we will have exactly the same result on all servers, since the file itself will be the same. It also means we don’t need to remember previous version numbers. Finally, there’s really no reason why we can’t trim the filename down to 8 characters, since the chances of generating duplicates would still be significantly low—and if we do get a duplicate, we can regenerate easily anyway.
    You’ve probably figured out that I intend to use the latter hashing method in my build, since it is probably the most robust solution and the one that I prefer.
  4. The HTML that links to our external static files will need to be updated to reflect the new versions of our built files. This simply means finding and replacing the old values.

We want to achieve these four steps with the minimum possible fuss, so we should be aiming for a “single button build process” where we can literally click a button, or run a single command to complete the build. Just because we’re only writing a small part of the final process here, doesn’t mean we should approach our solution any differently; after all, the final process will probably only run specific sub-builds in turn.

Choosing a build method

Scripting a build process on the server can be achieved in several ways:

As previously stated, I’m using Make, which is pre-installed on almost every *nix-based system (it wasn’t pre-installed on my JeOS VMs, but was trivial to install from packages—and is part of the “build-essential” package you may have already installed).

Using Make

Make basically processes a configuration known as a “Makefile”. The Makefile contains a list of all target files to be created, and the commands to create them.

A simple Makefile consists of “rules” with the following structure:

target … : prerequisites …
    command
    …
    …
    …

Where:

If you’re interested in learning more specifics about Make, I’d recommend taking a look at the GNU Make manual. The manual is available in a bunch of different formats.

Building static assets

So now that we understand the (very) basics of Make, let’s try writing a Makefile for our proposed build. I’m going to gradually build up a Makefile in this tutorial, so that it’s easy to cut and paste at any given point.

To begin, create a Makefile in your static assets directory like so:

$ vim /path/to/staticfiles/Makefile

It’s best to use a capital M on the Makefile simply to make sure it appears at the top of any ll -l commands. You may also see all caps used. Personally, I prefer the single capital M.

Let’s start by adding a comment header, defining a target for our CSS, and also the prerequisites required:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

/path/to/staticfiles/css/page.css: /path/to/staticfiles/css/src/reset.css /path/to/staticfiles/css/src/core.css /path/to/staticfiles/css/src/top-articles.css /path/to/staticfiles/css/src/tna-module.css

Straight away you should see a problem; there’s a lot of repetition of paths there. We can probably adjust that a little by assuming the Makefile is in the /path/to/staticfiles/ directory, and replacing that with ./. However, we could do a better job if we use a Makefile variable like so:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path = /path/to/staticfiles/css/

$(css-path)build/page.css: $(css-path)src/reset.css $(css-path)src/core.css $(css-path)src/top-articles.css $(css-path)src/tna-module.css

Here you can see we’ve defined the absolute path as a variable at the top of the Makefile, and are then reusing it in both our target and our prerequisites—using an absolute path seems safer to me, but you may disagree. If we plan ahead a bit more, we can add a few more variables like so:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

$(css-page-target): $(css-page-prereq)

Here I’ve added the prerequisites in a space-separated list, since I can use them in that format for the prerequisites, and again when I make use of the cat command later…

Concatenating the files

We now have a target for Make to examine, and a list or prerequisites that our Make rule will depend upon before it runs.

The first set of commands should handle the first stage in our build; the concatenation of the files into a single file:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"

Ok, so I have a few syntactical things to explain here:

Right, now that I’ve explained a little of the syntax, let’s take a look at what those commands actually do:

First of all, we make sure the target file doesn’t exist by forcing an rm. Then I’m echoing a message to show the CSS build has started. Next I cat the requisites together into a temporary file. Once this is all complete, I echo a “done” message (which will appear on the same line as the start message, since I ended the first echo with a /c character).

Minification

Now we have file containing all the prerequisite CSS files together, we want to minify it with the YUI compressor:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\t\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"

Here I’ve specified the path to the YUI Compressor .jar file as another variable and have run our temporary CSS file through it, outputting to the target file. I’ve also wrapped that process in the same echo trick as before.

Next, we just need to clean up that temporary file, and this rule is complete:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

Now if we run Make, our merged and compressed CSS file will be built:

$ cd /path/to/staticfiles
$ make
Merging CSS files…        [ Done ]
Compressing merged CSS…       [ Done ]
$ ls -lah css/build
total 8
drwxr-xr-x  3 auser  agroup   102B 23 Feb 14:17 .
drwxr-xr-x  5 auser  agroup   170B 19 Feb 22:45 ..
-rw-r–r–  1 auser  agroup   2.9K 23 Feb 14:17 page.css

Great!

At this point, it’s worth sense-checking the contents of your new file, to see that all your prerequisite files have been included, and that it has been correctly minified. Assuming everything is correct, let’s duplicate that rule for JavaScript files:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

I’m using two separate rules here; one for CSS, and one for JavaScript. If I run Make at this point, only the first rule (for the CSS) will be run. This is because Make only ever runs the first rule automatically. If I want both rules to be run, I should set up another rule that defines these rules as prerequisites.

Cleaning up old builds

It’s a good idea to define a “clean” target that removes all the files that are built in the Makefile. This means the user can start-over easily if they so desire:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

all: $(css-page-target) $(js-page-target)

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

clean:
    @rm -f $(css-page-target)
    @rm -f $(js-page-target)

To run a specific target, rather than the first in the list, you simply pass it as an argument to Make. Let’s run our clean target like so:

$ make clean

Now if you run Make again, it should build both the CSS and the JavaScript page files:

$ cd /path/to/staticfiles
$ make
Merging CSS files…        [ Done ]
Compressing merged CSS…       [ Done ]
Merging JS files…     [ Done ]
Compressing merged JS…        [ Done ]
$ 

Hash pipe (cut)

The next step is to hash our new page-specific CSS and JS files, and copy them to a higher level (so we don’t need to have the “build” directory on production). To do that we’ll want to pass the contents of the file to something that will generate a SHA1 checksum. On some systems, you can use shasum, but on my Mac that didn’t exist, so I’m using openssl sha1 instead, like so:

$ cat file | /usr/bin/openssl sha1

This will output a full SHA1 checksum to stdout. We actually want to use it as our filename, and the full checksum is likely to be too long. We can fix that by using the cut command as follows:

$ cat file | /usr/bin/openssl sha1 | cut -c1-8

Now if we want to use that as a filename, we can generate a variable in our Makefile. Let’s do that for both the JavaScript and the CSS:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

all: $(css-page-target) $(js-page-target)

install: css-build = `cat $(css-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.css
install: js-build = `cat $(js-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.js
install: all

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

clean:
    @rm -f $(css-page-target)
    @rm -f $(js-page-target)

Here I’ve added a new “install” target, which uses the “all” target as its only prerequisite. I’ve also defined two variables above the target as target-specific variables. This scopes the variables to this rule alone.

I’ve used the target “install” to conform with accepted Make standards. This means that, each time we want to refresh the CSS, we only need to type the following (which is another accepted standard):

$ make clean install

As you can see, you are able to pass multiple targets into the make command as arguments.

Using the hash for good, not evil

This is all very well and good, but even though we have the new filename stored in a variable, we’re not doing anything with it. Let’s rectify that:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

all: $(css-page-target) $(js-page-target)

install: css-build = `cat $(css-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.css
install: js-build = `cat $(js-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.js
install: all
    @cp $(css-page-target) $(css-path)$(css-build)
    @cp $(js-page-target) $(js-path)$(js-build)

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

clean:
    @rm -f $(css-page-target)
    @rm -f $(js-page-target)
    @rm -f $(css-path)*.css
    @rm -f $(js-path)*.js

Here I’ve copied the files to the higher css-path/ and js-path/ directories. This means I won’t need to sync the build/ and src/ directories to production.

I’ve also added two rm commands to the “clean” target that remove all built CSS and JS files in the respective directories, regardless of name. Note: make sure you’re not doing this recursively, otherwise you’ll trash all your valuable source files.

Now if you look in the css-path/ directory, you should see something like:

$ ls -lah
total 4.0K
drwxr-xr-x 5 auser agroup  170 2010-02-25 22:36 .
drwxr-xr-x 7 auser agroup  238 2010-02-25 22:14 ..
-rw-r–r– 1 auser agroup 2.9K 2010-02-25 22:36 6c7f5ed2.css
drwxr-xr-x 3 auser agroup  102 2010-02-25 22:36 build
drwxr-xr-x 4 auser agroup  136 2010-02-25 21:54 src
$

Great, so now we have a (reasonably) unique filename for our concatenated and minified CSS; and if you look in your JavaScript directory, you should see something similar.

The next step is to actually find and replace the inclusion of your file in your HTML.

Automagically updating the HTML

We basically need to run a find and replace on the HTML that includes your external CSS and JavaScript files, whether that is an SSI or a full HTML page. To do that, we should probably specify those files as variables, so that they can be easily maintained:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

css-inc-file =      /path/to/publicfiles/page.html
js-inc-file =       /path/to/publicfiles/page.html

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

all: $(css-page-target) $(js-page-target)

install: css-build = `cat $(css-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.css
install: js-build = `cat $(js-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.js
install: all
    @cp $(css-page-target) $(css-path)$(css-build)
    @cp $(js-page-target) $(js-path)$(js-build)

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

clean:
    @rm -f $(css-page-target)
    @rm -f $(js-page-target)
    @rm -f $(css-path)*.css
    @rm -f $(js-path)*.js

I’ve added two variables here in case, at some point in the future, we decide to break the inclusions out into separate files.

Now we’ve defined those files in the Makefile, we should really use them for something. I’m going to use sed to find and replace the references to the external static files:

#========================================
# Static Asset Build Makefile
#
# Author: Tim Huegdon
#========================================

domain =            yoursite.com

css-inc-file =      /path/to/publicfiles/page.html
js-inc-file =       /path/to/publicfiles/page.html

css-path =      /path/to/staticfiles/css/
css-page-target =   $(css-path)build/page.css
css-page-prereq =   $(css-path)src/reset.css \
            $(css-path)src/core.css \
            $(css-path)src/top-articles.css \
            $(css-path)src/tna-module.css

js-path =       /path/to/staticfiles/js/
js-page-target =    $(js-path)build/page.js
js-page-prereq =    $(js-path)src/carousel.js \
            $(js-path)src/awesome.js \
            $(js-path)src/beacon.js

yui-jar =       ~/tools/yui/yuicompressor-2.4.2.jar

all: $(css-page-target) $(js-page-target)

install: css-build = `cat $(css-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.css
install: js-build = `cat $(js-page-target) | /usr/bin/openssl sha1 | cut -c1-8`.js
install: all
    @cp $(css-page-target) $(css-path)$(css-build)
    @cp $(js-page-target) $(js-path)$(js-build)
    @echo "Linking to updated CSS and JavaScript…\t\c"
    @sed -i '' "s/http:\/\/static\.$(subst .,\.,$(domain))\/css.[^\"]*/http:\/\/static\.$(subst .,\.,$(domain))\/css\/$(subst .,\.,$(css-build))/" $(css-inc-file)
    @sed -i '' "s/http:\/\/static\.$(subst .,\.,$(domain))\/js.[^\"]*/http:\/\/static\.$(subst .,\.,$(domain))\/js\/$(subst .,\.,$(js-build))/" $(js-inc-file)
    @echo "[ Done ]"
    @echo "Installation is complete."

$(css-page-target): $(css-page-prereq)
    @rm -f $(css-page-target)
    @echo "Merging CSS files…\t\t\t\c"
    @cat $(css-page-prereq) > $(css-path)build/tmp.css
    @echo "[ Done ]"
    @echo "Compressing merged CSS…\t\c"
    @java -jar $(yui-jar) -o $(css-page-target) $(css-path)build/tmp.css
    @echo "[ Done ]"
    @rm -f $(css-path)build/tmp.css

$(js-page-target): $(js-page-prereq)
    @rm -f $(js-page-target)
    @echo "Merging JS files…\t\t\t\c"
    @cat $(js-page-prereq) > $(js-path)build/tmp.js
    @echo "[ Done ]"
    @echo "Compressing merged JS…\t\c"
    @java -jar $(yui-jar) -o $(js-page-target) $(js-path)build/tmp.js
    @echo "[ Done ]"
    @rm -f $(js-path)build/tmp.js

clean:
    @rm -f $(css-page-target)
    @rm -f $(js-page-target)
    @rm -f $(css-path)*.css
    @rm -f $(js-path)*.js

The first thing I’ve done here is to define another variable for the domain name of the site. This is because I’m going to need to use it to identify the links to the filenames in the regular expression.

Next I added the sed commands to our “install” target, using the -i option (edit files in-place). The regular expression is basically looking for a reference to http://static.yourdomain.com/css/something and replacing it with the full URI to our newly generated file.

Once this step is finished, the installation of our newly built static files is complete; and, as such, so is our Makefile. As previously stated, to run a new build—that also cleans up old builds—simply type the following:

$ make clean install

A better development environment

Instigating a build process in your environment allows you the flexibility to design your development environment for ease of maintenance, rather than having to think about optimisation and structure for production. Your development environment should be quite heavily different from staging or production; you want it to be optimised for development, maintenance, and bug-fixing, rather than optimised for speed, efficiency, and load.

Ultimately, this means that the build allows you the freedom to write better CSS and JavaScript, in well separated files. These files are then easily maintained by developers—since they’ll be easier to read and well commented—and also easier to maintain through source control—since the separation into a greater number of files means more control of more specific code.

Included in: CSS, Development, JavaScript, Linux, Mac OS X, Server, Tools, Tutorials, UNIX, Web

Categories:

  1. Accessibility
  2. Agile
  3. Ajax
  4. Apache
  5. API
  6. Architecture
  7. Books
  8. Browsers
  9. CMS
  10. CouchDB
  11. CSS
  12. Design
  13. Development
  14. Django
  15. Email
  16. Events
  17. Gaming
  18. Grammar
  19. Hardware
  20. HTML
  21. HTTP
  22. Humour
  23. Idea
  24. Information Architecture
  25. JavaScript
  26. jQuery
  27. Lean
  28. Life
  29. Linux
  30. Literature
  31. Mac OS X
  32. Management
  33. Meme
  34. Microformats
  35. Monday
  36. MySQL
  37. Networking
  38. News
  39. Personal
  40. Photoshop
  41. PHP
  42. Process
  43. Python
  44. Reference
  45. REST
  46. Science
  47. SEO
  48. Server
  49. Site
  50. Sitepimp
  51. Social
  52. Spelling
  53. Syndication
  54. Testing
  55. The Future
  56. Thoughts
  57. Tools
  58. Tutorial
  59. Tutorials
  60. Typography
  61. UI
  62. UNIX
  63. Virtualisation
  64. Web
  65. Web Standards
  66. Widgets
  67. Wii
  68. Writing
  69. Xbox
  70. XHTML