diff --git a/src/Bundle.js b/src/Bundle.js index 1f6413b..6f87104 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -40,7 +40,9 @@ export default class Bundle { this.internalNamespaceModules = []; this.assumedGlobals = blank(); - this.assumedGlobals.exports = true; // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles + + // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles + [ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true ); } build () { @@ -50,9 +52,11 @@ export default class Bundle { this.modules.forEach( module => { module.bindImportSpecifiers(); module.bindReferences(); - module.markAllSideEffects(); }); + this.modules.forEach( module => { + module.markAllSideEffects(); + }); const defaultExport = entryModule.exports.default; diff --git a/src/Module.js b/src/Module.js index d42d32b..9cb012a 100644 --- a/src/Module.js +++ b/src/Module.js @@ -2,7 +2,6 @@ import { parse } from 'acorn'; import MagicString from 'magic-string'; import Statement from './Statement'; import walk from './ast/walk'; -import Scope from './ast/Scope'; import { blank, keys } from './utils/object'; import { basename, extname } from './utils/path'; import getLocation from './utils/getLocation'; @@ -45,8 +44,7 @@ export default class Module { this.exportAlls = []; - this.scope = new Scope(); - this.definitions = blank(); + this.declarations = blank(); this.analyse(); } @@ -178,9 +176,8 @@ export default class Module { statement.analyse(); - keys( statement.scope.declarations ).forEach( name => { - const declaration = statement.scope.declarations[ name ]; - this.definitions[ name ] = { statement, declaration }; + statement.scope.eachDeclaration( ( name, declaration ) => { + this.declarations[ name ] = declaration; }); }); } @@ -208,38 +205,20 @@ export default class Module { } bindReferences () { - this.statements.forEach( ( statement, i ) => { + this.statements.forEach( statement => { statement.references.forEach( reference => { let declaration; // find in local scope... - declaration = reference.scope.findDeclaration( reference.name ); + declaration = reference.scope.findDeclaration( reference.name ) || + this.trace( reference.name ); if ( declaration ) { reference.declaration = declaration; - reference.definingStatement = statement; - return; - } - - let definition; - - // ...or in module... - definition = this.definitions[ reference.name ]; - - // ...or from import - if ( !definition ) { - const importDeclaration = this.imports[ reference.name ]; - if ( importDeclaration ) { - definition = importDeclaration.module.traceExport( importDeclaration.name ); - } - } - - if ( definition ) { - reference.declaration = definition.declaration; - reference.definingStatement = definition.statement; - definition.declaration.references.push( reference ); + declaration.references.push( reference ); } else { - //console.log( 'TODO no declaration. global?' ); + // TODO handle globals + this.bundle.assumedGlobals[ reference.name ] = true; } }); }); @@ -251,8 +230,8 @@ export default class Module { this.statements.forEach( statement => { statement.references.forEach( reference => { - if ( reference.definingStatement ) { - const module = reference.definingStatement.module; + if ( reference.declaration && reference.declaration.statement ) { + const module = reference.declaration.statement.module; weakDependencies[ module.id ] = module; } }); @@ -273,17 +252,6 @@ export default class Module { return this.replacements[ name ] || name; } - findDefiningStatement ( name ) { - if ( this.definitions[ name ] ) return this.definitions[ name ]; - - // TODO what about `default`/`*`? - - const importDeclaration = this.imports[ name ]; - if ( !importDeclaration ) return null; - - return importDeclaration.module.findDefiningStatement( name ); - } - getExports () { let exports = blank(); @@ -547,16 +515,41 @@ export default class Module { if ( !statement.isIncluded ) { magicString.remove( statement.start, statement.next ); } + + // modify exports as necessary + if ( statement.isExportDeclaration ) { + // remove `export` from `export class Foo {...}` or `export default Foo` + // TODO default exports need different treatment + if ( statement.node.declaration.id ) { + magicString.remove( statement.node.start, statement.node.declaration.start ); + } + + // else if ( statement.node.type === 'ExportDefaultDeclaration' ) { + // const defaultExport = this.exports.default; + // + // // anonymous functions should be converted into declarations + // if ( statement.node.declaration.type === 'FunctionExpression' ) { + // magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${defaultExport.name}` ); + // } else { + // magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${defaultExport.name} = ` ); + // } + // } + + else { + throw new Error( 'Unhandled export' ); + } + } }); return magicString.trim(); } trace ( name ) { - if ( name in this.definitions ) return this.definitions[ name ]; + if ( name in this.declarations ) return this.declarations[ name ]; if ( name in this.imports ) { - const otherModule = this.imports[ name ].module; - return otherModule.traceExport( name ); + const importDeclaration = this.imports[ name ]; + const otherModule = importDeclaration.module; + return otherModule.traceExport( importDeclaration.name ); } return null; @@ -584,6 +577,6 @@ export default class Module { } // TODO export * - throw new Error( 'could not trace export', name ); + return null; } } diff --git a/src/Statement.js b/src/Statement.js index 6cd00bd..3c95ad6 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -75,6 +75,12 @@ export default class Statement { // attach scopes attachScopes( this ); + // attach statement to each top-level declaration, + // so we can mark statements easily + this.scope.eachDeclaration( ( name, declaration ) => { + declaration.statement = this; + }); + let references = this.references; // find references @@ -102,8 +108,8 @@ export default class Statement { this.isIncluded = true; this.references.forEach( reference => { - if ( reference.definingStatement ) { - reference.definingStatement.mark(); + if ( reference.declaration && reference.declaration.statement ) { + reference.declaration.statement.mark(); } }); } diff --git a/src/ast/Scope.js b/src/ast/Scope.js index b6306b5..f64133b 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -1,4 +1,4 @@ -import { blank } from '../utils/object'; +import { blank, keys } from '../utils/object'; const extractors = { Identifier ( names, param ) { @@ -34,8 +34,9 @@ function extractNames ( param ) { } class Declaration { - constructor ( node ) { + constructor () { this.references = []; + this.statement = null; } } @@ -51,7 +52,7 @@ export default class Scope { if ( options.params ) { options.params.forEach( param => { extractNames( param ).forEach( name => { - this.declarations[ name ] = new Declaration( param ); + this.declarations[ name ] = new Declaration(); }); }); } @@ -64,7 +65,7 @@ export default class Scope { this.parent.addDeclaration( node, isBlockDeclaration, isVar ); } else { extractNames( node.id ).forEach( name => { - this.declarations[ name ] = new Declaration( node ); + this.declarations[ name ] = new Declaration(); }); } } @@ -74,6 +75,12 @@ export default class Scope { ( 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 ) ); diff --git a/test/form/self-contained-bundle/_config.js b/test/form/self-contained-bundle/_config.js index 81ba2de..29f06f0 100644 --- a/test/form/self-contained-bundle/_config.js +++ b/test/form/self-contained-bundle/_config.js @@ -1,5 +1,3 @@ module.exports = { - solo: true, - show: true, description: 'self-contained bundle' }; diff --git a/test/test.js b/test/test.js index f4e049a..b15456c 100644 --- a/test/test.js +++ b/test/test.js @@ -15,11 +15,11 @@ var SOURCEMAPS = path.resolve( __dirname, 'sourcemaps' ); var CLI = path.resolve( __dirname, 'cli' ); var PROFILES = [ - // { format: 'amd' }, + { format: 'amd' }, { format: 'cjs' }, - // { format: 'es6' }, - // { format: 'iife' }, - // { format: 'umd' } + { format: 'es6' }, + { format: 'iife' }, + { format: 'umd' } ]; function extend ( target ) { @@ -228,7 +228,7 @@ describe( 'rollup', function () { expectedMap.sourcesContent = expectedMap.sourcesContent.map( normaliseOutput ); } catch ( err ) {} - if ( config.show || unintendedError ) { + if ( config.show ) { console.log( actualCode + '\n\n\n' ); }