Browse Source

some basic functionality

declarations-and-references
Rich-Harris 9 years ago
parent
commit
24f4af72d9
  1. 8
      src/Bundle.js
  2. 87
      src/Module.js
  3. 10
      src/Statement.js
  4. 15
      src/ast/Scope.js
  5. 2
      test/form/self-contained-bundle/_config.js
  6. 10
      test/test.js

8
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;

87
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;
}
}

10
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();
}
});
}

15
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 ) );

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

@ -1,5 +1,3 @@
module.exports = {
solo: true,
show: true,
description: 'self-contained bundle'
};

10
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' );
}

Loading…
Cancel
Save