Browse Source

Merge branch 'master' into gh-37

contingency-plan
Rich Harris 9 years ago
parent
commit
20c2b66d00
  1. 19
      .eslintrc
  2. 10
      .jshintrc
  3. 42
      CHANGELOG.md
  4. 21
      LICENSE.md
  5. 168
      README.md
  6. 10
      bin/help.md
  7. 15
      bin/rollup
  8. 31
      bin/runRollup.js
  9. 6
      gobblefile.js
  10. 10
      package.json
  11. 476
      src/Bundle.js
  12. 25
      src/ExternalModule.js
  13. 565
      src/Module.js
  14. 105
      src/Statement.js
  15. 19
      src/ast/Scope.js
  16. 66
      src/ast/analyse.js
  17. 26
      src/finalisers/amd.js
  18. 25
      src/finalisers/cjs.js
  19. 73
      src/finalisers/es6.js
  20. 13
      src/finalisers/iife.js
  21. 18
      src/finalisers/shared/getExportBlock.js
  22. 12
      src/finalisers/shared/getInteropBlock.js
  23. 31
      src/finalisers/umd.js
  24. 8
      src/rollup.js
  25. 2
      src/utils/getExportMode.js
  26. 8
      src/utils/map-helpers.js
  27. 4
      src/utils/normalizePlatform.js
  28. 10
      src/utils/path.js
  29. 7
      test/form/banner-and-footer/_config.js
  30. 7
      test/form/banner-and-footer/_expected/amd.js
  31. 5
      test/form/banner-and-footer/_expected/cjs.js
  32. 3
      test/form/banner-and-footer/_expected/es6.js
  33. 7
      test/form/banner-and-footer/_expected/iife.js
  34. 11
      test/form/banner-and-footer/_expected/umd.js
  35. 1
      test/form/banner-and-footer/main.js
  36. 6
      test/form/dedupes-external-imports/_config.js
  37. 28
      test/form/dedupes-external-imports/_expected/amd.js
  38. 28
      test/form/dedupes-external-imports/_expected/cjs.js
  39. 26
      test/form/dedupes-external-imports/_expected/es6.js
  40. 28
      test/form/dedupes-external-imports/_expected/iife.js
  41. 32
      test/form/dedupes-external-imports/_expected/umd.js
  42. 8
      test/form/dedupes-external-imports/bar.js
  43. 8
      test/form/dedupes-external-imports/baz.js
  44. 8
      test/form/dedupes-external-imports/foo.js
  45. 7
      test/form/dedupes-external-imports/main.js
  46. 5
      test/form/exported-empty-vars/_expected/es6.js
  47. 9
      test/form/external-imports-custom-names/_config.js
  48. 9
      test/form/external-imports-custom-names/_expected/amd.js
  49. 8
      test/form/external-imports-custom-names/_expected/cjs.js
  50. 5
      test/form/external-imports-custom-names/_expected/es6.js
  51. 9
      test/form/external-imports-custom-names/_expected/iife.js
  52. 13
      test/form/external-imports-custom-names/_expected/umd.js
  53. 5
      test/form/external-imports-custom-names/main.js
  54. 6
      test/form/external-imports/_config.js
  55. 12
      test/form/external-imports/_expected/amd.js
  56. 14
      test/form/external-imports/_expected/cjs.js
  57. 10
      test/form/external-imports/_expected/es6.js
  58. 12
      test/form/external-imports/_expected/iife.js
  59. 16
      test/form/external-imports/_expected/umd.js
  60. 10
      test/form/external-imports/main.js
  61. 3
      test/form/multiple-exports/_expected/es6.js
  62. 8
      test/form/preserves-comments-after-imports/_config.js
  63. 9
      test/form/preserves-comments-after-imports/_expected/amd.js
  64. 7
      test/form/preserves-comments-after-imports/_expected/cjs.js
  65. 7
      test/form/preserves-comments-after-imports/_expected/es6.js
  66. 9
      test/form/preserves-comments-after-imports/_expected/iife.js
  67. 13
      test/form/preserves-comments-after-imports/_expected/umd.js
  68. 8
      test/form/preserves-comments-after-imports/main.js
  69. 6
      test/form/preserves-comments-after-imports/number.js
  70. 3
      test/form/removes-existing-sourcemap-comments/_config.js
  71. 9
      test/form/removes-existing-sourcemap-comments/_expected/amd.js
  72. 7
      test/form/removes-existing-sourcemap-comments/_expected/cjs.js
  73. 5
      test/form/removes-existing-sourcemap-comments/_expected/es6.js
  74. 9
      test/form/removes-existing-sourcemap-comments/_expected/iife.js
  75. 13
      test/form/removes-existing-sourcemap-comments/_expected/umd.js
  76. 5
      test/form/removes-existing-sourcemap-comments/foo.js
  77. 5
      test/form/removes-existing-sourcemap-comments/main.js
  78. 1
      test/form/self-contained-bundle/_expected/amd.js
  79. 1
      test/form/self-contained-bundle/_expected/cjs.js
  80. 1
      test/form/self-contained-bundle/_expected/es6.js
  81. 1
      test/form/self-contained-bundle/_expected/iife.js
  82. 1
      test/form/self-contained-bundle/_expected/umd.js
  83. 3
      test/form/unused-default-exports/_config.js
  84. 14
      test/form/unused-default-exports/_expected/amd.js
  85. 12
      test/form/unused-default-exports/_expected/cjs.js
  86. 10
      test/form/unused-default-exports/_expected/es6.js
  87. 14
      test/form/unused-default-exports/_expected/iife.js
  88. 18
      test/form/unused-default-exports/_expected/umd.js
  89. 8
      test/form/unused-default-exports/foo.js
  90. 2
      test/form/unused-default-exports/main.js
  91. 10
      test/function/deconflicts-exports/_config.js
  92. 8
      test/function/deconflicts-exports/main.js
  93. 13
      test/function/deconflicts-external-imports/_config.js
  94. 5
      test/function/deconflicts-external-imports/a.js
  95. 5
      test/function/deconflicts-external-imports/b.js
  96. 5
      test/function/deconflicts-external-imports/main.js
  97. 3
      test/function/deconflicts-generated-default-names/_config.js
  98. 9
      test/function/deconflicts-generated-default-names/foo.js
  99. 3
      test/function/deconflicts-generated-default-names/main.js
  100. 2
      test/function/deconflicts-globals/bar.js

19
.eslintrc

@ -0,0 +1,19 @@
{
"rules": {
"indent": [ 2, "tab", { "SwitchCase": 1}],
"quotes": [ 2, "single" ],
"linebreak-style": [ 2, "unix" ],
"semi": [ 2, "always" ],
"no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
"no-cond-assign": [ 0 ]
},
"env": {
"es6": true,
"browser": true,
"node": true
},
"extends": "eslint:recommended",
"ecmaFeatures": {
"modules": true
}
}

10
.jshintrc

@ -1,10 +0,0 @@
{
"esnext": true,
"undef": true,
"unused": true,
"globals": {
"process": true,
"module": true,
"assert": true
}
}

42
CHANGELOG.md

@ -1,5 +1,47 @@
# rollup changelog
## 0.14.1
* `export { name } from './other'` does not create a local binding ([#16](https://github.com/rollup/rollup/issues/16))
* A single binding can be exported under multiple names ([#18](https://github.com/rollup/rollup/issues/18))
* `useStrict` option exposed to CLI as `--strict`/`--no-strict` ([#81](https://github.com/rollup/rollup/issues/81))
* Neater exports from ES6 bundles
## 0.14.0
* Internal refactoring
* Correctly deconflict generated default export names ([#72](https://github.com/rollup/rollup/issues/72))
* Handle `export { x } from 'y'` declarations ([#74](https://github.com/rollup/rollup/issues/74))
* Dedupe named imports from external modules in ES6 bundles ([#77](https://github.com/rollup/rollup/issues/77))
## 0.13.0
* Support `banner` and `footer` options ([#66](https://github.com/rollup/rollup/pull/66))
* Remove pre-existing sourcemap comments ([#66](https://github.com/rollup/rollup/pull/66))
* Deconflict external imports ([#66](https://github.com/rollup/rollup/pull/66))
* Use existing AST, if provided ([#66](https://github.com/rollup/rollup/pull/66))
* Rename internal namespace exports as appropriate ([#66](https://github.com/rollup/rollup/pull/66))
* Remove uninitialised var declarations that get exported ([#66](https://github.com/rollup/rollup/pull/66))
* Rename variables named `exports` to avoid conflicts ([#66](https://github.com/rollup/rollup/pull/66))
## 0.12.1
* Don't attempt to mark statements belonging to external modules ([#68](https://github.com/rollup/rollup/issues/68))
* Correctly deshadow variables that conflict with imports ([#68](https://github.com/rollup/rollup/issues/68))
## 0.12.0
* Internal re-architecting, resulting in more efficient bundling with reduced memory usage
* Shorthand properties are expanded if necessary ([#61](https://github.com/rollup/rollup/issues/61))
* Fixed various bugs with bundle external dependencies, particularly when generating ES6 bundles ([#59](https://github.com/rollup/rollup/issues/59))
* Add `--globals` option to CLI ([#60](https://github.com/rollup/rollup/pull/60))
* Allow imports of external modules for side-effects ([#55](https://github.com/rollup/rollup/pull/55))
* Prevent Rollup hanging on non-existent external module ([#54](https://github.com/rollup/rollup/pull/54))
## 0.11.4
* Side-effect preservation applies to internal default exports ([#43](https://github.com/rollup/rollup/issues/43))
## 0.11.3
* Class methods are not incorrectly renamed ([#42](https://github.com/rollup/rollup/issues/42))

21
LICENSE.md

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 [these people](https://github.com/rollup/rollup/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

168
README.md

@ -1,136 +1,112 @@
# rollup
# Rollup
*I roll up, I roll up, I roll up, Shawty I roll up*
> *I roll up, I roll up, I roll up, Shawty I roll up*
>
> *I roll up, I roll up, I roll up*
> –[Wiz Khalifa](https://www.youtube.com/watch?v=UhQz-0QVmQ0)
*I roll up, I roll up, I roll up* –[Wiz Khalifa](https://www.youtube.com/watch?v=UhQz-0QVmQ0)
This is an early stage project under active development. If you find a bug, [please raise an issue!](https://github.com/rollup/rollup/issues).
## Quickstart
Rollup can be used via a [JavaScript API](https://github.com/rollup/rollup/wiki/JavaScript-API) or a [Command Line Interface](https://github.com/rollup/rollup/wiki/Command-Line-Interface). Install with `npm install -g rollup` and run `rollup --help` to get started.
## A next-generation ES6 module bundler
Right now, you have a few different options if you want to create a bundle out of your ES6 modules:
[Dive into the wiki](https://github.com/rollup/rollup/wiki) when you're ready to learn more about Rollup and ES6 modules.
* The best option, in terms of performance, size of the resulting bundle, and accurate representation of ES6 module semantics, is to use [esperanto](http://esperantojs.org). It's used by [ractive.js](http://ractivejs.org), [moment.js](http://momentjs.com/), Facebook's [immutable.js](https://github.com/facebook/immutable-js), the jQuery Foundation's [pointer events polyfill](https://github.com/jquery/PEP), [Ember CLI](http://www.ember-cli.com/) and a bunch of other libraries and apps
* You could use [jspm](http://jspm.io/), which combines a module bundler with a loader and a package manager
* Or you could use [browserify](http://browserify.org/) or [webpack](http://webpack.github.io/), transpiling your modules into CommonJS along the way
But there's a flaw in how these systems work. Pretend it's the future, and lodash is available as an ES6 module, and you want to use a single helper function from it:
## A next-generation ES6 module bundler
```js
// app.js
import { pluck } from 'lodash';
```
When you're developing software, it's much easier to break your library or application apart into separate pieces that you can work on separately. It's also very likely that you'll have dependencies on third party libraries. The result is lots of small files – but that's bad news for browsers, which get slowed down by having to make many requests. (It's also [bad news for Node!](https://kev.inburke.com/kevin/node-require-is-dog-slow/))
With that single import statement, you've just caused the whole of [lodash](https://lodash.com/) to be included in your bundle, even though you only need to use a tiny fraction of the code therein.
The solution is to write your code as **modules**, and use a **module bundler** to concatenate everything into a single file. [Browserify](http://browserify.org/) and [Webpack](http://webpack.github.io/) are examples of module bundlers.
If you're using esperanto, that's not totally disastrous, because a sufficiently good minifier will be able to determine through [static analysis](http://en.wikipedia.org/wiki/Static_program_analysis) that most of the code will never run, and so remove it. But there are lots of situations where static analysis fails, and unused code will continue to clutter up your bundle.
So far, so good, **but there's a problem**. When you include a library in your bundle...
If you're using any of the other tools, static analysis won't be able to even begin to determine which of lodash's exports aren't used by your app (AFAIK! please correct me if I'm wrong).
```js
var utils = require( 'utils' );
var query = 'Rollup';
utils.ajax( 'https://api.example.com?search=' + query ).then( handleResponse );
```
### The current solution is not future-proof
...you include the *whole* library, including lots of code you're not actually using.
I picked lodash because it does offer one solution to this problem: modular builds. Today, in your CommonJS modules, you can do this:
**ES6 modules solve this problem.** Instead of importing the whole of `utils`, we can just import the `ajax` function we need:
```js
var pluck = require( 'lodash/collection/pluck' );
```
**This is not the answer.** Using a folder structure to define an interface is a bad idea - it makes it harder to guarantee backwards compatibility, it imposes awkward constraints on library authors, and it allows developers to do this sort of thing:
import { ajax } from 'utils';
```js
var cheekyHack = require( 'undocumented/private/module' );
var query = 'Rollup';
ajax( 'https://api.example.com?search=' + query ).then( handleResponse );
```
Sure enough, you [won't be able to do this with ES6 modules](https://github.com/esperantojs/esperanto/issues/68#issuecomment-73302346).
Rollup statically analyses your code, and your dependencies, and includes the bare minimum in your bundle.
### A better approach?
## Shouldn't we be writing those utilities as small modules anyway?
This project is an attempt to prove a different idea: ES6 modules should define their interface through a single file (which, by convention, is currently exposed as the `jsnext:main` field in your package.json file), like so...
[Not always, no.](https://medium.com/@Rich_Harris/small-modules-it-s-not-quite-that-simple-3ca532d65de4)
```js
// snippet from future-lodash.js
export { partition } from './src/collection/partition';
export { pluck } from './src/collection/pluck';
export { reduce } from './src/collection/reduce';
/* ...and so on... */
```
...and ES6 *bundlers* should handle importing in a much more granular fashion. Rather than importing an entire module, an intelligent bundler should be able to reason like so:
## Don't minifiers already do this?
* I need to import `pluck` from `future-lodash.js`
* According to `future-lodash.js`, the definition of `pluck` can be found in `lodash/src/collection/pluck.js`
* It seems that `pluck` depends on `map` and `property`, which are in... *these* files
* ...
* Right, I've found all the function definitions I need. I can just include those in my bundle and disregard the rest
If you minify code with something like [UglifyJS](https://github.com/mishoo/UglifyJS2) (and you should!) then some unused code will be removed:
In other words, the 'tree-shaking' approach of throwing everything in then removing the bits you don't need is all wrong - instead, we should be selective about what we include in the first place.
This is not a trivial task. There are almost certainly a great many complex edge cases. Perhaps it's not possible. But I intend to find out.
## Goals
```js
(function () {
function foo () {
console.log( 'this function was included!' );
}
function bar () {
console.log( 'this function was not' );
baz();
}
function baz () {
console.log( 'neither was this' );
}
foo();
})();
```
* Maximally efficient bundling
* Ease of use
* Flexible output - CommonJS, AMD, UMD, globals, ES6, System etc
* Speed
* Character-accurate sourcemaps
* Eventually, port the functionality to esperanto
A minifier can detect that `foo` gets called, but that `bar` doesn't. When we remove `bar`, it turns out that we can also remove `baz`.
### Secondary goals
But because of the limitations of static analysis, and the dynamic nature of JavaScript, it can't do the same thing with code like this:
* Support for legacy module formats
* Respect for original formatting and code comments
```js
(function () {
var obj = {
foo: function () {
console.log( 'this method was included!' );
},
bar: function () {
console.log( 'so was this :-(' );
this.baz();
},
baz: function () {
console.log( 'and this :-(' );
}
};
obj.foo();
})();
```
### API
Unfortunately, **traditional modules – CommonJS and AMD – result in code more like the second example than the first, making them next-to-impossible to optimise**. Rather than *excluding dead code*, we should be *including live code*. That's only possible with ES6 modules.
The example below is aspirational. It isn't yet implemented - it exists in the name of [README driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html).
```js
rollup.rollup({
// The bundle's starting point
entry: 'app.js',
// Any external modules you don't want to include
// in the bundle (includes node built-ins)
external: [ 'path', 'fs', 'some-other-lib' ]
}).then( function ( bundle ) {
// generate code and a sourcemap
const { code, map } = bundle.generate({
// output format - 'amd', 'cjs', 'es6', 'iife', 'umd'
format: 'amd',
// exports - 'auto', 'none', 'default', 'named'
exports: 'auto',
// amd/umd options
moduleId: 'my-library',
// umd options
moduleName: 'MyLibrary', // necessary if the bundle has exports
globals: {
backbone: 'Backbone'
}
});
## What's the catch?
fs.writeFileSync( 'bundle.js', code + '\n//# sourceMappingURL=bundle.js.map' );
fs.writeFileSync( 'bundle.js.map', map.toString() );
Most libraries that you depend on aren't written as ES6 modules, so Rollup can't work with them directly. (You *can* bundle your own app or library code with Rollup as a CommonJS module, then pass the result over to Webpack or Browserify, of course.)
// possible convenience method
bundle.write({
dest: 'bundle.js', // also writes sourcemap
format: 'amd'
});
});
```
**You can help!** It's possible to write libraries as ES6 modules while still making it easy for other developers to use your code as they already do, using the [jsnext:main](https://github.com/rollup/rollup/wiki/jsnext:main) field in your package.json. You'll be writing your code in a more future-proof way, and helping to bring an end to the [dark days of JavaScript package management](https://medium.com/@trek/last-week-i-had-a-small-meltdown-on-twitter-about-npms-future-plans-around-front-end-packaging-b424dd8d367a).
The duplication (`rollup.rollup`) is intentional. You have to say it like you're in the circus, otherwise it won't work.
See [rollup-starter-project](https://github.com/eventualbuddha/rollup-starter-project) for inspiration on how to get started.
## License
Not that there's any code here at the time of writing, but this project is released under the MIT license.
Released under the [MIT license](TK).

10
bin/help.md

@ -9,17 +9,21 @@ Basic options:
-h, --help Show this help message
-i, --input Input (alternative to <entry file>)
-o, --output <output> Output (if absent, prints to stdout)
-f, --format [umd] Type of output (amd, cjs, es6, iife, umd)
-f, --format [es6] Type of output (amd, cjs, es6, iife, umd)
-e, --external Comma-separate list of module IDs to exclude
-g, --globals Comma-separate list of `module ID:Global` pairs
Any module IDs defined here are added to external
-n, --name Name for UMD export
-u, --id ID for AMD module (default is anonymous)
-m, --sourcemap Generate sourcemap (`-m inline` for inline map)
--no-strict Don't emit a `"use strict";` in the generated modules.
Example:
Examples:
rollup --format=cjs --output=bundle.js -- src/main.js
rollup -f iife --globals jquery:jQuery,angular:ng \
-i src/app.js -o build/app.js -m build/app.js.map
Notes:

15
bin/rollup

@ -5,15 +5,20 @@ var minimist = require( 'minimist' ),
command = minimist( process.argv.slice( 2 ), {
alias: {
i: 'input',
o: 'output',
v: 'version',
h: 'help',
// Aliases
strict: 'useStrict',
// Short options
e: 'external',
f: 'format',
g: 'globals',
h: 'help',
i: 'input',
m: 'sourcemap',
n: 'name',
o: 'output',
u: 'id',
e: 'external'
v: 'version'
}
});

31
bin/runRollup.js

@ -1,8 +1,5 @@
require( 'source-map-support' ).install();
var path = require( 'path' );
var sander = require( 'sander' );
var Promise = sander.Promise;
var handleError = require( './handleError' );
var rollup = require( '../' );
@ -19,6 +16,26 @@ module.exports = function ( options ) {
options.input = options._[0];
}
var external = options.external ? options.external.split( ',' ) : [];
if ( options.globals ) {
var globals = Object.create( null );
options.globals.split( ',' ).forEach(function ( str ) {
var names = str.split( ':' );
globals[ names[0] ] = names[1];
// Add missing Module IDs to external.
if ( external.indexOf( names[0] ) === -1 ) {
external.push( names[0] );
}
});
options.globals = globals;
}
options.external = external;
try {
bundle( options ).catch( handleError );
} catch ( err ) {
@ -27,19 +44,18 @@ module.exports = function ( options ) {
};
function bundle ( options, method ) {
var bundleOptions, file;
if ( !options.input ) {
handleError({ code: 'MISSING_INPUT_OPTION' });
}
return rollup.rollup({
entry: options.input,
external: options.external && options.external.split( ',' )
external: options.external
}).then( function ( bundle ) {
var generateOptions = {
dest: options.output,
format: options.format,
globals: options.globals,
moduleId: options.id,
moduleName: options.name,
sourceMap: options.sourcemap
@ -55,7 +71,8 @@ function bundle ( options, method ) {
var result = bundle.generate( generateOptions );
var code = result.code;
var code = result.code,
map = result.map;
if ( options.sourcemap === 'inline' ) {
code += '\n//# sourceMappingURL=' + map.toUrl();

6
gobblefile.js

@ -8,12 +8,10 @@ var node = src
entry: 'rollup.js',
dest: 'rollup.js',
format: 'cjs',
external: [ 'sander', 'path', 'acorn', 'magic-string' ]
external: [ 'sander', 'acorn' ]
})
.transform( 'babel' );
var absolutePath = /^(?:\/|(?:[A-Za-z]:)?\\)/;
var browserPlaceholders = {
sander: fs.readFileSync( 'browser/sander.js' ).toString()
};
@ -27,7 +25,7 @@ var browser = src
if ( ~id.indexOf( 'sander.js' ) ) return browserPlaceholders.sander;
return fs.readFileSync( id ).toString();
},
external: [ 'acorn', 'magic-string' ]
external: [ 'acorn' ]
})
.transform( 'browserify', {
entries: [ './rollup.browser' ],

10
package.json

@ -1,6 +1,6 @@
{
"name": "rollup",
"version": "0.11.3",
"version": "0.14.1",
"description": "Next-generation ES6 module bundler",
"main": "dist/rollup.js",
"jsnext:main": "src/rollup.js",
@ -11,7 +11,8 @@
"test": "mocha",
"pretest": "npm run build",
"build": "gobble build -f dist",
"prepublish": "npm test"
"prepublish": "npm test",
"lint": "eslint src"
},
"repository": {
"type": "git",
@ -33,12 +34,13 @@
"devDependencies": {
"babel-core": "^5.5.8",
"console-group": "^0.1.2",
"eslint": "^1.1.0",
"gobble": "^0.10.1",
"gobble-babel": "^5.5.8",
"gobble-browserify": "^0.6.1",
"gobble-cli": "^0.4.2",
"gobble-esperanto-bundle": "^0.2.0",
"gobble-rollup": "^0.5.0",
"gobble-rollup": "^0.7.0",
"gobble-rollup-babel": "^0.1.0",
"mocha": "^2.2.4",
"source-map": "^0.1.40"
@ -46,7 +48,7 @@
"dependencies": {
"acorn": "^1.1.0",
"chalk": "^1.0.0",
"magic-string": "^0.6.2",
"magic-string": "^0.6.5",
"minimist": "^1.1.1",
"sander": "^0.3.3",
"source-map-support": "^0.3.1"

476
src/Bundle.js

@ -13,15 +13,6 @@ import getExportMode from './utils/getExportMode';
import getIndentString from './utils/getIndentString';
import { unixizePath } from './utils/normalizePlatform.js';
function isEmptyExportedVarDeclaration ( node, module, allBundleExports ) {
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false;
const name = node.declarations[0].id.name;
const canonicalName = module.getCanonicalName( name );
return canonicalName in allBundleExports;
}
export default class Bundle {
constructor ( options ) {
this.entry = options.entry;
@ -39,7 +30,6 @@ export default class Bundle {
transform: ensureArray( options.transform )
};
this.varExports = blank();
this.toExport = null;
this.modulePromises = blank();
@ -48,7 +38,9 @@ export default class Bundle {
this.statements = null;
this.externalModules = [];
this.internalNamespaceModules = [];
this.assumedGlobals = blank();
this.assumedGlobals.exports = true; // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
}
build () {
@ -59,10 +51,12 @@ export default class Bundle {
this.entryModule = entryModule;
if ( defaultExport ) {
entryModule.needsDefault = true;
// `export default function foo () {...}` -
// use the declared name for the export
if ( defaultExport.declaredName ) {
entryModule.suggestName( 'default', defaultExport.declaredName );
if ( defaultExport.identifier ) {
entryModule.suggestName( 'default', defaultExport.identifier );
}
// `export default a + b` - generate an export name
@ -90,95 +84,89 @@ export default class Bundle {
return this.markAllModifierStatements();
})
.then( () => {
this.statements = this.sort();
this.deconflict();
this.orderedModules = this.sort();
});
}
deconflict () {
let definers = blank();
let conflicts = blank();
// TODO would be better to deconflict once, rather than per-render
deconflict ( es6 ) {
let usedNames = blank();
// ensure no conflicts with globals
keys( this.assumedGlobals ).forEach( name => usedNames[ name ] = true );
let allReplacements = blank();
// Assign names to external modules
this.externalModules.forEach( module => {
// TODO is this right?
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
// while we're here...
allReplacements[ module.id ] = blank();
if ( definers[ name ] ) {
conflicts[ name ] = true;
} else {
definers[ name ] = [];
}
definers[ name ].push( module );
module.name = name;
this.assumedGlobals[ name ] = true;
// TODO is this necessary in the ES6 case?
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
module.name = getSafeName( name );
});
// Discover conflicts (i.e. two statements in separate modules both define `foo`)
this.statements.forEach( statement => {
const module = statement.module;
const names = keys( statement.defines );
// with default exports that are expressions (`export default 42`),
// we need to ensure that the name chosen for the expression does
// not conflict
if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const name = module.getCanonicalName( 'default' );
let i = this.orderedModules.length;
while ( i-- ) {
const module = this.orderedModules[i];
// while we're here...
allReplacements[ module.id ] = blank();
keys( module.definitions ).forEach( name => {
const safeName = getSafeName( name );
if ( safeName !== name ) {
module.rename( name, safeName );
allReplacements[ module.id ][ name ] = safeName;
}
});
}
const isProxy = statement.node.declaration && statement.node.declaration.type === 'Identifier';
const shouldDeconflict = !isProxy || ( module.getCanonicalName( statement.node.declaration.name ) !== name );
// Assign non-conflicting names to internal default/namespace export
this.orderedModules.forEach( module => {
if ( !module.needsDefault && !module.needsAll ) return;
if ( shouldDeconflict && !~names.indexOf( name ) ) {
names.push( name );
}
if ( module.needsAll ) {
const namespaceName = getSafeName( module.suggestedNames[ '*' ] );
module.replacements[ '*' ] = namespaceName;
}
names.forEach( name => {
if ( definers[ name ] ) {
conflicts[ name ] = true;
} else {
definers[ name ] = [];
}
if ( module.needsDefault || module.needsAll && module.exports.default ) {
const defaultExport = module.exports.default;
// TODO in good js, there shouldn't be duplicate definitions
// per module... but some people write bad js
definers[ name ].push( module );
});
});
// only create a new name if either
// a) it's an expression (`export default 42`) or
// b) it's a name that is reassigned to (`export var a = 1; a = 2`)
if ( defaultExport && defaultExport.identifier && !defaultExport.isModified ) return; // TODO encapsulate check for whether we need synthetic default name
// Ensure we don't conflict with globals
keys( this.assumedGlobals ).forEach( name => {
if ( definers[ name ] ) {
conflicts[ name ] = true;
const defaultName = getSafeName( module.suggestedNames.default );
module.replacements.default = defaultName;
}
});
// Rename conflicting identifiers so they can live in the same scope
keys( conflicts ).forEach( name => {
const modules = definers[ name ];
if ( !this.assumedGlobals[ name ] ) {
// the module closest to the entryModule gets away with
// keeping things as they are, unless we have a conflict
// with a global name
modules.pop();
}
this.orderedModules.forEach( module => {
keys( module.imports ).forEach( localName => {
if ( !module.imports[ localName ].isUsed ) return;
modules.forEach( module => {
const replacement = getSafeName( name );
module.rename( name, replacement );
const bundleName = this.trace( module, localName, es6 );
if ( bundleName !== localName ) {
allReplacements[ module.id ][ localName ] = bundleName;
}
});
});
function getSafeName ( name ) {
while ( conflicts[ name ] ) {
while ( usedNames[ name ] ) {
name = `_${name}`;
}
conflicts[ name ] = true;
usedNames[ name ] = true;
return name;
}
return allReplacements;
}
fetchModule ( importee, importer ) {
@ -195,12 +183,24 @@ export default class Bundle {
return this.modulePromises[ importee ];
}
if ( id === importer ) {
throw new Error( `A module cannot import itself (${id})` );
}
if ( !this.modulePromises[ id ] ) {
this.modulePromises[ id ] = Promise.resolve( this.load( id, this.loadOptions ) )
.then( source => {
let ast;
if ( typeof source === 'object' ) {
ast = source.ast;
source = source.code;
}
const module = new Module({
id,
source,
ast,
bundle: this
});
@ -214,10 +214,65 @@ export default class Bundle {
});
}
generate ( options = {} ) {
let magicString = new MagicString.Bundle({ separator: '' });
markAllModifierStatements () {
let settled = true;
let promises = [];
this.modules.forEach( module => {
module.statements.forEach( statement => {
if ( statement.isIncluded ) return;
keys( statement.modifies ).forEach( name => {
const definingStatement = module.definitions[ name ];
const exportDeclaration = module.exports[ name ] || module.reexports[ name ] || (
module.exports.default && module.exports.default.identifier === name && module.exports.default
);
const shouldMark = ( definingStatement && definingStatement.isIncluded ) ||
( exportDeclaration && exportDeclaration.isUsed );
if ( shouldMark ) {
settled = false;
promises.push( statement.mark() );
return;
}
// special case - https://github.com/rollup/rollup/pull/40
const importDeclaration = module.imports[ name ];
if ( !importDeclaration ) return;
const promise = Promise.resolve( importDeclaration.module || this.fetchModule( importDeclaration.source, module.id ) )
.then( module => {
if ( module.isExternal ) return null;
importDeclaration.module = module;
const exportDeclaration = module.exports[ importDeclaration.name ];
// TODO things like `export default a + b` don't apply here... right?
return module.findDefiningStatement( exportDeclaration.localName );
})
.then( definingStatement => {
if ( !definingStatement ) return;
settled = false;
return statement.mark();
});
promises.push( promise );
});
});
});
return Promise.all( promises ).then( () => {
if ( !settled ) return this.markAllModifierStatements();
});
}
render ( options = {} ) {
const format = options.format || 'es6';
const allReplacements = this.deconflict( format === 'es6' );
// Determine export mode - 'default', 'named', 'none'
const exportMode = getExportMode( this, options.exports );
// If we have named exports from the bundle, and those exports
// are assigned to *within* the bundle, we may need to rewrite e.g.
@ -235,161 +290,87 @@ export default class Bundle {
//
// This doesn't apply if the bundle is exported as ES6!
let allBundleExports = blank();
let isVarDeclaration = blank();
let varExports = blank();
let getterExports = [];
if ( format !== 'es6' ) {
keys( this.entryModule.exports ).forEach( key => {
const exportDeclaration = this.entryModule.exports[ key ];
this.orderedModules.forEach( module => {
module.varDeclarations.forEach( name => {
isVarDeclaration[ module.replacements[ name ] || name ] = true;
});
});
const originalDeclaration = this.entryModule.findDeclaration( exportDeclaration.localName );
if ( format !== 'es6' && exportMode === 'named' ) {
keys( this.entryModule.exports )
.concat( keys( this.entryModule.reexports ) )
.forEach( name => {
const canonicalName = this.traceExport( this.entryModule, name );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.entryModule.getCanonicalName( exportDeclaration.localName );
if ( isVarDeclaration[ canonicalName ] ) {
varExports[ name ] = true;
allBundleExports[ canonicalName ] = `exports.${key}`;
this.varExports[ key ] = true;
}
});
// if the same binding is exported multiple ways, we need to
// use getters to keep all exports in sync
if ( allBundleExports[ canonicalName ] ) {
getterExports.push({ key: name, value: allBundleExports[ canonicalName ] });
} else {
allBundleExports[ canonicalName ] = `exports.${name}`;
}
}
});
}
// since we're rewriting variable exports, we want to
// ensure we don't try and export them again at the bottom
this.toExport = keys( this.entryModule.exports )
.filter( key => !this.varExports[ key ] );
.concat( keys( this.entryModule.reexports ) )
.filter( key => !varExports[ key ] );
// Apply new names and add to the output bundle
let previousModule = null;
let previousIndex = -1;
let previousMargin = 0;
let magicString = new MagicString.Bundle({ separator: '\n\n' });
this.statements.forEach( statement => {
// skip `export { foo, bar, baz }`
if ( statement.node.type === 'ExportNamedDeclaration' ) {
// skip `export { foo, bar, baz }`
if ( statement.node.specifiers.length ) return;
// skip `export var foo;` if foo is exported
if ( isEmptyExportedVarDeclaration( statement.node.declaration, statement.module, allBundleExports ) ) return;
this.orderedModules.forEach( module => {
const source = module.render( allBundleExports, allReplacements[ module.id ], format );
if ( source.toString().length ) {
magicString.addSource( source );
}
// skip empty var declarations for exported bindings
// (otherwise we're left with `exports.foo;`, which is useless)
if ( isEmptyExportedVarDeclaration( statement.node, statement.module, allBundleExports ) ) return;
let replacements = blank();
let bundleExports = blank();
keys( statement.dependsOn )
.concat( keys( statement.defines ) )
.forEach( name => {
const canonicalName = statement.module.getCanonicalName( name );
if ( allBundleExports[ canonicalName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ canonicalName ];
} else if ( name !== canonicalName ) {
replacements[ name ] = canonicalName;
}
});
const source = statement.replaceIdentifiers( replacements, bundleExports );
// modify exports as necessary
if ( statement.isExportDeclaration ) {
// remove `export` from `export var foo = 42`
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) {
source.remove( statement.node.start, statement.node.declaration.start );
}
// remove `export` from `export class Foo {...}` or `export default Foo`
// TODO default exports need different treatment
else if ( statement.node.declaration.id ) {
source.remove( statement.node.start, statement.node.declaration.start );
}
else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const module = statement.module;
const canonicalName = module.getCanonicalName( 'default' );
if ( statement.node.declaration.type === 'Identifier' && canonicalName === module.getCanonicalName( statement.node.declaration.name ) ) {
return;
}
// anonymous functions should be converted into declarations
if ( statement.node.declaration.type === 'FunctionExpression' ) {
source.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${canonicalName}` );
} else {
source.overwrite( statement.node.start, statement.node.declaration.start, `var ${canonicalName} = ` );
}
}
else {
throw new Error( 'Unhandled export' );
}
}
// ensure there is always a newline between statements, and add
// additional newlines as necessary to reflect original source
const minSeparation = ( previousModule !== statement.module ) || ( statement.index !== previousIndex + 1 ) ? 3 : 2;
const margin = Math.max( minSeparation, statement.margin[0], previousMargin );
let newLines = new Array( margin ).join( '\n' );
// add leading comments
if ( statement.leadingComments.length ) {
const commentBlock = newLines + statement.leadingComments.map( ({ separator, comment }) => {
return separator + ( comment.block ?
`/*${comment.text}*/` :
`//${comment.text}` );
}).join( '' );
magicString.addSource( new MagicString( commentBlock ) );
newLines = new Array( statement.margin[0] ).join( '\n' ); // TODO handle gaps between comment block and statement
}
// add the statement itself
magicString.addSource({
content: source,
separator: newLines
});
// add trailing comments
const comment = statement.trailingComment;
if ( comment ) {
const commentBlock = comment.block ?
` /*${comment.text}*/` :
` //${comment.text}`;
magicString.append( commentBlock );
}
previousMargin = statement.margin[1];
previousModule = statement.module;
previousIndex = statement.index;
});
// prepend bundle with internal namespaces
const indentString = magicString.getIndentString();
const indentString = getIndentString( magicString, options );
const namespaceBlock = this.internalNamespaceModules.map( module => {
const exportKeys = keys( module.exports );
const exports = keys( module.exports )
.concat( keys( module.reexports ) )
.map( name => {
const canonicalName = this.traceExport( module, name );
return `${indentString}get ${name} () { return ${canonicalName}; }`;
});
return `var ${module.getCanonicalName('*')} = {\n` +
exportKeys.map( key => `${indentString}get ${key} () { return ${module.getCanonicalName(key)}; }` ).join( ',\n' ) +
return `var ${module.replacements['*']} = {\n` +
exports.join( ',\n' ) +
`\n};\n\n`;
}).join( '' );
magicString.prepend( namespaceBlock );
if ( getterExports.length ) {
// TODO offer ES3-safe (but not spec-compliant) alternative?
const getterExportsBlock = `Object.defineProperties(exports, {\n` +
getterExports.map( ({ key, value }) => indentString + `${key}: { get: function () { return ${value}; } }` ).join( ',\n' ) +
`\n});`;
magicString.append( '\n\n' + getterExportsBlock );
}
const finalise = finalisers[ format ];
if ( !finalise ) {
throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` );
}
magicString = finalise( this, magicString.trim(), {
// Determine export mode - 'default', 'named', 'none'
exportMode: getExportMode( this, options.exports ),
magicString = finalise( this, magicString.trim(), { exportMode, indentString }, options );
// Determine indentation
indentString: getIndentString( magicString, options )
}, options );
if ( options.banner ) magicString.prepend( options.banner + '\n' );
if ( options.footer ) magicString.append( '\n' + options.footer );
const code = magicString.toString();
let map = null;
@ -408,55 +389,6 @@ export default class Bundle {
return { code, map };
}
markAllModifierStatements () {
let settled = true;
let promises = [];
this.modules.forEach( module => {
module.statements.forEach( statement => {
if ( statement.isIncluded ) return;
keys( statement.modifies ).forEach( name => {
const definingStatement = module.definitions[ name ];
const exportDeclaration = module.exports[ name ];
const shouldMark = ( definingStatement && definingStatement.isIncluded ) ||
( exportDeclaration && exportDeclaration.isUsed );
if ( shouldMark ) {
settled = false;
promises.push( statement.mark() );
return;
}
// special case - https://github.com/rollup/rollup/pull/40
const importDeclaration = module.imports[ name ];
if ( !importDeclaration ) return;
const promise = Promise.resolve( importDeclaration.module || this.fetchModule( importDeclaration.source, module.id ) )
.then( module => {
importDeclaration.module = module;
const exportDeclaration = module.exports[ importDeclaration.name ];
// TODO things like `export default a + b` don't apply here... right?
return module.findDefiningStatement( exportDeclaration.localName );
})
.then( definingStatement => {
if ( !definingStatement ) return;
settled = false;
return statement.mark();
});
promises.push( promise );
});
});
});
return Promise.all( promises ).then( () => {
if ( !settled ) return this.markAllModifierStatements();
});
}
sort () {
let seen = {};
let ordered = [];
@ -538,14 +470,46 @@ export default class Bundle {
});
}
let statements = [];
return ordered;
}
ordered.forEach( module => {
module.statements.forEach( statement => {
if ( statement.isIncluded ) statements.push( statement );
});
});
trace ( module, localName, es6 ) {
const importDeclaration = module.imports[ localName ];
// defined in this module
if ( !importDeclaration ) return module.replacements[ localName ] || localName;
// defined elsewhere
return this.traceExport( importDeclaration.module, importDeclaration.name, es6 );
}
traceExport ( module, name, es6 ) {
if ( module.isExternal ) {
if ( name === 'default' ) return module.needsNamed && !es6 ? `${module.name}__default` : module.name;
if ( name === '*' ) return module.name;
return es6 ? name : `${module.name}.${name}`;
}
const reexportDeclaration = module.reexports[ name ];
if ( reexportDeclaration ) {
return this.traceExport( reexportDeclaration.module, reexportDeclaration.localName );
}
if ( name === '*' ) return module.replacements[ '*' ];
if ( name === 'default' ) return module.defaultName();
const exportDeclaration = module.exports[ name ];
if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName );
for ( let i = 0; i < module.exportDelegates.length; i += 1 ) {
const delegate = module.exportDelegates[i];
const delegateExportDeclaration = delegate.module.exports[ name ];
if ( delegateExportDeclaration ) {
return this.trace( delegate.module, delegateExportDeclaration.localName, es6 );
}
}
return statements;
throw new Error( `Could not trace binding '${name}' from ${module.id}` );
}
}

25
src/ExternalModule.js

@ -8,32 +8,25 @@ export default class ExternalModule {
this.isExternal = true;
this.importedByBundle = [];
this.canonicalNames = blank();
this.suggestedNames = blank();
this.needsDefault = false;
// Invariant: needsNamed and needsAll are never both true at once.
// Because an import with both a namespace and named import is invalid:
//
// import * as ns, { a } from '...'
//
this.needsNamed = false;
this.needsAll = false;
}
findDefiningStatement () {
return null;
}
getCanonicalName ( name ) {
if ( name === 'default' ) {
return this.needsNamed ? `${this.name}__default` : this.name;
}
if ( name === '*' ) {
return this.name;
}
// TODO this depends on the output format... works for CJS etc but not ES6
return `${this.name}.${name}`;
}
rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement;
rename () {
// noop
}
suggestName ( exportName, suggestion ) {

565
src/Module.js

@ -1,17 +1,14 @@
import { dirname } from './utils/path';
import { Promise } from 'sander';
import { parse } from 'acorn';
import MagicString from 'magic-string';
import Statement from './Statement';
import walk from './ast/walk';
import analyse from './ast/analyse';
import { blank, keys } from './utils/object';
import { first, sequence } from './utils/promise';
import { isImportDeclaration, isExportDeclaration } from './utils/map-helpers';
import getLocation from './utils/getLocation';
import makeLegalIdentifier from './utils/makeLegalIdentifier';
const emptyArrayPromise = Promise.resolve([]);
const emptyPromise = Promise.resolve();
function deconflict ( name, names ) {
while ( name in names ) {
@ -21,8 +18,17 @@ function deconflict ( name, names ) {
return name;
}
function isEmptyExportedVarDeclaration ( node, allBundleExports, moduleReplacements ) {
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false;
const name = node.declarations[0].id.name;
const canonicalName = moduleReplacements[ name ] || name;
return canonicalName in allBundleExports;
}
export default class Module {
constructor ({ id, source, bundle }) {
constructor ({ id, source, ast, bundle }) {
this.source = source;
this.bundle = bundle;
@ -34,21 +40,31 @@ export default class Module {
filename: id
});
// remove existing sourceMappingURL comments
const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g;
let match;
while ( match = pattern.exec( source ) ) {
this.magicString.remove( match.index, match.index + match[0].length );
}
this.suggestedNames = blank();
this.comments = [];
this.statements = this._parse();
this.statements = this.parse( ast );
// imports and exports, indexed by ID
this.imports = blank();
this.exports = blank();
this.reexports = blank();
this.exportAlls = blank();
// array of all-export sources
this.exportDelegates = [];
this.canonicalNames = blank();
this.replacements = blank();
this.varDeclarations = [];
this.definitions = blank();
this.definitionPromises = blank();
@ -61,21 +77,45 @@ export default class Module {
const node = statement.node;
const source = node.source && node.source.value;
// export { name } from './other'
if ( source ) {
if ( node.type === 'ExportAllDeclaration' ) {
// Store `export * from '...'` statements in an array of delegates.
// When an unknown import is encountered, we see if one of them can satisfy it.
this.exportDelegates.push({
statement,
source
});
}
else {
node.specifiers.forEach( specifier => {
this.reexports[ specifier.exported.name ] = {
source,
localName: specifier.local.name,
module: null // filled in later
};
});
}
}
// export default function foo () {}
// export default foo;
// export default 42;
if ( node.type === 'ExportDefaultDeclaration' ) {
else if ( node.type === 'ExportDefaultDeclaration' ) {
const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type );
const declaredName = isDeclaration && node.declaration.id.name;
const identifier = node.declaration.type === 'Identifier' && node.declaration.name;
const identifier = isDeclaration ?
node.declaration.id.name :
node.declaration.type === 'Identifier' ?
node.declaration.name :
null;
this.exports.default = {
statement,
name: 'default',
localName: declaredName || 'default',
declaredName,
localName: identifier || 'default',
identifier,
isDeclaration,
isAnonymous,
@ -94,18 +134,10 @@ export default class Module {
const exportedName = specifier.exported.name;
this.exports[ exportedName ] = {
statement,
localName,
exportedName
};
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName,
name: localName
};
}
});
}
@ -129,15 +161,6 @@ export default class Module {
};
}
}
// Store `export * from '...'` statements in an array of delegates.
// When an unknown import is encountered, we see if one of them can satisfy it.
else {
this.exportDelegates.push({
statement,
source
});
}
}
addImport ( statement ) {
@ -169,18 +192,20 @@ export default class Module {
analyse () {
// discover this module's imports and exports
this.statements.forEach( statement => {
if ( isImportDeclaration( statement ) ) this.addImport( statement );
else if ( isExportDeclaration( statement ) ) this.addExport( statement );
});
if ( statement.isImportDeclaration ) this.addImport( statement );
else if ( statement.isExportDeclaration ) this.addExport( statement );
analyse( this.magicString, this );
statement.analyse();
// consolidate names that are defined/modified in this module
this.statements.forEach( statement => {
// consolidate names that are defined/modified in this module
keys( statement.defines ).forEach( name => {
this.definitions[ name ] = statement;
});
statement.scope.varDeclarations.forEach( name => {
this.varDeclarations.push( name );
});
keys( statement.modifies ).forEach( name => {
( this.modifications[ name ] || ( this.modifications[ name ] = [] ) ).push( statement );
});
@ -189,6 +214,8 @@ export default class Module {
// if names are referenced that are neither defined nor imported
// in this module, we assume that they're globals
this.statements.forEach( statement => {
if ( statement.isReexportDeclaration ) return;
keys( statement.dependsOn ).forEach( name => {
if ( !this.definitions[ name ] && !this.imports[ name ] ) {
this.bundle.assumedGlobals[ name ] = true;
@ -200,28 +227,45 @@ export default class Module {
consolidateDependencies () {
let strongDependencies = blank();
function addDependency ( dependencies, declaration ) {
if ( declaration && declaration.module && !declaration.module.isExternal ) {
dependencies[ declaration.module.id ] = declaration.module;
return true;
}
}
this.statements.forEach( statement => {
if ( statement.isImportDeclaration && !statement.node.specifiers.length ) {
if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) {
// include module for its side-effects
strongDependencies[ statement.module.id ] = statement.module; // TODO is this right? `statement.module` should be `this`, surely?
}
keys( statement.stronglyDependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return;
const exportAllDeclaration = this.exportAlls[ name ];
else if ( statement.isReexportDeclaration ) {
if ( statement.node.specifiers ) {
statement.node.specifiers.forEach( specifier => {
let reexport;
let module = this;
let name = specifier.exported.name;
while ( !module.isExternal && module.reexports[ name ] && module.reexports[ name ].isUsed ) {
reexport = module.reexports[ name ];
module = reexport.module;
name = reexport.localName;
}
if ( exportAllDeclaration && exportAllDeclaration.module && !exportAllDeclaration.module.isExternal ) {
strongDependencies[ exportAllDeclaration.module.id ] = exportAllDeclaration.module;
return;
addDependency( strongDependencies, reexport );
});
}
}
const importDeclaration = this.imports[ name ];
else {
keys( statement.stronglyDependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return;
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) {
strongDependencies[ importDeclaration.module.id ] = importDeclaration.module;
}
});
addDependency( strongDependencies, this.exportAlls[ name ] ) ||
addDependency( strongDependencies, this.imports[ name ] );
});
}
});
let weakDependencies = blank();
@ -230,17 +274,26 @@ export default class Module {
keys( statement.dependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return;
const importDeclaration = this.imports[ name ];
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) {
weakDependencies[ importDeclaration.module.id ] = importDeclaration.module;
}
addDependency( weakDependencies, this.exportAlls[ name ] ) ||
addDependency( weakDependencies, this.imports[ name ] );
});
});
return { strongDependencies, weakDependencies };
}
defaultName () {
const defaultExport = this.exports.default;
if ( !defaultExport ) return null;
const name = defaultExport.identifier && !defaultExport.isModified ?
defaultExport.identifier :
this.replacements.default;
return this.replacements[ name ] || name;
}
findDefiningStatement ( name ) {
if ( this.definitions[ name ] ) return this.definitions[ name ];
@ -256,85 +309,10 @@ export default class Module {
});
}
findDeclaration ( localName ) {
const importDeclaration = this.imports[ localName ];
// name was defined by another module
if ( importDeclaration ) {
const module = importDeclaration.module;
if ( module.isExternal ) return null;
const exportDeclaration = module.exports[ importDeclaration.name ];
return module.findDeclaration( exportDeclaration.localName );
}
// name was defined by this module, if any
let i = this.statements.length;
while ( i-- ) {
const declaration = this.statements[i].scope.declarations[ localName ];
if ( declaration ) {
return declaration;
}
}
return null;
}
getCanonicalName ( localName ) {
// Special case
if ( localName === 'default' && ( this.exports.default.isModified || !this.suggestedNames.default ) ) {
let canonicalName = makeLegalIdentifier( this.id.replace( dirname( this.bundle.entryModule.id ) + '/', '' ).replace( /\.js$/, '' ) );
return deconflict( canonicalName, this.definitions );
}
if ( this.suggestedNames[ localName ] ) {
localName = this.suggestedNames[ localName ];
}
if ( !this.canonicalNames[ localName ] ) {
let canonicalName;
if ( this.imports[ localName ] ) {
const importDeclaration = this.imports[ localName ];
const module = importDeclaration.module;
if ( importDeclaration.name === '*' ) {
canonicalName = module.suggestedNames[ '*' ];
} else {
let exporterLocalName;
if ( module.isExternal ) {
exporterLocalName = importDeclaration.name;
} else {
const exportDeclaration = module.exports[ importDeclaration.name ];
// The export declaration of the particular name is known.
if (exportDeclaration) {
exporterLocalName = exportDeclaration.localName;
} else { // export * from '...'
exporterLocalName = importDeclaration.name;
}
}
canonicalName = module.getCanonicalName( exporterLocalName );
}
}
else {
canonicalName = localName;
}
this.canonicalNames[ localName ] = canonicalName;
}
return this.canonicalNames[ localName ];
}
mark ( name ) {
// shortcut cycles. TODO this won't work everywhere...
// shortcut cycles
if ( this.definitionPromises[ name ] ) {
return emptyArrayPromise;
return emptyPromise;
}
let promise;
@ -342,6 +320,7 @@ export default class Module {
// The definition for this name is in a different module
if ( this.imports[ name ] ) {
const importDeclaration = this.imports[ name ];
importDeclaration.isUsed = true;
promise = this.bundle.fetchModule( importDeclaration.source, this.id )
.then( module => {
@ -366,15 +345,17 @@ export default class Module {
module.suggestName( 'default', `${suggestion}__default` );
}
if ( module.isExternal ) {
if ( importDeclaration.name === 'default' ) {
module.needsDefault = true;
} else {
module.needsNamed = true;
}
if ( importDeclaration.name === 'default' ) {
module.needsDefault = true;
} else if ( importDeclaration.name === '*' ) {
module.needsAll = true;
} else {
module.needsNamed = true;
}
if ( module.isExternal ) {
module.importedByBundle.push( importDeclaration );
return emptyArrayPromise;
return emptyPromise;
}
if ( importDeclaration.name === '*' ) {
@ -383,82 +364,19 @@ export default class Module {
this.bundle.internalNamespaceModules.push( module );
}
return module.markAllStatements();
}
const exportDeclaration = module.exports[ importDeclaration.name ];
if ( !exportDeclaration ) {
const noExport = new Error( `Module ${module.id} does not export ${importDeclaration.name} (imported by ${this.id})` );
// See if there exists an export delegate that defines `name`.
return first( module.exportDelegates, noExport, declaration => {
return module.bundle.fetchModule( declaration.source, module.id ).then( submodule => {
declaration.module = submodule;
return submodule.mark( name ).then( result => {
if ( !result.length ) throw noExport;
// It's found! This module exports `name` through declaration.
// It is however not imported into this scope.
module.exportAlls[ name ] = declaration;
declaration.statement.dependsOn[ name ] =
declaration.statement.stronglyDependsOn[ name ] = result;
return result;
});
});
});
return module.markAllExportStatements();
}
return module.mark( exportDeclaration.localName );
return module.markExport( importDeclaration.name, name, this );
});
}
// The definition is in this module
else if ( name === 'default' && this.exports.default.isDeclaration ) {
// We have something like `export default foo` - so we just start again,
// searching for `foo` instead of default
promise = this.mark( this.exports.default.name );
}
else {
let statement;
statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
promise = statement && !statement.isIncluded ? statement.mark() : emptyArrayPromise;
// Special case - `export default foo; foo += 1` - need to be
// vigilant about maintaining the correct order of the export
// declaration. Otherwise, the export declaration will always
// go at the end of the expansion, because the expansion of
// `foo` will include statements *after* the declaration
if ( name === 'default' && this.exports.default.identifier && this.exports.default.isModified ) {
const defaultExportStatement = this.exports.default.statement;
promise = promise.then( statements => {
// remove the default export statement...
// TODO could this be statements.pop()?
statements.splice( statements.indexOf( defaultExportStatement ), 1 );
let i = statements.length;
let inserted = false;
while ( i-- ) {
if ( statements[i].module === this && statements[i].index < defaultExportStatement.index ) {
statements.splice( i + 1, 0, defaultExportStatement );
inserted = true;
break;
}
}
if ( !inserted ) statements.push( statement );
return statements;
});
}
const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
promise = statement && statement.mark();
}
this.definitionPromises[ name ] = promise || emptyArrayPromise;
this.definitionPromises[ name ] = promise || emptyPromise;
return this.definitionPromises[ name ];
}
@ -474,6 +392,9 @@ export default class Module {
return this.bundle.fetchModule( statement.node.source.value, this.id )
.then( module => {
statement.module = module;
if ( module.isExternal ) {
return;
}
return module.markAllStatements();
});
}
@ -496,22 +417,77 @@ export default class Module {
});
}
// TODO rename this to parse, once https://github.com/rollup/rollup/issues/42 is fixed
_parse () {
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
let ast;
try {
ast = parse( this.source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
markAllExportStatements () {
return sequence( this.statements, statement => {
return statement.isExportDeclaration ?
statement.mark() :
null;
});
}
markExport ( name, suggestedName, importer ) {
const reexportDeclaration = this.reexports[ name ];
if ( reexportDeclaration ) {
reexportDeclaration.isUsed = true;
return this.bundle.fetchModule( reexportDeclaration.source, this.id )
.then( otherModule => {
reexportDeclaration.module = otherModule;
return otherModule.markExport( reexportDeclaration.localName, suggestedName, this );
});
}
const exportDeclaration = this.exports[ name ];
if ( exportDeclaration ) {
exportDeclaration.isUsed = true;
if ( name === 'default' ) {
this.needsDefault = true;
this.suggestName( 'default', suggestedName );
return exportDeclaration.statement.mark();
}
return this.mark( exportDeclaration.localName );
}
const noExport = new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` );
// See if there exists an export delegate that defines `name`.
return first( this.exportDelegates, noExport, declaration => {
return this.bundle.fetchModule( declaration.source, this.id ).then( submodule => {
declaration.module = submodule;
return submodule.mark( name ).then( result => {
if ( !result.length ) throw noExport;
// It's found! This module exports `name` through declaration.
// It is however not imported into this scope.
this.exportAlls[ name ] = declaration;
declaration.statement.dependsOn[ name ] =
declaration.statement.stronglyDependsOn[ name ] = result;
return result;
});
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
throw err;
});
}
parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be)
if ( !ast ) {
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
try {
ast = parse( this.source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
throw err;
}
}
walk( ast, {
@ -522,42 +498,167 @@ export default class Module {
});
let statements = [];
let lastChar = 0;
let commentIndex = 0;
ast.body.map( node => {
ast.body.forEach( node => {
// special case - top-level var declarations with multiple declarators
// should be split up. Otherwise, we may end up including code we
// don't need, just because an unwanted declarator is included
if ( node.type === 'VariableDeclaration' && node.declarations.length > 1 ) {
// remove the leading var/let/const
this.magicString.remove( node.start, node.declarations[0].start );
node.declarations.forEach( declarator => {
const magicString = this.magicString.snip( declarator.start, declarator.end ).trim();
magicString.prepend( `${node.kind} ` ).append( ';' );
const { start, end } = declarator;
const syntheticNode = {
type: 'VariableDeclaration',
kind: node.kind,
start: node.start,
end: node.end,
declarations: [ declarator ]
start,
end,
declarations: [ declarator ],
isSynthetic: true
};
const statement = new Statement( syntheticNode, magicString, this, statements.length );
const statement = new Statement( syntheticNode, this, start, end );
statements.push( statement );
});
lastChar = node.end; // TODO account for trailing line comment
}
else {
const magicString = this.magicString.snip( node.start, node.end ).trim();
const statement = new Statement( node, magicString, this, statements.length );
let comment;
do {
comment = this.comments[ commentIndex ];
if ( !comment ) break;
if ( comment.start > node.start ) break;
commentIndex += 1;
} while ( comment.end < lastChar );
const start = comment ? Math.min( comment.start, node.start ) : node.start;
const end = node.end; // TODO account for trailing line comment
const statement = new Statement( node, this, start, end );
statements.push( statement );
lastChar = end;
}
});
statements.forEach( ( statement, i ) => {
const nextStatement = statements[ i + 1 ];
statement.next = nextStatement ? nextStatement.start : statement.end;
});
return statements;
}
rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement;
this.replacements[ name ] = replacement;
}
render ( allBundleExports, moduleReplacements ) {
let magicString = this.magicString.clone();
this.statements.forEach( statement => {
if ( !statement.isIncluded ) {
magicString.remove( statement.start, statement.next );
return;
}
// skip `export { foo, bar, baz }`
if ( statement.node.type === 'ExportNamedDeclaration' ) {
// skip `export { foo, bar, baz }`
if ( statement.node.specifiers.length ) {
magicString.remove( statement.start, statement.next );
return;
}
// skip `export var foo;` if foo is exported
if ( isEmptyExportedVarDeclaration( statement.node.declaration, allBundleExports, moduleReplacements ) ) {
magicString.remove( statement.start, statement.next );
return;
}
}
// skip empty var declarations for exported bindings
// (otherwise we're left with `exports.foo;`, which is useless)
if ( isEmptyExportedVarDeclaration( statement.node, allBundleExports, moduleReplacements ) ) {
magicString.remove( statement.start, statement.next );
return;
}
// split up/remove var declarations as necessary
if ( statement.node.isSynthetic ) {
// insert `var/let/const` if necessary
if ( !allBundleExports[ statement.node.declarations[0].id.name ] ) {
magicString.insert( statement.start, `${statement.node.kind} ` );
}
magicString.overwrite( statement.end, statement.next, ';\n' ); // TODO account for trailing newlines
}
let replacements = blank();
let bundleExports = blank();
keys( statement.dependsOn )
.concat( keys( statement.defines ) )
.forEach( name => {
const bundleName = moduleReplacements[ name ] || name;
if ( allBundleExports[ bundleName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ];
} else if ( bundleName !== name ) { // TODO weird structure
replacements[ name ] = bundleName;
}
});
statement.replaceIdentifiers( magicString, replacements, bundleExports );
// modify exports as necessary
if ( statement.isExportDeclaration ) {
// remove `export` from `export var foo = 42`
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) {
magicString.remove( statement.node.start, statement.node.declaration.start );
}
// remove `export` from `export class Foo {...}` or `export default Foo`
// TODO default exports need different treatment
else if ( statement.node.declaration.id ) {
magicString.remove( statement.node.start, statement.node.declaration.start );
}
else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const canonicalName = this.defaultName();
if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) {
magicString.remove( statement.start, statement.next );
return;
}
// prevent `var undefined = sideEffectyDefault(foo)`
if ( canonicalName === undefined ) {
magicString.remove( statement.start, statement.node.declaration.start );
return;
}
// anonymous functions should be converted into declarations
if ( statement.node.declaration.type === 'FunctionExpression' ) {
magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${canonicalName}` );
} else {
magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${canonicalName} = ` );
}
}
else {
throw new Error( 'Unhandled export' );
}
}
});
return magicString.trim();
}
suggestName ( defaultOrBatch, suggestion ) {

105
src/Statement.js

@ -17,12 +17,12 @@ function isFunctionDeclaration ( node, parent ) {
}
export default class Statement {
constructor ( node, magicString, module, index ) {
constructor ( node, module, start, end ) {
this.node = node;
this.module = module;
this.magicString = magicString;
this.index = index;
this.id = module.id + '#' + index;
this.start = start;
this.end = end;
this.next = null; // filled in later
this.scope = new Scope();
this.defines = blank();
@ -32,36 +32,26 @@ export default class Statement {
this.isIncluded = false;
this.leadingComments = [];
this.trailingComment = null;
this.margin = [ 0, 0 ];
// some facts about this statement...
this.isImportDeclaration = node.type === 'ImportDeclaration';
this.isExportDeclaration = /^Export/.test( node.type );
this.isExportAllDeclaration = /^ExportAll/.test( node.type );
this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
}
analyse () {
if ( this.isImportDeclaration ) return; // nothing to analyse
const statement = this; // TODO use arrow functions instead
const magicString = this.magicString;
let scope = this.scope;
walk( this.node, {
enter ( node, parent ) {
let newScope;
magicString.addSourcemapLocation( node.start );
switch ( node.type ) {
case 'FunctionExpression':
case 'FunctionDeclaration':
case 'ArrowFunctionExpression':
if ( node.type === 'FunctionDeclaration' ) {
scope.addDeclaration( node.id.name, node );
scope.addDeclaration( node.id.name, node, false );
}
newScope = new Scope({
@ -73,7 +63,7 @@ export default class Statement {
// named function expressions - the name is considered
// part of the function's scope
if ( node.type === 'FunctionExpression' && node.id ) {
newScope.addDeclaration( node.id.name, node );
newScope.addDeclaration( node.id.name, node, false );
}
break;
@ -99,17 +89,21 @@ export default class Statement {
case 'VariableDeclaration':
node.declarations.forEach( declarator => {
scope.addDeclaration( declarator.id.name, node );
scope.addDeclaration( declarator.id.name, node, true );
});
break;
case 'ClassDeclaration':
scope.addDeclaration( node.id.name, node );
scope.addDeclaration( node.id.name, node, false );
break;
}
if ( newScope ) {
Object.defineProperty( node, '_scope', { value: newScope });
Object.defineProperty( node, '_scope', {
value: newScope,
configurable: true
});
scope = newScope;
}
},
@ -166,7 +160,7 @@ export default class Statement {
}
keys( scope.declarations ).forEach( name => {
statement.defines[ name ] = true;
this.defines[ name ] = true;
});
}
@ -185,9 +179,12 @@ export default class Statement {
// disregard the `bar` in `class Foo { bar () {...} }`
if ( parent.type === 'MethodDefinition' ) return;
// disregard the `bar` in `export { foo as bar }`
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return;
const definingScope = scope.findDefiningScope( node.name );
if ( ( !definingScope || definingScope.depth === 0 ) && !this.defines[ node.name ] ) {
if ( !definingScope || definingScope.depth === 0 ) {
this.dependsOn[ node.name ] = true;
if ( strong ) this.stronglyDependsOn[ node.name ] = true;
}
@ -265,6 +262,23 @@ export default class Statement {
if ( this.isIncluded ) return; // prevent infinite loops
this.isIncluded = true;
// `export { name } from './other'` is a special case
if ( this.isReexportDeclaration ) {
return this.module.bundle.fetchModule( this.node.source.value, this.module.id )
.then( otherModule => {
return sequence( this.node.specifiers, specifier => {
const reexport = this.module.reexports[ specifier.exported.name ];
reexport.isUsed = true;
reexport.module = otherModule;
return otherModule.isExternal ?
null :
otherModule.markExport( specifier.local.name, specifier.exported.name, this.module );
});
});
}
const dependencies = Object.keys( this.dependsOn );
return sequence( dependencies, name => {
@ -273,8 +287,7 @@ export default class Statement {
});
}
replaceIdentifiers ( names, bundleExports ) {
const magicString = this.magicString.clone();
replaceIdentifiers ( magicString, names, bundleExports ) {
const replacementStack = [ names ];
const nameList = keys( names );
@ -318,11 +331,13 @@ export default class Statement {
.map( name => `\n${bundleExports[name]} = ${name};` )
.join( '' );
// TODO clean this up
try {
magicString.insert( node.end, exportInitialisers );
} catch ( err ) {
magicString.append( exportInitialisers );
if ( exportInitialisers ) {
// TODO clean this up
try {
magicString.insert( node.end, exportInitialisers );
} catch ( err ) {
magicString.append( exportInitialisers );
}
}
}
}
@ -349,7 +364,7 @@ export default class Statement {
});
deshadowList.forEach( name => {
if ( ~scope.declarations[ name ] ) { // TODO is this right? no indexOf?
if ( scope.declarations[ name ] ) {
newNames[ name ] = name + '$$'; // TODO better mechanism
hasReplacements = true;
}
@ -363,18 +378,28 @@ export default class Statement {
replacementStack.push( newNames );
}
// We want to rewrite identifiers (that aren't property names etc)
if ( node.type !== 'Identifier' ) return;
// if there's no replacement, or it's the same, there's nothing more to do
const name = names[ node.name ];
if ( !name || name === node.name ) return;
// shorthand properties (`obj = { foo }`) need to be expanded
if ( parent.type === 'Property' && parent.shorthand ) {
magicString.insert( node.end, `: ${name}` );
parent.key._skip = true;
parent.value._skip = true; // redundant, but defensive
return;
}
// property names etc can be disregarded
if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return;
if ( parent.type === 'Property' && node !== parent.value ) return;
if ( parent.type === 'MethodDefinition' && node === parent.key ) return;
// TODO others...?
const name = names[ node.name ];
if ( name && name !== node.name ) {
magicString.overwrite( node.start, node.end, name );
}
// all other identifiers should be overwritten
magicString.overwrite( node.start, node.end, name );
},
leave ( node ) {
@ -389,4 +414,12 @@ export default class Statement {
return magicString;
}
source () {
return this.module.source.slice( this.start, this.end );
}
toString () {
return this.module.magicString.slice( this.start, this.end );
}
}

19
src/ast/Scope.js

@ -14,6 +14,8 @@ export default class Scope {
this.declarations = blank();
this.isBlockScope = !!options.block;
this.varDeclarations = [];
if ( options.params ) {
options.params.forEach( param => {
this.declarations[ param.name ] = param;
@ -21,25 +23,16 @@ export default class Scope {
}
}
// add ( name, isBlockDeclaration ) {
// if ( !isBlockDeclaration && this.isBlockScope ) {
// // it's a `var` or function declaration, and this
// // is a block scope, so we need to go up
// this.parent.add( name, isBlockDeclaration );
// } else {
// this.names.push( name );
// }
// }
addDeclaration ( name, declaration ) {
addDeclaration ( name, declaration, isVar ) {
const isBlockDeclaration = declaration.type === 'VariableDeclaration' && blockDeclarations[ declaration.kind ];
if ( !isBlockDeclaration && this.isBlockScope ) {
// it's a `var` or function declaration, and this
// is a block scope, so we need to go up
this.parent.addDeclaration( name, declaration );
this.parent.addDeclaration( name, declaration, isVar );
} else {
this.declarations[ name ] = declaration;
if ( isVar ) this.varDeclarations.push( name )
}
}
@ -48,7 +41,7 @@ export default class Scope {
}
findDefiningScope ( name ) {
if ( !!this.declarations[ name ] ) {
if ( this.declarations[ name ] ) {
return this;
}

66
src/ast/analyse.js

@ -1,66 +0,0 @@
export default function analyse ( magicString, module ) {
// first we need to generate comprehensive scope info
let previousStatement = null;
let commentIndex = 0;
module.statements.forEach( statement => {
const node = statement.node;
let trailing = !!previousStatement;
let previousComment;
// TODO surely this can be neater
// attach leading comment
do {
let comment = module.comments[ commentIndex ];
// prevent comments inside the previous statement being
// appended to it
if ( previousStatement ) {
while ( comment && comment.start < previousStatement.node.end ) {
commentIndex += 1;
comment = module.comments[ commentIndex ];
}
}
if ( !comment || ( comment.end > node.start ) ) break;
// attach any trailing comment to the previous statement
if ( trailing && !/\n/.test( module.source.slice( previousStatement.node.end, comment.start ) ) ) {
previousStatement.trailingComment = comment;
}
// then attach leading comments to this statement
else {
statement.leadingComments.push({
separator: previousComment ? magicString.slice( previousComment.end, comment.start ) : '\n',
comment
});
previousComment = comment;
}
commentIndex += 1;
trailing = false;
} while ( module.comments[ commentIndex ] );
// determine margin
const previousEnd = previousComment ?
previousComment.end :
previousStatement ?
( previousStatement.trailingComment || previousStatement.node ).end :
0;
//const start = ( statement.leadingComments[0] || node ).start;
const gap = magicString.original.slice( previousEnd, node.start );
const margin = gap.split( '\n' ).length;
if ( previousStatement ) previousStatement.margin[1] = margin;
statement.margin[0] = margin;
statement.analyse();
previousStatement = statement;
});
}

26
src/finalisers/amd.js

@ -1,4 +1,6 @@
import { getName, quoteId } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function amd ( bundle, magicString, { exportMode, indentString }, options ) {
let deps = bundle.externalModules.map( quoteId );
@ -10,26 +12,18 @@ export default function amd ( bundle, magicString, { exportMode, indentString },
}
const params =
( options.moduleId ? `['${options.moduleId}'], ` : `` ) +
( options.moduleId ? `'${options.moduleId}', ` : `` ) +
( deps.length ? `[${deps.join( ', ' )}], ` : `` );
const intro = `define(${params}function (${args.join( ', ' )}) { 'use strict';\n\n`;
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
const intro = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`;
const exports = bundle.entryModule.exports;
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
let exportBlock;
if ( exportMode === 'default' ) {
exportBlock = `return ${bundle.entryModule.getCanonicalName('default')};`;
} else {
exportBlock = bundle.toExport.map( name => {
return `exports.${name} = ${exports[name].localName};`;
}).join( '\n' );
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
}
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString
.indent( indentString )

25
src/finalisers/cjs.js

@ -1,5 +1,7 @@
export default function cjs ( bundle, magicString, { exportMode }) {
let intro = `'use strict';\n\n`;
import getExportBlock from './shared/getExportBlock';
export default function cjs ( bundle, magicString, { exportMode }, options ) {
let intro = options.useStrict === false ? `` : `'use strict';\n\n`;
// TODO handle empty imports, once they're supported
const importBlock = bundle.externalModules
@ -21,23 +23,8 @@ export default function cjs ( bundle, magicString, { exportMode }) {
magicString.prepend( intro );
let exportBlock;
if ( exportMode === 'default' && bundle.entryModule.exports.default ) {
exportBlock = `module.exports = ${bundle.entryModule.getCanonicalName('default')};`;
} else if ( exportMode === 'named' ) {
exportBlock = bundle.toExport
.map( key => {
const specifier = bundle.entryModule.exports[ key ];
const name = bundle.entryModule.getCanonicalName( specifier.localName );
return `exports.${key} = ${name};`;
})
.join( '\n' );
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
}
const exportBlock = getExportBlock( bundle, exportMode, 'module.exports =' );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString;
}

73
src/finalisers/es6.js

@ -1,25 +1,68 @@
import { keys } from '../utils/object';
import { blank, keys } from '../utils/object';
export default function es6 ( bundle, magicString, { exportMode }, options ) {
const introBlock = ''; // TODO...
function uniqueNames ( declarations ) {
let uniques = blank();
const exports = bundle.entryModule.exports;
const exportBlock = keys( exports ).map( exportedName => {
const specifier = exports[ exportedName ];
declarations
.filter( declaration => !/^(default|\*)$/.test( declaration.name ) )
.forEach( declaration => uniques[ declaration.name ] = true );
const canonicalName = bundle.entryModule.getCanonicalName( specifier.localName );
return keys( uniques );
}
function notDefault ( name ) {
return name !== 'default';
}
export default function es6 ( bundle, magicString ) {
const importBlock = bundle.externalModules
.map( module => {
const specifiers = [];
if ( module.needsDefault ) {
specifiers.push( module.importedByBundle.filter( declaration =>
declaration.name === 'default' )[0].localName );
}
if ( module.needsAll ) {
specifiers.push( '* as ' + module.importedByBundle.filter( declaration =>
declaration.name === '*' )[0].localName );
}
if ( module.needsNamed ) {
specifiers.push( '{ ' + uniqueNames( module.importedByBundle )
.join( ', ' ) + ' }' );
}
if ( exportedName === 'default' ) {
return `export default ${canonicalName};`;
}
return specifiers.length ?
`import ${specifiers.join( ', ' )} from '${module.id}';` :
`import '${module.id}';`;
})
.join( '\n' );
return exportedName === canonicalName ?
`export { ${exportedName} };` :
`export { ${canonicalName} as ${exportedName} };`;
}).join( '\n' );
if ( importBlock ) {
magicString.prepend( importBlock + '\n\n' );
}
const module = bundle.entryModule;
const specifiers = bundle.toExport.filter( notDefault ).map( name => {
const canonicalName = bundle.traceExport( module, name );
return canonicalName === name ?
name :
`${canonicalName} as ${name}`;
});
let exportBlock = specifiers.length ? `export { ${specifiers.join(', ')} };` : '';
const defaultExport = module.exports.default || module.reexports.default;
if ( defaultExport ) {
exportBlock += `export default ${bundle.traceExport(module,'default')};`;
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
magicString.append( '\n\n' + exportBlock.trim() );
}
return magicString.trim();

13
src/finalisers/iife.js

@ -1,5 +1,7 @@
import { blank } from '../utils/object';
import { getName } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function iife ( bundle, magicString, { exportMode, indentString }, options ) {
const globalNames = options.globals || blank();
@ -19,15 +21,20 @@ export default function iife ( bundle, magicString, { exportMode, indentString }
args.unshift( 'exports' );
}
let intro = `(function (${args}) { 'use strict';\n\n`;
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
let intro = `(function (${args}) {${useStrict}\n\n`;
let outro = `\n\n})(${dependencies});`;
if ( exportMode === 'default' ) {
intro = `var ${options.moduleName} = ${intro}`;
magicString.append( `\n\nreturn ${bundle.entryModule.getCanonicalName('default')};` );
}
// TODO named exports
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString
.indent( indentString )

18
src/finalisers/shared/getExportBlock.js

@ -0,0 +1,18 @@
export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) {
if ( exportMode === 'default' ) {
const defaultExport = bundle.entryModule.exports.default;
const defaultExportName = bundle.entryModule.replacements.default ||
defaultExport.identifier;
return `${mechanism} ${defaultExportName};`;
}
return bundle.toExport
.map( name => {
const prop = name === 'default' ? `['default']` : `.${name}`;
name = bundle.traceExport( bundle.entryModule, name );
return `exports${prop} = ${name};`;
})
.join( '\n' );
}

12
src/finalisers/shared/getInteropBlock.js

@ -0,0 +1,12 @@
export default function getInteropBlock ( bundle ) {
return bundle.externalModules
.map( module => {
return module.needsDefault ?
( module.needsNamed ?
`var ${module.name}__default = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` :
`${module.name} = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` ) :
null;
})
.filter( Boolean )
.join( '\n' );
}

31
src/finalisers/umd.js

@ -1,5 +1,7 @@
import { blank } from '../utils/object';
import { getName, quoteId, req } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function umd ( bundle, magicString, { exportMode, indentString }, options ) {
if ( exportMode !== 'none' && !options.moduleName ) {
@ -11,7 +13,7 @@ export default function umd ( bundle, magicString, { exportMode, indentString },
let amdDeps = bundle.externalModules.map( quoteId );
let cjsDeps = bundle.externalModules.map( req );
let globalDeps = bundle.externalModules.map( module => {
return globalNames[ module.id ] || module.name;
return 'global.' + (globalNames[ module.id ] || module.name);
});
let args = bundle.externalModules.map( getName );
@ -25,38 +27,29 @@ export default function umd ( bundle, magicString, { exportMode, indentString },
}
const amdParams =
( options.moduleId ? `['${options.moduleId}'], ` : `` ) +
( options.moduleId ? `'${options.moduleId}', ` : `` ) +
( amdDeps.length ? `[${amdDeps.join( ', ' )}], ` : `` );
const cjsExport = exportMode === 'default' ? `module.exports = ` : ``;
const defaultExport = exportMode === 'default' ? `global.${options.moduleName} = ` : '';
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
const intro =
`(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? ${cjsExport}factory(${cjsDeps.join( ', ' )}) :
typeof define === 'function' && define.amd ? define(${amdParams}factory) :
${defaultExport}factory(${globalDeps});
}(this, function (${args}) { 'use strict';
}(this, function (${args}) {${useStrict}
`.replace( /^\t\t/gm, '' ).replace( /^\t/gm, magicString.getIndentString() );
const exports = bundle.entryModule.exports;
let exportBlock;
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
if ( exportMode === 'default' ) {
const canonicalName = bundle.entryModule.getCanonicalName( 'default' );
exportBlock = `return ${canonicalName};`;
} else {
exportBlock = bundle.toExport.map( name => {
const canonicalName = bundle.entryModule.getCanonicalName( exports[ name ].localName );
return `exports.${name} = ${canonicalName};`;
}).join( '\n' );
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
}
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString
.trim()

8
src/rollup.js

@ -1,5 +1,6 @@
import { basename } from './utils/path';
import { writeFile } from 'sander';
import { keys } from './utils/object';
import Bundle from './Bundle';
let SOURCEMAPPING_URL = 'sourceMa';
@ -14,14 +15,17 @@ export function rollup ( options ) {
return bundle.build().then( () => {
return {
generate: options => bundle.generate( options ),
imports: bundle.externalModules.map( module => module.id ),
exports: keys( bundle.entryModule.exports ),
generate: options => bundle.render( options ),
write: options => {
if ( !options || !options.dest ) {
throw new Error( 'You must supply options.dest to bundle.write' );
}
const dest = options.dest;
let { code, map } = bundle.generate( options );
let { code, map } = bundle.render( options );
let promises = [];

2
src/utils/getExportMode.js

@ -5,7 +5,7 @@ function badExports ( option, keys ) {
}
export default function getExportMode ( bundle, exportMode ) {
const exportKeys = keys( bundle.entryModule.exports );
const exportKeys = keys( bundle.entryModule.exports ).concat( keys( bundle.entryModule.reexports ) );
if ( exportMode === 'default' ) {
if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) {

8
src/utils/map-helpers.js

@ -9,11 +9,3 @@ export function quoteId ( x ) {
export function req ( x ) {
return `require('${x.id}')`;
}
export function isImportDeclaration ( statement ) {
return statement.isImportDeclaration;
}
export function isExportDeclaration ( statement ) {
return statement.isExportDeclaration;
}

4
src/utils/normalizePlatform.js

@ -1,3 +1,3 @@
export function unixizePath( path ) {
return path.split( /[\/\\]/ ).join( '/' );
export function unixizePath ( path ) {
return path.split( /[\/\\]/ ).join( '/' );
}

10
src/utils/path.js

@ -11,15 +11,19 @@ export function basename ( path ) {
}
export function dirname ( path ) {
const match = /(\/|\\)[^\/\\]+$/.exec( path );
const match = /(\/|\\)[^\/\\]*$/.exec( path );
if ( !match ) return '.';
return path.slice( 0, -match[0].length );
const dir = path.slice( 0, -match[0].length );
// If `dir` is the empty string, we're at root.
return dir ? dir : '/';
}
export function extname ( path ) {
const match = /\.[^\.]+$/.exec( path );
if ( !match ) return '';
return match[0]
return match[0];
}
export function relative ( from, to ) {

7
test/form/banner-and-footer/_config.js

@ -0,0 +1,7 @@
module.exports = {
description: 'adds a banner/footer',
options: {
banner: '/* this is a banner */',
footer: '/* this is a footer */'
}
};

7
test/form/banner-and-footer/_expected/amd.js

@ -0,0 +1,7 @@
/* this is a banner */
define(function () { 'use strict';
console.log( 'hello world' );
});
/* this is a footer */

5
test/form/banner-and-footer/_expected/cjs.js

@ -0,0 +1,5 @@
/* this is a banner */
'use strict';
console.log( 'hello world' );
/* this is a footer */

3
test/form/banner-and-footer/_expected/es6.js

@ -0,0 +1,3 @@
/* this is a banner */
console.log( 'hello world' );
/* this is a footer */

7
test/form/banner-and-footer/_expected/iife.js

@ -0,0 +1,7 @@
/* this is a banner */
(function () { 'use strict';
console.log( 'hello world' );
})();
/* this is a footer */

11
test/form/banner-and-footer/_expected/umd.js

@ -0,0 +1,11 @@
/* this is a banner */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
console.log( 'hello world' );
}));
/* this is a footer */

1
test/form/banner-and-footer/main.js

@ -0,0 +1 @@
console.log( 'hello world' );

6
test/form/dedupes-external-imports/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'dedupes external imports',
options: {
external: [ 'external' ]
}
};

28
test/form/dedupes-external-imports/_expected/amd.js

@ -0,0 +1,28 @@
define(['external'], function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
});

28
test/form/dedupes-external-imports/_expected/cjs.js

@ -0,0 +1,28 @@
'use strict';
var external = require('external');
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

26
test/form/dedupes-external-imports/_expected/es6.js

@ -0,0 +1,26 @@
import { Component } from 'external';
class Foo extends Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

28
test/form/dedupes-external-imports/_expected/iife.js

@ -0,0 +1,28 @@
(function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
})(external);

32
test/form/dedupes-external-imports/_expected/umd.js

@ -0,0 +1,32 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('external')) :
typeof define === 'function' && define.amd ? define(['external'], factory) :
factory(global.external);
}(this, function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
}));

8
test/form/dedupes-external-imports/bar.js

@ -0,0 +1,8 @@
import { Component } from 'external';
export default class Bar extends Component {
constructor () {
super();
this.isBar = true;
}
}

8
test/form/dedupes-external-imports/baz.js

@ -0,0 +1,8 @@
import { Component as Comp } from 'external';
export default class Baz extends Comp {
constructor () {
super();
this.isBaz = true;
}
}

8
test/form/dedupes-external-imports/foo.js

@ -0,0 +1,8 @@
import { Component } from 'external';
export default class Foo extends Component {
constructor () {
super();
this.isFoo = true;
}
}

7
test/form/dedupes-external-imports/main.js

@ -0,0 +1,7 @@
import Foo from './foo';
import Bar from './bar';
import Baz from './baz';
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

5
test/form/exported-empty-vars/_expected/es6.js

@ -3,10 +3,7 @@ foo = 42;
var bar;
var baz;
bar = 43;
baz = 44;
export { foo };
export { bar };
export { baz };
export { foo, bar, baz };

9
test/form/external-imports-custom-names/_config.js

@ -0,0 +1,9 @@
module.exports = {
description: 'allows global names to be specified for IIFE/UMD exports',
options: {
external: [ 'jquery' ],
globals: {
jquery: 'jQuery'
}
}
};

9
test/form/external-imports-custom-names/_expected/amd.js

@ -0,0 +1,9 @@
define(['jquery'], function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});
});

8
test/form/external-imports-custom-names/_expected/cjs.js

@ -0,0 +1,8 @@
'use strict';
var $ = require('jquery');
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

5
test/form/external-imports-custom-names/_expected/es6.js

@ -0,0 +1,5 @@
import $ from 'jquery';
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

9
test/form/external-imports-custom-names/_expected/iife.js

@ -0,0 +1,9 @@
(function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});
})(jQuery);

13
test/form/external-imports-custom-names/_expected/umd.js

@ -0,0 +1,13 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
factory(global.jQuery);
}(this, function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});
}));

5
test/form/external-imports-custom-names/main.js

@ -0,0 +1,5 @@
import $ from 'jquery';
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

6
test/form/external-imports/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'prefixes global names with `global.` when creating UMD bundle (#57)',
options: {
external: [ 'factory', 'baz', 'shipping-port', 'alphabet' ]
}
};

12
test/form/external-imports/_expected/amd.js

@ -0,0 +1,12 @@
define(['factory', 'baz', 'shipping-port', 'alphabet'], function (factory, baz, containers, alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );
baz.foo( baz.bar );
containers.forEach( console.log, console );
console.log( alphabet.a );
console.log( alphabet__default.length );
});

14
test/form/external-imports/_expected/cjs.js

@ -0,0 +1,14 @@
'use strict';
var factory = require('factory');
factory = 'default' in factory ? factory['default'] : factory;
var baz = require('baz');
var containers = require('shipping-port');
var alphabet = require('alphabet');
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );
baz.foo( baz.bar );
containers.forEach( console.log, console );
console.log( alphabet.a );
console.log( alphabet__default.length );

10
test/form/external-imports/_expected/es6.js

@ -0,0 +1,10 @@
import factory from 'factory';
import { bar, foo } from 'baz';
import * as containers from 'shipping-port';
import alphabet, { a } from 'alphabet';
factory( null );
foo( bar );
containers.forEach( console.log, console );
console.log( a );
console.log( alphabet.length );

12
test/form/external-imports/_expected/iife.js

@ -0,0 +1,12 @@
(function (factory,baz,containers,alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );
baz.foo( baz.bar );
containers.forEach( console.log, console );
console.log( alphabet.a );
console.log( alphabet__default.length );
})(factory,baz,containers,alphabet);

16
test/form/external-imports/_expected/umd.js

@ -0,0 +1,16 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('factory'), require('baz'), require('shipping-port'), require('alphabet')) :
typeof define === 'function' && define.amd ? define(['factory', 'baz', 'shipping-port', 'alphabet'], factory) :
factory(global.factory,global.baz,global.containers,global.alphabet);
}(this, function (factory,baz,containers,alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );
baz.foo( baz.bar );
containers.forEach( console.log, console );
console.log( alphabet.a );
console.log( alphabet__default.length );
}));

10
test/form/external-imports/main.js

@ -0,0 +1,10 @@
import factory from 'factory';
import { foo, bar } from 'baz';
import * as containers from 'shipping-port';
import alphabet, { a, b } from 'alphabet';
factory( null );
foo( bar );
containers.forEach( console.log, console );
console.log( a );
console.log( alphabet.length );

3
test/form/multiple-exports/_expected/es6.js

@ -1,5 +1,4 @@
var foo = 1;
var bar = 2;
export { foo };
export { bar };
export { foo, bar };

8
test/form/preserves-comments-after-imports/_config.js

@ -0,0 +1,8 @@
module.exports = {
description: 'preserves comments between imports and first statement',
options: {
moduleName: 'myBundle'
}
};
// https://github.com/esperantojs/esperanto/issues/187

9
test/form/preserves-comments-after-imports/_expected/amd.js

@ -0,0 +1,9 @@
define(['exports'], function (exports) { 'use strict';
/** A comment for a number */
var number = 5;
/** A comment for obj */
exports.obj = { number };
});

7
test/form/preserves-comments-after-imports/_expected/cjs.js

@ -0,0 +1,7 @@
'use strict';
/** A comment for a number */
var number = 5;
/** A comment for obj */
exports.obj = { number };

7
test/form/preserves-comments-after-imports/_expected/es6.js

@ -0,0 +1,7 @@
/** A comment for a number */
var number = 5;
/** A comment for obj */
var obj = { number };
export { obj };

9
test/form/preserves-comments-after-imports/_expected/iife.js

@ -0,0 +1,9 @@
(function (exports) { 'use strict';
/** A comment for a number */
var number = 5;
/** A comment for obj */
exports.obj = { number };
})((this.myBundle = {}));

13
test/form/preserves-comments-after-imports/_expected/umd.js

@ -0,0 +1,13 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
factory((global.myBundle = {}));
}(this, function (exports) { 'use strict';
/** A comment for a number */
var number = 5;
/** A comment for obj */
exports.obj = { number };
}));

8
test/form/preserves-comments-after-imports/main.js

@ -0,0 +1,8 @@
import { number } from './number.js';
/** A comment for obj */
var obj = { number };
export {
obj
};

6
test/form/preserves-comments-after-imports/number.js

@ -0,0 +1,6 @@
/** A comment for a number */
var number = 5;
export {
number
};

3
test/form/removes-existing-sourcemap-comments/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'removes existing sourcemap comments'
};

9
test/form/removes-existing-sourcemap-comments/_expected/amd.js

@ -0,0 +1,9 @@
define(function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
});

7
test/form/removes-existing-sourcemap-comments/_expected/cjs.js

@ -0,0 +1,7 @@
'use strict';
function foo () {
return 42;
}
console.log( foo() );

5
test/form/removes-existing-sourcemap-comments/_expected/es6.js

@ -0,0 +1,5 @@
function foo () {
return 42;
}
console.log( foo() );

9
test/form/removes-existing-sourcemap-comments/_expected/iife.js

@ -0,0 +1,9 @@
(function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
})();

13
test/form/removes-existing-sourcemap-comments/_expected/umd.js

@ -0,0 +1,13 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
}));

5
test/form/removes-existing-sourcemap-comments/foo.js

@ -0,0 +1,5 @@
export default function () {
return 42;
}
//# sourceMappingURL=foo.js.map

5
test/form/removes-existing-sourcemap-comments/main.js

@ -0,0 +1,5 @@
import foo from './foo';
console.log( foo() );
//# sourceMappingURL=main.js.map

1
test/form/self-contained-bundle/_expected/amd.js

@ -8,7 +8,6 @@ define(function () { 'use strict';
return 42;
}
// comment before 1
console.log( 1 );

1
test/form/self-contained-bundle/_expected/cjs.js

@ -8,7 +8,6 @@ function bar () {
return 42;
}
// comment before 1
console.log( 1 );

1
test/form/self-contained-bundle/_expected/es6.js

@ -6,7 +6,6 @@ function bar () {
return 42;
}
// comment before 1
console.log( 1 );

1
test/form/self-contained-bundle/_expected/iife.js

@ -8,7 +8,6 @@
return 42;
}
// comment before 1
console.log( 1 );

1
test/form/self-contained-bundle/_expected/umd.js

@ -12,7 +12,6 @@
return 42;
}
// comment before 1
console.log( 1 );

3
test/form/unused-default-exports/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'does not name unused-but-included default export'
};

14
test/form/unused-default-exports/_expected/amd.js

@ -0,0 +1,14 @@
define(function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
});

12
test/form/unused-default-exports/_expected/cjs.js

@ -0,0 +1,12 @@
'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );

10
test/form/unused-default-exports/_expected/es6.js

@ -0,0 +1,10 @@
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );

14
test/form/unused-default-exports/_expected/iife.js

@ -0,0 +1,14 @@
(function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
})();

18
test/form/unused-default-exports/_expected/umd.js

@ -0,0 +1,18 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
}));

8
test/form/unused-default-exports/foo.js

@ -0,0 +1,8 @@
export var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
export default mutate( foo );

2
test/form/unused-default-exports/main.js

@ -0,0 +1,2 @@
import { foo } from './foo';
assert.equal( foo.value, 2 );

10
test/function/deconflicts-exports/_config.js

@ -0,0 +1,10 @@
var assert = require( 'assert' );
module.exports = {
description: 'renames variables named `exports` if necessary',
exports: function ( exports ) {
assert.deepEqual( Object.keys( exports ), [ 'a', 'b' ] );
assert.equal( exports.a, 'A' );
assert.equal( exports.b, 42 );
}
};

8
test/function/deconflicts-exports/main.js

@ -0,0 +1,8 @@
var exports = {
number: 21
};
export var a = 'A';
export var b = exports.number * 2;
assert.deepEqual( Object.keys( exports ), [ 'number' ]);

13
test/function/deconflicts-external-imports/_config.js

@ -0,0 +1,13 @@
module.exports = {
description: 'deconflicts external imports',
context: {
require: function ( id ) {
return function () {
return id;
};
}
},
options: {
external: [ 'foo', 'bar' ]
}
};

5
test/function/deconflicts-external-imports/a.js

@ -0,0 +1,5 @@
import foo from 'foo';
export default function () {
assert.equal( foo(), 'foo' );
}

5
test/function/deconflicts-external-imports/b.js

@ -0,0 +1,5 @@
import foo from 'bar';
export default function () {
assert.equal( foo(), 'bar' );
}

5
test/function/deconflicts-external-imports/main.js

@ -0,0 +1,5 @@
import a from './a';
import b from './b';
a();
b();

3
test/function/deconflicts-generated-default-names/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'deconflicts generated default export names'
};

9
test/function/deconflicts-generated-default-names/foo.js

@ -0,0 +1,9 @@
export default notActuallyFoo;
function notActuallyFoo () {
return 'not ' + foo();
}
function foo () {
return 'actually foo';
}

3
test/function/deconflicts-generated-default-names/main.js

@ -0,0 +1,3 @@
import foo from './foo';
assert.equal( foo(), 'not actually foo' );

2
test/function/deconflicts-globals/bar.js

@ -3,4 +3,4 @@ import foo from './foo';
export default function() {
assert.equal( foo(), 'foo' );
return Number;
};
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save