Browse Source

refactoring

contingency-plan
Rich Harris 9 years ago
parent
commit
eadbfcbdcd
  1. 11
      src/Bundle.js
  2. 311
      src/Module.js

11
src/Bundle.js

@ -45,14 +45,13 @@ export default class Bundle {
this.modulePromises = blank();
this.modules = [];
this.statements = [];
this.statements = null;
this.externalModules = [];
this.internalNamespaceModules = [];
this.assumedGlobals = blank();
}
build () {
// bring in top-level AST nodes from the entry module
return this.fetchModule( this.entry, undefined )
.then( entryModule => {
const defaultExport = entryModule.exports.default;
@ -87,11 +86,9 @@ export default class Bundle {
return entryModule.expandAllStatements( true );
})
.then( statements => {
this.statements = statements;
.then( () => {
this.statements = this.sort();
this.deconflict();
this.orderedStatements = this.sort();
});
}
@ -260,7 +257,7 @@ export default class Bundle {
let previousIndex = -1;
let previousMargin = 0;
this.orderedStatements.forEach( statement => {
this.statements.forEach( statement => {
// skip `export { foo, bar, baz }`
if ( statement.node.type === 'ExportNamedDeclaration' ) {
// skip `export { foo, bar, baz }`

311
src/Module.js

@ -37,194 +37,143 @@ export default class Module {
this.suggestedNames = blank();
this.comments = [];
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
let ast;
this.statements = this._parse();
try {
ast = parse( source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = id; // see above - not necessarily true, but true enough
throw err;
}
// imports and exports, indexed by ID
this.imports = blank();
this.exports = blank();
walk( ast, {
enter: node => {
this.magicString.addSourcemapLocation( node.start );
this.magicString.addSourcemapLocation( node.end );
}
});
this.canonicalNames = blank();
this.statements = [];
this.definitions = blank();
this.definitionPromises = blank();
this.modifications = blank();
ast.body.map( node => {
// special case - top-level var declarations with multiple declarators
// should be split up. Otherwise, we may end up including code we
// don't need, just because an unwanted declarator is included
if ( node.type === 'VariableDeclaration' && node.declarations.length > 1 ) {
node.declarations.forEach( declarator => {
const magicString = this.magicString.snip( declarator.start, declarator.end ).trim();
magicString.prepend( `${node.kind} ` ).append( ';' );
this.analyse();
}
const syntheticNode = {
type: 'VariableDeclaration',
kind: node.kind,
start: node.start,
end: node.end,
declarations: [ declarator ]
addExport ( statement ) {
const node = statement.node;
const source = node.source && node.source.value;
// export default function foo () {}
// export default foo;
// export default 42;
if ( node.type === 'ExportDefaultDeclaration' ) {
const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type );
const declaredName = isDeclaration && node.declaration.id.name;
const identifier = node.declaration.type === 'Identifier' && node.declaration.name;
this.exports.default = {
statement,
name: 'default',
localName: declaredName || 'default',
declaredName,
identifier,
isDeclaration,
isAnonymous,
isModified: false // in case of `export default foo; foo = somethingElse`
};
}
// export { foo, bar, baz }
// export var foo = 42;
// export function foo () {}
else if ( node.type === 'ExportNamedDeclaration' ) {
if ( node.specifiers.length ) {
// export { foo, bar, baz }
node.specifiers.forEach( specifier => {
const localName = specifier.local.name;
const exportedName = specifier.exported.name;
this.exports[ exportedName ] = {
localName,
exportedName
};
const statement = new Statement( syntheticNode, magicString, this, this.statements.length );
this.statements.push( statement );
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName,
name: localName
};
}
});
}
else {
const magicString = this.magicString.snip( node.start, node.end ).trim();
const statement = new Statement( node, magicString, this, this.statements.length );
this.statements.push( statement );
}
});
this.importDeclarations = this.statements.filter( isImportDeclaration );
this.exportDeclarations = this.statements.filter( isExportDeclaration );
this.analyse();
}
analyse () {
// imports and exports, indexed by ID
this.imports = blank();
this.exports = blank();
this.importDeclarations.forEach( statement => {
const node = statement.node;
const source = node.source.value;
let declaration = node.declaration;
node.specifiers.forEach( specifier => {
const isDefault = specifier.type === 'ImportDefaultSpecifier';
const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
let name;
const localName = specifier.local.name;
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
if ( this.imports[ localName ] ) {
const err = new Error( `Duplicated import '${localName}'` );
err.file = this.id;
err.loc = getLocation( this.source, specifier.start );
throw err;
if ( declaration.type === 'VariableDeclaration' ) {
// export var foo = 42
name = declaration.declarations[0].id.name;
} else {
// export function foo () {}
name = declaration.id.name;
}
this.imports[ localName ] = {
source,
name,
localName
};
});
});
this.exportDeclarations.forEach( statement => {
const node = statement.node;
const source = node.source && node.source.value;
// export default function foo () {}
// export default foo;
// export default 42;
if ( node.type === 'ExportDefaultDeclaration' ) {
const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type );
const declaredName = isDeclaration && node.declaration.id.name;
const identifier = node.declaration.type === 'Identifier' && node.declaration.name;
this.exports.default = {
this.exports[ name ] = {
statement,
name: 'default',
localName: declaredName || 'default',
declaredName,
identifier,
isDeclaration,
isAnonymous,
isModified: false // in case of `export default foo; foo = somethingElse`
localName: name,
expression: declaration
};
}
}
}
// export { foo, bar, baz }
// export var foo = 42;
// export function foo () {}
else if ( node.type === 'ExportNamedDeclaration' ) {
if ( node.specifiers.length ) {
// export { foo, bar, baz }
node.specifiers.forEach( specifier => {
const localName = specifier.local.name;
const exportedName = specifier.exported.name;
this.exports[ exportedName ] = {
localName,
exportedName
};
addImport ( statement ) {
const node = statement.node;
const source = node.source.value;
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName,
name: localName
};
}
});
}
node.specifiers.forEach( specifier => {
const isDefault = specifier.type === 'ImportDefaultSpecifier';
const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
else {
let declaration = node.declaration;
const localName = specifier.local.name;
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
let name;
if ( this.imports[ localName ] ) {
const err = new Error( `Duplicated import '${localName}'` );
err.file = this.id;
err.loc = getLocation( this.source, specifier.start );
throw err;
}
if ( declaration.type === 'VariableDeclaration' ) {
// export var foo = 42
name = declaration.declarations[0].id.name;
} else {
// export function foo () {}
name = declaration.id.name;
}
this.imports[ localName ] = {
source,
name,
localName
};
});
}
this.exports[ name ] = {
statement,
localName: name,
expression: declaration
};
}
}
analyse () {
// discover this module's imports and exports
this.statements.forEach( statement => {
if ( isImportDeclaration( statement ) ) this.addImport( statement );
else if ( isExportDeclaration( statement ) ) this.addExport( statement );
});
analyse( this.magicString, this );
this.canonicalNames = blank();
this.definitions = blank();
this.definitionPromises = blank();
this.modifications = blank();
// consolidate names that are defined/modified in this module
this.statements.forEach( statement => {
keys( statement.defines ).forEach( name => {
this.definitions[ name ] = statement;
});
keys( statement.modifies ).forEach( name => {
if ( !this.modifications[ name ] ) {
this.modifications[ name ] = [];
}
this.modifications[ name ].push( statement );
( this.modifications[ name ] || ( this.modifications[ name ] = [] ) ).push( statement );
});
});
// if names are referenced that are neither defined nor imported
// in this module, we assume that they're globals
this.statements.forEach( statement => {
keys( statement.dependsOn ).forEach( name => {
if ( !this.definitions[ name ] && !this.imports[ name ] ) {
@ -507,6 +456,66 @@ export default class Module {
return this.canonicalNames[ localName ];
}
// TODO rename this to parse, once https://github.com/rollup/rollup/issues/42 is fixed
_parse () {
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
let ast;
try {
ast = parse( this.source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
throw err;
}
walk( ast, {
enter: node => {
this.magicString.addSourcemapLocation( node.start );
this.magicString.addSourcemapLocation( node.end );
}
});
let statements = [];
ast.body.map( node => {
// special case - top-level var declarations with multiple declarators
// should be split up. Otherwise, we may end up including code we
// don't need, just because an unwanted declarator is included
if ( node.type === 'VariableDeclaration' && node.declarations.length > 1 ) {
node.declarations.forEach( declarator => {
const magicString = this.magicString.snip( declarator.start, declarator.end ).trim();
magicString.prepend( `${node.kind} ` ).append( ';' );
const syntheticNode = {
type: 'VariableDeclaration',
kind: node.kind,
start: node.start,
end: node.end,
declarations: [ declarator ]
};
const statement = new Statement( syntheticNode, magicString, this, statements.length );
statements.push( statement );
});
}
else {
const magicString = this.magicString.snip( node.start, node.end ).trim();
const statement = new Statement( node, magicString, this, statements.length );
statements.push( statement );
}
});
return statements;
}
rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement;
}

Loading…
Cancel
Save