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

311
src/Module.js

@ -37,194 +37,143 @@ export default class Module {
this.suggestedNames = blank(); this.suggestedNames = blank();
this.comments = []; this.comments = [];
// Try to extract a list of top-level statements/declarations. If this.statements = this._parse();
// the parse fails, attach file info and abort
let ast;
try { // imports and exports, indexed by ID
ast = parse( source, { this.imports = blank();
ecmaVersion: 6, this.exports = blank();
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;
}
walk( ast, { this.canonicalNames = blank();
enter: node => {
this.magicString.addSourcemapLocation( node.start );
this.magicString.addSourcemapLocation( node.end );
}
});
this.statements = []; this.definitions = blank();
this.definitionPromises = blank();
this.modifications = blank();
ast.body.map( node => { this.analyse();
// 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 = { addExport ( statement ) {
type: 'VariableDeclaration', const node = statement.node;
kind: node.kind, const source = node.source && node.source.value;
start: node.start,
end: node.end, // export default function foo () {}
declarations: [ declarator ] // 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 ); // export { foo } from './foo';
this.statements.push( statement ); if ( source ) {
this.imports[ localName ] = {
source,
localName,
name: localName
};
}
}); });
} }
else { else {
const magicString = this.magicString.snip( node.start, node.end ).trim(); let declaration = node.declaration;
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;
node.specifiers.forEach( specifier => { let name;
const isDefault = specifier.type === 'ImportDefaultSpecifier';
const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
const localName = specifier.local.name; if ( declaration.type === 'VariableDeclaration' ) {
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name; // export var foo = 42
name = declaration.declarations[0].id.name;
if ( this.imports[ localName ] ) { } else {
const err = new Error( `Duplicated import '${localName}'` ); // export function foo () {}
err.file = this.id; name = declaration.id.name;
err.loc = getLocation( this.source, specifier.start );
throw err;
} }
this.imports[ localName ] = { this.exports[ name ] = {
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 = {
statement, statement,
name: 'default', localName: name,
localName: declaredName || 'default', expression: declaration
declaredName,
identifier,
isDeclaration,
isAnonymous,
isModified: false // in case of `export default foo; foo = somethingElse`
}; };
} }
}
}
// export { foo, bar, baz } addImport ( statement ) {
// export var foo = 42; const node = statement.node;
// export function foo () {} const source = node.source.value;
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
};
// export { foo } from './foo'; node.specifiers.forEach( specifier => {
if ( source ) { const isDefault = specifier.type === 'ImportDefaultSpecifier';
this.imports[ localName ] = { const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
source,
localName,
name: localName
};
}
});
}
else { const localName = specifier.local.name;
let declaration = node.declaration; 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' ) { this.imports[ localName ] = {
// export var foo = 42 source,
name = declaration.declarations[0].id.name; name,
} else { localName
// export function foo () {} };
name = declaration.id.name; });
} }
this.exports[ name ] = { analyse () {
statement, // discover this module's imports and exports
localName: name, this.statements.forEach( statement => {
expression: declaration if ( isImportDeclaration( statement ) ) this.addImport( statement );
}; else if ( isExportDeclaration( statement ) ) this.addExport( statement );
}
}
}); });
analyse( this.magicString, this ); analyse( this.magicString, this );
this.canonicalNames = blank(); // consolidate names that are defined/modified in this module
this.definitions = blank();
this.definitionPromises = blank();
this.modifications = blank();
this.statements.forEach( statement => { this.statements.forEach( statement => {
keys( statement.defines ).forEach( name => { keys( statement.defines ).forEach( name => {
this.definitions[ name ] = statement; this.definitions[ name ] = statement;
}); });
keys( statement.modifies ).forEach( name => { 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 => { this.statements.forEach( statement => {
keys( statement.dependsOn ).forEach( name => { keys( statement.dependsOn ).forEach( name => {
if ( !this.definitions[ name ] && !this.imports[ name ] ) { if ( !this.definitions[ name ] && !this.imports[ name ] ) {
@ -507,6 +456,66 @@ export default class Module {
return this.canonicalNames[ localName ]; 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 ) { rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement; this.canonicalNames[ name ] = replacement;
} }

Loading…
Cancel
Save