Browse Source

Merge pull request #253 from rollup/side-effects

More accurate side-effect detection
better-aggressive
Rich Harris 9 years ago
parent
commit
fe0394ad08
  1. 40
      src/Bundle.js
  2. 230
      src/Declaration.js
  3. 39
      src/ExternalModule.js
  4. 181
      src/Module.js
  5. 97
      src/Statement.js
  6. 45
      src/ast/Scope.js
  7. 5
      src/ast/attachScopes.js
  8. 16
      src/ast/flatten.js
  9. 6
      src/ast/isFunctionDeclaration.js
  10. 21
      src/ast/isReference.js
  11. 4
      src/ast/modifierNodes.js
  12. 139
      src/utils/run.js
  13. 6
      test/form/aggressive/_config.js
  14. 9
      test/form/aggressive/_expected/amd.js
  15. 7
      test/form/aggressive/_expected/cjs.js
  16. 5
      test/form/aggressive/_expected/es6.js
  17. 9
      test/form/aggressive/_expected/iife.js
  18. 13
      test/form/aggressive/_expected/umd.js
  19. 9
      test/form/aggressive/foo.js
  20. 3
      test/form/aggressive/main.js
  21. 3
      test/form/dedupes-external-imports/_config.js
  22. 6
      test/form/dedupes-external-imports/_expected/amd.js
  23. 4
      test/form/dedupes-external-imports/_expected/cjs.js
  24. 2
      test/form/dedupes-external-imports/_expected/es6.js
  25. 8
      test/form/dedupes-external-imports/_expected/iife.js
  26. 12
      test/form/dedupes-external-imports/_expected/umd.js
  27. 6
      test/form/dedupes-external-imports/main.js
  28. 30
      test/form/namespace-optimization-b/_expected/amd.js
  29. 24
      test/form/namespace-optimization-b/_expected/cjs.js
  30. 22
      test/form/namespace-optimization-b/_expected/es6.js
  31. 30
      test/form/namespace-optimization-b/_expected/iife.js
  32. 32
      test/form/namespace-optimization-b/_expected/umd.js
  33. 5
      test/form/namespace-optimization-b/foo.js
  34. 17
      test/form/namespace-optimization-b/main.js
  35. 2
      test/form/self-contained-bundle/_expected/amd.js
  36. 2
      test/form/self-contained-bundle/_expected/cjs.js
  37. 2
      test/form/self-contained-bundle/_expected/es6.js
  38. 2
      test/form/self-contained-bundle/_expected/iife.js
  39. 2
      test/form/self-contained-bundle/_expected/umd.js
  40. 2
      test/form/self-contained-bundle/foo.js
  41. 6
      test/form/side-effect-b/_config.js
  42. 7
      test/form/side-effect-b/_expected/amd.js
  43. 5
      test/form/side-effect-b/_expected/cjs.js
  44. 3
      test/form/side-effect-b/_expected/es6.js
  45. 7
      test/form/side-effect-b/_expected/iife.js
  46. 11
      test/form/side-effect-b/_expected/umd.js
  47. 8
      test/form/side-effect-b/main.js
  48. 6
      test/form/side-effect-c/_config.js
  49. 7
      test/form/side-effect-c/_expected/amd.js
  50. 5
      test/form/side-effect-c/_expected/cjs.js
  51. 3
      test/form/side-effect-c/_expected/es6.js
  52. 7
      test/form/side-effect-c/_expected/iife.js
  53. 11
      test/form/side-effect-c/_expected/umd.js
  54. 10
      test/form/side-effect-c/main.js
  55. 6
      test/form/side-effect-d/_config.js
  56. 7
      test/form/side-effect-d/_expected/amd.js
  57. 5
      test/form/side-effect-d/_expected/cjs.js
  58. 3
      test/form/side-effect-d/_expected/es6.js
  59. 7
      test/form/side-effect-d/_expected/iife.js
  60. 11
      test/form/side-effect-d/_expected/umd.js
  61. 4
      test/form/side-effect-d/main.js
  62. 6
      test/form/side-effect-e/_config.js
  63. 20
      test/form/side-effect-e/_expected/amd.js
  64. 18
      test/form/side-effect-e/_expected/cjs.js
  65. 16
      test/form/side-effect-e/_expected/es6.js
  66. 20
      test/form/side-effect-e/_expected/iife.js
  67. 24
      test/form/side-effect-e/_expected/umd.js
  68. 14
      test/form/side-effect-e/main.js
  69. 6
      test/form/side-effect-f/_config.js
  70. 7
      test/form/side-effect-f/_expected/amd.js
  71. 5
      test/form/side-effect-f/_expected/cjs.js
  72. 3
      test/form/side-effect-f/_expected/es6.js
  73. 7
      test/form/side-effect-f/_expected/iife.js
  74. 11
      test/form/side-effect-f/_expected/umd.js
  75. 8
      test/form/side-effect-f/main.js
  76. 6
      test/form/side-effect-g/_config.js
  77. 7
      test/form/side-effect-g/_expected/amd.js
  78. 5
      test/form/side-effect-g/_expected/cjs.js
  79. 3
      test/form/side-effect-g/_expected/es6.js
  80. 7
      test/form/side-effect-g/_expected/iife.js
  81. 11
      test/form/side-effect-g/_expected/umd.js
  82. 3
      test/form/side-effect-g/main.js
  83. 6
      test/form/side-effect-h/_config.js
  84. 7
      test/form/side-effect-h/_expected/amd.js
  85. 5
      test/form/side-effect-h/_expected/cjs.js
  86. 3
      test/form/side-effect-h/_expected/es6.js
  87. 7
      test/form/side-effect-h/_expected/iife.js
  88. 11
      test/form/side-effect-h/_expected/umd.js
  89. 9
      test/form/side-effect-h/main.js
  90. 6
      test/form/side-effect-i/_config.js
  91. 11
      test/form/side-effect-i/_expected/amd.js
  92. 9
      test/form/side-effect-i/_expected/cjs.js
  93. 7
      test/form/side-effect-i/_expected/es6.js
  94. 11
      test/form/side-effect-i/_expected/iife.js
  95. 15
      test/form/side-effect-i/_expected/umd.js
  96. 5
      test/form/side-effect-i/main.js
  97. 6
      test/form/side-effect-j/_config.js
  98. 11
      test/form/side-effect-j/_expected/amd.js
  99. 9
      test/form/side-effect-j/_expected/cjs.js
  100. 7
      test/form/side-effect-j/_expected/es6.js

40
src/Bundle.js

@ -39,7 +39,6 @@ export default class Bundle {
.map( plugin => plugin.transform )
.filter( Boolean );
this.pending = blank();
this.moduleById = blank();
this.modules = [];
@ -50,21 +49,30 @@ export default class Bundle {
this.external = options.external || [];
this.onwarn = options.onwarn || onwarn;
this.aggressive = options.aggressive;
// TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
[ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true );
}
build () {
// Phase 1 – discovery. We load the entry module and find which
// modules it imports, and import those, until we have all
// of the entry module's dependencies
return Promise.resolve( this.resolveId( this.entry, undefined ) )
.then( id => this.fetchModule( id, undefined ) )
.then( entryModule => {
this.entryModule = entryModule;
// Phase 2 – binding. We link references to their declarations
// to generate a complete picture of the bundle
this.modules.forEach( module => module.bindImportSpecifiers() );
this.modules.forEach( module => module.bindAliases() );
this.modules.forEach( module => module.bindReferences() );
// Phase 3 – marking. We 'run' each statement to see which ones
// need to be included in the generated bundle
// mark all export statements
entryModule.getExports().forEach( name => {
const declaration = entryModule.traceExport( name );
@ -73,15 +81,23 @@ export default class Bundle {
declaration.use();
});
// mark statements that should appear in the bundle
let settled = false;
while ( !settled ) {
settled = true;
this.modules.forEach( module => {
if ( module.markAllSideEffects() ) settled = false;
});
if ( this.aggressive ) {
settled = !entryModule.run();
} else {
this.modules.forEach( module => {
if ( module.run() ) settled = false;
});
}
}
// 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
this.orderedModules = this.sort();
this.deconflict();
});
@ -121,8 +137,8 @@ export default class Bundle {
fetchModule ( id, importer ) {
// short-circuit cycles
if ( this.pending[ id ] ) return null;
this.pending[ id ] = true;
if ( id in this.moduleById ) return null;
this.moduleById[ id ] = null;
return Promise.resolve( this.load( id ) )
.catch( err => {
@ -258,12 +274,10 @@ export default class Bundle {
strongDeps[ module.id ] = [];
stronglyDependsOn[ module.id ] = {};
keys( strongDependencies ).forEach( id => {
const imported = strongDependencies[ id ];
strongDependencies.forEach( imported => {
strongDeps[ module.id ].push( imported );
if ( seen[ id ] ) {
if ( seen[ imported.id ] ) {
// we need to prevent an infinite loop, and note that
// we need to check for strong/weak dependency relationships
hasCycles = true;
@ -273,10 +287,8 @@ export default class Bundle {
visit( imported );
});
keys( weakDependencies ).forEach( id => {
const imported = weakDependencies[ id ];
if ( seen[ id ] ) {
weakDependencies.forEach( imported => {
if ( seen[ imported.id ] ) {
// we need to prevent an infinite loop, and note that
// we need to check for strong/weak dependency relationships
hasCycles = true;

230
src/Declaration.js

@ -0,0 +1,230 @@
import { blank, keys } from './utils/object.js';
import run from './utils/run.js';
export default class Declaration {
constructor ( node, isParam ) {
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;
}
}
this.statement = null;
this.name = null;
this.isParam = isParam;
this.isReassigned = false;
this.aliases = [];
}
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.isExported ) return this.name;
return `exports.${this.name}`;
}
run ( strongDependencies ) {
if ( this.tested ) return this.hasSideEffects;
this.tested = true;
if ( !this.statement || !this.functionNode ) {
this.hasSideEffects = true; // err on the side of caution. TODO handle unambiguous `var x; x = y => z` cases
} else {
this.hasSideEffects = run( this.functionNode.body, this.functionNode._scope, this.statement, strongDependencies );
}
return this.hasSideEffects;
}
use () {
this.isUsed = true;
if ( this.statement ) this.statement.mark();
this.aliases.forEach( alias => alias.use() );
}
}
export class SyntheticDefaultDeclaration {
constructor ( node, statement, name ) {
this.node = node;
this.statement = statement;
this.name = name;
this.original = null;
this.isExported = false;
this.aliases = [];
}
addAlias ( declaration ) {
this.aliases.push( declaration );
}
addReference ( reference ) {
// Don't change the name to `default`; it's not a valid identifier name.
if ( reference.name === 'default' ) return;
reference.declaration = this;
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 );
}
if ( /FunctionExpression/.test( this.node.declaration.type ) ) {
return run( this.node.declaration.body, this.statement.scope, this.statement, strongDependencies );
}
}
use () {
this.isUsed = true;
this.statement.mark();
if ( this.original ) this.original.use();
this.aliases.forEach( alias => alias.use() );
}
}
export class SyntheticNamespaceDeclaration {
constructor ( module ) {
this.module = module;
this.name = null;
this.needsNamespaceBlock = false;
this.aliases = [];
this.originals = blank();
module.getExports().forEach( name => {
this.originals[ name ] = module.traceExport( name );
});
}
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 ) {
reference.name = reference.parts.shift();
reference.end += reference.name.length + 1; // TODO this is brittle
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 ) {
this.needsNamespaceBlock = true;
this.module.bundle.internalNamespaces.push( this );
}
reference.declaration = this;
this.name = reference.name;
}
renderBlock ( indentString ) {
const members = keys( this.originals ).map( name => {
const original = this.originals[ name ];
if ( original.isReassigned ) {
return `${indentString}get ${name} () { return ${original.render()}; }`;
}
return `${indentString}${name}: ${original.render()}`;
});
return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`;
}
render () {
return this.name;
}
use () {
keys( this.originals ).forEach( name => {
this.originals[ name ].use();
});
this.aliases.forEach( alias => alias.use() );
}
}
export class ExternalDeclaration {
constructor ( module, name ) {
this.module = module;
this.name = name;
this.isExternal = true;
}
addAlias () {
// noop
}
addReference ( reference ) {
reference.declaration = this;
if ( this.name === 'default' || this.name === '*' ) {
this.module.suggestName( reference.name );
}
}
render ( es6 ) {
if ( this.name === '*' ) {
return this.module.name;
}
if ( this.name === 'default' ) {
return !es6 && this.module.exportsNames ?
`${this.module.name}__default` :
this.module.name;
}
return es6 ? this.name : `${this.module.name}.${this.name}`;
}
use () {
// noop?
}
}

39
src/ExternalModule.js

@ -1,43 +1,6 @@
import { blank } from './utils/object.js';
import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
class ExternalDeclaration {
constructor ( module, name ) {
this.module = module;
this.name = name;
this.isExternal = true;
}
addAlias () {
// noop
}
addReference ( reference ) {
reference.declaration = this;
if ( this.name === 'default' || this.name === '*' ) {
this.module.suggestName( reference.name );
}
}
render ( es6 ) {
if ( this.name === '*' ) {
return this.module.name;
}
if ( this.name === 'default' ) {
return !es6 && this.module.exportsNames ?
`${this.module.name}__default` :
this.module.name;
}
return es6 ? this.name : `${this.module.name}.${this.name}`;
}
use () {
// noop?
}
}
import { ExternalDeclaration } from './Declaration.js';
export default class ExternalModule {
constructor ( id ) {

181
src/Module.js

@ -7,134 +7,10 @@ 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, SyntheticNamespaceDeclaration } from './Declaration.js';
import { isFalsy, isTruthy } from './ast/conditions.js';
import { emptyBlockStatement } from './ast/create.js';
class SyntheticDefaultDeclaration {
constructor ( node, statement, name ) {
this.node = node;
this.statement = statement;
this.name = name;
this.original = null;
this.isExported = false;
this.aliases = [];
this.isUsed = false;
}
addAlias ( declaration ) {
this.aliases.push( declaration );
}
addReference ( reference ) {
// Don't change the name to `default`; it's not a valid identifier name.
if ( reference.name === 'default' ) return;
reference.declaration = this;
this.name = reference.name;
}
bind ( declaration ) {
this.original = declaration;
}
render () {
return !this.original || this.original.isReassigned ?
this.name :
this.original.render();
}
use () {
if ( this.isUsed ) return;
this.isUsed = true;
this.statement.mark();
if ( this.original ) this.original.use();
this.aliases.forEach( alias => alias.use() );
}
}
class SyntheticNamespaceDeclaration {
constructor ( module ) {
this.module = module;
this.name = null;
this.needsNamespaceBlock = false;
this.aliases = [];
this.originals = blank();
module.getExports().forEach( name => {
this.originals[ name ] = module.traceExport( name );
});
}
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 ) {
reference.name = reference.parts.shift();
reference.end += reference.name.length + 1; // TODO this is brittle
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 ) {
this.needsNamespaceBlock = true;
this.module.bundle.internalNamespaces.push( this );
}
reference.declaration = this;
this.name = reference.name;
}
renderBlock ( indentString ) {
const members = keys( this.originals ).map( name => {
const original = this.originals[ name ];
if ( original.isReassigned ) {
return `${indentString}get ${name} () { return ${original.render()}; }`;
}
return `${indentString}${name}: ${original.render()}`;
});
return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`;
}
render () {
return this.name;
}
use () {
keys( this.originals ).forEach( name => {
this.originals[ name ].use();
});
this.aliases.forEach( alias => alias.use() );
}
}
export default class Module {
constructor ({ id, code, originalCode, ast, sourceMapChain, bundle }) {
this.code = code;
@ -175,6 +51,8 @@ export default class Module {
this.declarations = blank();
this.analyse();
this.strongDependencies = [];
}
addExport ( statement ) {
@ -280,7 +158,7 @@ export default class Module {
if ( statement.isImportDeclaration ) this.addImport( statement );
else if ( statement.isExportDeclaration ) this.addExport( statement );
statement.analyse();
statement.firstPass();
statement.scope.eachDeclaration( ( name, declaration ) => {
this.declarations[ name ] = declaration;
@ -304,8 +182,11 @@ export default class Module {
if ( 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 || !reference.isImmediatelyUsed ) return;
if ( reference.name === name ) return;
const otherDeclaration = this.trace( reference.name );
if ( otherDeclaration ) otherDeclaration.addAlias( declaration );
@ -359,35 +240,21 @@ export default class Module {
}
consolidateDependencies () {
let strongDependencies = blank();
let weakDependencies = blank();
let strongDependencies = [];
let weakDependencies = [];
// treat all imports as weak dependencies
this.dependencies.forEach( source => {
const id = this.resolvedIds[ source ];
const dependency = this.bundle.moduleById[ id ];
if ( !dependency.isExternal ) {
weakDependencies[ dependency.id ] = dependency;
if ( !dependency.isExternal && !~weakDependencies.indexOf( dependency ) ) {
weakDependencies.push( dependency );
}
});
// identify strong dependencies to break ties in case of cycles
this.statements.forEach( statement => {
statement.references.forEach( reference => {
const declaration = reference.declaration;
if ( declaration && declaration.statement ) {
const module = declaration.statement.module;
if ( module === this ) return;
// TODO disregard function declarations
if ( reference.isImmediatelyUsed ) {
strongDependencies[ module.id ] = module;
}
}
});
});
strongDependencies = this.strongDependencies
.filter( module => module !== this );
return { strongDependencies, weakDependencies };
}
@ -412,16 +279,6 @@ export default class Module {
return keys( exports );
}
markAllSideEffects () {
let hasSideEffect = false;
this.statements.forEach( statement => {
if ( statement.markSideEffect() ) hasSideEffect = true;
});
return hasSideEffect;
}
namespace () {
if ( !this.declarations['*'] ) {
this.declarations['*'] = new SyntheticNamespaceDeclaration( this );
@ -701,6 +558,16 @@ export default class Module {
return magicString.trim();
}
run () {
let marked = false;
this.statements.forEach( statement => {
marked = marked || statement.run( this.strongDependencies );
});
return marked;
}
trace ( name ) {
if ( name in this.declarations ) return this.declarations[ name ];
if ( name in this.imports ) {

97
src/Statement.js

@ -1,43 +1,17 @@
import { walk } from 'estree-walker';
import Scope from './ast/Scope.js';
import attachScopes from './ast/attachScopes.js';
import modifierNodes from './ast/modifierNodes.js';
import isFunctionDeclaration from './ast/isFunctionDeclaration.js';
import isReference from './ast/isReference.js';
import getLocation from './utils/getLocation.js';
const modifierNodes = {
AssignmentExpression: 'left',
UpdateExpression: 'argument'
};
function isIife ( node, parent ) {
return parent && parent.type === 'CallExpression' && node === parent.callee;
}
function isReference ( node, parent ) {
if ( node.type === 'MemberExpression' ) {
return !node.computed && isReference( node.object, node );
}
if ( node.type === 'Identifier' ) {
// TODO is this right?
if ( parent.type === 'MemberExpression' ) return parent.computed || node === parent.object;
// disregard the `bar` in { bar: foo }
if ( parent.type === 'Property' && node !== parent.value ) return false;
// disregard the `bar` in `class Foo { bar () {...} }`
if ( parent.type === 'MethodDefinition' ) return false;
// disregard the `bar` in `export { foo as bar }`
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return;
return true;
}
}
import run from './utils/run.js';
class Reference {
constructor ( node, scope ) {
constructor ( node, scope, statement ) {
this.node = node;
this.scope = scope;
this.statement = statement;
this.declaration = null; // bound later
@ -71,13 +45,17 @@ export default class Statement {
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 );
}
analyse () {
firstPass () {
if ( this.isImportDeclaration ) return; // nothing to analyse
// attach scopes
@ -90,6 +68,7 @@ export default class Statement {
});
// find references
const statement = this;
let { module, references, scope, stringLiteralRanges } = this;
let readDepth = 0;
@ -109,7 +88,7 @@ export default class Statement {
}
if ( node._scope ) scope = node._scope;
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1;
if ( /Function/.test( node.type ) ) readDepth += 1;
// special case – shorthand properties. because node.key === node.value,
// we can't differentiate once we've descended into the node
@ -156,18 +135,17 @@ export default class Statement {
scope.parent :
scope;
const reference = new Reference( node, referenceScope );
references.push( reference );
reference.isImmediatelyUsed = !readDepth;
const reference = new Reference( node, referenceScope, statement );
reference.isReassignment = isReassignment;
references.push( reference );
this.skip(); // don't descend from `foo.bar.baz` into `foo.bar`
}
},
leave ( node, parent ) {
leave ( node ) {
if ( node._scope ) scope = scope.parent;
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1;
if ( /Function/.test( node.type ) ) readDepth -= 1;
}
});
}
@ -181,39 +159,14 @@ export default class Statement {
});
}
markSideEffect () {
if ( this.isIncluded ) return;
const statement = this;
let hasSideEffect = false;
walk( this.node, {
enter ( node, parent ) {
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) return this.skip();
// If this is a top-level call expression, or an assignment to a global,
// this statement will need to be marked
if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) {
hasSideEffect = true;
}
else if ( node.type in modifierNodes ) {
let subject = node[ modifierNodes[ node.type ] ];
while ( subject.type === 'MemberExpression' ) subject = subject.object;
run ( strongDependencies ) {
if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return;
this.ran = true;
const declaration = statement.module.trace( subject.name );
if ( !declaration || declaration.isExternal || declaration.statement.isIncluded ) {
hasSideEffect = true;
}
}
if ( hasSideEffect ) this.skip();
}
});
if ( hasSideEffect ) statement.mark();
return hasSideEffect;
if ( run( this.node, this.scope, this, strongDependencies ) ) {
this.mark();
return true;
}
}
source () {

45
src/ast/Scope.js

@ -1,4 +1,5 @@
import { blank, keys } from '../utils/object.js';
import Declaration from '../Declaration.js';
const extractors = {
Identifier ( names, param ) {
@ -33,58 +34,20 @@ function extractNames ( param ) {
return names;
}
class Declaration {
constructor () {
this.statement = null;
this.name = null;
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.isExported ) return this.name;
return `exports.${this.name}`;
}
use () {
if ( this.isUsed ) return;
this.isUsed = true;
if ( this.statement ) this.statement.mark();
this.aliases.forEach( alias => alias.use() );
}
}
export default class Scope {
constructor ( options ) {
options = options || {};
this.parent = options.parent;
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( name );
this.declarations[ name ] = new Declaration( param, true );
});
});
}
@ -97,7 +60,7 @@ export default class Scope {
this.parent.addDeclaration( node, isBlockDeclaration, isVar );
} else {
extractNames( node.id ).forEach( name => {
this.declarations[ name ] = new Declaration( name );
this.declarations[ name ] = new Declaration( node );
});
}
}

5
src/ast/attachScopes.js

@ -20,7 +20,10 @@ export default function attachScopes ( statement ) {
// var foo = 1, bar = 2
if ( node.type === 'VariableDeclaration' ) {
const isBlockDeclaration = blockDeclarations[ node.kind ];
node.declarations.forEach( declaration => scope.addDeclaration( declaration, isBlockDeclaration, true ) );
node.declarations.forEach( declarator => {
scope.addDeclaration( declarator, isBlockDeclaration, true );
});
}
let newScope;

16
src/ast/flatten.js

@ -0,0 +1,16 @@
export default function flatten ( node ) {
let parts = [];
while ( node.type === 'MemberExpression' ) {
if ( node.computed ) return null;
parts.unshift( node.property.name );
node = node.object;
}
if ( node.type !== 'Identifier' ) return null;
const name = node.name;
parts.unshift( name );
return { name, keypath: parts.join( '.' ) };
}

6
src/ast/isFunctionDeclaration.js

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

21
src/ast/isReference.js

@ -0,0 +1,21 @@
export default function isReference ( node, parent ) {
if ( node.type === 'MemberExpression' ) {
return !node.computed && isReference( node.object, node );
}
if ( node.type === 'Identifier' ) {
// TODO is this right?
if ( parent.type === 'MemberExpression' ) return parent.computed || node === parent.object;
// disregard the `bar` in { bar: foo }
if ( parent.type === 'Property' && node !== parent.value ) return false;
// disregard the `bar` in `class Foo { bar () {...} }`
if ( parent.type === 'MethodDefinition' ) return false;
// disregard the `bar` in `export { foo as bar }`
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return;
return true;
}
}

4
src/ast/modifierNodes.js

@ -0,0 +1,4 @@
export default {
AssignmentExpression: 'left',
UpdateExpression: 'argument'
};

139
src/utils/run.js

@ -0,0 +1,139 @@
import { walk } from 'estree-walker';
import modifierNodes from '../ast/modifierNodes.js';
import isReference from '../ast/isReference.js';
import flatten from '../ast/flatten';
let 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 = [];
simdTypes.forEach( t => {
simdMethods.forEach( m => {
allSimdMethods.push( `SIMD.${t}.${m}` );
});
});
[
'Array.isArray',
'Error', 'EvalError', 'InternalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape',
'Object', 'Object.create', 'Object.getNotifier', 'Object.getOwn', 'Object.getOwnPropertyDescriptor', 'Object.getOwnPropertyNames', 'Object.getOwnPropertySymbols', 'Object.getPrototypeOf', 'Object.is', 'Object.isExtensible', 'Object.isFrozen', 'Object.isSealed', 'Object.keys',
'Function', 'Boolean',
'Number', 'Number.isFinite', 'Number.isInteger', 'Number.isNaN', 'Number.isSafeInteger', 'Number.parseFloat', 'Number.parseInt',
'Symbol', 'Symbol.for', 'Symbol.keyFor',
'Math.abs', 'Math.acos', 'Math.acosh', 'Math.asin', 'Math.asinh', 'Math.atan', 'Math.atan2', 'Math.atanh', 'Math.cbrt', 'Math.ceil', 'Math.clz32', 'Math.cos', 'Math.cosh', 'Math.exp', 'Math.expm1', 'Math.floor', 'Math.fround', 'Math.hypot', 'Math.imul', 'Math.log', 'Math.log10', 'Math.log1p', 'Math.log2', 'Math.max', 'Math.min', 'Math.pow', 'Math.random', 'Math.round', 'Math.sign', 'Math.sin', 'Math.sinh', 'Math.sqrt', 'Math.tan', 'Math.tanh', 'Math.trunc',
'Date', 'Date.UTC', 'Date.now', 'Date.parse',
'String', 'String.fromCharCode', 'String.fromCodePoint', 'String.raw',
'RegExp',
'Map', 'Set', 'WeakMap', 'WeakSet',
'ArrayBuffer', 'ArrayBuffer.isView',
'DataView',
'JSON.parse', 'JSON.stringify',
'Promise', 'Promise.all', 'Promise.race', 'Promise.reject', 'Promise.resolve',
'Intl.Collator', 'Intl.Collator.supportedLocalesOf', 'Intl.DateTimeFormat', 'Intl.DateTimeFormat.supportedLocalesOf', 'Intl.NumberFormat', 'Intl.NumberFormat.supportedLocalesOf'
// TODO properties of e.g. window...
].concat(
arrayTypes,
arrayTypes.map( t => `${t}.from` ),
arrayTypes.map( t => `${t}.of` ),
simdTypes.map( t => `SIMD.${t}` ),
allSimdMethods
).forEach( name => pureFunctions[ name ] = true );
// TODO add others to this list from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
export default function run ( node, scope, statement, strongDependencies, force ) {
let hasSideEffect = false;
walk( node, {
enter ( node, parent ) {
if ( !force && /Function/.test( node.type ) ) return this.skip();
if ( node._scope ) scope = node._scope;
if ( isReference( node, parent ) ) {
const flattened = flatten( node );
if ( flattened.name === 'arguments' ) {
hasSideEffect = true;
} if ( !scope.contains( flattened.name ) ) {
const declaration = statement.module.trace( flattened.name );
if ( declaration && !declaration.isExternal ) {
const module = declaration.module || declaration.statement.module; // TODO is this right?
if ( !module.isExternal && !~strongDependencies.indexOf( module ) ) strongDependencies.push( module );
}
}
}
else if ( node.type === 'ThrowStatement' ) {
// we only care about errors thrown at the top level, otherwise
// any function with error checking gets included if called
if ( scope.isTopLevel ) hasSideEffect = true;
}
else if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) {
if ( node.callee.type === 'Identifier' ) {
const declaration = scope.findDeclaration( node.callee.name ) ||
statement.module.trace( node.callee.name );
if ( declaration ) {
if ( declaration.isExternal || declaration.run( strongDependencies ) ) {
hasSideEffect = true;
}
} else if ( !pureFunctions[ node.callee.name ] ) {
hasSideEffect = true;
}
}
else if ( node.callee.type === 'MemberExpression' ) {
const flattened = flatten( node.callee );
if ( flattened ) {
// if we're calling e.g. Object.keys(thing), there are no side-effects
// TODO make pureFunctions configurable
const declaration = scope.findDeclaration( flattened.name ) || statement.module.trace( flattened.name );
if ( !!declaration || !pureFunctions[ flattened.keypath ] ) {
hasSideEffect = true;
}
} else {
// is not a keypath like `foo.bar.baz` – could be e.g.
// `(a || b).foo()`. Err on the side of caution
hasSideEffect = true;
}
}
// otherwise we're probably dealing with a function expression
else if ( run( node.callee, scope, statement, strongDependencies, true ) ) {
hasSideEffect = true;
}
}
else if ( node.type in modifierNodes ) {
let subject = node[ modifierNodes[ node.type ] ];
while ( subject.type === 'MemberExpression' ) subject = subject.object;
let declaration = scope.findDeclaration( subject.name );
if ( declaration ) {
if ( declaration.isParam ) hasSideEffect = true;
} else {
declaration = statement.module.trace( subject.name );
if ( !declaration || declaration.isExternal || declaration.isUsed ) {
hasSideEffect = true;
}
}
}
},
leave ( node ) {
if ( node._scope ) scope = scope.parent;
}
});
return hasSideEffect;
}

6
test/form/aggressive/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'ignores side-effects outside entry module in aggressive mode',
options: {
aggressive: true
}
}

9
test/form/aggressive/_expected/amd.js

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

7
test/form/aggressive/_expected/cjs.js

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

5
test/form/aggressive/_expected/es6.js

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

9
test/form/aggressive/_expected/iife.js

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

13
test/form/aggressive/_expected/umd.js

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

9
test/form/aggressive/foo.js

@ -0,0 +1,9 @@
function x () {
console.log( 'side-effect' );
}
x();
export function foo () {
return 42;
}

3
test/form/aggressive/main.js

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

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

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

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

@ -1,4 +1,4 @@
define(['external'], function (external) { 'use strict';
define(['exports', 'external'], function (exports, external) { 'use strict';
class Foo extends external.Component {
constructor () {
@ -25,4 +25,8 @@ define(['external'], function (external) { 'use strict';
const bar = new Bar();
const baz = new Baz();
exports.foo = foo;
exports.bar = bar;
exports.baz = baz;
});

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

@ -26,3 +26,7 @@ class Baz extends external.Component {
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
exports.foo = foo;
exports.bar = bar;
exports.baz = baz;

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

@ -24,3 +24,5 @@ class Baz extends Component {
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
export { foo, bar, baz };

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

@ -1,4 +1,4 @@
(function (external) { 'use strict';
(function (exports,external) { 'use strict';
class Foo extends external.Component {
constructor () {
@ -25,4 +25,8 @@
const bar = new Bar();
const baz = new Baz();
})(external);
exports.foo = foo;
exports.bar = bar;
exports.baz = baz;
})((this.myBundle = {}),external);

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

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

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

@ -2,6 +2,6 @@ import Foo from './foo';
import Bar from './bar';
import Baz from './baz';
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
export const foo = new Foo();
export const bar = new Bar();
export const baz = new Baz();

30
test/form/namespace-optimization-b/_expected/amd.js

@ -1,15 +1,19 @@
define(function () { 'use strict';
function foo() {
};
function a() {
foo();
foo();
var a;
if (a.b) {
}
}
a();
});
function foo () {
console.log( 'foo' );
}
function a () {
foo();
foo();
var a;
if ( a.b ) {
// empty
}
}
a();
});

24
test/form/namespace-optimization-b/_expected/cjs.js

@ -1,13 +1,17 @@
'use strict';
function foo() {
};
function a() {
foo();
foo();
var a;
if (a.b) {
}
function foo () {
console.log( 'foo' );
}
a();
function a () {
foo();
foo();
var a;
if ( a.b ) {
// empty
}
}
a();

22
test/form/namespace-optimization-b/_expected/es6.js

@ -1,11 +1,15 @@
function foo() {
};
function foo () {
console.log( 'foo' );
}
function a () {
foo();
foo();
function a() {
foo();
foo();
var a;
if (a.b) {
}
var a;
if ( a.b ) {
// empty
}
}
a();
a();

30
test/form/namespace-optimization-b/_expected/iife.js

@ -1,15 +1,19 @@
(function () { 'use strict';
function foo() {
};
function a() {
foo();
foo();
var a;
if (a.b) {
}
}
a();
})();
function foo () {
console.log( 'foo' );
}
function a () {
foo();
foo();
var a;
if ( a.b ) {
// empty
}
}
a();
})();

32
test/form/namespace-optimization-b/_expected/umd.js

@ -1,19 +1,23 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
function foo() {
};
function foo () {
console.log( 'foo' );
}
function a() {
foo();
foo();
var a;
if (a.b) {
}
}
a();
function a () {
foo();
foo();
}));
var a;
if ( a.b ) {
// empty
}
}
a();
}));

5
test/form/namespace-optimization-b/foo.js

@ -1,2 +1,3 @@
export function foo() {
};
export function foo () {
console.log( 'foo' );
}

17
test/form/namespace-optimization-b/main.js

@ -1,10 +1,13 @@
import * as foo from './foo';
function a() {
foo.foo();
foo.foo();
var a;
if (a.b) {
}
function a () {
foo.foo();
foo.foo();
var a;
if ( a.b ) {
// empty
}
}
a();
a();

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

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

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

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

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

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

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

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

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

@ -5,7 +5,7 @@
}(this, function () { 'use strict';
function foo () {
return bar();
console.log( bar() );
}
function bar () {

2
test/form/self-contained-bundle/foo.js

@ -1,5 +1,5 @@
export default function foo () {
return bar();
console.log( bar() );
}
function bar () {

6
test/form/side-effect-b/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'discards IIFE with no side-effects',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-b/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-b/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-b/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-b/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-b/_expected/umd.js

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

8
test/form/side-effect-b/main.js

@ -0,0 +1,8 @@
var Unused = (function () {
function Unused () {}
Unused.prototype = {};
return Unused;
}());
export default 42;

6
test/form/side-effect-c/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'discards function with no side-effects',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-c/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-c/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-c/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-c/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-c/_expected/umd.js

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

10
test/form/side-effect-c/main.js

@ -0,0 +1,10 @@
var factory = function () {
function Unused () {}
Unused.prototype = {};
return Unused;
};
var Unused = factory();
export default 42;

6
test/form/side-effect-d/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'excludes functions that are known to be pure',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-d/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-d/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-d/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-d/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-d/_expected/umd.js

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

4
test/form/side-effect-d/main.js

@ -0,0 +1,4 @@
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
export default 42;

6
test/form/side-effect-e/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'accounts for local scopes when tested function purity',
options: {
moduleName: 'myBundle'
}
};

20
test/form/side-effect-e/_expected/amd.js

@ -0,0 +1,20 @@
define(function () { 'use strict';
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
var main = 42;
return main;
});

18
test/form/side-effect-e/_expected/cjs.js

@ -0,0 +1,18 @@
'use strict';
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
var main = 42;
module.exports = main;

16
test/form/side-effect-e/_expected/es6.js

@ -0,0 +1,16 @@
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
var main = 42;
export default main;

20
test/form/side-effect-e/_expected/iife.js

@ -0,0 +1,20 @@
var myBundle = (function () { 'use strict';
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
var main = 42;
return main;
})();

24
test/form/side-effect-e/_expected/umd.js

@ -0,0 +1,24 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
global.myBundle = factory();
}(this, function () { 'use strict';
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
var main = 42;
return main;
}));

14
test/form/side-effect-e/main.js

@ -0,0 +1,14 @@
function foo () {
var Object = {
keys: function () {
console.log( 'side-effect' );
}
};
var obj = { foo: 1, bar: 2 };
var keys = Object.keys( obj );
}
foo();
export default 42;

6
test/form/side-effect-f/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'disregards side-effects that are contained within a function',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-f/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-f/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-f/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-f/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-f/_expected/umd.js

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

8
test/form/side-effect-f/main.js

@ -0,0 +1,8 @@
function foo () {
var a, b, c;
b = 1;
}
foo();
export default 42;

6
test/form/side-effect-g/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'excludes constructors that are known to be pure',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-g/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-g/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-g/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-g/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-g/_expected/umd.js

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

3
test/form/side-effect-g/main.js

@ -0,0 +1,3 @@
var err = new Error( 'this will be ignored' );
export default 42;

6
test/form/side-effect-h/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'excludes non-top-level throw statements',
options: {
moduleName: 'myBundle'
}
};

7
test/form/side-effect-h/_expected/amd.js

@ -0,0 +1,7 @@
define(function () { 'use strict';
var main = 42;
return main;
});

5
test/form/side-effect-h/_expected/cjs.js

@ -0,0 +1,5 @@
'use strict';
var main = 42;
module.exports = main;

3
test/form/side-effect-h/_expected/es6.js

@ -0,0 +1,3 @@
var main = 42;
export default main;

7
test/form/side-effect-h/_expected/iife.js

@ -0,0 +1,7 @@
var myBundle = (function () { 'use strict';
var main = 42;
return main;
})();

11
test/form/side-effect-h/_expected/umd.js

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

9
test/form/side-effect-h/main.js

@ -0,0 +1,9 @@
function foo ( ok ) {
if ( !ok ) {
throw new Error( 'this will be ignored' );
}
}
foo();
export default 42;

6
test/form/side-effect-i/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'includes top-level throw statements',
options: {
moduleName: 'myBundle'
}
};

11
test/form/side-effect-i/_expected/amd.js

@ -0,0 +1,11 @@
define(function () { 'use strict';
if ( !ok ) {
throw new Error( 'this will be included' );
}
var main = 42;
return main;
});

9
test/form/side-effect-i/_expected/cjs.js

@ -0,0 +1,9 @@
'use strict';
if ( !ok ) {
throw new Error( 'this will be included' );
}
var main = 42;
module.exports = main;

7
test/form/side-effect-i/_expected/es6.js

@ -0,0 +1,7 @@
if ( !ok ) {
throw new Error( 'this will be included' );
}
var main = 42;
export default main;

11
test/form/side-effect-i/_expected/iife.js

@ -0,0 +1,11 @@
var myBundle = (function () { 'use strict';
if ( !ok ) {
throw new Error( 'this will be included' );
}
var main = 42;
return main;
})();

15
test/form/side-effect-i/_expected/umd.js

@ -0,0 +1,15 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
global.myBundle = factory();
}(this, function () { 'use strict';
if ( !ok ) {
throw new Error( 'this will be included' );
}
var main = 42;
return main;
}));

5
test/form/side-effect-i/main.js

@ -0,0 +1,5 @@
if ( !ok ) {
throw new Error( 'this will be included' );
}
export default 42;

6
test/form/side-effect-j/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'includes late function declarations with side-effects',
options: {
moduleName: 'myBundle'
}
};

11
test/form/side-effect-j/_expected/amd.js

@ -0,0 +1,11 @@
define(function () { 'use strict';
var augment;
augment = x => x.augmented = true;
function x () {}
augment( x );
return x;
});

9
test/form/side-effect-j/_expected/cjs.js

@ -0,0 +1,9 @@
'use strict';
var augment;
augment = x => x.augmented = true;
function x () {}
augment( x );
module.exports = x;

7
test/form/side-effect-j/_expected/es6.js

@ -0,0 +1,7 @@
var augment;
augment = x => x.augmented = true;
function x () {}
augment( x );
export default x;

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

Loading…
Cancel
Save