Browse Source

merge master -> gh-447

gh-1132
Rich-Harris 8 years ago
parent
commit
2d7f8a7b38
  1. 2
      .eslintignore
  2. 27
      .eslintrc
  3. 18
      .github/ISSUE_TEMPLATE.md
  4. 7
      .github/PULL_REQUEST_TEMPLATE.md
  5. 4
      .gitignore
  6. 1
      .travis.yml
  7. 256
      CHANGELOG.md
  8. 2
      LICENSE.md
  9. 6
      README.md
  10. 1
      appveyor.yml
  11. 49
      bin/handleError.js
  12. 37
      bin/rollup
  13. 161
      bin/runRollup.js
  14. 13
      bin/showHelp.js
  15. 65
      bin/src/handleError.js
  16. 5
      bin/src/help.md
  17. 39
      bin/src/index.js
  18. 247
      bin/src/runRollup.js
  19. 4
      bin/src/sourceMappingUrl.js
  20. 80
      browser/path.js
  21. 1
      browser/promise.js
  22. 71
      package.json
  23. 5
      rollup.config.browser.js
  24. 34
      rollup.config.cli.js
  25. 21
      rollup.config.js
  26. 337
      src/Bundle.js
  27. 258
      src/Declaration.js
  28. 6
      src/ExternalModule.js
  29. 544
      src/Module.js
  30. 30
      src/Reference.js
  31. 155
      src/Statement.js
  32. 100
      src/ast/Node.js
  33. 52
      src/ast/Scope.js
  34. 78
      src/ast/attachScopes.js
  35. 38
      src/ast/conditions.js
  36. 7
      src/ast/create.js
  37. 63
      src/ast/enhance.js
  38. 6
      src/ast/isFunctionDeclaration.js
  39. 4
      src/ast/keys.js
  40. 19
      src/ast/modifierNodes.js
  41. 8
      src/ast/nodes/ArrayExpression.js
  42. 38
      src/ast/nodes/ArrowFunctionExpression.js
  43. 50
      src/ast/nodes/AssignmentExpression.js
  44. 38
      src/ast/nodes/BinaryExpression.js
  45. 59
      src/ast/nodes/BlockStatement.js
  46. 43
      src/ast/nodes/CallExpression.js
  47. 45
      src/ast/nodes/ClassDeclaration.js
  48. 26
      src/ast/nodes/ClassExpression.js
  49. 65
      src/ast/nodes/ConditionalExpression.js
  50. 9
      src/ast/nodes/EmptyStatement.js
  51. 11
      src/ast/nodes/ExportAllDeclaration.js
  52. 105
      src/ast/nodes/ExportDefaultDeclaration.js
  53. 80
      src/ast/nodes/ExportNamedDeclaration.js
  54. 8
      src/ast/nodes/ExpressionStatement.js
  55. 22
      src/ast/nodes/ForInStatement.js
  56. 22
      src/ast/nodes/ForOfStatement.js
  57. 23
      src/ast/nodes/ForStatement.js
  58. 53
      src/ast/nodes/FunctionDeclaration.js
  59. 21
      src/ast/nodes/FunctionExpression.js
  60. 35
      src/ast/nodes/Identifier.js
  61. 76
      src/ast/nodes/IfStatement.js
  62. 16
      src/ast/nodes/ImportDeclaration.js
  63. 17
      src/ast/nodes/Literal.js
  64. 84
      src/ast/nodes/MemberExpression.js
  65. 8
      src/ast/nodes/NewExpression.js
  66. 8
      src/ast/nodes/ObjectExpression.js
  67. 7
      src/ast/nodes/ReturnStatement.js
  68. 8
      src/ast/nodes/TemplateLiteral.js
  69. 26
      src/ast/nodes/ThisExpression.js
  70. 7
      src/ast/nodes/ThrowStatement.js
  71. 34
      src/ast/nodes/UnaryExpression.js
  72. 39
      src/ast/nodes/UpdateExpression.js
  73. 107
      src/ast/nodes/VariableDeclaration.js
  74. 91
      src/ast/nodes/VariableDeclarator.js
  75. 76
      src/ast/nodes/index.js
  76. 16
      src/ast/nodes/shared/Statement.js
  77. 27
      src/ast/nodes/shared/assignTo.js
  78. 67
      src/ast/nodes/shared/callHasEffects.js
  79. 28
      src/ast/nodes/shared/disallowIllegalReassignment.js
  80. 38
      src/ast/nodes/shared/isUsedByBundle.js
  81. 4
      src/ast/nodes/shared/pureFunctions.js
  82. 40
      src/ast/scopes/BundleScope.js
  83. 53
      src/ast/scopes/ModuleScope.js
  84. 96
      src/ast/scopes/Scope.js
  85. 0
      src/ast/utils/extractNames.js
  86. 2
      src/ast/utils/flatten.js
  87. 10
      src/ast/utils/isProgramLevel.js
  88. 0
      src/ast/utils/isReference.js
  89. 8
      src/ast/values.js
  90. 19
      src/finalisers/amd.js
  91. 18
      src/finalisers/cjs.js
  92. 24
      src/finalisers/es.js
  93. 40
      src/finalisers/iife.js
  94. 4
      src/finalisers/index.js
  95. 1
      src/finalisers/shared/esModuleExport.js
  96. 6
      src/finalisers/shared/getExportBlock.js
  97. 4
      src/finalisers/shared/getInteropBlock.js
  98. 18
      src/finalisers/shared/propertyStringFor.js
  99. 48
      src/finalisers/umd.js
  100. 81
      src/rollup.js

2
.eslintignore

@ -0,0 +1,2 @@
test/*
!test/test.js

27
.eslintrc

@ -7,17 +7,38 @@
"space-before-blocks": [ 2, "always" ],
"space-before-function-paren": [ 2, "always" ],
"no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
"no-cond-assign": [ 0 ]
"no-cond-assign": 0,
"no-unused-vars": 2,
"object-shorthand": [ 2, "always" ],
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"quote-props": [ 2, "as-needed" ],
"one-var": [ 2, "never" ],
"prefer-arrow-callback": 2,
"prefer-const": [ 2, { "destructuring": "all" } ],
"arrow-spacing": 2
},
"env": {
"es6": true,
"browser": true,
"mocha": true,
"node": true
},
"extends": "eslint:recommended",
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"settings": {
"import/ignore": [ 0, [
"\\.path.js$"
] ]
}
}

18
.github/ISSUE_TEMPLATE.md

@ -0,0 +1,18 @@
<!--
Thanks for raising an issue! To help us help you, if you've found a bug please consider the following:
* If you can demonstrate the bug using the interactive http://rollupjs.org demo, please do.
* If that's not possible, perhaps because your bug involves plugins, we recommend creating a small repo that illustrates the problem.
Reproductions should be small, self-contained, correct examples – http://sscce.org. For example, if your bug isn't related to Gulp, **do not include Gulp in the repro**. (Side-note: if it *is* related to Gulp, this is the wrong issue tracker!) If possible, use a config file as it's the standard, favoured approach to using Rollup: https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file
Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Rollup is run by unpaid volunteers in their free time, and we will prioritise bugs that follow these instructions.
If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:
<details>
<summary>Stack trace</summary>
Stack trace goes here...
</details>
-->

7
.github/PULL_REQUEST_TEMPLATE.md

@ -0,0 +1,7 @@
<!--
Thank you for creating a pull request. Before submitting, please note the following:
* If your pull request implements a new feature, please raise an issue to discuss it before sending code. In many cases features are absent for a reason.
* This message body should clearly illustrate what problems it solves. If there are related issues, remember to reference them.
* Ideally, include a test that fails without this PR but passes with it. PRs will only be merged once they pass CI. (Remember to `npm run lint`!)
-->

4
.gitignore

@ -1,9 +1,9 @@
.DS_Store
node_modules
!test/node_modules
/node_modules
.gobble*
dist
_actual
coverage
.commithash
.idea
bin/rollup

1
.travis.yml

@ -3,6 +3,7 @@ language: node_js
node_js:
- "0.12"
- "4"
- "6"
env:
global:
- BUILD_TIMEOUT=10000

256
CHANGELOG.md

@ -1,5 +1,261 @@
# rollup changelog
## 0.37.0
* [BREAKING] Default exports are not included in reified namespaces ([#1028](https://github.com/rollup/rollup/issues/1028))
* Parentheses do not defeat tree-shaking ([#1101](https://github.com/rollup/rollup/issues/1101), [#1128](https://github.com/rollup/rollup/issues/1128))
* More `legacy` fixes: do not create getters ([#1069](https://github.com/rollup/rollup/pull/1069)), do not include `__esModule` ([#1068](https://github.com/rollup/rollup/pull/1068)), quote reserved property names ([#1057](https://github.com/rollup/rollup/pull/1057))
* Fix missing namespace member warnings ([#1045](https://github.com/rollup/rollup/issues/1045))
* Fix TypeError in arrow function without braces returning a function ([#1062](https://github.com/rollup/rollup/pull/1062))
## 0.36.4
* Only depend on program-level call expressions ([#977](https://github.com/rollup/rollup/issues/977))
## 0.36.3
* Add `legacy` option for IE8 support ([#989](https://github.com/rollup/rollup/pull/989))
## 0.36.2
* Insert semicolons where necessary to fix broken code ([#1004](https://github.com/rollup/rollup/issues/1004))
* Include module ID and location when warning about top-level `this` ([#1012](https://github.com/rollup/rollup/pull/1012))
* More informative error for missing exports ([#1033](https://github.com/rollup/rollup/issues/1033))
* `options.moduleContext` for per-module context overrides ([#1023](https://github.com/rollup/rollup/pull/1023))
## 0.36.1
* Include naked block statements ([#981](https://github.com/rollup/rollup/issues/981))
* Correctly include falsy alternate statements in optimised if blocks ([#973](https://github.com/rollup/rollup/issues/973))
* Prevent omission of default exports that are only used by the exporting module ([#967](https://github.com/rollup/rollup/pull/967))
* Prevent warning on `auto` exports with ES output ([#966](https://github.com/rollup/rollup/pull/966))
## 0.36.0
* `export { foo as default }` no longer creates a live binding ([#860](https://github.com/rollup/rollup/issues/860))
## 0.35.15
* Warn on missing unused imports in deshadowing phase ([#928](https://github.com/rollup/rollup/issues/928))
* Always add a newline to the end of bundles ([#958](https://github.com/rollup/rollup/issues/958))
## 0.35.14
* Include all parent statements of expression with effects, up to function boundary ([#930](https://github.com/rollup/rollup/issues/930))
## 0.35.13
* Include superclasses when including their subclasses ([#932](https://github.com/rollup/rollup/issues/932))
## 0.35.12
* Add `interop: false` option to disable unwrapping of external imports ([#939](https://github.com/rollup/rollup/issues/939))
## 0.35.11
* Deconflict reified namespaces with other declarations ([#910](https://github.com/rollup/rollup/issues/910))
## 0.35.10
* Only remove EmptyStatement nodes directly inside blocks ([#913](https://github.com/rollup/rollup/issues/931))
## 0.35.9
* Support Node 0.12 ([#909](https://github.com/rollup/rollup/issues/909))
## 0.35.8
* Correctly deshadow re-assigned module functions ([#910](https://github.com/rollup/rollup/issues/910))
## 0.35.7
* Refactor `flushTime.js` ([#922](https://github.com/rollup/rollup/pull/922))
## 0.35.6
* Fix browser build
## 0.35.5
* Allow empty for loop heads ([#919](https://github.com/rollup/rollup/issues/919))
## 0.35.4
* Preserve effects in for-of and for-in loops ([#870](https://github.com/rollup/rollup/issues/870))
* Remove empty statements ([#918](https://github.com/rollup/rollup/pull/918))
## 0.35.3
* Render identifiers inside template literals
## 0.35.2
* Fix broken build caused by out of date locally installed dependencies
## 0.35.1
* Rewrite deconflicted class identifiers ([#915](https://github.com/rollup/rollup/pull/915))
* Include `dependencies` in `bundle.modules` objects ([#903](https://github.com/rollup/rollup/issues/903))
* Update to Acorn 4 ([#914](https://github.com/rollup/rollup/pull/914))
## 0.35.0
* Rewrite analysis/tree-shaking code ([#902](https://github.com/rollup/rollup/pull/902))
* Include conditional mutations of global objects ([#901](https://github.com/rollup/rollup/issues/901))
* Only reify namespaces if necessary ([#898](https://github.com/rollup/rollup/issues/898))
* Track mutations of aliased globals ([#893](https://github.com/rollup/rollup/issues/893))
* Include duplicated var declarations ([#716](https://github.com/rollup/rollup/issues/716))
## 0.34.13
* Pass `{ format }` through to `transformBundle` ([#867](https://github.com/rollup/rollup/issues/867))
## 0.34.12
* Fix `rollup --watch` ([#887](https://github.com/rollup/rollup/issues/887))
* Case-sensitive paths ([#862](https://github.com/rollup/rollup/issues/862))
## 0.34.11
* Prevent leaky state when `bundle` is reused ([#875](https://github.com/rollup/rollup/issues/875))
* Ensure `intro` appears before interop block ([#880](https://github.com/rollup/rollup/issues/880))
## 0.34.10
* Allow custom `options.context` to replace top-level `this` ([#851](https://github.com/rollup/rollup/issues/851))
* Fix `noConflict` when used via `rollup --config` ([#846](https://github.com/rollup/rollup/issues/846))
* Place `outro` block *after* export block ([#852](https://github.com/rollup/rollup/issues/852))
## 0.34.9
* Disable indentation by default, for faster bundle generation ([#812](https://github.com/rollup/rollup/pull/812))
* More helpful error on missing entry file ([#802](https://github.com/rollup/rollup/issues/802))
* Preserve comments before import declarations ([#815](https://github.com/rollup/rollup/pull/815))
## 0.34.8
* Wrap UMD factory function in parens to avoid lazy parsing ([#774](https://github.com/rollup/rollup/pull/774))
## 0.34.7
* Leave it up to resolveId to normalize the entry path ([#835](https://github.com/rollup/rollup/pull/835))
* Cache decoded mappings ([#834](https://github.com/rollup/rollup/pull/834))
## 0.34.5
* Fix circular export ([#813](https://github.com/rollup/rollup/issues/813))
## 0.34.4
* Module render performance tweak ([#823](https://github.com/rollup/rollup/pull/823))
## 0.34.3
* Avoid infinite recursion in `Bundle.sort()` ([#800](https://github.com/rollup/rollup/pull/800))
## 0.34.2
* resolveId calls are cached now to improve incremental build
* Fixed error message recursion in plugins
## 0.34.1
* Support `paths` config ([#754](https://github.com/rollup/rollup/issues/754))
* Allow `export *` from external module, internally
## 0.34.0
* Use resolved IDs for relative imports that are also external modules, to allow `options.globals` to work with them ([#763](https://github.com/rollup/rollup/issues/763))
* Ensure reassigned exports are declared in an ES bundle, and remove empty `exports.foo;` statements ([#755](https://github.com/rollup/rollup/issues/755))
* Add newline after sourcemap comment ([#756](https://github.com/rollup/rollup/issues/756))
## 0.33.2
* Add `bundle` as second argument to `ongenerate` and `onwrite` hooks ([#773](https://github.com/rollup/rollup/pull/773))
* Warn on top-level `this` ([#770](https://github.com/rollup/rollup/issues/770))
## 0.33.1
* Fix `--no-strict` option ([#751](https://github.com/rollup/rollup/pull/751))
* Fix Windows edge case with case-sensitive paths ([#760](https://github.com/rollup/rollup/pull/760))
## 0.33.0
* Downgrade missing transformer sourcemap to a warning, not an error, and print the name of the offending plugin if possible ([#746](https://github.com/rollup/rollup/issues/746))
* Warn if same name is re-exported from two modules ([#722](https://github.com/rollup/rollup/issues/722))
## 0.32.4
* Add `ongenerate` and `onwrite` plugin hooks ([#742](https://github.com/rollup/rollup/pull/742))
## 0.32.3
* Generated correct sourcemaps with reified namespaces ([#668](https://github.com/rollup/rollup/issues/668))
* Exclude plugin helper modules from sourcemaps ([#747](https://github.com/rollup/rollup/pull/747))
## 0.32.2
* Allow `--globals` to work with `--external` or `options.external` in whatever configuration ([#743](https://github.com/rollup/rollup/issues/743))
## 0.32.1
* Preserve side-effects to default exports that coincide with used named exports ([#733](https://github.com/rollup/rollup/issues/733))
* Support `rollup -c node:pkgname` ([#736](https://github.com/rollup/rollup/issues/736))
## 0.32.0
* Deprecate `es6` format in favour of `es` ([#468](https://github.com/rollup/rollup/issues/468))
* Add correct `jsnext:main` build ([#726](https://github.com/rollup/rollup/pull/726))
## 0.31.2
* Allow `load` plugins to provide sourcemap ([#715](https://github.com/rollup/rollup/pull/715))
* Allow `sourceMapFile` in config options ([#717](https://github.com/rollup/rollup/issues/717))
## 0.31.1
* Logging for errors emitted by `rollup-watch` ([#712](https://github.com/rollup/rollup/issues/712))
## 0.31.0
* Rewrite top-level `this` as `undefined` ([#707](https://github.com/rollup/rollup/pull/707))
* Pass `options.acorn` to Acorn ([#564](https://github.com/rollup/rollup/issues/564))
## 0.30.0
* Bundle CLI ([#700](https://github.com/rollup/rollup/issues/700))
* Ensure absolute paths are normalised ([#704](https://github.com/rollup/rollup/issues/704))
* Allow `rollup --watch` to work with targets
## 0.29.1
* Merge `target` options with main options ([#701](https://github.com/rollup/rollup/issues/701))
* Update magic-string ([#690](https://github.com/rollup/rollup/issues/690))
## 0.29.0
* `rollup --watch` ([#284](https://github.com/rollup/rollup/issues/284))
## 0.28.0
* Experimental support for incremental rebuilds ([#658](https://github.com/rollup/rollup/pull/658))
## 0.27.1
* Ensure names exported from a module are not replaced with reserved words ([#696](https://github.com/rollup/rollup/pull/696))
* Revert ([#692](https://github.com/rollup/rollup/pull/692)) – resolved IDs must be strings
## 0.27.0
* Use native promises instead of `es6-promise` ([#689](https://github.com/rollup/rollup/issues/689))
* Support multiple targets in config files ([#655](https://github.com/rollup/rollup/issues/655))
* Allow `resolveId` plugin functions to return non-strings ([#692](https://github.com/rollup/rollup/pull/692))
## 0.26.7
* Distinguish between default and namespace imports of external module ([#637](https://github.com/rollup/rollup/issues/637))
* Add `__esModule` property to named exports in AMD, CJS and UMD modes ([#650](https://github.com/rollup/rollup/issues/650))
## 0.26.6
* Deconflict named imports from external modules in ES bundles ([#659](https://github.com/rollup/rollup/issues/659))

2
LICENSE.md

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 [these people](https://github.com/rollup/rollup/graphs/contributors)
Copyright (c) 2016 [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

6
README.md

@ -14,11 +14,11 @@
alt="license">
</a>
<a href="https://david-dm.org/rollup/rollup">
<img src="https://david-dm.org/rollup/rollup.svg"
<img src="https://david-dm.org/rollup/rollup/status.svg"
alt="dependency status">
</a>
<a href="https://codecov.io/github/rollup/rollup?branch=master">
<img src="https://codecov.io/github/rollup/rollup/coverage.svg?branch=master" alt="Coverage via Codecov" />
<img src="https://codecov.io/gh/rollup/rollup/branch/master/graph/badge.svg" alt="Coverage via Codecov" />
</a>
<a href='https://gitter.im/rollup/rollup?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge'>
<img src='https://badges.gitter.im/rollup/rollup.svg'
@ -36,6 +36,8 @@
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.
If the command line's not your jam, there's also a [step-by-step tutorial video series](https://code.lengstorf.com/learn-rollup-js/) (with accompanying written walkthrough).
[Dive into the wiki](https://github.com/rollup/rollup/wiki) when you're ready to learn more about Rollup and ES6 modules.

1
appveyor.yml

@ -12,6 +12,7 @@ environment:
# node.js
- nodejs_version: 0.12
- nodejs_version: 4
- nodejs_version: 6
install:
- ps: Install-Product node $env:nodejs_version

49
bin/handleError.js

@ -1,49 +0,0 @@
var chalk = require( 'chalk' );
var handlers = {
MISSING_CONFIG: function () {
console.error( chalk.red( 'Config file must export an options object. See https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' ) );
},
MISSING_INPUT_OPTION: function () {
console.error( chalk.red( 'You must specify an --input (-i) option' ) );
},
MISSING_OUTPUT_OPTION: function () {
console.error( chalk.red( 'You must specify an --output (-o) option when creating a file with a sourcemap' ) );
},
MISSING_NAME: function ( err ) {
console.error( chalk.red( 'You must supply a name for UMD exports (e.g. `--name myModule`)' ) );
},
PARSE_ERROR: function ( err ) {
console.error( chalk.red( 'Error parsing ' + err.file + ': ' + err.message ) );
},
ONE_AT_A_TIME: function ( err ) {
console.error( chalk.red( 'rollup can only bundle one file at a time' ) );
},
DUPLICATE_IMPORT_OPTIONS: function ( err ) {
console.error( chalk.red( 'use --input, or pass input path as argument' ) );
}
};
module.exports = function handleError ( err ) {
var handler;
if ( handler = handlers[ err && err.code ] ) {
handler( err );
} else {
console.error( chalk.red( err.message || err ) );
if ( err.stack ) {
console.error( chalk.grey( err.stack ) );
}
}
console.error( 'Type ' + chalk.cyan( 'rollup --help' ) + ' for help, or visit https://github.com/rollup/rollup/wiki' );
process.exit( 1 );
};

37
bin/rollup

@ -1,37 +0,0 @@
#!/usr/bin/env node
var minimist = require( 'minimist' ),
command;
command = minimist( process.argv.slice( 2 ), {
alias: {
// Aliases
strict: 'useStrict',
// Short options
c: 'config',
d: 'indent',
e: 'external',
f: 'format',
g: 'globals',
h: 'help',
i: 'input',
m: 'sourcemap',
n: 'name',
o: 'output',
u: 'id',
v: 'version'
}
});
if ( command.help || ( process.argv.length <= 2 && process.stdin.isTTY ) ) {
require( './showHelp' )();
}
else if ( command.version ) {
console.log( 'rollup version ' + require( '../package.json' ).version );
}
else {
require( './runRollup' )( command );
}

161
bin/runRollup.js

@ -1,161 +0,0 @@
require( 'source-map-support' ).install();
var path = require( 'path' );
var handleError = require( './handleError' );
var rollup = require( '../' );
// log to stderr to keep `rollup main.js > bundle.js` from breaking
var log = console.error.bind(console);
module.exports = function ( command ) {
if ( command._.length > 1 ) {
handleError({ code: 'ONE_AT_A_TIME' });
}
if ( command._.length === 1 ) {
if ( command.input ) {
handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' });
}
command.input = command._[0];
}
if ( command.environment ) {
command.environment.split( ',' ).forEach( function ( pair ) {
var index = pair.indexOf( ':' );
if ( ~index ) {
process.env[ pair.slice( 0, index ) ] = pair.slice( index + 1 );
} else {
process.env[ pair ] = true;
}
});
}
var config = command.config === true ? 'rollup.config.js' : command.config;
if ( config ) {
config = path.resolve( config );
rollup.rollup({
entry: config,
onwarn: function ( message ) {
if ( /Treating .+ as external dependency/.test( message ) ) return;
log( message );
}
}).then( function ( bundle ) {
var code = bundle.generate({
format: 'cjs'
}).code;
// temporarily override require
var defaultLoader = require.extensions[ '.js' ];
require.extensions[ '.js' ] = function ( m, filename ) {
if ( filename === config ) {
m._compile( code, filename );
} else {
defaultLoader( m, filename );
}
};
try {
var options = require( path.resolve( config ) );
if ( Object.keys( options ).length === 0 ) {
handleError({ code: 'MISSING_CONFIG' });
}
} catch ( err ) {
handleError( err );
}
execute( options, command );
require.extensions[ '.js' ] = defaultLoader;
})
.catch(log);
} else {
execute( {}, command );
}
};
var equivalents = {
banner: 'banner',
footer: 'footer',
format: 'format',
globals: 'globals',
id: 'moduleId',
indent: 'indent',
input: 'entry',
intro: 'intro',
name: 'moduleName',
output: 'dest',
outro: 'outro',
sourcemap: 'sourceMap',
treeshake: 'treeshake'
};
function execute ( options, command ) {
var external = ( options.external || [] )
.concat( command.external ? command.external.split( ',' ) : [] );
if ( command.globals ) {
var globals = Object.create( null );
command.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] );
}
});
command.globals = globals;
}
options.onwarn = options.onwarn || log;
options.external = external;
options.noConflict = command.conflict === false;
delete command.conflict;
// Use any options passed through the CLI as overrides.
Object.keys( equivalents ).forEach( function ( cliOption ) {
if ( command.hasOwnProperty( cliOption ) ) {
options[ equivalents[ cliOption ] ] = command[ cliOption ];
}
});
try {
bundle( options ).catch( handleError );
} catch ( err ) {
handleError( err );
}
}
function bundle ( options ) {
if ( !options.entry ) {
handleError({ code: 'MISSING_INPUT_OPTION' });
}
return rollup.rollup( options ).then( function ( bundle ) {
if ( options.dest ) {
return bundle.write( options );
}
if ( options.sourceMap && options.sourceMap !== 'inline' ) {
handleError({ code: 'MISSING_OUTPUT_OPTION' });
}
var result = bundle.generate( options );
var code = result.code,
map = result.map;
if ( options.sourceMap === 'inline' ) {
code += '\n//# sourceMappingURL=' + map.toUrl();
}
process.stdout.write( code );
});
}

13
bin/showHelp.js

@ -1,13 +0,0 @@
var fs = require( 'fs' );
var path = require( 'path' );
module.exports = function () {
fs.readFile( path.join( __dirname, 'help.md' ), function ( err, result ) {
var help;
if ( err ) throw err;
help = result.toString().replace( '<%= version %>', require( '../package.json' ).version );
console.log( '\n' + help + '\n' );
});
};

65
bin/src/handleError.js

@ -0,0 +1,65 @@
import * as chalk from 'chalk';
function stderr ( msg ) {
console.error( msg ); // eslint-disable-line no-console
}
const handlers = {
MISSING_CONFIG: () => {
stderr( chalk.red( 'Config file must export an options object. See https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' ) );
},
MISSING_EXTERNAL_CONFIG: err => {
stderr( chalk.red( `Could not resolve config file ${err.config}` ) );
},
MISSING_INPUT_OPTION: () => {
stderr( chalk.red( 'You must specify an --input (-i) option' ) );
},
MISSING_OUTPUT_OPTION: () => {
stderr( chalk.red( 'You must specify an --output (-o) option when creating a file with a sourcemap' ) );
},
MISSING_NAME: () => {
stderr( chalk.red( 'You must supply a name for UMD exports (e.g. `--name myModule`)' ) );
},
PARSE_ERROR: err => {
stderr( chalk.red( `Error parsing ${err.file}: ${err.message}` ) );
},
ONE_AT_A_TIME: () => {
stderr( chalk.red( 'rollup can only bundle one file at a time' ) );
},
DUPLICATE_IMPORT_OPTIONS: () => {
stderr( chalk.red( 'use --input, or pass input path as argument' ) );
},
ROLLUP_WATCH_NOT_INSTALLED: () => {
stderr( chalk.red( 'rollup --watch depends on the rollup-watch package, which could not be found. You can install it globally (recommended) with ' ) + chalk.cyan( 'npm install -g rollup-watch' ) );
},
WATCHER_MISSING_INPUT_OR_OUTPUT: () => {
stderr( chalk.red( 'must specify --input and --output when using rollup --watch' ) );
}
};
export default function handleError ( err, recover ) {
const handler = handlers[ err && err.code ];
if ( handler ) {
handler( err );
} else {
stderr( chalk.red( err.message || err ) );
if ( err.stack ) {
stderr( chalk.grey( err.stack ) );
}
}
stderr( `Type ${chalk.cyan( 'rollup --help' )} for help, or visit https://github.com/rollup/rollup/wiki` );
if ( !recover ) process.exit( 1 );
}

5
bin/help.md → bin/src/help.md

@ -1,4 +1,4 @@
rollup version <%= version %>
rollup version __VERSION__
=====================================
Usage: rollup [options] <entry file>
@ -9,9 +9,10 @@ Basic options:
-h, --help Show this help message
-c, --config Use this config file (if argument is used but value
is unspecified, defaults to rollup.config.js)
-w, --watch Watch files in bundle and rebuild on changes
-i, --input Input (alternative to <entry file>)
-o, --output <output> Output (if absent, prints to stdout)
-f, --format [es6] Type of output (amd, cjs, es6, iife, umd)
-f, --format [es] Type of output (amd, cjs, es, 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

39
bin/src/index.js

@ -0,0 +1,39 @@
import minimist from 'minimist';
import help from './help.md';
import { version } from '../../package.json';
import runRollup from './runRollup';
const command = minimist( process.argv.slice( 2 ), {
alias: {
// Aliases
strict: 'useStrict',
// Short options
c: 'config',
d: 'indent',
e: 'external',
f: 'format',
g: 'globals',
h: 'help',
i: 'input',
l: 'legacy',
m: 'sourcemap',
n: 'name',
o: 'output',
u: 'id',
v: 'version',
w: 'watch'
}
});
if ( command.help || ( process.argv.length <= 2 && process.stdin.isTTY ) ) {
console.log( `\n${help.replace('__VERSION__', version)}\n` ); // eslint-disable-line no-console
}
else if ( command.version ) {
console.log( `rollup version ${version}` ); // eslint-disable-line no-console
}
else {
runRollup( command );
}

247
bin/src/runRollup.js

@ -0,0 +1,247 @@
import { realpathSync } from 'fs';
import * as rollup from 'rollup';
import relative from 'require-relative';
import handleError from './handleError';
import SOURCEMAPPING_URL from './sourceMappingUrl.js';
import { install as installSourcemapSupport } from 'source-map-support';
installSourcemapSupport();
// stderr to stderr to keep `rollup main.js > bundle.js` from breaking
const stderr = console.error.bind( console ); // eslint-disable-line no-console
export default function runRollup ( command ) {
if ( command._.length > 1 ) {
handleError({ code: 'ONE_AT_A_TIME' });
}
if ( command._.length === 1 ) {
if ( command.input ) {
handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' });
}
command.input = command._[0];
}
if ( command.environment ) {
command.environment.split( ',' ).forEach( pair => {
const index = pair.indexOf( ':' );
if ( ~index ) {
process.env[ pair.slice( 0, index ) ] = pair.slice( index + 1 );
} else {
process.env[ pair ] = true;
}
});
}
let config = command.config === true ? 'rollup.config.js' : command.config;
if ( config ) {
if ( config.slice( 0, 5 ) === 'node:' ) {
const pkgName = config.slice( 5 );
try {
config = relative.resolve( `rollup-config-${pkgName}`, process.cwd() );
} catch ( err ) {
try {
config = relative.resolve( pkgName, process.cwd() );
} catch ( err ) {
if ( err.code === 'MODULE_NOT_FOUND' ) {
handleError({ code: 'MISSING_EXTERNAL_CONFIG', config });
}
throw err;
}
}
} else {
// find real path of config so it matches what Node provides to callbacks in require.extensions
config = realpathSync( config );
}
rollup.rollup({
entry: config,
onwarn: message => {
if ( /Treating .+ as external dependency/.test( message ) ) return;
stderr( message );
}
}).then( bundle => {
const { code } = bundle.generate({
format: 'cjs'
});
// temporarily override require
const defaultLoader = require.extensions[ '.js' ];
require.extensions[ '.js' ] = ( m, filename ) => {
if ( filename === config ) {
m._compile( code, filename );
} else {
defaultLoader( m, filename );
}
};
try {
const options = require( config );
if ( Object.keys( options ).length === 0 ) {
handleError({ code: 'MISSING_CONFIG' });
}
execute( options, command );
require.extensions[ '.js' ] = defaultLoader;
} catch ( err ) {
handleError( err );
}
})
.catch( stderr );
} else {
execute( {}, command );
}
}
const equivalents = {
useStrict: 'useStrict',
banner: 'banner',
footer: 'footer',
format: 'format',
globals: 'globals',
id: 'moduleId',
indent: 'indent',
input: 'entry',
intro: 'intro',
legacy: 'legacy',
name: 'moduleName',
output: 'dest',
outro: 'outro',
sourcemap: 'sourceMap',
treeshake: 'treeshake'
};
function execute ( options, command ) {
let external;
const commandExternal = ( command.external || '' ).split( ',' );
const optionsExternal = options.external;
if ( command.globals ) {
let globals = Object.create( null );
command.globals.split( ',' ).forEach( str => {
const names = str.split( ':' );
globals[ names[0] ] = names[1];
// Add missing Module IDs to external.
if ( commandExternal.indexOf( names[0] ) === -1 ) {
commandExternal.push( names[0] );
}
});
command.globals = globals;
}
if ( typeof optionsExternal === 'function' ) {
external = id => {
return optionsExternal( id ) || ~commandExternal.indexOf( id );
};
} else {
external = ( optionsExternal || [] ).concat( commandExternal );
}
options.onwarn = options.onwarn || stderr;
options.external = external;
// Use any options passed through the CLI as overrides.
Object.keys( equivalents ).forEach( cliOption => {
if ( command.hasOwnProperty( cliOption ) ) {
options[ equivalents[ cliOption ] ] = command[ cliOption ];
}
});
try {
if ( command.watch ) {
if ( !options.entry || ( !options.dest && !options.targets ) ) {
handleError({ code: 'WATCHER_MISSING_INPUT_OR_OUTPUT' });
}
try {
const watch = relative( 'rollup-watch', process.cwd() );
const watcher = watch( rollup, options );
watcher.on( 'event', event => {
switch ( event.code ) {
case 'STARTING':
stderr( 'checking rollup-watch version...' );
break;
case 'BUILD_START':
stderr( 'bundling...' );
break;
case 'BUILD_END':
stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' );
break;
case 'ERROR':
handleError( event.error, true );
break;
default:
stderr( 'unknown event', event );
}
});
} catch ( err ) {
if ( err.code === 'MODULE_NOT_FOUND' ) {
err.code = 'ROLLUP_WATCH_NOT_INSTALLED';
}
handleError( err );
}
} else {
bundle( options ).catch( handleError );
}
} catch ( err ) {
handleError( err );
}
}
function clone ( object ) {
return assign( {}, object );
}
function assign ( target, source ) {
Object.keys( source ).forEach( key => {
target[ key ] = source[ key ];
});
return target;
}
function bundle ( options ) {
if ( !options.entry ) {
handleError({ code: 'MISSING_INPUT_OPTION' });
}
return rollup.rollup( options ).then( bundle => {
if ( options.dest ) {
return bundle.write( options );
}
if ( options.targets ) {
let result = null;
options.targets.forEach( target => {
result = bundle.write( assign( clone( options ), target ) );
});
return result;
}
if ( options.sourceMap && options.sourceMap !== 'inline' ) {
handleError({ code: 'MISSING_OUTPUT_OPTION' });
}
let { code, map } = bundle.generate( options );
if ( options.sourceMap === 'inline' ) {
code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`;
}
process.stdout.write( code );
});
}

4
bin/src/sourceMappingUrl.js

@ -0,0 +1,4 @@
let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';
export default SOURCEMAPPING_URL;

80
browser/path.js

@ -0,0 +1,80 @@
export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/;
export const relativePath = /^\.?\.\//;
export function isAbsolute ( path ) {
return absolutePath.test( path );
}
export function isRelative ( path ) {
return relativePath.test( path );
}
export function normalize ( path ) {
return path.replace( /\\/g, '/' );
}
export function basename ( path ) {
return path.split( /(\/|\\)/ ).pop();
}
export function dirname ( path ) {
const match = /(\/|\\)[^\/\\]*$/.exec( path );
if ( !match ) return '.';
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( basename( path ) );
if ( !match ) return '';
return match[0];
}
export function relative ( from, to ) {
const fromParts = from.split( /[\/\\]/ ).filter( Boolean );
const toParts = to.split( /[\/\\]/ ).filter( Boolean );
while ( fromParts[0] && toParts[0] && fromParts[0] === toParts[0] ) {
fromParts.shift();
toParts.shift();
}
while ( toParts[0] === '.' || toParts[0] === '..' ) {
const toPart = toParts.shift();
if ( toPart === '..' ) {
fromParts.pop();
}
}
while ( fromParts.pop() ) {
toParts.unshift( '..' );
}
return toParts.join( '/' );
}
export function resolve ( ...paths ) {
let resolvedParts = paths.shift().split( /[\/\\]/ );
paths.forEach( path => {
if ( isAbsolute( path ) ) {
resolvedParts = path.split( /[\/\\]/ );
} else {
const parts = path.split( /[\/\\]/ );
while ( parts[0] === '.' || parts[0] === '..' ) {
const part = parts.shift();
if ( part === '..' ) {
resolvedParts.pop();
}
}
resolvedParts.push.apply( resolvedParts, parts );
}
});
return resolvedParts.join( '/' ); // TODO windows...
}

1
browser/promise.js

@ -1 +0,0 @@
export default window.Promise;

71
package.json

@ -1,23 +1,29 @@
{
"name": "rollup",
"version": "0.26.6",
"version": "0.37.0",
"description": "Next-generation ES6 module bundler",
"main": "dist/rollup.js",
"jsnext:main": "src/rollup.js",
"module": "dist/rollup.es.js",
"jsnext:main": "dist/rollup.es.js",
"bin": {
"rollup": "./bin/rollup"
},
"scripts": {
"pretest": "npm run build",
"test": "mocha --compilers js:buble/register",
"pretest": "npm run build && npm run build:cli",
"test": "mocha",
"test:quick": "rollup -c && mocha",
"pretest-coverage": "npm run build",
"test-coverage": "rm -rf coverage/* && istanbul cover --report json node_modules/.bin/_mocha -- -u exports -R spec test/test.js",
"posttest-coverage": "remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.json -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.lcov -t lcovonly -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped -t html -b dist",
"ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov",
"build": "git rev-parse HEAD > .commithash && rollup -c -o dist/rollup.js",
"build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js -o dist/rollup.browser.js",
"build": "git rev-parse HEAD > .commithash && rollup -c",
"build:cli": "rollup -c rollup.config.cli.js",
"build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js",
"watch": "rollup -c -w",
"watch:browser": "rollup -c rollup.config.browser.js -w",
"watch:cli": "rollup -c rollup.config.cli.js -w",
"prepublish": "npm run lint && npm test && npm run build:browser",
"lint": "eslint src browser"
"lint": "eslint src browser test/test.js test/utils test/**/_config.js"
},
"repository": {
"type": "git",
@ -32,7 +38,8 @@
],
"author": "Rich Harris",
"contributors": [
"Oskar Segersvärd <victorystick@gmail.com>"
"Oskar Segersvärd <victorystick@gmail.com>",
"Bogdan Chadkin <trysound@yandex.ru>"
],
"license": "MIT",
"bugs": {
@ -40,36 +47,38 @@
},
"homepage": "https://github.com/rollup/rollup",
"devDependencies": {
"acorn": "^3.1.0",
"babel-core": "^5.8.32",
"buble": "^0.6.4",
"acorn": "^4.0.1",
"buble": "^0.12.5",
"chalk": "^1.1.3",
"codecov.io": "^0.1.6",
"console-group": "^0.2.0",
"es6-promise": "^3.0.2",
"eslint": "^2.9.0",
"estree-walker": "^0.2.0",
"istanbul": "^0.4.0",
"magic-string": "^0.10.1",
"mocha": "^2.3.3",
"remap-istanbul": "^0.5.1",
"rollup": "^0.26.2",
"rollup-plugin-buble": "^0.6.0",
"rollup-plugin-node-resolve": "^1.5.0",
"rollup-plugin-replace": "^1.0.1",
"sander": "^0.5.0",
"source-map": "^0.5.3",
"sourcemap-codec": "^1.2.1",
"uglify-js": "^2.6.1"
"console-group": "^0.3.1",
"eslint": "^2.13.0",
"eslint-plugin-import": "^1.14.0",
"estree-walker": "^0.2.1",
"istanbul": "^0.4.3",
"magic-string": "^0.15.2",
"minimist": "^1.2.0",
"mocha": "^3.0.0",
"remap-istanbul": "^0.6.4",
"require-relative": "^0.8.7",
"rollup": "^0.34.0",
"rollup-plugin-buble": "^0.12.1",
"rollup-plugin-commonjs": "^3.0.0",
"rollup-plugin-json": "^2.0.0",
"rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-replace": "^1.1.0",
"rollup-plugin-string": "^2.0.0",
"sander": "^0.5.1",
"source-map": "^0.5.6",
"sourcemap-codec": "^1.3.0",
"uglify-js": "^2.6.2"
},
"dependencies": {
"chalk": "^1.1.1",
"minimist": "^1.2.0",
"source-map-support": "^0.4.0"
},
"files": [
"src",
"dist",
"bin",
"bin/rollup",
"README.md"
]
}

5
rollup.config.browser.js

@ -3,11 +3,12 @@ import config from './rollup.config.js';
config.plugins.push({
load: function ( id ) {
if ( ~id.indexOf( 'fs.js' ) ) return readFileSync( 'browser/fs.js' ).toString();
if ( ~id.indexOf( 'es6-promise' ) ) return readFileSync( 'browser/promise.js' ).toString();
if ( ~id.indexOf( 'fs.js' ) ) return readFileSync( 'browser/fs.js', 'utf-8' );
if ( ~id.indexOf( 'path.js' ) ) return readFileSync( 'browser/path.js', 'utf-8' );
}
});
config.format = 'umd';
config.dest = 'dist/rollup.browser.js';
export default config;

34
rollup.config.cli.js

@ -0,0 +1,34 @@
import buble from 'rollup-plugin-buble';
import json from 'rollup-plugin-json';
import string from 'rollup-plugin-string';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
export default {
entry: 'bin/src/index.js',
dest: 'bin/rollup',
format: 'cjs',
banner: '#!/usr/bin/env node',
plugins: [
string({ include: '**/*.md' }),
json(),
buble(),
commonjs({
include: 'node_modules/**',
namedExports: { chalk: [ 'red', 'cyan', 'grey' ] }
}),
nodeResolve({
main: true
})
],
external: [
'fs',
'path',
'module',
'source-map-support',
'rollup'
],
paths: {
rollup: '../dist/rollup.js'
}
};

21
rollup.config.js

@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
import buble from 'rollup-plugin-buble';
import npm from 'rollup-plugin-node-resolve';
import nodeResolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
var pkg = JSON.parse( readFileSync( 'package.json', 'utf-8' ) );
@ -20,13 +20,15 @@ var banner = readFileSync( 'src/banner.js', 'utf-8' )
export default {
entry: 'src/rollup.js',
format: 'cjs',
plugins: [
buble({
include: [ 'src/**', 'node_modules/acorn/**' ]
include: [ 'src/**', 'node_modules/acorn/**' ],
target: {
node: '0.12'
}
}),
npm({
nodeResolve({
jsnext: true
}),
@ -37,8 +39,15 @@ export default {
values: { 'VERSION': pkg.version }
})
],
external: [ 'fs' ],
external: [
'fs',
'path'
],
banner: banner,
sourceMap: true,
moduleName: 'rollup'
moduleName: 'rollup',
targets: [
{ dest: 'dist/rollup.js', format: 'cjs' },
{ dest: 'dist/rollup.es.js', format: 'es' }
]
};

337
src/Bundle.js

@ -1,5 +1,8 @@
import MagicString from 'magic-string';
import { timeStart, timeEnd } from './utils/flushTime.js';
import { decode } from 'sourcemap-codec';
import { Bundle as MagicStringBundle } from 'magic-string';
import first from './utils/first.js';
import { find } from './utils/array.js';
import { blank, forOwn, keys } from './utils/object.js';
import Module from './Module.js';
import ExternalModule from './ExternalModule.js';
@ -8,26 +11,32 @@ import ensureArray from './utils/ensureArray.js';
import { load, makeOnwarn, resolveId } from './utils/defaults.js';
import getExportMode from './utils/getExportMode.js';
import getIndentString from './utils/getIndentString.js';
import { unixizePath } from './utils/normalizePlatform.js';
import { mapSequence } from './utils/promise.js';
import transform from './utils/transform.js';
import transformBundle from './utils/transformBundle.js';
import collapseSourcemaps from './utils/collapseSourcemaps.js';
import SOURCEMAPPING_URL from './utils/sourceMappingURL.js';
import callIfFunction from './utils/callIfFunction.js';
import { dirname, isRelative, isAbsolute, relative, resolve } from './utils/path.js';
import { dirname, isRelative, isAbsolute, normalize, relative, resolve } from './utils/path.js';
import BundleScope from './ast/scopes/BundleScope.js';
export default class Bundle {
constructor ( options ) {
this.cachedModules = new Map();
if ( options.cache ) {
options.cache.modules.forEach( module => {
this.cachedModules.set( module.id, module );
});
}
this.plugins = ensureArray( options.plugins );
this.plugins.forEach( plugin => {
if ( plugin.options ) {
options = plugin.options( options ) || options;
}
});
options = this.plugins.reduce( ( acc, plugin ) => {
if ( plugin.options ) return plugin.options( acc ) || acc;
return acc;
}, options);
this.entry = unixizePath( options.entry );
this.entry = options.entry;
this.entryId = null;
this.entryModule = null;
@ -39,42 +48,56 @@ export default class Bundle {
.concat( resolveId )
);
this.load = first(
this.plugins
const loaders = this.plugins
.map( plugin => plugin.load )
.filter( Boolean )
.concat( load )
);
this.transformers = this.plugins
.map( plugin => plugin.transform )
.filter( Boolean );
this.hasLoaders = loaders.length !== 0;
this.load = first( loaders.concat( load ) );
this.bundleTransformers = this.plugins
.map( plugin => plugin.transformBundle )
.filter( Boolean );
this.getPath = typeof options.paths === 'function' ?
( id => options.paths( id ) || this.getPathRelativeToEntryDirname( id ) ) :
options.paths ?
( id => options.paths.hasOwnProperty( id ) ? options.paths[ id ] : this.getPathRelativeToEntryDirname( id ) ) :
id => this.getPathRelativeToEntryDirname( id );
this.moduleById = blank();
this.modules = [];
this.scope = new BundleScope();
// TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
[ 'module', 'exports', '_interopDefault' ].forEach( name => {
this.scope.findDeclaration( name ); // creates global declaration as side-effect
});
this.moduleById = new Map();
this.modules = [];
this.externalModules = [];
this.internalNamespaces = [];
this.assumedGlobals = blank();
this.context = String( options.context );
if ( typeof options.moduleContext === 'function' ) {
this.getModuleContext = id => options.moduleContext( id ) || this.context;
} else if ( typeof options.moduleContext === 'object' ) {
const moduleContext = new Map();
Object.keys( options.moduleContext ).forEach( key => {
moduleContext.set( resolve( key ), options.moduleContext[ key ] );
});
this.getModuleContext = id => moduleContext.get( id ) || this.context;
} else {
this.getModuleContext = () => this.context;
}
if ( typeof options.external === 'function' ) {
this.isExternal = options.external;
} else {
const ids = ensureArray( options.external ).map( id => id.replace( /[\/\\]/g, '/' ) );
const ids = ensureArray( options.external );
this.isExternal = id => ids.indexOf( id ) !== -1;
}
this.onwarn = options.onwarn || makeOnwarn();
// TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
[ 'module', 'exports', '_interopDefault' ].forEach( global => this.assumedGlobals[ global ] = true );
this.varOrConst = options.preferConst ? 'const' : 'var';
this.legacy = options.legacy;
this.acornOptions = options.acorn || {};
this.dependentExpressions = [];
}
build () {
@ -83,6 +106,7 @@ export default class Bundle {
// of the entry module's dependencies
return this.resolveId( this.entry, undefined )
.then( id => {
if ( id == null ) throw new Error( `Could not resolve entry (${this.entry})` );
this.entryId = id;
return this.fetchModule( id, undefined );
})
@ -91,44 +115,79 @@ export default class Bundle {
// Phase 2 – binding. We link references to their declarations
// to generate a complete picture of the bundle
timeStart( 'phase 2' );
this.modules.forEach( module => module.bindImportSpecifiers() );
this.modules.forEach( module => module.bindAliases() );
this.modules.forEach( module => module.bindReferences() );
timeEnd( 'phase 2' );
// Phase 3 – marking. We 'run' each statement to see which ones
// need to be included in the generated bundle
timeStart( 'phase 3' );
// mark all export statements
entryModule.getExports().forEach( name => {
const declaration = entryModule.traceExport( name );
declaration.exportName = name;
declaration.activate();
declaration.use();
if ( declaration.isNamespace ) {
declaration.needsNamespaceBlock = true;
}
});
// mark statements that should appear in the bundle
if ( this.treeshake ) {
this.modules.forEach( module => {
module.run();
});
let settled = false;
while ( !settled ) {
settled = true;
this.modules.forEach( module => {
if ( module.run( this.treeshake ) ) settled = false;
});
let i = this.dependentExpressions.length;
while ( i-- ) {
const expression = this.dependentExpressions[i];
let statement = expression;
while ( statement.parent && !/Function/.test( statement.parent.type ) ) statement = statement.parent;
if ( !statement || statement.ran ) {
this.dependentExpressions.splice( i, 1 );
} else if ( expression.isUsedByBundle() ) {
settled = false;
statement.run( statement.findScope() );
this.dependentExpressions.splice( i, 1 );
}
}
}
}
timeEnd( 'phase 3' );
// Phase 4 – final preparation. We order the modules with an
// enhanced topological sort that accounts for cycles, then
// ensure that names are deconflicted throughout the bundle
timeStart( 'phase 4' );
this.orderedModules = this.sort();
this.deconflict();
timeEnd( 'phase 4' );
});
}
deconflict () {
let used = blank();
const used = blank();
// ensure no conflicts with globals
keys( this.assumedGlobals ).forEach( name => used[ name ] = 1 );
keys( this.scope.declarations ).forEach( name => used[ name ] = 1 );
function getSafeName ( name ) {
while ( used[ name ] ) {
@ -139,33 +198,45 @@ export default class Bundle {
return name;
}
const toDeshadow = new Map();
this.externalModules.forEach( module => {
module.name = getSafeName( module.name );
const safeName = getSafeName( module.name );
toDeshadow.set( safeName, true );
module.name = safeName;
// ensure we don't shadow named external imports, if
// we're creating an ES6 bundle
forOwn( module.declarations, ( declaration, name ) => {
declaration.setSafeName( getSafeName( name ) );
const safeName = getSafeName( name );
toDeshadow.set( safeName, true );
declaration.setSafeName( safeName );
});
});
this.modules.forEach( module => {
forOwn( module.declarations, ( declaration, originalName ) => {
if ( declaration.isGlobal ) return;
if ( originalName === 'default' ) {
if ( declaration.original && !declaration.original.isReassigned ) return;
forOwn( module.scope.declarations, ( declaration ) => {
if ( declaration.isDefault && declaration.declaration.id ) {
return;
}
declaration.name = getSafeName( declaration.name );
});
// deconflict reified namespaces
const namespace = module.namespace();
if ( namespace.needsNamespaceBlock ) {
namespace.name = getSafeName( namespace.name );
}
});
this.scope.deshadow( toDeshadow );
}
fetchModule ( id, importer ) {
// short-circuit cycles
if ( id in this.moduleById ) return null;
this.moduleById[ id ] = null;
if ( this.moduleById.has( id ) ) return null;
this.moduleById.set( id, null );
return this.load( id )
.catch( err => {
@ -181,57 +252,85 @@ export default class Bundle {
throw new Error( `Error loading ${id}: load hook should return a string, a { code, map } object, or nothing/null` );
})
.then( source => transform( source, id, this.transformers ) )
.then( source => {
const { code, originalCode, ast, sourceMapChain } = source;
if ( typeof source === 'string' ) {
source = {
code: source,
ast: null
};
}
const module = new Module({ id, code, originalCode, ast, sourceMapChain, bundle: this });
if ( this.cachedModules.has( id ) && this.cachedModules.get( id ).originalCode === source.code ) {
return this.cachedModules.get( id );
}
return transform( source, id, this.plugins );
})
.then( source => {
const { code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds } = source;
const module = new Module({
id,
code,
originalCode,
originalSourceMap,
ast,
sourceMapChain,
resolvedIds,
bundle: this
});
this.modules.push( module );
this.moduleById[ id ] = module;
this.moduleById.set( id, module );
return this.fetchAllDependencies( module ).then( () => module );
return this.fetchAllDependencies( module ).then( () => {
keys( module.exports ).forEach( name => {
module.exportsAll[name] = module.id;
});
module.exportAllSources.forEach( source => {
const id = module.resolvedIds[ source ];
const exportAllModule = this.moduleById.get( id );
if ( exportAllModule.isExternal ) return;
keys( exportAllModule.exportsAll ).forEach( name => {
if ( name in module.exportsAll ) {
this.onwarn( `Conflicting namespaces: ${module.id} re-exports '${name}' from both ${module.exportsAll[ name ]} (will be ignored) and ${exportAllModule.exportsAll[ name ]}.` );
}
module.exportsAll[ name ] = exportAllModule.exportsAll[ name ];
});
});
return module;
});
});
}
fetchAllDependencies ( module ) {
return mapSequence( module.sources, source => {
return this.resolveId( source, module.id )
const resolvedId = module.resolvedIds[ source ];
return ( resolvedId ? Promise.resolve( resolvedId ) : this.resolveId( source, module.id ) )
.then( resolvedId => {
let externalName;
if ( resolvedId ) {
// If the `resolvedId` is supposed to be external, make it so.
externalName = resolvedId.replace( /[\/\\]/g, '/' );
} else if ( isRelative( source ) ) {
// This could be an external, relative dependency, based on the current module's parent dir.
externalName = resolve( module.id, '..', source );
}
const forcedExternal = externalName && this.isExternal( externalName );
if ( !resolvedId || forcedExternal ) {
let normalizedExternal = source;
if ( !forcedExternal ) {
if ( isRelative( source ) ) throw new Error( `Could not resolve ${source} from ${module.id}` );
if ( !this.isExternal( source ) ) this.onwarn( `Treating '${source}' as external dependency` );
} else if ( resolvedId ) {
if ( isRelative(resolvedId) || isAbsolute(resolvedId) ) {
// Try to deduce relative path from entry dir if resolvedId is defined as a relative path.
normalizedExternal = this.getPathRelativeToEntryDirname( resolvedId );
} else {
normalizedExternal = resolvedId;
}
const externalId = resolvedId || (
isRelative( source ) ? resolve( module.id, '..', source ) : source
);
let isExternal = this.isExternal( externalId );
if ( !resolvedId && !isExternal ) {
if ( isRelative( source ) ) throw new Error( `Could not resolve '${source}' from ${module.id}` );
this.onwarn( `Treating '${source}' as external dependency` );
isExternal = true;
}
module.resolvedIds[ source ] = normalizedExternal;
if ( !this.moduleById[ normalizedExternal ] ) {
const module = new ExternalModule( normalizedExternal );
if ( isExternal ) {
module.resolvedIds[ source ] = externalId;
if ( !this.moduleById.has( externalId ) ) {
const module = new ExternalModule( externalId, this.getPath( externalId ) );
this.externalModules.push( module );
this.moduleById[ normalizedExternal ] = module;
this.moduleById.set( externalId, module );
}
}
else {
} else {
if ( resolvedId === module.id ) {
throw new Error( `A module cannot import itself (${resolvedId})` );
}
@ -244,51 +343,62 @@ export default class Bundle {
}
getPathRelativeToEntryDirname ( resolvedId ) {
// Get a path relative to the resolved entry directory
if ( isRelative( resolvedId ) || isAbsolute( resolvedId ) ) {
const entryDirname = dirname( this.entryId );
const relativeToEntry = relative( entryDirname, resolvedId );
const relativeToEntry = normalize( relative( entryDirname, resolvedId ) );
if ( isRelative( relativeToEntry )) {
return relativeToEntry;
return isRelative( relativeToEntry ) ? relativeToEntry : `./${relativeToEntry}`;
}
// The path is missing the `./` prefix
return `./${relativeToEntry}`;
return resolvedId;
}
render ( options = {} ) {
const format = options.format || 'es6';
if ( options.format === 'es6' ) {
this.onwarn( 'The es6 format is deprecated – use `es` instead' );
options.format = 'es';
}
const format = options.format || 'es';
// Determine export mode - 'default', 'named', 'none'
const exportMode = getExportMode( this, options.exports, options.moduleName );
const exportMode = getExportMode( this, options );
let magicString = new MagicStringBundle({ separator: '\n\n' });
const usedModules = [];
let magicString = new MagicString.Bundle({ separator: '\n\n' });
let usedModules = [];
timeStart( 'render modules' );
this.orderedModules.forEach( module => {
const source = module.render( format === 'es6' );
const source = module.render( format === 'es', this.legacy );
if ( source.toString().length ) {
magicString.addSource( source );
usedModules.push( module );
}
});
const intro = [ options.intro ]
timeEnd( 'render modules' );
let intro = [ options.intro ]
.concat(
this.plugins.map( plugin => plugin.intro && plugin.intro() )
)
.filter( Boolean )
.join( '\n\n' );
if ( intro ) magicString.prepend( intro + '\n' );
if ( options.outro ) magicString.append( '\n' + options.outro );
if ( intro ) intro += '\n';
const indentString = getIndentString( magicString, options );
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(), { exportMode, indentString }, options );
timeStart( 'render format' );
magicString = finalise( this, magicString.trim(), { exportMode, indentString, intro }, options );
timeEnd( 'render format' );
const banner = [ options.banner ]
.concat( this.plugins.map( plugin => plugin.banner ) )
@ -307,34 +417,43 @@ export default class Bundle {
let code = magicString.toString();
let map = null;
let bundleSourcemapChain = [];
const bundleSourcemapChain = [];
code = transformBundle( code, this.bundleTransformers, bundleSourcemapChain )
code = transformBundle( code, this.plugins, bundleSourcemapChain, options )
.replace( new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ), '' );
if ( options.sourceMap ) {
timeStart( 'sourceMap' );
let file = options.sourceMapFile || options.dest;
if ( file ) file = resolve( typeof process !== 'undefined' ? process.cwd() : '', file );
if ( this.hasLoaders || find( this.plugins, plugin => plugin.transform || plugin.transformBundle ) ) {
map = magicString.generateMap( {} );
if ( typeof map.mappings === 'string' ) {
map.mappings = decode( map.mappings );
}
map = collapseSourcemaps( file, map, usedModules, bundleSourcemapChain, this.onwarn );
} else {
map = magicString.generateMap({ file, includeContent: true });
if ( this.transformers.length || this.bundleTransformers.length ) {
map = collapseSourcemaps( map, usedModules, bundleSourcemapChain );
}
map.sources = map.sources.map( unixizePath );
map.sources = map.sources.map( normalize );
timeEnd( 'sourceMap' );
}
if ( code[ code.length - 1 ] !== '\n' ) code += '\n';
return { code, map };
}
sort () {
let seen = {};
let hasCycles;
let ordered = [];
const seen = {};
const ordered = [];
let stronglyDependsOn = blank();
let dependsOn = blank();
const stronglyDependsOn = blank();
const dependsOn = blank();
this.modules.forEach( module => {
stronglyDependsOn[ module.id ] = blank();
@ -384,15 +503,17 @@ export default class Bundle {
// b imports a, a is placed before b. We need to find the module
// in question, so we can provide a useful error message
let parent = '[[unknown]]';
const visited = {};
const findParent = module => {
if ( dependsOn[ module.id ][ a.id ] && dependsOn[ module.id ][ b.id ] ) {
parent = module.id;
} else {
return true;
}
visited[ module.id ] = true;
for ( let i = 0; i < module.dependencies.length; i += 1 ) {
const dependency = module.dependencies[i];
if ( findParent( dependency ) ) return;
}
if ( !visited[ dependency.id ] && findParent( dependency ) ) return true;
}
};

258
src/Declaration.js

@ -1,174 +1,41 @@
import { blank, forOwn, keys } from './utils/object.js';
import run from './utils/run.js';
import { SyntheticReference } from './Reference.js';
const use = alias => alias.use();
import makeLegalIdentifier, { reservedWords } from './utils/makeLegalIdentifier.js';
import { UNKNOWN } from './ast/values.js';
export default class Declaration {
constructor ( node, isParam, statement ) {
if ( node ) {
if ( node.type === 'FunctionDeclaration' ) {
this.isFunctionDeclaration = true;
this.functionNode = node;
} else if ( node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) {
this.isFunctionDeclaration = true;
this.functionNode = node.init;
}
}
constructor ( node, isParam ) {
this.node = node;
this.statement = statement;
this.name = null;
this.name = node.id ? node.id.name : node.name;
this.exportName = null;
this.isParam = isParam;
this.isReassigned = false;
this.aliases = [];
this.isUsed = false;
}
addAlias ( declaration ) {
this.aliases.push( declaration );
}
addReference ( reference ) {
reference.declaration = this;
this.name = reference.name; // TODO handle differences of opinion
if ( reference.isReassignment ) this.isReassigned = true;
}
render ( es6 ) {
if ( es6 ) return this.name;
if ( !this.isReassigned || !this.exportName ) return this.name;
return `exports.${this.exportName}`;
}
run ( strongDependencies ) {
if ( this.tested ) return this.hasSideEffects;
if ( !this.functionNode ) {
this.hasSideEffects = true; // err on the side of caution. TODO handle unambiguous `var x; x = y => z` cases
} else {
if ( this.running ) return true; // short-circuit infinite loop
this.running = true;
this.hasSideEffects = run( this.functionNode.body, this.functionNode._scope, this.statement, strongDependencies, false );
this.running = false;
}
this.tested = true;
return this.hasSideEffects;
}
use () {
if ( this.isUsed ) return;
this.isUsed = true;
if ( this.statement ) this.statement.mark();
this.aliases.forEach( use );
}
}
export class SyntheticDefaultDeclaration {
constructor ( node, statement, name ) {
this.node = node;
this.statement = statement;
this.name = name;
this.original = null;
this.exportName = null;
this.aliases = [];
}
activate () {
if ( this.activated ) return;
this.activated = true;
addAlias ( declaration ) {
this.aliases.push( declaration );
if ( this.isParam ) return;
this.node.activate();
}
addReference ( reference ) {
// Bind the reference to `this` declaration.
reference.declaration = this;
// Don't change the name to `default`; it's not a valid identifier name.
if ( reference.name === 'default' ) return;
this.name = reference.name;
}
bind ( declaration ) {
this.original = declaration;
}
render () {
return !this.original || this.original.isReassigned ?
this.name :
this.original.render();
}
run ( strongDependencies ) {
if ( this.original ) {
return this.original.run( strongDependencies );
}
let declaration = this.node.declaration;
while ( declaration.type === 'ParenthesizedExpression' ) declaration = declaration.expression;
if ( /FunctionExpression/.test( declaration.type ) ) {
return run( declaration.body, this.statement.scope, this.statement, strongDependencies, false );
}
// otherwise assume the worst
return true;
if ( reference.name !== this.name ) {
this.name = makeLegalIdentifier( reference.name ); // TODO handle differences of opinion
}
use () {
this.isUsed = true;
this.statement.mark();
if ( this.original ) this.original.use();
this.aliases.forEach( use );
}
}
export class SyntheticGlobalDeclaration {
constructor ( name ) {
this.name = name;
this.isExternal = true;
this.isGlobal = true;
this.isReassigned = false;
this.aliases = [];
this.isUsed = false;
}
addAlias ( declaration ) {
this.aliases.push( declaration );
}
addReference ( reference ) {
reference.declaration = this;
if ( reference.isReassignment ) this.isReassigned = true;
}
render () {
return this.name;
}
run () {
return true;
}
use () {
if ( this.isUsed ) return;
this.isUsed = true;
render ( es ) {
if ( es ) return this.name;
if ( !this.isReassigned || !this.exportName ) return this.name;
this.aliases.forEach( use );
return `exports.${this.exportName}`;
}
}
@ -176,10 +43,9 @@ export class SyntheticNamespaceDeclaration {
constructor ( module ) {
this.isNamespace = true;
this.module = module;
this.name = null;
this.name = module.basename();
this.needsNamespaceBlock = false;
this.aliases = [];
this.originals = blank();
module.getExports().forEach( name => {
@ -187,70 +53,42 @@ export class SyntheticNamespaceDeclaration {
});
}
addAlias ( declaration ) {
this.aliases.push( declaration );
}
addReference ( reference ) {
// if we have e.g. `foo.bar`, we can optimise
// the reference by pointing directly to `bar`
if ( reference.parts.length ) {
const ref = reference.parts.shift();
reference.name = ref.name;
reference.end = ref.end;
const original = this.originals[ reference.name ];
// throw with an informative error message if the reference doesn't exist.
if ( !original ) {
this.module.bundle.onwarn( `Export '${reference.name}' is not defined by '${this.module.id}'` );
reference.isUndefined = true;
return;
}
original.addReference( reference );
return;
}
// otherwise we're accessing the namespace directly,
// which means we need to mark all of this module's
// exports and render a namespace block in the bundle
if ( !this.needsNamespaceBlock ) {
activate () {
this.needsNamespaceBlock = true;
this.module.bundle.internalNamespaces.push( this );
// add synthetic references, in case of chained
// namespace imports
forOwn( this.originals, ( original, name ) => {
original.addReference( new SyntheticReference( name ) );
forOwn( this.originals, original => {
original.activate();
});
}
reference.declaration = this;
this.name = reference.name;
addReference ( node ) {
this.name = node.name;
}
renderBlock ( indentString ) {
const members = keys( this.originals ).map( name => {
const original = this.originals[ name ];
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
}
if ( original.isReassigned ) {
return `${indentString}get ${name} () { return ${original.render()}; }`;
getName () {
return this.name;
}
return `${indentString}${name}: ${original.render()}`;
});
renderBlock ( es, legacy, indentString ) {
const members = keys( this.originals ).map( name => {
const original = this.originals[ name ];
return `${this.module.bundle.varOrConst} ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`;
if ( original.isReassigned && !legacy ) {
return `${indentString}get ${name} () { return ${original.getName( es )}; }`;
}
render () {
return this.name;
}
if ( legacy && ~reservedWords.indexOf( name ) ) name = `'${name}'`;
return `${indentString}${name}: ${original.getName( es )}`;
});
use () {
forOwn( this.originals, use );
this.aliases.forEach( use );
const callee = legacy ? `(Object.freeze || Object)` : `Object.freeze`;
return `${this.module.bundle.varOrConst} ${this.getName( es )} = ${callee}({\n${members.join( ',\n' )}\n});\n\n`;
}
}
@ -261,10 +99,12 @@ export class ExternalDeclaration {
this.safeName = null;
this.isExternal = true;
this.activated = true;
this.isNamespace = name === '*';
}
addAlias () {
activate () {
// noop
}
@ -276,29 +116,25 @@ export class ExternalDeclaration {
}
}
render ( es6 ) {
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
}
getName ( es ) {
if ( this.name === '*' ) {
return this.module.name;
}
if ( this.name === 'default' ) {
return this.module.exportsNamespace || ( !es6 && this.module.exportsNames ) ?
return this.module.exportsNamespace || ( !es && this.module.exportsNames ) ?
`${this.module.name}__default` :
this.module.name;
}
return es6 ? this.safeName : `${this.module.name}.${this.name}`;
}
run () {
return true;
return es ? this.safeName : `${this.module.name}.${this.name}`;
}
setSafeName ( name ) {
this.safeName = name;
}
use () {
// noop?
}
}

6
src/ExternalModule.js

@ -3,9 +3,11 @@ import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
import { ExternalDeclaration } from './Declaration.js';
export default class ExternalModule {
constructor ( id ) {
constructor ( id, relativePath ) {
this.id = id;
this.name = makeLegalIdentifier( id );
this.path = relativePath;
this.name = makeLegalIdentifier( relativePath );
this.nameSuggestions = blank();
this.mostCommonSuggestion = 0;

544
src/Module.js

@ -1,38 +1,64 @@
import { timeStart, timeEnd } from './utils/flushTime.js';
import { parse } from 'acorn/src/index.js';
import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Statement from './Statement.js';
import { blank, keys } from './utils/object.js';
import { assign, blank, deepClone, keys } from './utils/object.js';
import { basename, extname } from './utils/path.js';
import getLocation from './utils/getLocation.js';
import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
import SOURCEMAPPING_URL from './utils/sourceMappingURL.js';
import {
SyntheticDefaultDeclaration,
SyntheticGlobalDeclaration,
SyntheticNamespaceDeclaration
} from './Declaration.js';
import { isFalsy, isTruthy } from './ast/conditions.js';
import { emptyBlockStatement } from './ast/create.js';
import extractNames from './ast/extractNames.js';
import error from './utils/error.js';
import relativeId from './utils/relativeId.js';
import { SyntheticNamespaceDeclaration } from './Declaration.js';
import extractNames from './ast/utils/extractNames.js';
import enhance from './ast/enhance.js';
import ModuleScope from './ast/scopes/ModuleScope.js';
function tryParse ( code, comments, acornOptions, id ) {
try {
return parse( code, assign({
ecmaVersion: 7,
sourceType: 'module',
onComment: ( block, text, start, end ) => comments.push({ block, text, start, end }),
preserveParens: false
}, acornOptions ));
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = id; // see above - not necessarily true, but true enough
err.message += ` in ${id}`;
throw err;
}
}
export default class Module {
constructor ({ id, code, originalCode, ast, sourceMapChain, bundle }) {
constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds, bundle }) {
this.code = code;
this.originalCode = originalCode;
this.originalSourceMap = originalSourceMap;
this.sourceMapChain = sourceMapChain;
this.comments = [];
timeStart( 'ast' );
this.ast = ast || tryParse( code, this.comments, bundle.acornOptions, id ); // TODO what happens to comments if AST is provided?
this.astClone = deepClone( this.ast );
timeEnd( 'ast' );
this.bundle = bundle;
this.id = id;
this.excludeFromSourcemap = /\0/.test( id );
this.context = bundle.getModuleContext( id );
// all dependencies
this.sources = [];
this.dependencies = [];
this.resolvedIds = blank();
this.resolvedIds = resolvedIds || blank();
// imports and exports, indexed by local name
this.imports = blank();
this.exports = blank();
this.exportsAll = blank();
this.reexports = blank();
this.exportAllSources = [];
@ -41,7 +67,7 @@ export default class Module {
// By default, `id` is the filename. Custom resolvers and loaders
// can change that, but it makes sense to use it for the source filename
this.magicString = new MagicString( code, {
filename: id,
filename: this.excludeFromSourcemap ? null : id, // don't include plugin helpers in sourcemap
indentExclusionRanges: []
});
@ -52,17 +78,20 @@ export default class Module {
this.magicString.remove( match.index, match.index + match[0].length );
}
this.comments = [];
this.statements = this.parse( ast );
this.declarations = blank();
this.type = 'Module'; // TODO only necessary so that Scope knows this should be treated as a function scope... messy
this.scope = new ModuleScope( this );
timeStart( 'analyse' );
this.analyse();
timeEnd( 'analyse' );
this.strongDependencies = [];
}
addExport ( statement ) {
const node = statement.node;
addExport ( node ) {
const source = node.source && node.source.value;
// export { name } from './other.js'
@ -110,7 +139,7 @@ export default class Module {
};
// create a synthetic declaration
this.declarations.default = new SyntheticDefaultDeclaration( node, statement, identifier || this.basename() );
//this.declarations.default = new SyntheticDefaultDeclaration( node, identifier || this.basename() );
}
// export var { foo, bar } = ...
@ -118,7 +147,7 @@ export default class Module {
// export var a = 1, b = 2, c = 3;
// export function foo () {}
else if ( node.declaration ) {
let declaration = node.declaration;
const declaration = node.declaration;
if ( declaration.type === 'VariableDeclaration' ) {
declaration.declarations.forEach( decl => {
@ -144,7 +173,13 @@ export default class Module {
throw new Error( `A module cannot have multiple exports with the same name ('${exportedName}')` );
}
// `export { default as foo }` – special case. We want importers
// to use the UnboundDefaultExport proxy, not the original declaration
if ( exportedName === 'default' ) {
this.exports[ exportedName ] = { localName: 'default' };
} else {
this.exports[ exportedName ] = { localName };
}
});
} else {
this.bundle.onwarn( `Module ${this.id} has an empty export declaration` );
@ -152,8 +187,7 @@ export default class Module {
}
}
addImport ( statement ) {
const node = statement.node;
addImport ( node ) {
const source = node.source.value;
if ( !~this.sources.indexOf( source ) ) this.sources.push( source );
@ -172,22 +206,26 @@ export default class Module {
const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
this.imports[ localName ] = { source, name, module: null };
this.imports[ localName ] = { source, specifier, name, module: null };
});
}
analyse () {
enhance( this.ast, this, this.comments );
// discover this module's imports and exports
this.statements.forEach( statement => {
if ( statement.isImportDeclaration ) this.addImport( statement );
else if ( statement.isExportDeclaration ) this.addExport( statement );
let lastNode;
statement.firstPass();
for ( const node of this.ast.body ) {
if ( node.isImportDeclaration ) {
this.addImport( node );
} else if ( node.isExportDeclaration ) {
this.addExport( node );
}
statement.scope.eachDeclaration( ( name, declaration ) => {
this.declarations[ name ] = declaration;
});
});
if ( lastNode ) lastNode.next = node.leadingCommentStart || node.start;
lastNode = node;
}
}
basename () {
@ -197,81 +235,53 @@ export default class Module {
return makeLegalIdentifier( ext ? base.slice( 0, -ext.length ) : base );
}
bindAliases () {
keys( this.declarations ).forEach( name => {
if ( name === '*' ) return;
const declaration = this.declarations[ name ];
const statement = declaration.statement;
if ( !statement || statement.node.type !== 'VariableDeclaration' ) return;
const init = statement.node.declarations[0].init;
if ( !init || init.type === 'FunctionExpression' ) return;
statement.references.forEach( reference => {
if ( reference.name === name ) return;
const otherDeclaration = this.trace( reference.name );
if ( otherDeclaration ) otherDeclaration.addAlias( declaration );
});
});
}
bindImportSpecifiers () {
[ this.imports, this.reexports ].forEach( specifiers => {
keys( specifiers ).forEach( name => {
const specifier = specifiers[ name ];
const id = this.resolvedIds[ specifier.source ];
specifier.module = this.bundle.moduleById[ id ];
specifier.module = this.bundle.moduleById.get( id );
});
});
this.exportAllModules = this.exportAllSources.map( source => {
const id = this.resolvedIds[ source ];
return this.bundle.moduleById[ id ];
return this.bundle.moduleById.get( id );
});
this.sources.forEach( source => {
const id = this.resolvedIds[ source ];
const module = this.bundle.moduleById[ id ];
const module = this.bundle.moduleById.get( id );
if ( !module.isExternal ) this.dependencies.push( module );
});
}
bindReferences () {
if ( this.declarations.default ) {
if ( this.exports.default.identifier ) {
const declaration = this.trace( this.exports.default.identifier );
if ( declaration ) this.declarations.default.bind( declaration );
}
for ( const node of this.ast.body ) {
node.bind( this.scope );
}
this.statements.forEach( statement => {
// skip `export { foo, bar, baz }`...
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) {
// ...unless this is the entry module
if ( this !== this.bundle.entryModule ) return;
// if ( this.declarations.default ) {
// if ( this.exports.default.identifier ) {
// const declaration = this.trace( this.exports.default.identifier );
// if ( declaration ) this.declarations.default.bind( declaration );
// }
// }
}
statement.references.forEach( reference => {
const declaration = reference.scope.findDeclaration( reference.name ) ||
this.trace( reference.name );
if ( declaration ) {
declaration.addReference( reference );
} else {
// TODO handle globals
this.bundle.assumedGlobals[ reference.name ] = true;
findParent () {
// TODO what does it mean if we're here?
return null;
}
});
});
findScope () {
return this.scope;
}
getExports () {
let exports = blank();
const exports = blank();
keys( this.exports ).forEach( name => {
exports[ name ] = true;
@ -282,6 +292,8 @@ export default class Module {
});
this.exportAllModules.forEach( module => {
if ( module.isExternal ) return; // TODO
module.getExports().forEach( name => {
if ( name !== 'default' ) exports[ name ] = true;
});
@ -298,347 +310,46 @@ export default class Module {
return this.declarations['*'];
}
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.code, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end }),
preserveParens: true
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
err.message += ` in ${this.id}`;
throw err;
}
}
walk( ast, {
enter: node => {
// eliminate dead branches early
if ( node.type === 'IfStatement' ) {
if ( isFalsy( node.test ) ) {
this.magicString.overwrite( node.consequent.start, node.consequent.end, '{}' );
node.consequent = emptyBlockStatement( node.consequent.start, node.consequent.end );
} else if ( node.alternate && isTruthy( node.test ) ) {
this.magicString.overwrite( node.alternate.start, node.alternate.end, '{}' );
node.alternate = emptyBlockStatement( node.alternate.start, node.alternate.end );
}
}
this.magicString.addSourcemapLocation( node.start );
this.magicString.addSourcemapLocation( node.end );
},
leave: ( node, parent, prop ) => {
// eliminate dead branches early
if ( node.type === 'ConditionalExpression' ) {
if ( isFalsy( node.test ) ) {
this.magicString.remove( node.start, node.alternate.start );
parent[prop] = node.alternate;
} else if ( isTruthy( node.test ) ) {
this.magicString.remove( node.start, node.consequent.start );
this.magicString.remove( node.consequent.end, node.end );
parent[prop] = node.consequent;
}
}
}
});
let statements = [];
let lastChar = 0;
let commentIndex = 0;
ast.body.forEach( node => {
if ( node.type === 'EmptyStatement' ) return;
if (
node.type === 'ExportNamedDeclaration' &&
node.declaration &&
node.declaration.type === 'VariableDeclaration' &&
node.declaration.declarations &&
node.declaration.declarations.length > 1
) {
// push a synthetic export declaration
const syntheticNode = {
type: 'ExportNamedDeclaration',
specifiers: node.declaration.declarations.map( declarator => {
const id = { name: declarator.id.name };
return {
local: id,
exported: id
};
}),
isSynthetic: true
};
const statement = new Statement( syntheticNode, this, node.start, node.start );
statements.push( statement );
render ( es, legacy ) {
const magicString = this.magicString.clone();
this.magicString.remove( node.start, node.declaration.start );
node = node.declaration;
for ( const node of this.ast.body ) {
node.render( magicString, es );
}
// 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... UNLESS the previous node
// was also a synthetic node, in which case it'll get removed anyway
const lastStatement = statements[ statements.length - 1 ];
if ( !lastStatement || !lastStatement.node.isSynthetic ) {
this.magicString.remove( node.start, node.declarations[0].start );
if ( this.namespace().needsNamespaceBlock ) {
magicString.append( '\n\n' + this.namespace().renderBlock( es, legacy, '\t' ) ); // TODO use correct indentation
}
node.declarations.forEach( declarator => {
const { start, end } = declarator;
const syntheticNode = {
type: 'VariableDeclaration',
kind: node.kind,
start,
end,
declarations: [ declarator ],
isSynthetic: true
};
const statement = new Statement( syntheticNode, this, start, end );
statements.push( statement );
});
lastChar = node.end; // TODO account for trailing line comment
}
else {
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;
}
});
let i = statements.length;
let next = this.code.length;
while ( i-- ) {
statements[i].next = next;
if ( !statements[i].isSynthetic ) next = statements[i].start;
}
return statements;
}
render ( es6 ) {
let magicString = this.magicString.clone();
this.statements.forEach( statement => {
if ( !statement.isIncluded ) {
magicString.remove( statement.start, statement.next );
return;
}
statement.stringLiteralRanges.forEach( range => magicString.indentExclusionRanges.push( range ) );
// skip `export { foo, bar, baz }`
if ( statement.node.type === 'ExportNamedDeclaration' ) {
if ( statement.node.isSynthetic ) return;
// skip `export { foo, bar, baz }`
if ( statement.node.specifiers.length ) {
magicString.remove( statement.start, statement.next );
return;
}
}
// split up/remove var declarations as necessary
if ( statement.node.type === 'VariableDeclaration' ) {
const declarator = statement.node.declarations[0];
if ( declarator.id.type === 'Identifier' ) {
const declaration = this.declarations[ declarator.id.name ];
if ( declaration.exportName && declaration.isReassigned ) { // `var foo = ...` becomes `exports.foo = ...`
magicString.remove( statement.start, declarator.init ? declarator.start : statement.next );
if ( !declarator.init ) return;
}
}
else {
// we handle destructuring differently, because whereas we can rewrite
// `var foo = ...` as `exports.foo = ...`, in a case like `var { a, b } = c()`
// where `a` or `b` is exported and reassigned, we have to append
// `exports.a = a;` and `exports.b = b` instead
extractNames( declarator.id ).forEach( name => {
const declaration = this.declarations[ name ];
if ( declaration.exportName && declaration.isReassigned ) {
magicString.insert( statement.end, `;\nexports.${name} = ${declaration.render( es6 )}` );
}
});
}
if ( statement.node.isSynthetic ) {
// insert `var/let/const` if necessary
magicString.insert( statement.start, `${statement.node.kind} ` );
magicString.overwrite( statement.end, statement.next, ';\n' ); // TODO account for trailing newlines
}
}
let toDeshadow = blank();
statement.references.forEach( reference => {
const { start, end } = reference;
if ( reference.isUndefined ) {
magicString.overwrite( start, end, 'undefined', true );
}
const declaration = reference.declaration;
if ( declaration ) {
const name = declaration.render( es6 );
// the second part of this check is necessary because of
// namespace optimisation – name of `foo.bar` could be `bar`
if ( reference.name === name && name.length === end - start ) return;
reference.rewritten = true;
// prevent local variables from shadowing renamed references
const identifier = name.match( /[^\.]+/ )[0];
if ( reference.scope.contains( identifier ) ) {
toDeshadow[ identifier ] = `${identifier}$$`; // TODO more robust mechanism
}
if ( reference.isShorthandProperty ) {
magicString.insert( end, `: ${name}` );
} else {
magicString.overwrite( start, end, name, true );
}
}
});
if ( keys( toDeshadow ).length ) {
statement.references.forEach( reference => {
if ( !reference.rewritten && reference.name in toDeshadow ) {
const replacement = toDeshadow[ reference.name ];
magicString.overwrite( reference.start, reference.end, reference.isShorthandProperty ? `${reference.name}: ${replacement}` : replacement, true );
}
});
}
// modify exports as necessary
if ( statement.isExportDeclaration ) {
// remove `export` from `export var foo = 42`
// TODO: can we do something simpler here?
// we just want to remove `export`, right?
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) {
const name = extractNames( statement.node.declaration.declarations[ 0 ].id )[ 0 ];
const declaration = this.declarations[ name ];
if ( !declaration ) throw new Error( `Missing declaration for ${name}!` );
const end = declaration.exportName && declaration.isReassigned ?
statement.node.declaration.declarations[0].start :
statement.node.declaration.start;
magicString.remove( statement.node.start, end );
}
else if ( statement.node.type === 'ExportAllDeclaration' ) {
// TODO: remove once `export * from 'external'` is supported.
magicString.remove( statement.start, statement.next );
}
// 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 defaultDeclaration = this.declarations.default;
// prevent `var foo = foo`
if ( defaultDeclaration.original && !defaultDeclaration.original.isReassigned ) {
magicString.remove( statement.start, statement.next );
return;
}
const defaultName = defaultDeclaration.render();
// prevent `var undefined = sideEffectyDefault(foo)`
if ( !defaultDeclaration.exportName && !defaultDeclaration.isUsed ) {
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 ${defaultName}` );
} else {
magicString.overwrite( statement.node.start, statement.node.declaration.start, `${this.bundle.varOrConst} ${defaultName} = ` );
}
return magicString.trim();
}
else {
throw new Error( 'Unhandled export' );
run () {
for ( const node of this.ast.body ) {
if ( node.hasEffects( this.scope ) ) {
node.run( this.scope );
}
}
});
// add namespace block if necessary
const namespace = this.declarations['*'];
if ( namespace && namespace.needsNamespaceBlock ) {
magicString.append( '\n\n' + namespace.renderBlock( magicString.getIndentString() ) );
}
return magicString.trim();
}
/**
* Statically runs the module marking the top-level statements that must be
* included for the module to execute successfully.
*
* @param {boolean} treeshake - if we should tree-shake the module
* @return {boolean} marked - if any new statements were marked for inclusion
*/
run ( treeshake ) {
if ( !treeshake ) {
this.statements.forEach( statement => {
if ( statement.isImportDeclaration || ( statement.isExportDeclaration && statement.node.isSynthetic ) ) return;
statement.mark();
});
return false;
toJSON () {
return {
id: this.id,
dependencies: this.dependencies.map( module => module.id ),
code: this.code,
originalCode: this.originalCode,
ast: this.astClone,
sourceMapChain: this.sourceMapChain,
resolvedIds: this.resolvedIds
};
}
let marked = false;
this.statements.forEach( statement => {
marked = statement.run( this.strongDependencies ) || marked;
});
return marked;
trace ( name ) {
// TODO this is slightly circular
if ( name in this.scope.declarations ) {
return this.scope.declarations[ name ];
}
trace ( name ) {
if ( name in this.declarations ) return this.declarations[ name ];
if ( name in this.imports ) {
const importDeclaration = this.imports[ name ];
const otherModule = importDeclaration.module;
@ -649,7 +360,14 @@ export default class Module {
const declaration = otherModule.traceExport( importDeclaration.name );
if ( !declaration ) throw new Error( `Module ${otherModule.id} does not export ${importDeclaration.name} (imported by ${this.id})` );
if ( !declaration ) {
error({
message: `'${importDeclaration.name}' is not exported by ${relativeId( otherModule.id )} (imported by ${relativeId( this.id )}). For help fixing this error see https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module`,
file: this.id,
loc: getLocation( this.code, importDeclaration.specifier.start )
});
}
return declaration;
}
@ -663,10 +381,11 @@ export default class Module {
const declaration = reexportDeclaration.module.traceExport( reexportDeclaration.localName );
if ( !declaration ) {
const err = new Error( `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')` );
err.file = this.id;
err.loc = getLocation( this.code, reexportDeclaration.start );
throw err;
error({
message: `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')`,
file: this.id,
loc: getLocation( this.code, reexportDeclaration.start )
});
}
return declaration;
@ -677,12 +396,11 @@ export default class Module {
const name = exportDeclaration.localName;
const declaration = this.trace( name );
if ( declaration ) return declaration;
this.bundle.assumedGlobals[ name ] = true;
return ( this.declarations[ name ] = new SyntheticGlobalDeclaration( name ) );
return declaration || this.bundle.scope.findDeclaration( name );
}
if ( name === 'default' ) return;
for ( let i = 0; i < this.exportAllModules.length; i += 1 ) {
const module = this.exportAllModules[i];
const declaration = module.traceExport( name );

30
src/Reference.js

@ -1,30 +0,0 @@
export class Reference {
constructor ( node, scope, statement ) {
this.node = node;
this.scope = scope;
this.statement = statement;
this.declaration = null; // bound later
this.parts = [];
let root = node;
while ( root.type === 'MemberExpression' ) {
this.parts.unshift( root.property );
root = root.object;
}
this.name = root.name;
this.start = node.start;
this.end = node.start + this.name.length; // can be overridden in the case of namespace members
this.rewritten = false;
}
}
export class SyntheticReference {
constructor ( name ) {
this.name = name;
this.parts = [];
}
}

155
src/Statement.js

@ -1,155 +0,0 @@
import { walk } from 'estree-walker';
import Scope from './ast/Scope.js';
import attachScopes from './ast/attachScopes.js';
import modifierNodes, { isModifierNode } from './ast/modifierNodes.js';
import isFunctionDeclaration from './ast/isFunctionDeclaration.js';
import isReference from './ast/isReference.js';
import getLocation from './utils/getLocation.js';
import run from './utils/run.js';
import { Reference } from './Reference.js';
export default class Statement {
constructor ( node, module, start, end ) {
this.node = node;
this.module = module;
this.start = start;
this.end = end;
this.next = null; // filled in later
this.scope = new Scope({ statement: this });
this.references = [];
this.stringLiteralRanges = [];
this.isIncluded = false;
this.ran = false;
this.isImportDeclaration = node.type === 'ImportDeclaration';
this.isExportDeclaration = /^Export/.test( node.type );
this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
this.isFunctionDeclaration = isFunctionDeclaration( node ) ||
this.isExportDeclaration && isFunctionDeclaration( node.declaration );
}
firstPass () {
if ( this.isImportDeclaration ) return; // nothing to analyse
// attach scopes
attachScopes( this );
// find references
const statement = this;
let { module, references, scope, stringLiteralRanges } = this;
let readDepth = 0;
walk( this.node, {
enter ( node, parent, prop ) {
// warn about eval
if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) {
// TODO show location
module.bundle.onwarn( `Use of \`eval\` (in ${module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` );
}
// skip re-export declarations
if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip();
if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]);
if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) {
stringLiteralRanges.push([ node.start + 1, node.end - 1 ]);
}
if ( node._scope ) scope = node._scope;
if ( /Function/.test( node.type ) ) readDepth += 1;
let isReassignment;
if ( parent && isModifierNode( parent ) ) {
let subject = parent[ modifierNodes[ parent.type ] ];
if ( node === subject ) {
let depth = 0;
while ( subject.type === 'MemberExpression' ) {
subject = subject.object;
depth += 1;
}
const importDeclaration = module.imports[ subject.name ];
if ( !scope.contains( subject.name ) && importDeclaration ) {
const minDepth = importDeclaration.name === '*' ?
2 : // cannot do e.g. `namespace.foo = bar`
1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine
if ( depth < minDepth ) {
const err = new Error( `Illegal reassignment to import '${subject.name}'` );
err.file = module.id;
err.loc = getLocation( module.magicString.original, subject.start );
throw err;
}
}
isReassignment = !depth;
}
}
if ( isReference( node, parent ) ) {
// function declaration IDs are a special case – they're associated
// with the parent scope
const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ?
scope.parent :
scope;
const isShorthandProperty = parent.type === 'Property' && parent.shorthand;
// Since `node.key` can equal `node.value` for shorthand properties
// we must use the `prop` argument provided by `estree-walker` to determine
// if we're looking at the key or the value.
// If they are equal, we'll return to not create duplicate references.
if ( isShorthandProperty && parent.value === parent.key && prop === 'value' ) {
return;
}
const reference = new Reference( node, referenceScope, statement );
reference.isReassignment = isReassignment;
reference.isShorthandProperty = isShorthandProperty;
references.push( reference );
this.skip(); // don't descend from `foo.bar.baz` into `foo.bar`
}
},
leave ( node ) {
if ( node._scope ) scope = scope.parent;
if ( /Function/.test( node.type ) ) readDepth -= 1;
}
});
}
mark () {
if ( this.isIncluded ) return; // prevent infinite loops
this.isIncluded = true;
this.references.forEach( reference => {
if ( reference.declaration ) reference.declaration.use();
});
}
run ( strongDependencies ) {
if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return;
this.ran = true;
if ( run( this.node, this.scope, this, strongDependencies, false ) ) {
this.mark();
return true;
}
}
source () {
return this.module.source.slice( this.start, this.end );
}
toString () {
return this.module.magicString.slice( this.start, this.end );
}
}

100
src/ast/Node.js

@ -0,0 +1,100 @@
import { UNKNOWN } from './values.js';
import getLocation from '../utils/getLocation.js';
export default class Node {
bind ( scope ) {
this.eachChild( child => child.bind( this.scope || scope ) );
}
eachChild ( callback ) {
for ( const key of this.keys ) {
if ( this.shorthand && key === 'key' ) continue; // key and value are the same
const value = this[ key ];
if ( value ) {
if ( 'length' in value ) {
for ( const child of value ) {
if ( child ) callback( child );
}
} else if ( value ) {
callback( value );
}
}
}
}
findParent ( selector ) {
return selector.test( this.type ) ? this : this.parent.findParent( selector );
}
// TODO abolish findScope. if a node needs to store scope, store it
findScope ( functionScope ) {
return this.parent.findScope( functionScope );
}
gatherPossibleValues ( values ) {
//this.eachChild( child => child.gatherPossibleValues( values ) );
values.add( UNKNOWN );
}
getValue () {
return UNKNOWN;
}
hasEffects ( scope ) {
if ( this.scope ) scope = this.scope;
for ( const key of this.keys ) {
const value = this[ key ];
if ( value ) {
if ( 'length' in value ) {
for ( const child of value ) {
if ( child && child.hasEffects( scope ) ) {
return true;
}
}
} else if ( value && value.hasEffects( scope ) ) {
return true;
}
}
}
}
initialise ( scope ) {
this.eachChild( child => child.initialise( this.scope || scope ) );
}
insertSemicolon ( code ) {
if ( code.original[ this.end - 1 ] !== ';' ) {
code.insertLeft( this.end, ';' );
}
}
locate () {
// useful for debugging
const location = getLocation( this.module.code, this.start );
location.file = this.module.id;
location.toString = () => JSON.stringify( location );
return location;
}
render ( code, es ) {
this.eachChild( child => child.render( code, es ) );
}
run ( scope ) {
if ( this.ran ) return;
this.ran = true;
this.eachChild( child => {
child.run( this.scope || scope );
});
}
toString () {
return this.module.code.slice( this.start, this.end );
}
}

52
src/ast/Scope.js

@ -1,52 +0,0 @@
import { blank, keys } from '../utils/object.js';
import Declaration from '../Declaration.js';
import extractNames from './extractNames.js';
export default class Scope {
constructor ( options ) {
options = options || {};
this.parent = options.parent;
this.statement = options.statement || this.parent.statement;
this.isBlockScope = !!options.block;
this.isTopLevel = !this.parent || ( this.parent.isTopLevel && this.isBlockScope );
this.declarations = blank();
if ( options.params ) {
options.params.forEach( param => {
extractNames( param ).forEach( name => {
this.declarations[ name ] = new Declaration( param, true, this.statement );
});
});
}
}
addDeclaration ( node, isBlockDeclaration, isVar ) {
if ( !isBlockDeclaration && this.isBlockScope ) {
// it's a `var` or function node, and this
// is a block scope, so we need to go up
this.parent.addDeclaration( node, isBlockDeclaration, isVar );
} else {
extractNames( node.id ).forEach( name => {
this.declarations[ name ] = new Declaration( node, false, this.statement );
});
}
}
contains ( name ) {
return this.declarations[ name ] ||
( this.parent ? this.parent.contains( name ) : false );
}
eachDeclaration ( fn ) {
keys( this.declarations ).forEach( key => {
fn( key, this.declarations[ key ] );
});
}
findDeclaration ( name ) {
return this.declarations[ name ] ||
( this.parent && this.parent.findDeclaration( name ) );
}
}

78
src/ast/attachScopes.js

@ -1,78 +0,0 @@
import { walk } from 'estree-walker';
import Scope from './Scope.js';
const blockDeclarations = {
'const': true,
'let': true
};
export default function attachScopes ( statement ) {
let { node, scope } = statement;
walk( node, {
enter ( node, parent ) {
// function foo () {...}
// class Foo {...}
if ( /(Function|Class)Declaration/.test( node.type ) ) {
scope.addDeclaration( node, false, false );
}
// var foo = 1, bar = 2
if ( node.type === 'VariableDeclaration' ) {
const isBlockDeclaration = blockDeclarations[ node.kind ];
node.declarations.forEach( declarator => {
scope.addDeclaration( declarator, isBlockDeclaration, true );
});
}
let newScope;
// create new function scope
if ( /(Function|Class)/.test( node.type ) ) {
newScope = new Scope({
parent: scope,
block: false,
params: node.params
});
// named function expressions - the name is considered
// part of the function's scope
if ( /(Function|Class)Expression/.test( node.type ) && node.id ) {
newScope.addDeclaration( node, false, false );
}
}
// create new block scope
if ( node.type === 'BlockStatement' && ( !parent || !/Function/.test( parent.type ) ) ) {
newScope = new Scope({
parent: scope,
block: true
});
}
// catch clause has its own block scope
if ( node.type === 'CatchClause' ) {
newScope = new Scope({
parent: scope,
params: [ node.param ],
block: true
});
}
if ( newScope ) {
Object.defineProperty( node, '_scope', {
value: newScope,
configurable: true
});
scope = newScope;
}
},
leave ( node ) {
if ( node._scope ) {
scope = scope.parent;
}
}
});
}

38
src/ast/conditions.js

@ -1,38 +0,0 @@
export function isTruthy ( node ) {
if ( node.type === 'Literal' ) return !!node.value;
if ( node.type === 'ParenthesizedExpression' ) return isTruthy( node.expression );
if ( node.operator in operators ) return operators[ node.operator ]( node );
}
export function isFalsy ( node ) {
return not( isTruthy( node ) );
}
function not ( value ) {
return value === undefined ? value : !value;
}
function equals ( a, b, strict ) {
if ( a.type !== b.type ) return undefined;
if ( a.type === 'Literal' ) return strict ? a.value === b.value : a.value == b.value;
}
const operators = {
'==': x => {
return equals( x.left, x.right, false );
},
'!=': x => not( operators['==']( x ) ),
'===': x => {
return equals( x.left, x.right, true );
},
'!==': x => not( operators['===']( x ) ),
'!': x => isFalsy( x.argument ),
'&&': x => isTruthy( x.left ) && isTruthy( x.right ),
'||': x => isTruthy( x.left ) || isTruthy( x.right )
};

7
src/ast/create.js

@ -1,7 +0,0 @@
export function emptyBlockStatement ( start, end ) {
return {
start, end,
type: 'BlockStatement',
body: []
};
}

63
src/ast/enhance.js

@ -0,0 +1,63 @@
import nodes from './nodes/index.js';
import Node from './Node.js';
import keys from './keys.js';
const newline = /\n/;
export default function enhance ( ast, module, comments ) {
enhanceNode( ast, module, module, module.magicString );
let comment = comments.shift();
for ( const node of ast.body ) {
if ( comment && ( comment.start < node.start ) ) {
node.leadingCommentStart = comment.start;
}
while ( comment && comment.end < node.end ) comment = comments.shift();
// if the next comment is on the same line as the end of the node,
// treat is as a trailing comment
if ( comment && !newline.test( module.code.slice( node.end, comment.start ) ) ) {
node.trailingCommentEnd = comment.end; // TODO is node.trailingCommentEnd used anywhere?
comment = comments.shift();
}
node.initialise( module.scope );
}
}
function enhanceNode ( raw, parent, module, code ) {
if ( !raw ) return;
if ( 'length' in raw ) {
for ( let i = 0; i < raw.length; i += 1 ) {
enhanceNode( raw[i], parent, module, code );
}
return;
}
// with e.g. shorthand properties, key and value are
// the same node. We don't want to enhance an object twice
if ( raw.__enhanced ) return;
raw.__enhanced = true;
if ( !keys[ raw.type ] ) {
keys[ raw.type ] = Object.keys( raw ).filter( key => typeof raw[ key ] === 'object' );
}
raw.parent = parent;
raw.module = module;
raw.keys = keys[ raw.type ];
code.addSourcemapLocation( raw.start );
code.addSourcemapLocation( raw.end );
for ( const key of keys[ raw.type ] ) {
enhanceNode( raw[ key ], raw, module, code );
}
const type = nodes[ raw.type ] || Node;
raw.__proto__ = type.prototype;
}

6
src/ast/isFunctionDeclaration.js

@ -1,6 +0,0 @@
export default function isFunctionDeclaration ( node ) {
if ( !node ) return false;
return node.type === 'FunctionDeclaration' ||
( node.type === 'VariableDeclaration' && node.init && /FunctionExpression/.test( node.init.type ) );
}

4
src/ast/keys.js

@ -0,0 +1,4 @@
export default {
Program: [ 'body' ],
Literal: []
};

19
src/ast/modifierNodes.js

@ -1,19 +0,0 @@
const modifierNodes = {
AssignmentExpression: 'left',
UpdateExpression: 'argument',
UnaryExpression: 'argument'
};
export default modifierNodes;
export function isModifierNode ( node ) {
if ( !( node.type in modifierNodes ) ) {
return false;
}
if ( node.type === 'UnaryExpression' ) {
return node.operator === 'delete';
}
return true;
}

8
src/ast/nodes/ArrayExpression.js

@ -0,0 +1,8 @@
import Node from '../Node.js';
import { ARRAY } from '../values.js';
export default class ArrayExpression extends Node {
gatherPossibleValues ( values ) {
values.add( ARRAY );
}
}

38
src/ast/nodes/ArrowFunctionExpression.js

@ -0,0 +1,38 @@
import Node from '../Node.js';
import Scope from '../scopes/Scope.js';
import extractNames from '../utils/extractNames.js';
export default class ArrowFunctionExpression extends Node {
bind ( scope ) {
super.bind( this.scope || scope );
}
findScope ( functionScope ) {
return this.scope || this.parent.findScope( functionScope );
}
hasEffects () {
return false;
}
initialise ( scope ) {
if ( this.body.type === 'BlockStatement' ) {
this.body.createScope( scope );
this.scope = this.body.scope;
} else {
this.scope = new Scope({
parent: scope,
isBlockScope: false,
isLexicalBoundary: false
});
for ( const param of this.params ) {
for ( const name of extractNames( param ) ) {
this.scope.addDeclaration( name, null, null, true ); // TODO ugh
}
}
}
super.initialise( this.scope );
}
}

50
src/ast/nodes/AssignmentExpression.js

@ -0,0 +1,50 @@
import Node from '../Node.js';
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js';
import isUsedByBundle from './shared/isUsedByBundle.js';
import isProgramLevel from '../utils/isProgramLevel.js';
import { NUMBER, STRING } from '../values.js';
export default class AssignmentExpression extends Node {
bind ( scope ) {
const subject = this.left;
this.subject = subject;
disallowIllegalReassignment( scope, subject );
if ( subject.type === 'Identifier' ) {
const declaration = scope.findDeclaration( subject.name );
declaration.isReassigned = true;
if ( declaration.possibleValues ) { // TODO this feels hacky
if ( this.operator === '=' ) {
declaration.possibleValues.add( this.right );
} else if ( this.operator === '+=' ) {
declaration.possibleValues.add( STRING ).add( NUMBER );
} else {
declaration.possibleValues.add( NUMBER );
}
}
}
super.bind( scope );
}
hasEffects ( scope ) {
const hasEffects = this.isUsedByBundle() || this.right.hasEffects( scope );
return hasEffects;
}
initialise ( scope ) {
this.scope = scope;
if ( isProgramLevel( this ) ) {
this.module.bundle.dependentExpressions.push( this );
}
super.initialise( scope );
}
isUsedByBundle () {
return isUsedByBundle( this.scope, this.subject );
}
}

38
src/ast/nodes/BinaryExpression.js

@ -0,0 +1,38 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
const operators = {
'==': ( left, right ) => left == right,
'!=': ( left, right ) => left != right,
'===': ( left, right ) => left === right,
'!==': ( left, right ) => left !== right,
'<': ( left, right ) => left < right,
'<=': ( left, right ) => left <= right,
'>': ( left, right ) => left > right,
'>=': ( left, right ) => left >= right,
'<<': ( left, right ) => left << right,
'>>': ( left, right ) => left >> right,
'>>>': ( left, right ) => left >>> right,
'+': ( left, right ) => left + right,
'-': ( left, right ) => left - right,
'*': ( left, right ) => left * right,
'/': ( left, right ) => left / right,
'%': ( left, right ) => left % right,
'|': ( left, right ) => left | right,
'^': ( left, right ) => left ^ right,
'&': ( left, right ) => left & right,
in: ( left, right ) => left in right,
instanceof: ( left, right ) => left instanceof right
};
export default class BinaryExpression extends Node {
getValue () {
const leftValue = this.left.getValue();
if ( leftValue === UNKNOWN ) return UNKNOWN;
const rightValue = this.right.getValue();
if ( rightValue === UNKNOWN ) return UNKNOWN;
return operators[ this.operator ]( leftValue, rightValue );
}
}

59
src/ast/nodes/BlockStatement.js

@ -0,0 +1,59 @@
import Statement from './shared/Statement.js';
import Scope from '../scopes/Scope.js';
import extractNames from '../utils/extractNames.js';
export default class BlockStatement extends Statement {
bind () {
for ( const node of this.body ) {
node.bind( this.scope );
}
}
createScope ( parent ) {
this.parentIsFunction = /Function/.test( this.parent.type );
this.isFunctionBlock = this.parentIsFunction || this.parent.type === 'Module';
this.scope = new Scope({
parent,
isBlockScope: !this.isFunctionBlock,
isLexicalBoundary: this.isFunctionBlock && this.parent.type !== 'ArrowFunctionExpression',
owner: this // TODO is this used anywhere?
});
const params = this.parent.params || ( this.parent.type === 'CatchClause' && [ this.parent.param ] );
if ( params && params.length ) {
params.forEach( node => {
extractNames( node ).forEach( name => {
this.scope.addDeclaration( name, node, false, true );
});
});
}
}
findScope ( functionScope ) {
return functionScope && !this.isFunctionBlock ? this.parent.findScope( functionScope ) : this.scope;
}
initialise ( scope ) {
if ( !this.scope ) this.createScope( scope ); // scope can be created early in some cases, e.g for (let i... )
let lastNode;
for ( const node of this.body ) {
node.initialise( this.scope );
if ( lastNode ) lastNode.next = node.start;
lastNode = node;
}
}
render ( code, es ) {
if (this.body.length) {
for ( const node of this.body ) {
node.render( code, es );
}
} else {
Statement.prototype.render.call(this, code, es);
}
}
}

43
src/ast/nodes/CallExpression.js

@ -0,0 +1,43 @@
import getLocation from '../../utils/getLocation.js';
import error from '../../utils/error.js';
import Node from '../Node.js';
import isProgramLevel from '../utils/isProgramLevel.js';
import callHasEffects from './shared/callHasEffects.js';
export default class CallExpression extends Node {
bind ( scope ) {
if ( this.callee.type === 'Identifier' ) {
const declaration = scope.findDeclaration( this.callee.name );
if ( declaration.isNamespace ) {
error({
message: `Cannot call a namespace ('${this.callee.name}')`,
file: this.module.id,
pos: this.start,
loc: getLocation( this.module.code, this.start )
});
}
if ( this.callee.name === 'eval' && declaration.isGlobal ) {
this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` );
}
}
super.bind( scope );
}
hasEffects ( scope ) {
return callHasEffects( scope, this.callee );
}
initialise ( scope ) {
if ( isProgramLevel( this ) ) {
this.module.bundle.dependentExpressions.push( this );
}
super.initialise( scope );
}
isUsedByBundle () {
return this.hasEffects( this.findScope() );
}
}

45
src/ast/nodes/ClassDeclaration.js

@ -0,0 +1,45 @@
import Node from '../Node.js';
// TODO is this basically identical to FunctionDeclaration?
export default class ClassDeclaration extends Node {
activate () {
if ( this.activated ) return;
this.activated = true;
if ( this.superClass ) this.superClass.run( this.scope );
this.body.run();
}
addReference () {
/* noop? */
}
gatherPossibleValues ( values ) {
values.add( this );
}
getName () {
return this.name;
}
hasEffects () {
return false;
}
initialise ( scope ) {
this.scope = scope;
this.name = this.id.name;
scope.addDeclaration( this.name, this, false, false );
super.initialise( scope );
}
render ( code, es ) {
if ( this.activated ) {
super.render( code, es );
} else {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
}
}
}

26
src/ast/nodes/ClassExpression.js

@ -0,0 +1,26 @@
import Node from '../Node.js';
import Scope from '../scopes/Scope.js';
export default class ClassExpression extends Node {
bind () {
super.bind( this.scope );
}
findScope () {
return this.scope;
}
initialise () {
this.scope = new Scope({
isBlockScope: true,
parent: this.parent.findScope( false )
});
if ( this.id ) {
// function expression IDs belong to the child scope...
this.scope.addDeclaration( this.id.name, this, false, true );
}
super.initialise( this.scope );
}
}

65
src/ast/nodes/ConditionalExpression.js

@ -0,0 +1,65 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
export default class ConditionalExpression extends Node {
initialise ( scope ) {
if ( this.module.bundle.treeshake ) {
this.testValue = this.test.getValue();
if ( this.testValue === UNKNOWN ) {
super.initialise( scope );
}
else if ( this.testValue ) {
this.consequent.initialise( scope );
this.alternate = null;
} else if ( this.alternate ) {
this.alternate.initialise( scope );
this.consequent = null;
}
}
else {
super.initialise( scope );
}
}
gatherPossibleValues ( values ) {
const testValue = this.test.getValue();
if ( testValue === UNKNOWN ) {
values.add( this.consequent ).add( this.alternate );
} else {
values.add( testValue ? this.consequent : this.alternate );
}
}
getValue () {
const testValue = this.test.getValue();
if ( testValue === UNKNOWN ) return UNKNOWN;
return testValue ? this.consequent.getValue() : this.alternate.getValue();
}
render ( code, es ) {
if ( !this.module.bundle.treeshake ) {
super.render( code, es );
}
else {
if ( this.testValue === UNKNOWN ) {
super.render( code, es );
}
else if ( this.testValue ) {
code.remove( this.start, this.consequent.start );
code.remove( this.consequent.end, this.end );
this.consequent.render( code, es );
} else {
code.remove( this.start, this.alternate.start );
code.remove( this.alternate.end, this.end );
this.alternate.render( code, es );
}
}
}
}

9
src/ast/nodes/EmptyStatement.js

@ -0,0 +1,9 @@
import Statement from './shared/Statement.js';
export default class EmptyStatement extends Statement {
render ( code ) {
if ( this.parent.type === 'BlockStatement' || this.parent.type === 'Program' ) {
code.remove( this.start, this.end );
}
}
}

11
src/ast/nodes/ExportAllDeclaration.js

@ -0,0 +1,11 @@
import Node from '../Node.js';
export default class ExportAllDeclaration extends Node {
initialise () {
this.isExportDeclaration = true;
}
render ( code ) {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
}
}

105
src/ast/nodes/ExportDefaultDeclaration.js

@ -0,0 +1,105 @@
import Node from '../Node.js';
const functionOrClassDeclaration = /^(?:Function|Class)Declaration/;
export default class ExportDefaultDeclaration extends Node {
initialise ( scope ) {
this.isExportDeclaration = true;
this.isDefault = true;
this.name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name || this.module.basename();
scope.declarations.default = this;
this.declaration.initialise( scope );
}
activate () {
if ( this.activated ) return;
this.activated = true;
this.run();
}
addReference ( reference ) {
this.name = reference.name;
if ( this.original ) this.original.addReference( reference );
}
bind ( scope ) {
const name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name;
if ( name ) this.original = scope.findDeclaration( name );
this.declaration.bind( scope );
}
gatherPossibleValues ( values ) {
this.declaration.gatherPossibleValues( values );
}
getName ( es ) {
if ( this.original && !this.original.isReassigned ) {
return this.original.getName( es );
}
return this.name;
}
// TODO this is total chaos, tidy it up
render ( code, es ) {
const treeshake = this.module.bundle.treeshake;
const name = this.getName( es );
// paren workaround: find first non-whitespace character position after `export default`
let declaration_start;
if ( this.declaration ) {
const statementStr = code.original.slice( this.start, this.end );
declaration_start = this.start + statementStr.match(/^\s*export\s+default\s+/)[0].length;
}
if ( this.shouldInclude || this.declaration.activated ) {
if ( this.activated ) {
if ( functionOrClassDeclaration.test( this.declaration.type ) ) {
if ( this.declaration.id ) {
code.remove( this.start, declaration_start );
} else {
throw new Error( 'TODO anonymous class/function declaration' );
}
}
else {
if ( this.original && this.original.getName( es ) === name ) {
// prevent `var foo = foo`
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
return; // don't render children. TODO this seems like a bit of a hack
} else {
code.overwrite( this.start, declaration_start, `${this.module.bundle.varOrConst} ${name} = ` );
}
this.insertSemicolon( code );
}
} else {
// remove `var foo` from `var foo = bar()`, if `foo` is unused
code.remove( this.start, declaration_start );
}
super.render( code, es );
} else {
if ( treeshake ) {
if ( functionOrClassDeclaration.test( this.declaration.type ) ) {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
} else {
const hasEffects = this.declaration.hasEffects( this.module.scope );
code.remove( this.start, hasEffects ? declaration_start : this.next || this.end );
}
} else {
code.overwrite( this.start, declaration_start, `${this.module.bundle.varOrConst} ${name} = ` );
}
// code.remove( this.start, this.next || this.end );
}
}
run ( scope ) {
this.shouldInclude = true;
super.run( scope );
}
}

80
src/ast/nodes/ExportNamedDeclaration.js

@ -0,0 +1,80 @@
import { find } from '../../utils/array.js';
import Node from '../Node.js';
class UnboundDefaultExport {
constructor ( original ) {
this.original = original;
this.name = original.name;
}
activate () {
if ( this.activated ) return;
this.activated = true;
this.original.activate();
}
addReference ( reference ) {
this.name = reference.name;
this.original.addReference( reference );
}
bind ( scope ) {
this.original.bind( scope );
}
gatherPossibleValues ( values ) {
this.original.gatherPossibleValues( values );
}
getName ( es ) {
if ( this.original && !this.original.isReassigned ) {
return this.original.getName( es );
}
return this.name;
}
}
export default class ExportNamedDeclaration extends Node {
initialise ( scope ) {
this.scope = scope;
this.isExportDeclaration = true;
// special case – `export { foo as default }` should not create a live binding
const defaultExport = find( this.specifiers, specifier => specifier.exported.name === 'default' );
if ( defaultExport ) {
const declaration = this.scope.findDeclaration( defaultExport.local.name );
this.defaultExport = new UnboundDefaultExport( declaration );
scope.declarations.default = this.defaultExport;
}
if ( this.declaration ) this.declaration.initialise( scope );
}
bind ( scope ) {
if ( this.declaration ) this.declaration.bind( scope );
}
render ( code, es ) {
if ( this.declaration ) {
code.remove( this.start, this.declaration.start );
this.declaration.render( code, es );
} else {
const start = this.leadingCommentStart || this.start;
const end = this.next || this.end;
if ( this.defaultExport ) {
const name = this.defaultExport.getName( es );
const originalName = this.defaultExport.original.getName( es );
if ( name !== originalName ) {
code.overwrite( start, end, `var ${name} = ${originalName};` );
return;
}
}
code.remove( start, end );
}
}
}

8
src/ast/nodes/ExpressionStatement.js

@ -0,0 +1,8 @@
import Statement from './shared/Statement.js';
export default class ExpressionStatement extends Statement {
render ( code, es ) {
super.render( code, es );
if ( this.shouldInclude ) this.insertSemicolon( code );
}
}

22
src/ast/nodes/ForInStatement.js

@ -0,0 +1,22 @@
import Statement from './shared/Statement.js';
import assignTo from './shared/assignTo.js';
import Scope from '../scopes/Scope.js';
import { STRING } from '../values.js';
export default class ForInStatement extends Statement {
initialise ( scope ) {
if ( this.body.type === 'BlockStatement' ) {
this.body.createScope( scope );
this.scope = this.body.scope;
} else {
this.scope = new Scope({
parent: scope,
isBlockScope: true,
isLexicalBoundary: false
});
}
super.initialise( this.scope );
assignTo( this.left, this.scope, STRING );
}
}

22
src/ast/nodes/ForOfStatement.js

@ -0,0 +1,22 @@
import Statement from './shared/Statement.js';
import assignTo from './shared/assignTo.js';
import Scope from '../scopes/Scope.js';
import { UNKNOWN } from '../values.js';
export default class ForOfStatement extends Statement {
initialise ( scope ) {
if ( this.body.type === 'BlockStatement' ) {
this.body.createScope( scope );
this.scope = this.body.scope;
} else {
this.scope = new Scope({
parent: scope,
isBlockScope: true,
isLexicalBoundary: false
});
}
super.initialise( this.scope );
assignTo( this.left, this.scope, UNKNOWN );
}
}

23
src/ast/nodes/ForStatement.js

@ -0,0 +1,23 @@
import Statement from './shared/Statement.js';
import Scope from '../scopes/Scope.js';
export default class ForStatement extends Statement {
initialise ( scope ) {
if ( this.body.type === 'BlockStatement' ) {
this.body.createScope( scope );
this.scope = this.body.scope;
} else {
this.scope = new Scope({
parent: scope,
isBlockScope: true,
isLexicalBoundary: false
});
}
// can't use super, because we need to control the order
if ( this.init ) this.init.initialise( this.scope );
if ( this.test ) this.test.initialise( this.scope );
if ( this.update ) this.update.initialise( this.scope );
this.body.initialise( this.scope );
}
}

53
src/ast/nodes/FunctionDeclaration.js

@ -0,0 +1,53 @@
import Node from '../Node.js';
export default class FunctionDeclaration extends Node {
activate () {
if ( this.activated ) return;
this.activated = true;
const scope = this.body.scope;
this.params.forEach( param => param.run( scope ) ); // in case of assignment patterns
this.body.run();
}
addReference () {
/* noop? */
}
bind ( scope ) {
this.id.bind( scope );
this.params.forEach( param => param.bind( this.body.scope ) );
this.body.bind( scope );
}
gatherPossibleValues ( values ) {
values.add( this );
}
getName () {
return this.name;
}
hasEffects () {
return false;
}
initialise ( scope ) {
this.name = this.id.name; // may be overridden by bundle.deconflict
scope.addDeclaration( this.name, this, false, false );
this.body.createScope( scope );
this.id.initialise( scope );
this.params.forEach( param => param.initialise( this.body.scope ) );
this.body.initialise();
}
render ( code, es ) {
if ( !this.module.bundle.treeshake || this.activated ) {
super.render( code, es );
} else {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
}
}
}

21
src/ast/nodes/FunctionExpression.js

@ -0,0 +1,21 @@
import Node from '../Node.js';
export default class FunctionExpression extends Node {
bind () {
if ( this.id ) this.id.bind( this.body.scope );
this.params.forEach( param => param.bind( this.body.scope ) );
this.body.bind();
}
hasEffects () {
return false;
}
initialise ( scope ) {
this.body.createScope( scope );
if ( this.id ) this.id.initialise( this.body.scope );
this.params.forEach( param => param.initialise( this.body.scope ) );
this.body.initialise();
}
}

35
src/ast/nodes/Identifier.js

@ -0,0 +1,35 @@
import Node from '../Node.js';
import isReference from '../utils/isReference.js';
export default class Identifier extends Node {
bind ( scope ) {
if ( isReference( this, this.parent ) ) {
this.declaration = scope.findDeclaration( this.name );
this.declaration.addReference( this ); // TODO necessary?
}
}
gatherPossibleValues ( values ) {
if ( isReference( this, this.parent ) ) {
values.add( this );
}
}
render ( code, es ) {
if ( this.declaration ) {
const name = this.declaration.getName( es );
if ( name !== this.name ) {
code.overwrite( this.start, this.end, name, true );
// special case
if ( this.parent.type === 'Property' && this.parent.shorthand ) {
code.insertLeft( this.start, `${this.name}: ` );
}
}
}
}
run () {
if ( this.declaration ) this.declaration.activate();
}
}

76
src/ast/nodes/IfStatement.js

@ -0,0 +1,76 @@
import Statement from './shared/Statement.js';
import { UNKNOWN } from '../values.js';
// Statement types which may contain if-statements as direct children.
const statementsWithIfStatements = new Set([
'DoWhileStatement',
'ForInStatement',
'ForOfStatement',
'ForStatement',
'IfStatement',
'WhileStatement'
]);
// TODO DRY this out
export default class IfStatement extends Statement {
initialise ( scope ) {
this.testValue = this.test.getValue();
if ( this.module.bundle.treeshake ) {
if ( this.testValue === UNKNOWN ) {
super.initialise( scope );
}
else if ( this.testValue ) {
this.consequent.initialise( scope );
this.alternate = null;
}
else {
if ( this.alternate ) this.alternate.initialise( scope );
this.consequent = null;
}
}
else {
super.initialise( scope );
}
}
render ( code, es ) {
if ( this.module.bundle.treeshake ) {
if ( this.testValue === UNKNOWN ) {
super.render( code, es );
}
else {
code.overwrite( this.test.start, this.test.end, JSON.stringify( this.testValue ) );
// TODO if no block-scoped declarations, remove enclosing
// curlies and dedent block (if there is a block)
if ( this.testValue ) {
code.remove( this.start, this.consequent.start );
code.remove( this.consequent.end, this.end );
this.consequent.render( code, es );
}
else {
code.remove( this.start, this.alternate ? this.alternate.start : this.next || this.end );
if ( this.alternate ) {
this.alternate.render( code, es );
}
else if ( statementsWithIfStatements.has( this.parent.type ) ) {
code.insertRight( this.start, '{}' );
}
}
}
}
else {
super.render( code, es );
}
}
}

16
src/ast/nodes/ImportDeclaration.js

@ -0,0 +1,16 @@
import Node from '../Node.js';
export default class ImportDeclaration extends Node {
bind () {
// noop
// TODO do the inter-module binding setup here?
}
initialise () {
this.isImportDeclaration = true;
}
render ( code ) {
code.remove( this.start, this.next || this.end );
}
}

17
src/ast/nodes/Literal.js

@ -0,0 +1,17 @@
import Node from '../Node.js';
export default class Literal extends Node {
getValue () {
return this.value;
}
gatherPossibleValues ( values ) {
values.add( this );
}
render ( code ) {
if ( typeof this.value === 'string' ) {
code.indentExclusionRanges.push([ this.start + 1, this.end - 1 ]);
}
}
}

84
src/ast/nodes/MemberExpression.js

@ -0,0 +1,84 @@
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
import isReference from '../utils/isReference.js';
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
class Keypath {
constructor ( node ) {
this.parts = [];
while ( node.type === 'MemberExpression' ) {
this.parts.unshift( node.property );
node = node.object;
}
this.root = node;
}
}
export default class MemberExpression extends Node {
bind ( scope ) {
// if this resolves to a namespaced declaration, prepare
// to replace it
// TODO this code is a bit inefficient
if ( isReference( this ) ) { // TODO optimise namespace access like `foo['bar']` as well
const keypath = new Keypath( this );
let declaration = scope.findDeclaration( keypath.root.name );
while ( declaration.isNamespace && keypath.parts.length ) {
const exporterId = declaration.module.id;
const part = keypath.parts[0];
declaration = declaration.module.traceExport( part.name );
if ( !declaration ) {
const { line, column } = getLocation( this.module.code, this.start );
this.module.bundle.onwarn( `${relativeId( this.module.id )} (${line}:${column}) '${part.name}' is not exported by '${relativeId( exporterId )}'. See https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` );
this.replacement = 'undefined';
return;
}
keypath.parts.shift();
}
if ( keypath.parts.length ) {
super.bind( scope );
return; // not a namespaced declaration
}
this.declaration = declaration;
if ( declaration.isExternal ) {
declaration.module.suggestName( keypath.root.name );
}
}
else {
super.bind( scope );
}
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN ); // TODO
}
render ( code, es ) {
if ( this.declaration ) {
const name = this.declaration.getName( es );
if ( name !== this.name ) code.overwrite( this.start, this.end, name, true );
}
else if ( this.replacement ) {
code.overwrite( this.start, this.end, this.replacement, true );
}
super.render( code, es );
}
run ( scope ) {
if ( this.declaration ) this.declaration.activate();
super.run( scope );
}
}

8
src/ast/nodes/NewExpression.js

@ -0,0 +1,8 @@
import Node from '../Node.js';
import callHasEffects from './shared/callHasEffects.js';
export default class NewExpression extends Node {
hasEffects ( scope ) {
return callHasEffects( scope, this.callee );
}
}

8
src/ast/nodes/ObjectExpression.js

@ -0,0 +1,8 @@
import Node from '../Node.js';
import { OBJECT } from '../values.js';
export default class ObjectExpression extends Node {
gatherPossibleValues ( values ) {
values.add( OBJECT );
}
}

7
src/ast/nodes/ReturnStatement.js

@ -0,0 +1,7 @@
import Node from '../Node.js';
export default class ReturnStatement extends Node {
// hasEffects () {
// return true;
// }
}

8
src/ast/nodes/TemplateLiteral.js

@ -0,0 +1,8 @@
import Node from '../Node.js';
export default class TemplateLiteral extends Node {
render ( code, es ) {
code.indentExclusionRanges.push([ this.start, this.end ]);
super.render( code, es );
}
}

26
src/ast/nodes/ThisExpression.js

@ -0,0 +1,26 @@
import Node from '../Node.js';
import getLocation from '../../utils/getLocation.js';
import relativeId from '../../utils/relativeId.js';
const warning = `The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten. See https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined for more information`;
export default class ThisExpression extends Node {
initialise ( scope ) {
const lexicalBoundary = scope.findLexicalBoundary();
if ( lexicalBoundary.isModuleScope ) {
this.alias = this.module.context;
if ( this.alias === 'undefined' ) {
const { line, column } = getLocation( this.module.code, this.start );
const detail = `${relativeId( this.module.id )} (${line}:${column + 1})`; // use one-based column number convention
this.module.bundle.onwarn( `${detail} ${warning}` );
}
}
}
render ( code ) {
if ( this.alias ) {
code.overwrite( this.start, this.end, this.alias, true );
}
}
}

7
src/ast/nodes/ThrowStatement.js

@ -0,0 +1,7 @@
import Node from '../Node.js';
export default class ThrowStatement extends Node {
hasEffects ( scope ) {
return scope.findLexicalBoundary().isModuleScope; // TODO should this just be `true`? probably...
}
}

34
src/ast/nodes/UnaryExpression.js

@ -0,0 +1,34 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
const operators = {
"-": value => -value,
"+": value => +value,
"!": value => !value,
"~": value => ~value,
typeof: value => typeof value,
void: () => undefined,
delete: () => UNKNOWN
};
export default class UnaryExpression extends Node {
bind ( scope ) {
if ( this.value === UNKNOWN ) super.bind( scope );
}
getValue () {
const argumentValue = this.argument.getValue();
if ( argumentValue === UNKNOWN ) return UNKNOWN;
return operators[ this.operator ]( argumentValue );
}
hasEffects ( scope ) {
return this.operator === 'delete' || this.argument.hasEffects( scope );
}
initialise ( scope ) {
this.value = this.getValue();
if ( this.value === UNKNOWN ) super.initialise( scope );
}
}

39
src/ast/nodes/UpdateExpression.js

@ -0,0 +1,39 @@
import Node from '../Node.js';
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js';
import isUsedByBundle from './shared/isUsedByBundle.js';
import { NUMBER } from '../values.js';
export default class UpdateExpression extends Node {
bind ( scope ) {
const subject = this.argument;
this.subject = subject;
disallowIllegalReassignment( scope, this.argument );
if ( subject.type === 'Identifier' ) {
const declaration = scope.findDeclaration( subject.name );
declaration.isReassigned = true;
if ( declaration.possibleValues ) {
declaration.possibleValues.add( NUMBER );
}
}
super.bind( scope );
}
hasEffects ( scope ) {
return isUsedByBundle( scope, this.subject );
}
initialise ( scope ) {
this.scope = scope;
this.module.bundle.dependentExpressions.push( this );
super.initialise( scope );
}
isUsedByBundle () {
return isUsedByBundle( this.scope, this.subject );
}
}

107
src/ast/nodes/VariableDeclaration.js

@ -0,0 +1,107 @@
import Node from '../Node.js';
import extractNames from '../utils/extractNames.js';
function getSeparator ( code, start ) {
let c = start;
while ( c > 0 && code[ c - 1 ] !== '\n' ) {
c -= 1;
if ( code[c] === ';' || code[c] === '{' ) return '; ';
}
const lineStart = code.slice( c, start ).match( /^\s*/ )[0];
return `;\n${lineStart}`;
}
const forStatement = /^For(?:Of|In)?Statement/;
export default class VariableDeclaration extends Node {
initialise ( scope ) {
this.scope = scope;
super.initialise( scope );
}
render ( code, es ) {
const treeshake = this.module.bundle.treeshake;
let shouldSeparate = false;
let separator;
if ( this.scope.isModuleScope && !forStatement.test( this.parent.type ) ) {
shouldSeparate = true;
separator = getSeparator( this.module.code, this.start );
}
let c = this.start;
let empty = true;
for ( let i = 0; i < this.declarations.length; i += 1 ) {
const declarator = this.declarations[i];
const prefix = empty ? '' : separator; // TODO indentation
if ( declarator.id.type === 'Identifier' ) {
const proxy = declarator.proxies.get( declarator.id.name );
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned;
if ( isExportedAndReassigned ) {
if ( declarator.init ) {
if ( shouldSeparate ) code.overwrite( c, declarator.start, prefix );
c = declarator.end;
empty = false;
}
} else if ( !treeshake || proxy.activated ) {
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
c = declarator.end;
empty = false;
}
}
else {
const exportAssignments = [];
let activated = false;
extractNames( declarator.id ).forEach( name => {
const proxy = declarator.proxies.get( name );
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned;
if ( isExportedAndReassigned ) {
// code.overwrite( c, declarator.start, prefix );
// c = declarator.end;
// empty = false;
exportAssignments.push( 'TODO' );
} else if ( declarator.activated ) {
activated = true;
}
});
if ( !treeshake || activated ) {
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
c = declarator.end;
empty = false;
}
if ( exportAssignments.length ) {
throw new Error( 'TODO' );
}
}
declarator.render( code, es );
}
if ( treeshake && empty ) {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
} else {
// always include a semi-colon (https://github.com/rollup/rollup/pull/1013),
// unless it's a var declaration in a loop head
const needsSemicolon = !forStatement.test( this.parent.type );
if ( this.end > c ) {
code.overwrite( c, this.end, needsSemicolon ? ';' : '' );
} else if ( needsSemicolon ) {
this.insertSemicolon( code );
}
}
}
}

91
src/ast/nodes/VariableDeclarator.js

@ -0,0 +1,91 @@
import Node from '../Node.js';
import extractNames from '../utils/extractNames.js';
import { UNKNOWN } from '../values.js';
class DeclaratorProxy {
constructor ( name, declarator, isTopLevel, init ) {
this.name = name;
this.declarator = declarator;
this.activated = false;
this.isReassigned = false;
this.exportName = null;
this.duplicates = [];
this.possibleValues = new Set( init ? [ init ] : null );
}
activate () {
this.activated = true;
this.declarator.activate();
this.duplicates.forEach( dupe => dupe.activate() );
}
addReference () {
/* noop? */
}
gatherPossibleValues ( values ) {
this.possibleValues.forEach( value => values.add( value ) );
}
getName ( es ) {
// TODO desctructuring...
if ( es ) return this.name;
if ( !this.isReassigned || !this.exportName ) return this.name;
return `exports.${this.exportName}`;
}
toString () {
return this.name;
}
}
export default class VariableDeclarator extends Node {
activate () {
if ( this.activated ) return;
this.activated = true;
this.run( this.findScope() );
}
hasEffects ( scope ) {
return this.init && this.init.hasEffects( scope );
}
initialise ( scope ) {
this.proxies = new Map();
const lexicalBoundary = scope.findLexicalBoundary();
const init = this.init ?
( this.id.type === 'Identifier' ? this.init : UNKNOWN ) : // TODO maybe UNKNOWN is unnecessary
null;
extractNames( this.id ).forEach( name => {
const proxy = new DeclaratorProxy( name, this, lexicalBoundary.isModuleScope, init );
this.proxies.set( name, proxy );
scope.addDeclaration( name, proxy, this.parent.kind === 'var' );
});
super.initialise( scope );
}
render ( code, es ) {
extractNames( this.id ).forEach( name => {
const declaration = this.proxies.get( name );
if ( !es && declaration.exportName && declaration.isReassigned ) {
if ( this.init ) {
code.overwrite( this.start, this.id.end, declaration.getName( es ) );
} else if ( this.module.bundle.treeshake ) {
code.remove( this.start, this.end );
}
}
});
super.render( code, es );
}
}

76
src/ast/nodes/index.js

@ -0,0 +1,76 @@
import ArrayExpression from './ArrayExpression.js';
import ArrowFunctionExpression from './ArrowFunctionExpression.js';
import AssignmentExpression from './AssignmentExpression.js';
import BinaryExpression from './BinaryExpression.js';
import BlockStatement from './BlockStatement.js';
import CallExpression from './CallExpression.js';
import ClassDeclaration from './ClassDeclaration.js';
import ClassExpression from './ClassExpression.js';
import ConditionalExpression from './ConditionalExpression.js';
import EmptyStatement from './EmptyStatement.js';
import ExportAllDeclaration from './ExportAllDeclaration.js';
import ExportDefaultDeclaration from './ExportDefaultDeclaration.js';
import ExportNamedDeclaration from './ExportNamedDeclaration.js';
import ExpressionStatement from './ExpressionStatement.js';
import ForStatement from './ForStatement.js';
import ForInStatement from './ForInStatement.js';
import ForOfStatement from './ForOfStatement.js';
import FunctionDeclaration from './FunctionDeclaration.js';
import FunctionExpression from './FunctionExpression.js';
import Identifier from './Identifier.js';
import IfStatement from './IfStatement.js';
import ImportDeclaration from './ImportDeclaration.js';
import Literal from './Literal.js';
import MemberExpression from './MemberExpression.js';
import NewExpression from './NewExpression.js';
import ObjectExpression from './ObjectExpression.js';
import ReturnStatement from './ReturnStatement.js';
import Statement from './shared/Statement.js';
import TemplateLiteral from './TemplateLiteral.js';
import ThisExpression from './ThisExpression.js';
import ThrowStatement from './ThrowStatement.js';
import UnaryExpression from './UnaryExpression.js';
import UpdateExpression from './UpdateExpression.js';
import VariableDeclarator from './VariableDeclarator.js';
import VariableDeclaration from './VariableDeclaration.js';
export default {
ArrayExpression,
ArrowFunctionExpression,
AssignmentExpression,
BinaryExpression,
BlockStatement,
CallExpression,
ClassDeclaration,
ClassExpression,
ConditionalExpression,
DoWhileStatement: Statement,
EmptyStatement,
ExportAllDeclaration,
ExportDefaultDeclaration,
ExportNamedDeclaration,
ExpressionStatement,
ForStatement,
ForInStatement,
ForOfStatement,
FunctionDeclaration,
FunctionExpression,
Identifier,
IfStatement,
ImportDeclaration,
Literal,
MemberExpression,
NewExpression,
ObjectExpression,
ReturnStatement,
SwitchStatement: Statement,
TemplateLiteral,
ThisExpression,
ThrowStatement,
TryStatement: Statement,
UnaryExpression,
UpdateExpression,
VariableDeclarator,
VariableDeclaration,
WhileStatement: Statement
};

16
src/ast/nodes/shared/Statement.js

@ -0,0 +1,16 @@
import Node from '../../Node.js';
export default class Statement extends Node {
render ( code, es ) {
if ( !this.module.bundle.treeshake || this.shouldInclude ) {
super.render( code, es );
} else {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
}
}
run ( scope ) {
this.shouldInclude = true;
super.run( scope );
}
}

27
src/ast/nodes/shared/assignTo.js

@ -0,0 +1,27 @@
import extractNames from '../../utils/extractNames.js';
export default function assignToForLoopLeft ( node, scope, value ) {
if ( node.type === 'VariableDeclaration' ) {
for ( const proxy of node.declarations[0].proxies.values() ) {
proxy.possibleValues.add( value );
}
}
else {
if ( node.type === 'MemberExpression' ) {
// apparently this is legal JavaScript? Though I don't know what
// kind of monster would write `for ( foo.bar of thing ) {...}`
// for now, do nothing, as I'm not sure anything needs to happen...
}
else {
for ( const name of extractNames( node ) ) {
const declaration = scope.findDeclaration( name );
if ( declaration.possibleValues ) {
declaration.possibleValues.add( value );
}
}
}
}
}

67
src/ast/nodes/shared/callHasEffects.js

@ -0,0 +1,67 @@
import flatten from '../../utils/flatten.js';
import isReference from '../../utils/isReference.js';
import pureFunctions from './pureFunctions.js';
import { UNKNOWN } from '../../values.js';
const currentlyCalling = new Set();
function fnHasEffects ( fn ) {
if ( currentlyCalling.has( fn ) ) return false; // prevent infinite loops... TODO there must be a better way
currentlyCalling.add( fn );
// handle body-less arrow functions
const scope = fn.body.scope || fn.scope;
const body = fn.body.type === 'BlockStatement' ? fn.body.body : [ fn.body ];
for ( const node of body ) {
if ( node.hasEffects( scope ) ) {
currentlyCalling.delete( fn );
return true;
}
}
currentlyCalling.delete( fn );
return false;
}
export default function callHasEffects ( scope, callee ) {
const values = new Set([ callee ]);
for ( const node of values ) {
if ( node === UNKNOWN ) return true; // err on side of caution
if ( /Function/.test( node.type ) ) {
if ( fnHasEffects( node ) ) return true;
}
else if ( isReference( node ) ) {
const flattened = flatten( node );
const declaration = scope.findDeclaration( flattened.name );
if ( declaration.isGlobal ) {
if ( !pureFunctions[ flattened.keypath ] ) return true;
}
else if ( declaration.isExternal ) {
return true; // TODO make this configurable? e.g. `path.[whatever]`
}
else {
if ( node.declaration ) {
node.declaration.gatherPossibleValues( values );
} else {
return true;
}
}
}
else {
if ( !node.gatherPossibleValues ) {
throw new Error( 'TODO' );
}
node.gatherPossibleValues( values );
}
}
return false;
}

28
src/ast/nodes/shared/disallowIllegalReassignment.js

@ -0,0 +1,28 @@
import getLocation from '../../../utils/getLocation.js';
import error from '../../../utils/error.js';
// TODO tidy this up a bit (e.g. they can both use node.module.imports)
export default function disallowIllegalReassignment ( scope, node ) {
if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) {
const declaration = scope.findDeclaration( node.object.name );
if ( declaration.isNamespace ) {
error({
message: `Illegal reassignment to import '${node.object.name}'`,
file: node.module.id,
pos: node.start,
loc: getLocation( node.module.code, node.start )
});
}
}
else if ( node.type === 'Identifier' ) {
if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) {
error({
message: `Illegal reassignment to import '${node.name}'`,
file: node.module.id,
pos: node.start,
loc: getLocation( node.module.code, node.start )
});
}
}
}

38
src/ast/nodes/shared/isUsedByBundle.js

@ -0,0 +1,38 @@
import { UNKNOWN } from '../../values.js';
export default function isUsedByBundle ( scope, node ) {
// const expression = node;
while ( node.type === 'MemberExpression' ) node = node.object;
const declaration = scope.findDeclaration( node.name );
if ( declaration.isParam ) {
return true;
// TODO if we mutate a parameter, assume the worst
// return node !== expression;
}
if ( declaration.activated ) return true;
const values = new Set();
declaration.gatherPossibleValues( values );
for ( const value of values ) {
if ( value === UNKNOWN ) {
return true;
}
if ( value.type === 'Identifier' ) {
if ( value.declaration.activated ) {
return true;
}
value.declaration.gatherPossibleValues( values );
}
else if ( value.gatherPossibleValues ) {
value.gatherPossibleValues( values );
}
}
return false;
}

4
src/utils/pureFunctions.js → src/ast/nodes/shared/pureFunctions.js

@ -1,9 +1,9 @@
let pureFunctions = {};
const pureFunctions = {};
const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split( ' ' );
const simdTypes = 'Int8x16 Int16x8 Int32x4 Float32x4 Float64x2'.split( ' ' );
const simdMethods = 'abs add and bool check div equal extractLane fromFloat32x4 fromFloat32x4Bits fromFloat64x2 fromFloat64x2Bits fromInt16x8Bits fromInt32x4 fromInt32x4Bits fromInt8x16Bits greaterThan greaterThanOrEqual lessThan lessThanOrEqual load max maxNum min minNum mul neg not notEqual or reciprocalApproximation reciprocalSqrtApproximation replaceLane select selectBits shiftLeftByScalar shiftRightArithmeticByScalar shiftRightLogicalByScalar shuffle splat sqrt store sub swizzle xor'.split( ' ' );
let allSimdMethods = [];
const allSimdMethods = [];
simdTypes.forEach( t => {
simdMethods.forEach( m => {
allSimdMethods.push( `SIMD.${t}.${m}` );

40
src/ast/scopes/BundleScope.js

@ -0,0 +1,40 @@
import Scope from './Scope.js';
import { UNKNOWN } from '../values';
class SyntheticGlobalDeclaration {
constructor ( name ) {
this.name = name;
this.isExternal = true;
this.isGlobal = true;
this.isReassigned = false;
this.activated = true;
}
activate () {
/* noop */
}
addReference ( reference ) {
reference.declaration = this;
if ( reference.isReassignment ) this.isReassigned = true;
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
}
getName () {
return this.name;
}
}
export default class BundleScope extends Scope {
findDeclaration ( name ) {
if ( !this.declarations[ name ] ) {
this.declarations[ name ] = new SyntheticGlobalDeclaration( name );
}
return this.declarations[ name ];
}
}

53
src/ast/scopes/ModuleScope.js

@ -0,0 +1,53 @@
import { forOwn } from '../../utils/object.js';
import Scope from './Scope.js';
export default class ModuleScope extends Scope {
constructor ( module ) {
super({
isBlockScope: false,
isLexicalBoundary: true,
isModuleScope: true,
parent: module.bundle.scope
});
this.module = module;
}
deshadow ( names ) {
names = new Map( names );
forOwn( this.module.imports, specifier => {
if ( specifier.module.isExternal ) return;
specifier.module.getExports().forEach( name => {
names.set(name);
});
if ( specifier.name !== '*' ) {
const declaration = specifier.module.traceExport( specifier.name );
if ( !declaration ) {
this.module.bundle.onwarn( `Non-existent export '${specifier.name}' is imported from ${specifier.module.id} by ${this.module.id}` );
return;
}
const name = declaration.getName( true );
if ( name !== specifier.name ) {
names.set( declaration.getName( true ) );
}
}
});
super.deshadow( names );
}
findDeclaration ( name ) {
if ( this.declarations[ name ] ) {
return this.declarations[ name ];
}
return this.module.trace( name ) || this.parent.findDeclaration( name );
}
findLexicalBoundary () {
return this;
}
}

96
src/ast/scopes/Scope.js

@ -0,0 +1,96 @@
import { blank, keys } from '../../utils/object.js';
import { UNKNOWN } from '../values.js';
class Parameter {
constructor ( name ) {
this.name = name;
this.isParam = true;
this.activated = true;
}
activate () {
// noop
}
addReference () {
// noop?
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN ); // TODO populate this at call time
}
getName () {
return this.name;
}
}
export default class Scope {
constructor ( options = {} ) {
this.parent = options.parent;
this.isBlockScope = !!options.isBlockScope;
this.isLexicalBoundary = !!options.isLexicalBoundary;
this.isModuleScope = !!options.isModuleScope;
this.children = [];
if ( this.parent ) this.parent.children.push( this );
this.declarations = blank();
if ( this.isLexicalBoundary && !this.isModuleScope ) {
this.declarations.arguments = new Parameter( 'arguments' );
}
}
addDeclaration ( name, declaration, isVar, isParam ) {
if ( isVar && this.isBlockScope ) {
this.parent.addDeclaration( name, declaration, isVar, isParam );
} else {
const existingDeclaration = this.declarations[ name ];
if ( existingDeclaration && existingDeclaration.duplicates ) {
// TODO warn/throw on duplicates?
existingDeclaration.duplicates.push( declaration );
} else {
this.declarations[ name ] = isParam ? new Parameter( name ) : declaration;
}
}
}
contains ( name ) {
return !!this.declarations[ name ] ||
( this.parent ? this.parent.contains( name ) : false );
}
deshadow ( names ) {
keys( this.declarations ).forEach( key => {
const declaration = this.declarations[ key ];
// we can disregard exports.foo etc
if ( declaration.exportName && declaration.isReassigned ) return;
const name = declaration.getName( true );
let deshadowed = name;
let i = 1;
while ( names.has( deshadowed ) ) {
deshadowed = `${name}$$${i++}`;
}
declaration.name = deshadowed;
});
this.children.forEach( scope => scope.deshadow( names ) );
}
findDeclaration ( name ) {
return this.declarations[ name ] ||
( this.parent && this.parent.findDeclaration( name ) );
}
findLexicalBoundary () {
return this.isLexicalBoundary ? this : this.parent.findLexicalBoundary();
}
}

0
src/ast/extractNames.js → src/ast/utils/extractNames.js

2
src/ast/flatten.js → src/ast/utils/flatten.js

@ -1,5 +1,5 @@
export default function flatten ( node ) {
let parts = [];
const parts = [];
while ( node.type === 'MemberExpression' ) {
if ( node.computed ) return null;
parts.unshift( node.property.name );

10
src/ast/utils/isProgramLevel.js

@ -0,0 +1,10 @@
export default function isProgramLevel ( node ) {
do {
if ( node.type === 'Program' ) {
return true;
}
node = node.parent;
} while ( node && !/Function/.test( node.type ) );
return false;
}

0
src/ast/isReference.js → src/ast/utils/isReference.js

8
src/ast/values.js

@ -0,0 +1,8 @@
// properties are for debugging purposes only
export const ARRAY = { ARRAY: true, toString: () => '[[ARRAY]]' };
export const BOOLEAN = { BOOLEAN: true, toString: () => '[[BOOLEAN]]' };
export const FUNCTION = { FUNCTION: true, toString: () => '[[FUNCTION]]' };
export const NUMBER = { NUMBER: true, toString: () => '[[NUMBER]]' };
export const OBJECT = { OBJECT: true, toString: () => '[[OBJECT]]' };
export const STRING = { STRING: true, toString: () => '[[STRING]]' };
export const UNKNOWN = { UNKNOWN: true, toString: () => '[[UNKNOWN]]' };

19
src/finalisers/amd.js

@ -1,10 +1,11 @@
import { getName, quoteId } from '../utils/map-helpers.js';
import { getName, quotePath } from '../utils/map-helpers.js';
import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js';
import esModuleExport from './shared/esModuleExport.js';
export default function amd ( bundle, magicString, { exportMode, indentString }, options ) {
let deps = bundle.externalModules.map( quoteId );
let args = bundle.externalModules.map( getName );
export default function amd ( bundle, magicString, { exportMode, indentString, intro }, options ) {
const deps = bundle.externalModules.map( quotePath );
const args = bundle.externalModules.map( getName );
if ( exportMode === 'named' ) {
args.unshift( `exports` );
@ -16,17 +17,21 @@ export default function amd ( bundle, magicString, { exportMode, indentString },
( deps.length ? `[${deps.join( ', ' )}], ` : `` );
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
const intro = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`;
const wrapperStart = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`;
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
const interopBlock = getInteropBlock( bundle, options );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
if ( intro ) magicString.prepend( intro );
const exportBlock = getExportBlock( bundle.entryModule, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
if ( exportMode === 'named' && options.legacy !== true ) magicString.append( `\n\n${esModuleExport}` );
if ( options.outro ) magicString.append( `\n${options.outro}` );
return magicString
.indent( indentString )
.append( '\n\n});' )
.prepend( intro );
.prepend( wrapperStart );
}

18
src/finalisers/cjs.js

@ -1,31 +1,34 @@
import getExportBlock from './shared/getExportBlock.js';
import esModuleExport from './shared/esModuleExport.js';
export default function cjs ( bundle, magicString, { exportMode }, options ) {
let intro = options.useStrict === false ? `` : `'use strict';\n\n`;
export default function cjs ( bundle, magicString, { exportMode, intro }, options ) {
intro = ( options.useStrict === false ? intro : `'use strict';\n\n${intro}` ) +
( exportMode === 'named' && options.legacy !== true ? `${esModuleExport}\n\n` : '' );
let needsInterop = false;
const varOrConst = bundle.varOrConst;
const interop = options.interop !== false;
// TODO handle empty imports, once they're supported
const importBlock = bundle.externalModules
.map( module => {
if ( module.declarations.default ) {
if ( interop && module.declarations.default ) {
if ( module.exportsNamespace ) {
return `${varOrConst} ${module.name} = require('${module.id}');` +
return `${varOrConst} ${module.name} = require('${module.path}');` +
`\n${varOrConst} ${module.name}__default = ${module.name}['default'];`;
}
needsInterop = true;
if ( module.exportsNames ) {
return `${varOrConst} ${module.name} = require('${module.id}');` +
return `${varOrConst} ${module.name} = require('${module.path}');` +
`\n${varOrConst} ${module.name}__default = _interopDefault(${module.name});`;
}
return `${varOrConst} ${module.name} = _interopDefault(require('${module.id}'));`;
return `${varOrConst} ${module.name} = _interopDefault(require('${module.path}'));`;
} else {
return `${varOrConst} ${module.name} = require('${module.id}');`;
return `${varOrConst} ${module.name} = require('${module.path}');`;
}
})
.join( '\n' );
@ -42,6 +45,7 @@ export default function cjs ( bundle, magicString, { exportMode }, options ) {
const exportBlock = getExportBlock( bundle.entryModule, exportMode, 'module.exports =' );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
if ( options.outro ) magicString.append( `\n${options.outro}` );
return magicString;
}

24
src/finalisers/es6.js → src/finalisers/es.js

@ -4,7 +4,7 @@ function notDefault ( name ) {
return name !== 'default';
}
export default function es6 ( bundle, magicString ) {
export default function es ( bundle, magicString, { intro }, options ) {
const importBlock = bundle.externalModules
.map( module => {
const specifiers = [];
@ -26,8 +26,8 @@ export default function es6 ( bundle, magicString ) {
}
}
const namespaceSpecifier = module.declarations['*'] ? `* as ${module.name}` : null;
const namedSpecifier = importedNames.length ? `{ ${importedNames.join( ', ' )} }` : null;
const namespaceSpecifier = module.declarations['*'] ? `* as ${module.name}` : null; // TODO prevent unnecessary namespace import, e.g form/external-imports
const namedSpecifier = importedNames.length ? `{ ${importedNames.sort().join( ', ' )} }` : null;
if ( namespaceSpecifier && namedSpecifier ) {
// Namespace and named specifiers cannot be combined.
@ -42,22 +42,21 @@ export default function es6 ( bundle, magicString ) {
return specifiersList
.map( specifiers =>
specifiers.length ?
`import ${specifiers.join( ', ' )} from '${module.id}';` :
`import '${module.id}';`
`import ${specifiers.join( ', ' )} from '${module.path}';` :
`import '${module.path}';`
)
.join( '\n' );
})
.join( '\n' );
if ( importBlock ) {
magicString.prepend( importBlock + '\n\n' );
}
if ( importBlock ) intro += importBlock + '\n\n';
if ( intro ) magicString.prepend( intro );
const module = bundle.entryModule;
const specifiers = module.getExports().filter( notDefault ).map( name => {
const declaration = module.traceExport( name );
const rendered = declaration.render( true );
const rendered = declaration.getName( true );
return rendered === name ?
name :
@ -68,12 +67,11 @@ export default function es6 ( bundle, magicString ) {
const defaultExport = module.exports.default || module.reexports.default;
if ( defaultExport ) {
exportBlock += `export default ${module.traceExport( 'default' ).render( true )};`;
exportBlock += `export default ${module.traceExport( 'default' ).getName( true )};`;
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock.trim() );
}
if ( exportBlock ) magicString.append( '\n\n' + exportBlock.trim() );
if ( options.outro ) magicString.append( `\n${options.outro}` );
return magicString.trim();
}

40
src/finalisers/iife.js

@ -3,60 +3,70 @@ import { getName } from '../utils/map-helpers.js';
import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js';
import getGlobalNameMaker from './shared/getGlobalNameMaker.js';
import propertyStringFor from './shared/propertyStringFor';
// thisProp('foo.bar-baz.qux') === "this.foo['bar-baz'].qux"
const thisProp = propertyStringFor('this');
// propString('foo.bar-baz.qux') === ".foo['bar-baz'].qux"
const propString = propertyStringFor('');
function setupNamespace ( keypath ) {
let parts = keypath.split( '.' ); // TODO support e.g. `foo['something-hyphenated']`?
const parts = keypath.split( '.' );
parts.pop();
let acc = 'this';
return parts
.map( part => ( acc += `.${part}`, `${acc} = ${acc} || {};` ) )
.map( part => ( acc += propString(part), `${acc} = ${acc} || {};` ) )
.join( '\n' ) + '\n';
}
export default function iife ( bundle, magicString, { exportMode, indentString }, options ) {
export default function iife ( bundle, magicString, { exportMode, indentString, intro }, options ) {
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn );
const name = options.moduleName;
const isNamespaced = name && ~name.indexOf( '.' );
let dependencies = bundle.externalModules.map( globalNameMaker );
const dependencies = bundle.externalModules.map( globalNameMaker );
let args = bundle.externalModules.map( getName );
const args = bundle.externalModules.map( getName );
if ( exportMode !== 'none' && !name ) {
throw new Error( 'You must supply options.moduleName for IIFE bundles' );
}
if ( exportMode === 'named' ) {
dependencies.unshift( `(this.${name} = this.${name} || {})` );
dependencies.unshift( `(${thisProp(name)} = ${thisProp(name)} || {})` );
args.unshift( 'exports' );
}
const useStrict = options.useStrict !== false ? `'use strict';` : ``;
const useStrict = options.useStrict !== false ? `${indentString}'use strict';\n\n` : ``;
let intro = `(function (${args}) {\n`;
let outro = `\n\n}(${dependencies}));`;
let wrapperIntro = `(function (${args}) {\n${useStrict}`;
const wrapperOutro = `\n\n}(${dependencies}));`;
if ( exportMode === 'default' ) {
intro = ( isNamespaced ? `this.` : `${bundle.varOrConst} ` ) + `${name} = ${intro}`;
wrapperIntro = ( isNamespaced ? thisProp(name) : `${bundle.varOrConst} ${name}` ) + ` = ${wrapperIntro}`;
}
if ( isNamespaced ) {
intro = setupNamespace( name ) + intro;
wrapperIntro = setupNamespace( name ) + wrapperIntro;
}
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
const interopBlock = getInteropBlock( bundle, options );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
if ( useStrict ) magicString.prepend( useStrict + '\n\n' );
if ( intro ) magicString.prepend( intro );
const exportBlock = getExportBlock( bundle.entryModule, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
if ( options.outro ) magicString.append( `\n${options.outro}` );
return magicString
.indent( indentString )
.prepend( intro )
.append( outro );
.prepend( wrapperIntro )
.append( wrapperOutro );
}

4
src/finalisers/index.js

@ -1,7 +1,7 @@
import amd from './amd.js';
import cjs from './cjs.js';
import es6 from './es6.js';
import es from './es.js';
import iife from './iife.js';
import umd from './umd.js';
export default { amd, cjs, es6, iife, umd };
export default { amd, cjs, es, iife, umd };

1
src/finalisers/shared/esModuleExport.js

@ -0,0 +1 @@
export default `Object.defineProperty(exports, '__esModule', { value: true });`;

6
src/finalisers/shared/getExportBlock.js

@ -1,6 +1,6 @@
export default function getExportBlock ( entryModule, exportMode, mechanism = 'return' ) {
if ( exportMode === 'default' ) {
return `${mechanism} ${entryModule.traceExport( 'default' ).render( false )};`;
return `${mechanism} ${entryModule.traceExport( 'default' ).getName( false )};`;
}
return entryModule.getExports()
@ -9,7 +9,9 @@ export default function getExportBlock ( entryModule, exportMode, mechanism = 'r
const declaration = entryModule.traceExport( name );
const lhs = `exports${prop}`;
const rhs = declaration.render( false );
const rhs = declaration ?
declaration.getName( false ) :
name; // exporting a global
// prevent `exports.count = exports.count`
if ( lhs === rhs ) return null;

4
src/finalisers/shared/getInteropBlock.js

@ -1,7 +1,7 @@
export default function getInteropBlock ( bundle ) {
export default function getInteropBlock ( bundle, options ) {
return bundle.externalModules
.map( module => {
if ( !module.declarations.default ) return null;
if ( !module.declarations.default || options.interop === false ) return null;
if ( module.exportsNamespace ) {
return `${bundle.varOrConst} ${module.name}__default = ${module.name}['default'];`;

18
src/finalisers/shared/propertyStringFor.js

@ -0,0 +1,18 @@
// Generate strings which dereference dotted properties, but use array notation `['prop-deref']`
// if the property name isn't trivial
const shouldUseDot = /^[a-zA-Z$_][a-zA-Z0-9$_]*$/;
const dereferenceString = prop =>
prop.match(shouldUseDot) ? `.${prop}` : `['${prop}']`;
/**
* returns a function which generates property dereference strings for the given name
*
* const getGlobalProp = propertyStringFor('global');
* getGlobalProp('foo.bar-baz.qux') => `global.bar['bar-baz'].qux`
*/
const propertyStringFor = objName => propName =>
objName + propName.split('.').map(dereferenceString).join('');
export default propertyStringFor;

48
src/finalisers/umd.js

@ -1,8 +1,16 @@
import { blank } from '../utils/object.js';
import { getName, quoteId, req } from '../utils/map-helpers.js';
import { getName, quotePath, req } from '../utils/map-helpers.js';
import getInteropBlock from './shared/getInteropBlock.js';
import getExportBlock from './shared/getExportBlock.js';
import getGlobalNameMaker from './shared/getGlobalNameMaker.js';
import esModuleExport from './shared/esModuleExport.js';
import propertyStringFor from './shared/propertyStringFor.js';
// globalProp('foo.bar-baz') === "global.foo['bar-baz']"
const globalProp = propertyStringFor('global');
// propString('foo.bar-baz') === ".foo['bar']"
const propString = propertyStringFor('');
function setupNamespace ( name ) {
const parts = name.split( '.' );
@ -10,28 +18,30 @@ function setupNamespace ( name ) {
let acc = 'global';
return parts
.map( part => ( acc += `.${part}`, `${acc} = ${acc} || {}` ) )
.concat( `global.${name}` )
.map( part => ( acc += propString(part), `${acc} = ${acc} || {}` ) )
.concat( globalProp(name) )
.join( ', ' );
}
export default function umd ( bundle, magicString, { exportMode, indentString }, options ) {
const wrapperOutro = '\n\n})));';
export default function umd ( bundle, magicString, { exportMode, indentString, intro }, options ) {
if ( exportMode !== 'none' && !options.moduleName ) {
throw new Error( 'You must supply options.moduleName for UMD bundles' );
}
const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn );
let amdDeps = bundle.externalModules.map( quoteId );
let cjsDeps = bundle.externalModules.map( req );
let globalDeps = bundle.externalModules.map( module => `global.${globalNameMaker( module )}` );
const amdDeps = bundle.externalModules.map( quotePath );
const cjsDeps = bundle.externalModules.map( req );
const globalDeps = bundle.externalModules.map( module => globalProp(globalNameMaker( module )) );
let args = bundle.externalModules.map( getName );
const args = bundle.externalModules.map( getName );
if ( exportMode === 'named' ) {
amdDeps.unshift( `'exports'` );
cjsDeps.unshift( `exports` );
globalDeps.unshift( `(${setupNamespace(options.moduleName)} = global.${options.moduleName} || {})` );
globalDeps.unshift( `(${setupNamespace(options.moduleName)} = ${globalProp(options.moduleName)} || {})` );
args.unshift( 'exports' );
}
@ -47,31 +57,35 @@ export default function umd ( bundle, magicString, { exportMode, indentString },
const globalExport = options.noConflict === true ?
`(function() {
var current = global.${options.moduleName};
var current = ${globalProp(options.moduleName)};
var exports = factory(${globalDeps});
global.${options.moduleName} = exports;
exports.noConflict = function() { global.${options.moduleName} = current; return exports; };
${globalProp(options.moduleName)} = exports;
exports.noConflict = function() { ${globalProp(options.moduleName)} = current; return exports; };
})()` : `(${defaultExport}factory(${globalDeps}))`;
const intro =
const wrapperIntro =
`(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? ${cjsExport}factory(${cjsDeps.join( ', ' )}) :
typeof define === 'function' && define.amd ? define(${amdParams}factory) :
${globalExport};
}(this, function (${args}) {${useStrict}
}(this, (function (${args}) {${useStrict}
`.replace( /^\t\t/gm, '' ).replace( /^\t/gm, magicString.getIndentString() );
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
const interopBlock = getInteropBlock( bundle, options );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
if ( intro ) magicString.prepend( intro );
const exportBlock = getExportBlock( bundle.entryModule, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
if ( exportMode === 'named' && options.legacy !== true ) magicString.append( `\n\n${esModuleExport}` );
if ( options.outro ) magicString.append( `\n${options.outro}` );
return magicString
.trim()
.indent( indentString )
.append( '\n\n}));' )
.prepend( intro );
.append( wrapperOutro )
.prepend( wrapperIntro );
}

81
src/rollup.js

@ -1,7 +1,8 @@
import Promise from 'es6-promise/lib/es6-promise/promise.js';
import { timeStart, timeEnd, flushTime } from './utils/flushTime.js';
import { basename } from './utils/path.js';
import { writeFile } from './utils/fs.js';
import { keys } from './utils/object.js';
import { assign, keys } from './utils/object.js';
import { mapSequence } from './utils/promise.js';
import validateKeys from './utils/validateKeys.js';
import SOURCEMAPPING_URL from './utils/sourceMappingURL.js';
import Bundle from './Bundle.js';
@ -9,7 +10,10 @@ import Bundle from './Bundle.js';
export const VERSION = '<@VERSION@>';
const ALLOWED_KEYS = [
'acorn',
'banner',
'cache',
'context',
'dest',
'entry',
'exports',
@ -18,54 +22,87 @@ const ALLOWED_KEYS = [
'format',
'globals',
'indent',
'interop',
'intro',
'legacy',
'moduleContext',
'moduleId',
'moduleName',
'noConflict',
'onwarn',
'outro',
'paths',
'plugins',
'preferConst',
'sourceMap',
'sourceMapFile',
'targets',
'treeshake',
'useStrict'
];
export function rollup ( options ) {
function checkOptions ( options ) {
if ( !options || !options.entry ) {
return Promise.reject( new Error( 'You must supply options.entry to rollup' ) );
return new Error( 'You must supply options.entry to rollup' );
}
if ( options.transform || options.load || options.resolveId || options.resolveExternal ) {
return Promise.reject( new Error( 'The `transform`, `load`, `resolveId` and `resolveExternal` options are deprecated in favour of a unified plugin API. See https://github.com/rollup/rollup/wiki/Plugins for details' ) );
return new Error( 'The `transform`, `load`, `resolveId` and `resolveExternal` options are deprecated in favour of a unified plugin API. See https://github.com/rollup/rollup/wiki/Plugins for details' );
}
const error = validateKeys( options, ALLOWED_KEYS );
const error = validateKeys( keys(options), ALLOWED_KEYS );
if ( error ) return error;
if ( error ) {
return Promise.reject( error );
}
return null;
}
export function rollup ( options ) {
const error = checkOptions ( options );
if ( error ) return Promise.reject( error );
const bundle = new Bundle( options );
timeStart( '--BUILD--' );
return bundle.build().then( () => {
return {
timeEnd( '--BUILD--' );
function generate ( options ) {
timeStart( '--GENERATE--' );
const rendered = bundle.render( options );
timeEnd( '--GENERATE--' );
bundle.plugins.forEach( plugin => {
if ( plugin.ongenerate ) {
plugin.ongenerate( assign({
bundle: result
}, options ), rendered);
}
});
flushTime();
return rendered;
}
const result = {
imports: bundle.externalModules.map( module => module.id ),
exports: keys( bundle.entryModule.exports ),
modules: bundle.orderedModules.map( module => {
return { id: module.id };
}),
modules: bundle.orderedModules.map( module => module.toJSON() ),
generate: options => bundle.render( options ),
generate,
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.render( options );
const output = generate( options );
let { code, map } = output;
let promises = [];
const promises = [];
if ( options.sourceMap ) {
let url;
@ -77,12 +114,20 @@ export function rollup ( options ) {
promises.push( writeFile( dest + '.map', map.toString() ) );
}
code += `\n//# ${SOURCEMAPPING_URL}=${url}`;
code += `//# ${SOURCEMAPPING_URL}=${url}\n`;
}
promises.push( writeFile( dest, code ) );
return Promise.all( promises );
return Promise.all( promises ).then( () => {
return mapSequence( bundle.plugins.filter( plugin => plugin.onwrite ), plugin => {
return Promise.resolve( plugin.onwrite( assign({
bundle: result
}, options ), output));
});
});
}
};
return result;
});
}

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

Loading…
Cancel
Save