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.
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.
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!
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
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.
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!