-
Notifications
You must be signed in to change notification settings - Fork 2k
[HowTo] Compiling and Setting Up Build Tools
What is this page about? In this article you will learn how to set up your own build tools for either the server or client side. In the process we will automate the task of compiling your *.js
files from *.coffee
. You will also learn how to concatenate, compile and minify your application so it results in one very optimised file you can serve to browsers.
cake
is coffee
's little sister (or so I picture things). If you installed CoffeeScript using npm
, then you should already have access to it. If you used cake install
, you have in fact had first-hand experience with the tool.
If you have symlinked bin/coffee
to a new location on your $PATH
, then you should also symlink bin/cake
:
$ ln -s /home/stan/public/coffee-script/bin/cake /usr/local/bin/cake
The above assumes /usr/local/bin
is in your $PATH
.
Running cake
outside of CoffeeScript's main directory is very likely to end up throwing an exception and printing out a stack trace. If you look closely, you can see why things don't work just yet:
Error: Cakefile not found in /home/stan/
So, how about we create one:
$ cd ~
$ vim Cakefile
Leave the file empty for the time being. Run cake
again:
$ cd ~
$ cake
This should return immediately stating that:
Cakefile defines the following tasks:
(No tasks should be listed)
Yes, we are doing the infamous Hello World example, but with a spin -- inside a Cakefile
. If you have worked with rake
or make
, then you should already have an idea of how things work. You define a task and use cake [task name]
to execute it. Let's get started:
$ cd ~
$ vim Cakefile
Define the task as say:hello
:
task 'say:hello', 'Description of task', ->
console.log 'Hello World!'
Save and exit back to the shell. Next, let's run our newly created task:
$ cake say:hello
Hello World!
Running cake
again, but without any arguments, you get a list of all available tasks and their description:
$ cake
cake say:hello # Description of task
Try running cake
inside CoffeeScript's main directory, and observe the output.
Using coffee
on the command line allows you to compile a directory recursively and output all resulting JavaScript files to another directory while preserving the structure. Let's see how we can do that:
$ cd ~/public/coffee-script
$ coffee --compile --output lib/ src/
The above command will compile all src/*.coffee
files to lib/*.js
. Your application may have different directories from the ones used above, but for the sake of this example we will assume src
and lib
. Caveat: None of the compiled files will be executable and none will have shebangs.
Let's move on. Create our compile task inside a new Cakefile
under our project's main directory:
$ cd ~/projects/my-coffee-script-project
$ vim Cakefile
Paste the following code:
{exec} = require 'child_process'
task 'build', 'Build project from src/*.coffee to lib/*.js', ->
exec 'coffee --compile --output lib/ src/', (err, stdout, stderr) ->
throw err if err
console.log stdout + stderr
The build
task launches the command we used earlier and waits for it to complete. Upon successful compilation it prints any output (nothing usually so add your own indicators) or throws an exception, should coffee
have exited with a status code greater than 0, indicating a compilation failure.
If you are working on a browser application, you may want to concatenate all your files into one and serve that instead. The caveat here is that you should concatenate all your source *.coffee
files before you compile them. If you simply concatenate the resulting *.js
files, you end up with a closure for each file and duplication of utility functions (CoffeeScript produces these when extending classes, binding functions, etc.)
When we are dealing with multiple files, ordering usually matters, unfortunately. We could iterate the src/
directory looking for all *.coffee
files using node.js, but the API is asynchronous and the results we get back are not ordered in any way. Instead, we should define an ordered list of files we want to process to build our application file:
fs = require 'fs'
{exec} = require 'child_process'
appFiles = [
# omit src/ and .coffee to make the below lines a little shorter
'content/scripts/statusbar'
'content/scripts/command/quickMacro'
'content/scripts/command/selectionTools/general'
]
task 'build', 'Build single application file from source files', ->
appContents = new Array remaining = appFiles.length
for file, index in appFiles then do (file, index) ->
fs.readFile "src/#{file}.coffee", 'utf8', (err, fileContents) ->
throw err if err
appContents[index] = fileContents
process() if --remaining is 0
process = ->
fs.writeFile 'lib/app.coffee', appContents.join('\n\n'), 'utf8', (err) ->
throw err if err
exec 'coffee --compile lib/app.coffee', (err, stdout, stderr) ->
throw err if err
console.log stdout + stderr
fs.unlink 'lib/app.coffee', (err) ->
throw err if err
console.log 'Done.'
We start off by defining the appFiles
we want to concatenate and then process. The build
task starts by reading all of the files asynchronously and calls process()
when all files have been read. process()
in turn writes a temporary file under lib/
and compiles that to lib/app.js
. Study the source and modify it as you see fit for your own needs.
Another option would be to use a tool to help you deal with your file dependencies. There are many approaches to dealing with file-dependency order out there, so you should pick the option that makes the most sense in your scenario. A few options:
You can easily extend your Cakefile
to include a task that calls to a compression utility, once your *.js
files have been generated:
task 'minify', 'Minify the resulting application file after build', ->
exec 'java -jar "/home/stan/public/compiler.jar" --js lib/app.js --js_output_file lib/app.production.js', (err, stdout, stderr) ->
throw err if err
console.log stdout + stderr
The above executes Google's Closure Compiler. You can easily tweak it to call the YUI Compressor or any other command-line utility.
Tasks in Cakefile
s can take options. This is important if you want to be able to provide parameters such as the target environment (e.g., production or development), source or target directory, etc. For more information, check out CoffeeScript's own Cakefile. If you rely on options, make sure you always have sensible defaults in place:
option '-e', '--environment [ENVIRONMENT_NAME]', 'set the environment for `task:withDefaults`'
task 'task:withDefaults', 'Description of task', (options) ->
options.environment or= 'production'
Then you can execute the task with your defined options:
cake -e "myEnv" task:withDefaults
You should be fairly comfortable writing your own Cakefile
s at this point. Study the coffee
binary and all its available options (coffee --help
). By using node.js and the child_process.exec
method you can automate much/all of your build process.
If you find this page to be missing information on a particular topic or an issue you are running into, please feel free to edit it.