Browse Source

find importer of badly-ordered modules in order to provide useful feedback

gh-438-b
Rich-Harris 9 years ago
parent
commit
995737eb6d
  1. 61
      src/Bundle.js
  2. 32
      src/Module.js

61
src/Bundle.js

@ -169,7 +169,7 @@ export default class Bundle {
} }
fetchAllDependencies ( module ) { fetchAllDependencies ( module ) {
const promises = module.dependencies.map( source => { const promises = module.sources.map( source => {
return this.resolveId( source, module.id ) return this.resolveId( source, module.id )
.then( resolvedId => { .then( resolvedId => {
// If the `resolvedId` is supposed to be external, make it so. // If the `resolvedId` is supposed to be external, make it so.
@ -276,60 +276,83 @@ export default class Bundle {
} }
sort () { sort () {
const moduleById = this.moduleById;
let seen = {}; let seen = {};
let hasCycles;
let ordered = []; let ordered = [];
let stronglyDependsOn = blank(); let stronglyDependsOn = blank();
let dependsOn = blank();
this.modules.forEach( module => { this.modules.forEach( module => {
stronglyDependsOn[ module.id ] = blank(); stronglyDependsOn[ module.id ] = blank();
dependsOn[ module.id ] = blank();
}); });
this.modules.forEach( module => { this.modules.forEach( module => {
function processDependency ( dependency ) { function processStrongDependency ( dependency ) {
if ( dependency === module || stronglyDependsOn[ module.id ][ dependency.id ] ) return; if ( dependency === module || stronglyDependsOn[ module.id ][ dependency.id ] ) return;
stronglyDependsOn[ module.id ][ dependency.id ] = true; stronglyDependsOn[ module.id ][ dependency.id ] = true;
dependency.strongDependencies.forEach( processDependency ); dependency.strongDependencies.forEach( processStrongDependency );
} }
module.strongDependencies.forEach( processDependency ); function processDependency ( dependency ) {
if ( dependency === module || dependsOn[ module.id ][ dependency.id ] ) return;
dependsOn[ module.id ][ dependency.id ] = true;
dependency.dependencies.forEach( processDependency );
}
module.strongDependencies.forEach( processStrongDependency );
module.dependencies.forEach( processDependency );
}); });
const visit = module => { const visit = module => {
if ( seen[ module.id ] ) return; if ( seen[ module.id ] ) {
seen[ module.id ] = true; hasCycles = true;
return;
}
const dependencies = module.dependencies seen[ module.id ] = true;
.map( source => {
const resolved = module.resolvedIds[ source ];
return moduleById[ resolved ];
})
.filter( dependency => !dependency.isExternal );
dependencies.forEach( visit ); module.dependencies.forEach( visit );
ordered.push( module ); ordered.push( module );
}; };
visit( this.entryModule ); visit( this.entryModule );
if ( hasCycles ) {
ordered.forEach( ( a, i ) => { ordered.forEach( ( a, i ) => {
const b = ordered[ i + 1 ]; for ( i += 1; i < ordered.length; i += 1 ) {
if ( !b ) return; const b = ordered[i];
if ( stronglyDependsOn[ a.id ][ b.id ] ) { if ( stronglyDependsOn[ a.id ][ b.id ] ) {
// somewhere, there is a module that imports b before a. Because // somewhere, there is a module that imports b before a. Because
// b imports a, a is placed before b. We need to find the module // b imports a, a is placed before b. We need to find the module
// in question, so we can provide a useful error message // in question, so we can provide a useful error message
const parent = { id: 'TODO' }; let parent = '[[unknown]]';
const findParent = module => {
if ( dependsOn[ module.id ][ a.id ] && dependsOn[ module.id ][ b.id ] ) {
parent = module.id;
} else {
for ( let i = 0; i < module.dependencies.length; i += 1 ) {
const dependency = module.dependencies[i];
if ( findParent( dependency ) ) return;
}
}
};
findParent( this.entryModule );
throw new Error( throw new Error(
`Module ${a.id} may be unable to evaluate without ${b.id}, but is included first due to a cyclical dependency. Consider swapping the import statements in ${parent.id} to ensure correct ordering` `Module ${a.id} may be unable to evaluate without ${b.id}, but is included first due to a cyclical dependency. Consider swapping the import statements in ${parent} to ensure correct ordering`
); );
} }
}
}); });
}
return ordered; return ordered;
} }

32
src/Module.js

@ -22,6 +22,7 @@ export default class Module {
this.id = id; this.id = id;
// all dependencies // all dependencies
this.sources = [];
this.dependencies = []; this.dependencies = [];
this.resolvedIds = blank(); this.resolvedIds = blank();
@ -62,7 +63,7 @@ export default class Module {
// export { name } from './other.js' // export { name } from './other.js'
if ( source ) { if ( source ) {
if ( !~this.dependencies.indexOf( source ) ) this.dependencies.push( source ); if ( !~this.sources.indexOf( source ) ) this.sources.push( source );
if ( node.type === 'ExportAllDeclaration' ) { if ( node.type === 'ExportAllDeclaration' ) {
// Store `export * from '...'` statements in an array of delegates. // Store `export * from '...'` statements in an array of delegates.
@ -136,7 +137,7 @@ export default class Module {
const node = statement.node; const node = statement.node;
const source = node.source.value; const source = node.source.value;
if ( !~this.dependencies.indexOf( source ) ) this.dependencies.push( source ); if ( !~this.sources.indexOf( source ) ) this.sources.push( source );
node.specifiers.forEach( specifier => { node.specifiers.forEach( specifier => {
const localName = specifier.local.name; const localName = specifier.local.name;
@ -212,6 +213,13 @@ export default class Module {
const id = this.resolvedIds[ source ]; const id = this.resolvedIds[ source ];
return this.bundle.moduleById[ id ]; return this.bundle.moduleById[ id ];
}); });
this.sources.forEach( source => {
const id = this.resolvedIds[ source ];
const module = this.bundle.moduleById[ id ];
if ( !module.isExternal ) this.dependencies.push( module );
});
} }
bindReferences () { bindReferences () {
@ -243,26 +251,6 @@ export default class Module {
}); });
} }
consolidateDependencies () {
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.indexOf( dependency ) ) {
weakDependencies.push( dependency );
}
});
strongDependencies = this.strongDependencies
.filter( module => module !== this );
return { strongDependencies, weakDependencies };
}
getExports () { getExports () {
let exports = blank(); let exports = blank();

Loading…
Cancel
Save