Coffeescript RequireJS and You!

Contents

* Auto generated table of contents {:toc}

Best friends together at last!

So you want to create a javascript application with some kind of non trivial complexity, but separating the functionality out into different script files gets hairy. You need to forward declare certain functions before you execute certain functions, and things need to be loaded before they can be displayed. 

Then when you add CoffeeScript into the mix you've got a whole new set of problems. Ultimately you want one big file containing the logic of your application, but unless you've passed your discrete .coffee files to the coffee compiler (or included them in the head of your html) in a specific order, you won't be able to execute functions before they've been declared. 

Oh no save me, RequireJS!

What RequireJS does in this situation is provide a solution to the dependency problem mentioned above. What you do is create your separate script files to the AMD format, which tells you to explicitly mention the files (modules) that each script file depends upon. You do this by wrapping your script in a call to the function define, where the parameters passed to define reference the dependencies of your new script.

By returning from this define function, you decide what to expose from this module when it's requested by something else. This is similar to the exports.blah = ExportedObject format within CommonJS. If we manipulate exactly what is returned we can expose any number of class definitions, functions or arbitrary values that we choose.

Let's Go

Setup

We're going to make a test project to understand how to make these things work together. This will give us an idea of how to structure our application in a way that removes the problems of dependency resolution. The first thing we'll need to do is to get the required libraries to run RequireJS with CoffeeScript. The specific files we need are as follows:

Next we'll create some folders for our application, and for the sake of this example we'll organise the above files that we've downloaded as follows. 

Our file layout on disk {: .notice }

Next we'll create our html file for loading the scripts, this'll be nothing more than a standard html document with a script tag in the head to load the require.js library.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>RequireJS + Coffeescript</title>
    <script type="text/javascript" src="lib/require.js" data-main="src/main"></script>
</head>
<body>
    
</body>
</html>

Note the data-main attribute on the script tag. This is specific to RequireJS, and it defines the entry point to our application. What we're doing is telling RequireJS where the first file we want to execute is located, in this case it's at “src/main.js” (we've intentionally left out the .js suffix on this path). 

We could set this to be anywhere in the directory tree, but it's important that the coffee-script.js and cs.js files are located in the same place as the entry point. Also at this point we want to rename coffee-script.js to CoffeeScript.js. This is a requirement specific to the cs.js plugin.

Now we'll create a simple main.js file for use in our application. We'll make sure it follows the AMD format by wrapping everything in a define call, and because we don't yet depend on anything it'll be straightforward.

define([], function(){
    console.log("Running main.js!");
});

Our tree should now look like this:

{: .notice }

From here we can serve the files from the root of the project, and we should see our message output in the console for the index page.

It's humble, but that little message means we've almost configured everything correctly, and we can finally get down to writing the logic of our application. whoopty doo!

The application

We'll create a simple test application in coffeescript. Let's make it simple and add some text to the dom when the user clicks on the page. We'll do it in three steps

  • populate the dom with a super cool html button
  • bind to a click event and write to the console
  • bind to the same event and add some text to the page

For demonstrating how to organise an application, we'll also experiment with different ways of returning values from a module to show how we can expose different objects.

Adding a button to the page

We'll do this by creating a module which simply adds a standard button element to the page. We'll call into this module from our main.js module utilising the CoffeeScript plugin we have sitting nicely in the src folder.

define [], () ->
    ->
        input = document.createElement "button"
        input.innerText = "Super Cool Test Button!"
        input.setAttribute "id", "the-button"
        document.body.appendChild "input"

As before, we've created a file that follows the AMD format. This is implemented via wrapping the entire module in a define call. The empty array is to say that this module depends on nothing, and the empty parameter list for the function reflects that we aren't passing any dependencies in.

What's happening here is that within the define call we are saying “here comes a module with no dependencies” - then within that function we are defining a single function which manipulates the dom to add a button to the document

        input = document.createElement "button"
        input.innerText = "Super Cool Test Button!"
        input.setAttribute "id", "the-button"
        document.body.appendChild "input"

Because this is CoffeeScript, the last expression of a function is returned. In this case, the last expression within the define call is the definition the function above - the one that adds the button. This means that when we import this module elsewhere, all we are importing is the function above. Which we're going to do now!

define(["cs!app/add_button"]), function (AddButton){
    console.log("Running main.js!");
    AddButton();
});

In this case we've imported the module we just created and associated it with the parameter AddButton. Now within the context of main.js we have access to whatever we exported, which in this case was the single function we defined. By executing it directly we're performing the add button functionality.

Nice!

Nifty tech tag lists from Wouter Beeftink