319

On Coffeescript.org:

bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10) 

would compile to:

var bawbag;
bawbag = function(x, y) {
  var z;
  return (z = (x * y));
};
bawbag(5, 10);

compiling via coffee-script under node.js wraps that so:

(function() {
  var bawbag;
  bawbag = function(x, y) {
    var z;
    return (z = (x * y));
  };
  bawbag(5, 10);
}).call(this);

Docs say:

If you'd like to create top-level variables for other scripts to use, attach them as properties on window, or on the exports object in CommonJS. The existential operator (covered below), gives you a reliable way to figure out where to add them, if you're targeting both CommonJS and the browser: root = exports ? this

How do I define Global Variables then in CoffeeScript. What does 'attach them as properties on window' mean?

sam
  • 40,318
  • 2
  • 41
  • 37
handloomweaver
  • 4,971
  • 7
  • 29
  • 33
  • 4
    Do note that using global variables is bad, http://c2.com/cgi/wiki?GlobalVariablesAreBad, and even considered harmful, http://c2.com/cgi/wiki?GotoConsideredHarmful. And there is really no reason to use them in JavaScript at all, as you have great features like closures that can solve most problems you are using global variables to solve. – Evgeny Jun 16 '11 at 17:58
  • 9
    @Evgeny While I agree with you here, in some cases it's necessary to create a central 'app' object and have modules be attached to it. – jackyalcine Jul 06 '12 at 19:32
  • 1
    central objects can be saved into existing global state objects, like the `window` object or the `exports` object. no need to create global variables. – Evgeny Jul 07 '12 at 00:54
  • 9
    @Evgeny global variables are saved as properties of the `window` (or `global` on nodejs) object – shesek Jan 22 '13 at 05:37
  • 21
    Yeah, it's not insta-bad to have a global var. Just bad practice to hose your app down with them thoughtlessly. Declaring one and using that as an adapter factory like jQuery or some kind of a namespace is really common practice. – Erik Reppen Feb 11 '13 at 07:30

9 Answers9

419

Since coffee script has no var statement it automatically inserts it for all variables in the coffee-script, that way it prevents the compiled JavaScript version from leaking everything into the global namespace.

So since there's no way to make something "leak" into the global namespace from the coffee-script side of things on purpose, you need to define your global variables as properties of the global object.

attach them as properties on window

This means you need to do something like window.foo = 'baz';, which handles the browser case, since there the global object is the window.

Node.js

In Node.js there's no window object, instead there's the exports object that gets passed into the wrapper that wraps the Node.js module (See: https://github.com/ry/node/blob/master/src/node.js#L321 ), so in Node.js what you would need to do is exports.foo = 'baz';.

Now let us take a look at what it states in your quote from the docs:

...targeting both CommonJS and the browser: root = exports ? this

This is obviously coffee-script, so let's take a look into what this actually compiles to:

var root;
root = (typeof exports !== "undefined" && exports !== null) ? exports : this;

First it will check whether exports is defined, since trying to reference a non existent variable in JavaScript would otherwise yield an SyntaxError (except when it's used with typeof)

So if exports exists, which is the case in Node.js (or in a badly written WebSite...) root will point to exports, otherwise to this. So what's this?

(function() {...}).call(this);

Using .call on a function will bind the this inside the function to the first parameter passed, in case of the browser this would now be the window object, in case of Node.js it would be the global context which is also available as the global object.

But since you have the require function in Node.js, there's no need to assign something to the global object in Node.js, instead you assign to the exports object which then gets returned by the require function.

Coffee-Script

After all that explanation, here's what you need to do:

root = exports ? this
root.foo = -> 'Hello World'

This will declare our function foo in the global namespace (whatever that happens to be).
That's all :)

Charlie
  • 10,227
  • 10
  • 51
  • 92
Ivo Wetzel
  • 46,459
  • 16
  • 98
  • 112
  • 1
    @IvoWetzel - What's the difference between the `global`, `GLOBAL`, and `root` objects in node.js? – Aadit M Shah Dec 02 '11 at 05:10
  • 1
    *trying to reference a non existent variable in JavaScript would otherwise yield an SyntaxError* Don't you mean `ReferenceError` ? – alex Feb 22 '12 at 02:24
  • 12
    Or even shorter: ``(exports ? this).foo = -> 'Hello World'`` – Dane O'Connor Mar 06 '12 at 15:43
  • I declared (exports ? this).db = redis.createClient() in the app.coffee and tried to access the db in the routes.index.coffee using db.set('online',Date.now(), (err,reply) -> console.log(reply.toString()) ) this doesn't seem to work...what is happening..I am on node 0.8.9 – coool Sep 17 '12 at 19:03
  • 4
    this.foo is often != window.foo though if you're 'this' context is an object already. This is a confusing syntax. – Kevin Oct 21 '12 at 03:52
  • but if you want to assing exports istelf, you have to inject plain javascript with back-ticks? – Alexander Ulitin Dec 12 '13 at 17:54
  • this isn't creating a global variable at all - its creating a module's exports. That wasn't the question – B T May 01 '14 at 07:22
  • 1
    While I agree with using `global = exports ? this`. The claim that "in case of Node.js it would be the global context..." is wrong because the `this` variable, when required or run by node.js, is evaluated as the module scope. So if you expect setting props to it will make it accessible globally, you'll be disappointed. **If** you do want to set things globally in node.js context, you need to use the [`global`](http://nodejs.org/api/globals.html#globals_global) variable, rather than `this`. – KFL Sep 01 '14 at 09:08
  • If you don't need the `this` fallback, you can simply say: `exports?.foo = -> 'Hello World'` This prevents CoffeeScript from declaring the variable. I've used this trick in a node module: `module?.exports = …` – vbwx Jul 09 '15 at 20:17
  • not work here, is it need the bare flag on transpilation? – Adi Prasetyo Mar 09 '21 at 09:11
59

To me it seems @atomicules has the simplest answer, but I think it can be simplified a little more. You need to put an @ before anything you want to be global, so that it compiles to this.anything and this refers to the global object.

so...

@bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10)

compiles to...

this.bawbag = function(x, y) {
  var z;
  return z = x * y;
};
bawbag(5, 10);

and works inside and outside of the wrapper given by node.js

(function() {
    this.bawbag = function(x, y) {
      var z;
      return z = x * y;
    };
    console.log(bawbag(5,13)) // works here
}).call(this);

console.log(bawbag(5,11)) // works here
Billy Moon
  • 57,113
  • 24
  • 136
  • 237
  • 7
    But this won't work if you're already inside another scope right? Because then `this` no longer refers to the global object – Sherwin Yu Aug 08 '12 at 00:02
  • 1
    That is correct, so you can either define your variable in an appropriate scope (and use it in others), or define as `window.myVariable` which will work anywhere. – Billy Moon Aug 08 '12 at 07:36
  • 2
    You don't need to define another variable just use `=>` instead of `->` that instructs coffeescript to create the function under the this/global namespace – Ricardo Villamil Apr 30 '13 at 15:56
  • 2
    this was so helpful, now i can create global objects and functions in a separate coffee script – Diego Fernando Murillo Valenci Jan 31 '14 at 16:00
  • This is a lot better. Transferring JS to CS needed me to change a lot of function calls to use the window object, now I can revert that – casraf Mar 27 '14 at 13:59
  • I'm afraid beginners will misunderstand "put an `@` before anything you want to be global" :/ – sam Jan 25 '16 at 20:44
  • Just that you need to [identify the calling context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) to know whether `this` (`@`) refers to the global object or something else. – sam Jan 31 '16 at 02:26
33

Ivo nailed it, but I'll mention that there is one dirty trick you can use, though I don't recommend it if you're going for style points: You can embed JavaScript code directly in your CoffeeScript by escaping it with backticks.

However, here's why this is usually a bad idea: The CoffeeScript compiler is unaware of those variables, which means they won't obey normal CoffeeScript scoping rules. So,

`foo = 'bar'`
foo = 'something else'

compiles to

foo = 'bar';
var foo = 'something else';

and now you've got yourself two foos in different scopes. There's no way to modify the global foo from CoffeeScript code without referencing the global object, as Ivy described.

Of course, this is only a problem if you make an assignment to foo in CoffeeScript—if foo became read-only after being given its initial value (i.e. it's a global constant), then the embedded JavaScript solution approach might be kinda sorta acceptable (though still not recommended).

Trevor Burnham
  • 76,828
  • 33
  • 160
  • 196
  • 1
    This was a useful solution for me since I'm using Titanium with CoffeeScript. Exports and window object are inexistant there. – Pier-Olivier Thibault Jan 25 '12 at 13:44
  • Actually that's just one local `foo` variable, because of `var` hoisting (JS scans ahead for all `var` declarations and interprets them as if they were at top of the function) – Kornel Apr 24 '12 at 21:42
  • @porneL You are correct; I picked a bad example. The point is that the CoffeeScript compiler performs no analysis of backtick-escaped JavaScript, so you can get odd output. – Trevor Burnham Apr 25 '12 at 00:51
  • 2
    @Pier-OlivierThibault If you want to use Globals in Titanium you can use Ti.App.myGlobalVar = "ImAGlobalVar" and don´t need backticks – Jakob Lnr Jul 30 '12 at 08:40
  • this is the correct answer, for Node.js at least. doing `expect = require('chai').expect;` makes `expect` variable available in all my test files! – pocesar Jun 23 '13 at 05:51
11

You can pass -b option when you compile code via coffee-script under node.js. The compiled code will be the same as on coffeescript.org.

phongnh
  • 111
  • 1
  • 2
10

I think what you are trying to achieve can simply be done like this :

While you are compiling the coffeescript, use the "-b" parameter.

-b / --bare Compile the JavaScript without the top-level function safety wrapper.

So something like this : coffee -b --compile somefile.coffee whatever.js

This will output your code just like in the CoffeeScript.org site.

ocodo
  • 29,401
  • 18
  • 105
  • 117
Sankalp Singha
  • 4,461
  • 5
  • 39
  • 58
9

To add to Ivo Wetzel's answer

There seems to be a shorthand syntax for exports ? this that I can only find documented/mentioned on a Google group posting.

I.e. in a web page to make a function available globally you declare the function again with an @ prefix:

<script type="text/coffeescript">
    @aglobalfunction = aglobalfunction = () ->
         alert "Hello!"
</script>

<a href="javascript:aglobalfunction()" >Click me!</a>
Community
  • 1
  • 1
atomicules
  • 2,155
  • 25
  • 24
  • 9
    The '@' in @aglobalfunction is simply replaced by 'this.', so compiling to 'this.aglobalfunction'. This works because the scope of the coffeescript wrapper function (if applied) is the global scope. – Chris Nov 09 '11 at 14:38
7

If you're a bad person (I'm a bad person.), you can get as simple as this: (->@)()

As in,

(->@)().im_a_terrible_programmer = yes
console.log im_a_terrible_programmer

This works, because when invoking a Reference to a Function ‘bare’ (that is, func(), instead of new func() or obj.func()), something commonly referred to as the ‘function-call invocation pattern’, always binds this to the global object for that execution context.

The CoffeeScript above simply compiles to (function(){ return this })(); so we're exercising that behavior to reliably access the global object.

ELLIOTTCABLE
  • 17,185
  • 12
  • 62
  • 78
4

Since coffeescript is rarely used on it's own, you can use global variable supplied by either node.js or browserify (and any descendants like coffeeify, gulp build scripts, etc).

In node.js global is global namespace.

In browserify global is equal to window.

So, just:

somefunc = ->
  global.variable = 123
metalim
  • 1,479
  • 1
  • 12
  • 22
0

Personally, I think you're too focused on the fact that in CoffeeScript, you can't say:

let x

in your main code. But there's nothing preventing you from saying:

x = undefined

in your main code, which will create the variable x. I wanted to be sure that when you later refer to (or assign to) x inside a function, that it would be using the very same x, so I wrote this script:

x = undefined

func = () ->
    console.log "x = #{x}"
    x = 5
    console.log "x = #{x}"

func()
console.log "x = #{x}"

which outputs:

x = undefined
x = 5
x = 5

so there is only one x, which you can use inside a function.

John Deighan
  • 4,329
  • 4
  • 18
  • 19