square/es6-module-transpiler — pull request #99 by rpflorence (2014-02-26 05:05:39+00:00)
Title: more automatic global support

## The problem:

### Incongruent Module Output and Unexpected Overriding

Consider this module at `foo.js`:

```js
function bar() {}
export { bar };
```

With this config:

```js
var name = 'foo';
var options = {};
new Compiler(src, name, options).toGlobals();
new Compiler(src, name, options).toAMD();
new Compiler(src, name, options).toCJS();
```

We get this output:

```js
// global
(function(__exports__) {
"use strict";
function bar() {}
__exports__.bar = bar;
})(window);

// amd
define("foo",
["exports"],
function(__exports__) {
"use strict";
function bar() {}
__exports__.bar = bar;
});

// cjs
"use strict";
function bar() {}
exports.bar = bar;
```

Now let's compare the consumption of this module in all four formats.

```js
import { bar } from 'foo';

require('./foo').bar();

require(['foo'], function(foo) {
foo.bar();
});

window['bar']();
```

For these outputs to be congruent, one would expect to find `bar` as a property of something named `foo` in the globals output like everywhere else, but we missed 'foo' altogether.

### `window` is not a suitable export target

Even more problematic, if you have a project with two modules that both export something named `bar`, then the second will destroy the first in the globals output because the module does not have a namespace to export its properties to.

## Make globals build congruent with other formats

This PR removes the sometimes-but-not-always-useful `into` option and instead has the concept of a `namespace`.

Considering the same module as before at `foo.js`:

```js
function bar() {}
export { bar };
```

With this new config:

```js
var name = 'foo';
var options = { namespace: 'myLib' };
new Compiler(src, name, options).toGlobals();
```

We get this new globals output

```js
(function(__exports__) {
"use strict";
function bar() {}
__exports__.bar = bar;
})((window['myLib']||(window['myLib']={}))['foo']={});
```

And now the consumption comparison again, but this time consider its a
third-party lib on npm/bower etc. named 'myLib':

```js
import { bar } from 'myLib/foo';

require('myLib/foo').bar();

require(['myLib/foo'], function(foo) {
foo.bar();
});

window.myLib.foo.bar();
```

In cases of nested modules, the paths start to look even more similar:

```js
require('myLib/foo/bar').baz();
myLib['foo/bar'].baz();
```

This pull request also allows for modules names that look like paths like this.

## What about `into`?

One could create these namespaces with `into` across a project with some
effort, but how do you get `myLib` onto the page in the first place?
This new output doesn't require anything like that, nor will any module
overwite the exports of another.

## Default Exports

Global builds now require a `namespace` and the `name` argument to
compile. With these two bits of information we could support anonymous
default exports now: we know the right thing to assign the default to.

_This is not in this pull request but it would follow very quickly_.

```js
export default function(){};
```

```js
new Compiler(src, 'foo', { namespace: 'myLib' }).toGlobals();
```

Could be compiled as:

```js
(function(__namespace__) {
"use strict";
__namespace__['foo'] = function () {};
})((window['myLib']||(window['myLib']={})));
```

## In the wild

I am currently using this branch in the [broccoli-es6-module-filter][filter]
and indirectly in the [broccoli-dist-es6-module][dist], so far so good :dancers:

Thanks!

**note**: there are a couple cleanup items in this PR, but wanted to get the conversation started asap.

[filter]:https://github.com/rpflorence/broccoli-es6-module-filter
[dist]:https://github.com/rpflorence/broccoli-dist-es6-module