32

When I create a new coffeescript file, I cannot access the code in the compiled code from another file because it gets wrapped in some function scope. For example:

CoffeeScript:

class ChatService
  constructor: (@io) ->

Generated Javascript:

(function() {
  var ChatService;    
  ChatService = (function() {    
    function ChatService(io) {
      this.io = io;
    }    
    return ChatService;    
  })();    
}).call(this);

When trying to call ChatService in another file, it's not defined. How do I handle multiple files with coffeescript?

wwkudu
  • 2,778
  • 3
  • 28
  • 41
Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406
  • If you're using Rails, you have to make sure any dependent coffeescript file is called *before* you try and reference it. Once you set "require" directives in the files which actually need the others, you'll get access to the variables etc. – Richard Peck Aug 09 '15 at 13:40

3 Answers3

56

Depending on whether this is client- or server-side code, there are two slightly different approaches.

Client-side: Here we attach things that should be available across files to the global namespace (window) as follows:

class window.ChatService
  constructor: (@io) ->

Then, in another file both ChatService and window.ChatService will allow access to the class.


Server-side: Here we must use exports and require. In the ChatService.coffee file, you would have the following:

class exports.ChatService
  constructor: (@io) ->

Then, to get at it from another file you can use:

ChatService = require('ChatService.coffee').ChatService

Note: If there are multiple classes that you are getting from ChatService.coffee, this is one place where CoffeeScript's dict unpacking really shines, such as:

{ChatService, OtherService} = require('ChatService.coffee')

Both: Basically, we choose whether to run server-side or client-side code based on which environment we're in. A common way to do it:

class ChatService
  constructor: (@io) ->

if typeof module != "undefined" && module.exports
  #On a server
  exports.ChatService = ChatService
else
  #On a client
  window.ChatService = ChatService

To get it:

if typeof module != "undefined" && module.exports
  #On a server
  ChatService = require("ChatService.coffee").ChatService
else
  #On a client
  ChatService = window.ChatService

The else clause of the second block can be skipped, since ChatService already refers to the reference attached to window.

If you're going to define a lot of classes in this file, it may be easier to define them like:

self = {}

class self.ChatService

And then attach them like module.exports = self on the server and _.extend(window, self) on the client (replace _.extend with another extend function as appropriate).

Aaron Dufour
  • 17,288
  • 1
  • 47
  • 69
  • 2
    Thanks for giving both node.js and client side approach, I needed both. – Shawn Mclean Feb 15 '12 at 20:35
  • Like the answer from "mu is too short", you may be able to do an exports.App.ClassName as well. I guess you could call the App portion of that a "namespace". Please correct me if I'm wrong. – Chris Oct 10 '12 at 00:13
  • How does this work if the code is used both client-side and server-side? e.g. I have a client-side code, but am testing it with jasmine-node where there is no window object. – kindohm Oct 12 '12 at 13:30
  • @AaronDufour thanks, that helps, but this is leading to more questions. One of my goals is to keep from adding classes to the window object as I don't want to accidentally overwrite an existing object from some other library or package. Thus, I want to wrap my classes from multiple files in another object. I haven't been able to successfully do this. It is easy to wrap multiple classes from a single file in a container object, but hard to wrap classes from multiple files in a container object. Not sure if this is on-topic... – kindohm Oct 15 '12 at 14:18
  • @kindohm You're veering a bit too far from the topic of this question. I would suggest creating a new question. – Aaron Dufour Oct 15 '12 at 18:03
  • Just a small detail to add: `if typeof module != "undefined" && module.exports` can be simplified greatly to `if module?.exports?`. – snickle Jan 13 '14 at 04:06
  • @snickle I agree, but here I avoided use of `?` operator to keep it readable for people who are using js and don't know coffeescript. – Aaron Dufour Jan 13 '14 at 14:53
24

The usual approach is to define a global namespace in window:

window.App = { }

That would go somewhere in your application's initialization code before anything else happens. And then, for your class:

class App.ChatService
  constructor: (@io) ->

That allows you to reference your class through App anywhere you want and you don't have to worry about polluting the global namespace:

chatter = new App.ChatService

If you wanted to make your ChatService truly global then you could use class window.ChatService but I'd recommend against that except in the most trivial of applications.

AFAIK, node.js has something similar to window but I'm not familiar enough with node.js to tell you what it is.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 1
    In node.js, you export symbols by attaching them to `exports`. In one module: exports.ChatService = ChatService; In the other: ChatService = require("./chat").ChatService. – Linus Thiel Feb 15 '12 at 12:19
  • @Linus: Thanks, I think Aaron Dufour covered that before I got to it. – mu is too short Feb 15 '12 at 17:11
0

Separate your classes with namespaces and use cake to compile them all in one (or more) resulting .js file(s). Cakefile is used as configuration which controls in which order your coffee scripts are compiled - quite handy with bigger projects.

Cake is quite easy to install and setup, invoking cake from vim while you are editing your project is then simply

:!cake build

and you can refresh your browser and see results.

As I'm also busy to learn the best way of structuring the files and use coffeescript in combination with backbone and cake, I have created a small project on github to keep it as a reference for myself, maybe it will help you too around cake and some basic things. All compiled files are in www folder so that you can open them in your browser and all source files (except for cake configuration) are in src folder. In this example, all .coffee files are compiled and combined in one output .js file which is then included in html.

Perica Zivkovic
  • 2,610
  • 24
  • 32