|
|
@ -1,14 +1,17 @@ |
|
|
|
import { dirname, relative, resolve } from 'path'; |
|
|
|
import { Promise } from 'sander'; |
|
|
|
import { parse } from 'acorn'; |
|
|
|
import MagicString from 'magic-string'; |
|
|
|
import analyse from '../ast/analyse'; |
|
|
|
import { hasOwnProp } from '../utils/object'; |
|
|
|
import { sequence } from '../utils/promise'; |
|
|
|
|
|
|
|
const emptyArrayPromise = Promise.resolve([]); |
|
|
|
|
|
|
|
export default class Module { |
|
|
|
constructor ({ path, code, bundle }) { |
|
|
|
this.path = path; |
|
|
|
this.relativePath = relative( bundle.base, path ); |
|
|
|
this.relativePath = relative( bundle.base, path ).slice( 0, -3 ); // remove .js
|
|
|
|
this.code = new MagicString( code ); |
|
|
|
this.bundle = bundle; |
|
|
|
|
|
|
@ -17,9 +20,12 @@ export default class Module { |
|
|
|
sourceType: 'module' |
|
|
|
}); |
|
|
|
|
|
|
|
console.log( '\nanalysing %s\n========', path ); |
|
|
|
analyse( this.ast, this.code ); |
|
|
|
console.log( '========\n\n' ); |
|
|
|
|
|
|
|
this.definitions = {}; |
|
|
|
this.definitionPromises = {}; |
|
|
|
this.modifications = {}; |
|
|
|
|
|
|
|
this.ast.body.forEach( statement => { |
|
|
@ -52,20 +58,14 @@ export default class Module { |
|
|
|
} |
|
|
|
|
|
|
|
else if ( node.type === 'ExportDefaultDeclaration' ) { |
|
|
|
//const isDeclaration = /Declaration$/.test( node)
|
|
|
|
const isDeclaration = /Declaration$/.test( node.declaration.type ); |
|
|
|
|
|
|
|
this.exports.default = { |
|
|
|
node, |
|
|
|
localName: 'default', |
|
|
|
isDeclaration: false, |
|
|
|
//expression: node.declaration
|
|
|
|
name: isDeclaration ? node.declaration.id.name : null, |
|
|
|
isDeclaration |
|
|
|
}; |
|
|
|
|
|
|
|
// special case - need to transfer top-level node tracking info to expression
|
|
|
|
// TODO this is fugly, refactor it
|
|
|
|
// if ( this.exports.default.expression ) {
|
|
|
|
// this.exports.default.expression._dependsOn = node._dependsOn;
|
|
|
|
// this.exports.default.expression._source = node._source;
|
|
|
|
// }
|
|
|
|
} |
|
|
|
|
|
|
|
else if ( node.type === 'ExportNamedDeclaration' ) { |
|
|
@ -103,15 +103,21 @@ export default class Module { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
define ( name, importer ) { |
|
|
|
let statement; |
|
|
|
define ( name ) { |
|
|
|
// shortcut cycles. TODO this won't work everywhere...
|
|
|
|
if ( hasOwnProp.call( this.definitionPromises, name ) ) { |
|
|
|
return emptyArrayPromise; |
|
|
|
} |
|
|
|
|
|
|
|
if ( !hasOwnProp.call( this.definitionPromises, name ) ) { |
|
|
|
let promise; |
|
|
|
|
|
|
|
// The definition for this name is in a different module
|
|
|
|
if ( hasOwnProp.call( this.imports, name ) ) { |
|
|
|
const importDeclaration = this.imports[ name ]; |
|
|
|
const path = resolve( dirname( this.path ), importDeclaration.source ) + '.js'; |
|
|
|
|
|
|
|
return this.bundle.fetchModule( path ) |
|
|
|
promise = this.bundle.fetchModule( path ) |
|
|
|
.then( module => { |
|
|
|
const exportDeclaration = module.exports[ importDeclaration.name ]; |
|
|
|
|
|
|
@ -124,55 +130,62 @@ export default class Module { |
|
|
|
// with something slightly different
|
|
|
|
this.bundle.suggestName( module, exportDeclaration.localName, importDeclaration.localName ); |
|
|
|
|
|
|
|
|
|
|
|
return module.define( exportDeclaration.localName, this ); |
|
|
|
return module.define( exportDeclaration.localName ); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// The definition is in this module; it's the default export
|
|
|
|
else if ( name === 'default' ) { |
|
|
|
const defaultExport = this.exports.default; |
|
|
|
|
|
|
|
// The definition is in this module
|
|
|
|
else if ( name === 'default' && this.exports.default.isDeclaration ) { |
|
|
|
// We have something like `export default foo` - so we just start again,
|
|
|
|
// searching for `foo` instead of default
|
|
|
|
if ( defaultExport.isDeclaration ) { |
|
|
|
return this.define( defaultExport.name, this ); |
|
|
|
promise = this.define( this.exports.default.name ); |
|
|
|
} |
|
|
|
|
|
|
|
// Otherwise, we have an expression, e.g. `export default 42`. We have
|
|
|
|
else { |
|
|
|
let statement; |
|
|
|
|
|
|
|
if ( name === 'default' ) { |
|
|
|
// We have an expression, e.g. `export default 42`. We have
|
|
|
|
// to assign that expression to a variable
|
|
|
|
const name = this.bundle.getName( this, 'default' ); |
|
|
|
|
|
|
|
statement = defaultExport.node; |
|
|
|
statement = this.exports.default.node; |
|
|
|
|
|
|
|
if ( !statement._imported ) { |
|
|
|
statement._source.overwrite( statement.start, statement.declaration.start, `var ${name} = ` ) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
statement = this.definitions[ name ]; |
|
|
|
|
|
|
|
if ( /^Export/.test( statement.type ) ) { |
|
|
|
if ( statement && /^Export/.test( statement.type ) ) { |
|
|
|
statement._source.remove( statement.start, statement.declaration.start ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ( statement ) { |
|
|
|
if ( statement && !statement._imported ) { |
|
|
|
const nodes = []; |
|
|
|
|
|
|
|
return sequence( Object.keys( statement._dependsOn ), name => { |
|
|
|
return this.define( name, this ); |
|
|
|
promise = sequence( Object.keys( statement._dependsOn ), name => { |
|
|
|
return this.define( name ); |
|
|
|
}) |
|
|
|
.then( definitions => { |
|
|
|
definitions.forEach( definition => nodes.push.apply( nodes, definition ) ); |
|
|
|
}) |
|
|
|
.then( () => { |
|
|
|
statement._imported = true; |
|
|
|
nodes.push( statement ); |
|
|
|
}) |
|
|
|
.then( () => { |
|
|
|
return nodes; |
|
|
|
}); |
|
|
|
} else { |
|
|
|
throw new Error( `Could not define ${name}` ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.definitionPromises[ name ] = promise || emptyArrayPromise; |
|
|
|
} |
|
|
|
|
|
|
|
return this.definitionPromises[ name ]; |
|
|
|
} |
|
|
|
} |