mirror of https://github.com/lukechilds/rollup.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
706 lines
22 KiB
706 lines
22 KiB
import { parse } from 'acorn/src/index.js';
|
|
import MagicString from 'magic-string';
|
|
import { walk } from 'estree-walker';
|
|
import Statement from './Statement.js';
|
|
import { assign, blank, keys } from './utils/object.js';
|
|
import { basename, extname } from './utils/path.js';
|
|
import getLocation from './utils/getLocation.js';
|
|
import makeLegalIdentifier from './utils/makeLegalIdentifier.js';
|
|
import SOURCEMAPPING_URL from './utils/sourceMappingURL.js';
|
|
import {
|
|
SyntheticDefaultDeclaration,
|
|
SyntheticGlobalDeclaration,
|
|
SyntheticNamespaceDeclaration
|
|
} from './Declaration.js';
|
|
import { isFalsy, isTruthy } from './ast/conditions.js';
|
|
import { emptyBlockStatement } from './ast/create.js';
|
|
import extractNames from './ast/extractNames.js';
|
|
|
|
export default class Module {
|
|
constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, bundle }) {
|
|
this.code = code;
|
|
this.originalCode = originalCode;
|
|
this.originalSourceMap = originalSourceMap;
|
|
this.sourceMapChain = sourceMapChain;
|
|
|
|
this.bundle = bundle;
|
|
this.id = id;
|
|
|
|
// all dependencies
|
|
this.sources = [];
|
|
this.dependencies = [];
|
|
this.resolvedIds = blank();
|
|
|
|
// imports and exports, indexed by local name
|
|
this.imports = blank();
|
|
this.exports = blank();
|
|
this.reexports = blank();
|
|
|
|
this.exportAllSources = [];
|
|
this.exportAllModules = null;
|
|
|
|
// By default, `id` is the filename. Custom resolvers and loaders
|
|
// can change that, but it makes sense to use it for the source filename
|
|
this.magicString = new MagicString( code, {
|
|
filename: id,
|
|
indentExclusionRanges: []
|
|
});
|
|
|
|
// remove existing sourceMappingURL comments
|
|
const pattern = new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' );
|
|
let match;
|
|
while ( match = pattern.exec( code ) ) {
|
|
this.magicString.remove( match.index, match.index + match[0].length );
|
|
}
|
|
|
|
this.comments = [];
|
|
this.ast = ast;
|
|
this.statements = this.parse();
|
|
|
|
this.declarations = blank();
|
|
this.analyse();
|
|
|
|
this.strongDependencies = [];
|
|
}
|
|
|
|
addExport ( statement ) {
|
|
const node = statement.node;
|
|
const source = node.source && node.source.value;
|
|
|
|
// export { name } from './other.js'
|
|
if ( source ) {
|
|
if ( !~this.sources.indexOf( source ) ) this.sources.push( source );
|
|
|
|
if ( node.type === 'ExportAllDeclaration' ) {
|
|
// Store `export * from '...'` statements in an array of delegates.
|
|
// When an unknown import is encountered, we see if one of them can satisfy it.
|
|
this.exportAllSources.push( source );
|
|
}
|
|
|
|
else {
|
|
node.specifiers.forEach( specifier => {
|
|
const name = specifier.exported.name;
|
|
|
|
if ( this.exports[ name ] || this.reexports[ name ] ) {
|
|
throw new Error( `A module cannot have multiple exports with the same name ('${name}')` );
|
|
}
|
|
|
|
this.reexports[ name ] = {
|
|
start: specifier.start,
|
|
source,
|
|
localName: specifier.local.name,
|
|
module: null // filled in later
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
// export default function foo () {}
|
|
// export default foo;
|
|
// export default 42;
|
|
else if ( node.type === 'ExportDefaultDeclaration' ) {
|
|
const identifier = ( node.declaration.id && node.declaration.id.name ) || node.declaration.name;
|
|
|
|
if ( this.exports.default ) {
|
|
// TODO indicate location
|
|
throw new Error( 'A module can only have one default export' );
|
|
}
|
|
|
|
this.exports.default = {
|
|
localName: 'default',
|
|
identifier
|
|
};
|
|
|
|
// create a synthetic declaration
|
|
this.declarations.default = new SyntheticDefaultDeclaration( node, statement, identifier || this.basename() );
|
|
}
|
|
|
|
// export var { foo, bar } = ...
|
|
// export var foo = 42;
|
|
// export var a = 1, b = 2, c = 3;
|
|
// export function foo () {}
|
|
else if ( node.declaration ) {
|
|
let declaration = node.declaration;
|
|
|
|
if ( declaration.type === 'VariableDeclaration' ) {
|
|
declaration.declarations.forEach( decl => {
|
|
extractNames( decl.id ).forEach( localName => {
|
|
this.exports[ localName ] = { localName };
|
|
});
|
|
});
|
|
} else {
|
|
// export function foo () {}
|
|
const localName = declaration.id.name;
|
|
this.exports[ localName ] = { localName };
|
|
}
|
|
}
|
|
|
|
// export { foo, bar, baz }
|
|
else {
|
|
if ( node.specifiers.length ) {
|
|
node.specifiers.forEach( specifier => {
|
|
const localName = specifier.local.name;
|
|
const exportedName = specifier.exported.name;
|
|
|
|
if ( this.exports[ exportedName ] || this.reexports[ exportedName ] ) {
|
|
throw new Error( `A module cannot have multiple exports with the same name ('${exportedName}')` );
|
|
}
|
|
|
|
this.exports[ exportedName ] = { localName };
|
|
});
|
|
} else {
|
|
this.bundle.onwarn( `Module ${this.id} has an empty export declaration` );
|
|
}
|
|
}
|
|
}
|
|
|
|
addImport ( statement ) {
|
|
const node = statement.node;
|
|
const source = node.source.value;
|
|
|
|
if ( !~this.sources.indexOf( source ) ) this.sources.push( source );
|
|
|
|
node.specifiers.forEach( specifier => {
|
|
const localName = specifier.local.name;
|
|
|
|
if ( this.imports[ localName ] ) {
|
|
const err = new Error( `Duplicated import '${localName}'` );
|
|
err.file = this.id;
|
|
err.loc = getLocation( this.code, specifier.start );
|
|
throw err;
|
|
}
|
|
|
|
const isDefault = specifier.type === 'ImportDefaultSpecifier';
|
|
const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
|
|
|
|
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
|
|
this.imports[ localName ] = { source, name, module: null };
|
|
});
|
|
}
|
|
|
|
analyse () {
|
|
// discover this module's imports and exports
|
|
this.statements.forEach( statement => {
|
|
if ( statement.isImportDeclaration ) this.addImport( statement );
|
|
else if ( statement.isExportDeclaration ) this.addExport( statement );
|
|
|
|
statement.firstPass();
|
|
|
|
statement.scope.eachDeclaration( ( name, declaration ) => {
|
|
this.declarations[ name ] = declaration;
|
|
});
|
|
});
|
|
}
|
|
|
|
basename () {
|
|
const base = basename( this.id );
|
|
const ext = extname( this.id );
|
|
|
|
return makeLegalIdentifier( ext ? base.slice( 0, -ext.length ) : base );
|
|
}
|
|
|
|
bindAliases () {
|
|
keys( this.declarations ).forEach( name => {
|
|
if ( name === '*' ) return;
|
|
|
|
const declaration = this.declarations[ name ];
|
|
const statement = declaration.statement;
|
|
|
|
if ( !statement || statement.node.type !== 'VariableDeclaration' ) return;
|
|
|
|
const init = statement.node.declarations[0].init;
|
|
if ( !init || init.type === 'FunctionExpression' ) return;
|
|
|
|
statement.references.forEach( reference => {
|
|
if ( reference.name === name ) return;
|
|
|
|
const otherDeclaration = this.trace( reference.name );
|
|
if ( otherDeclaration ) otherDeclaration.addAlias( declaration );
|
|
});
|
|
});
|
|
}
|
|
|
|
bindImportSpecifiers () {
|
|
[ this.imports, this.reexports ].forEach( specifiers => {
|
|
keys( specifiers ).forEach( name => {
|
|
const specifier = specifiers[ name ];
|
|
|
|
const id = this.resolvedIds[ specifier.source ];
|
|
specifier.module = this.bundle.moduleById.get( id );
|
|
});
|
|
});
|
|
|
|
this.exportAllModules = this.exportAllSources.map( source => {
|
|
const id = this.resolvedIds[ source ];
|
|
return this.bundle.moduleById.get( id );
|
|
});
|
|
|
|
this.sources.forEach( source => {
|
|
const id = this.resolvedIds[ source ];
|
|
const module = this.bundle.moduleById.get( id );
|
|
|
|
if ( !module.isExternal ) this.dependencies.push( module );
|
|
});
|
|
}
|
|
|
|
bindReferences () {
|
|
if ( this.declarations.default ) {
|
|
if ( this.exports.default.identifier ) {
|
|
const declaration = this.trace( this.exports.default.identifier );
|
|
if ( declaration ) this.declarations.default.bind( declaration );
|
|
}
|
|
}
|
|
|
|
this.statements.forEach( statement => {
|
|
// skip `export { foo, bar, baz }`...
|
|
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) {
|
|
// ...unless this is the entry module
|
|
if ( this !== this.bundle.entryModule ) return;
|
|
}
|
|
|
|
statement.references.forEach( reference => {
|
|
const declaration = reference.scope.findDeclaration( reference.name ) ||
|
|
this.trace( reference.name );
|
|
|
|
if ( declaration ) {
|
|
declaration.addReference( reference );
|
|
} else {
|
|
// TODO handle globals
|
|
this.bundle.assumedGlobals[ reference.name ] = true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
getExports () {
|
|
let exports = blank();
|
|
|
|
keys( this.exports ).forEach( name => {
|
|
exports[ name ] = true;
|
|
});
|
|
|
|
keys( this.reexports ).forEach( name => {
|
|
exports[ name ] = true;
|
|
});
|
|
|
|
this.exportAllModules.forEach( module => {
|
|
module.getExports().forEach( name => {
|
|
if ( name !== 'default' ) exports[ name ] = true;
|
|
});
|
|
});
|
|
|
|
return keys( exports );
|
|
}
|
|
|
|
namespace () {
|
|
if ( !this.declarations['*'] ) {
|
|
this.declarations['*'] = new SyntheticNamespaceDeclaration( this );
|
|
}
|
|
|
|
return this.declarations['*'];
|
|
}
|
|
|
|
parse () {
|
|
// The ast can be supplied programmatically (but usually won't be)
|
|
if ( !this.ast ) {
|
|
// Try to extract a list of top-level statements/declarations. If
|
|
// the parse fails, attach file info and abort
|
|
try {
|
|
this.ast = parse( this.code, assign({
|
|
ecmaVersion: 6,
|
|
sourceType: 'module',
|
|
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end }),
|
|
preserveParens: true
|
|
}, this.bundle.acornOptions ));
|
|
} catch ( err ) {
|
|
err.code = 'PARSE_ERROR';
|
|
err.file = this.id; // see above - not necessarily true, but true enough
|
|
err.message += ` in ${this.id}`;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
walk( this.ast, {
|
|
enter: node => {
|
|
// eliminate dead branches early
|
|
if ( node.type === 'IfStatement' ) {
|
|
if ( isFalsy( node.test ) ) {
|
|
this.magicString.overwrite( node.consequent.start, node.consequent.end, '{}' );
|
|
node.consequent = emptyBlockStatement( node.consequent.start, node.consequent.end );
|
|
} else if ( node.alternate && isTruthy( node.test ) ) {
|
|
this.magicString.overwrite( node.alternate.start, node.alternate.end, '{}' );
|
|
node.alternate = emptyBlockStatement( node.alternate.start, node.alternate.end );
|
|
}
|
|
}
|
|
|
|
this.magicString.addSourcemapLocation( node.start );
|
|
this.magicString.addSourcemapLocation( node.end );
|
|
},
|
|
|
|
leave: ( node, parent, prop ) => {
|
|
// eliminate dead branches early
|
|
if ( node.type === 'ConditionalExpression' ) {
|
|
if ( isFalsy( node.test ) ) {
|
|
this.magicString.remove( node.start, node.alternate.start );
|
|
parent[prop] = node.alternate;
|
|
} else if ( isTruthy( node.test ) ) {
|
|
this.magicString.remove( node.start, node.consequent.start );
|
|
this.magicString.remove( node.consequent.end, node.end );
|
|
parent[prop] = node.consequent;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let statements = [];
|
|
let lastChar = 0;
|
|
let commentIndex = 0;
|
|
|
|
this.ast.body.forEach( node => {
|
|
if ( node.type === 'EmptyStatement' ) return;
|
|
|
|
if (
|
|
node.type === 'ExportNamedDeclaration' &&
|
|
node.declaration &&
|
|
node.declaration.type === 'VariableDeclaration' &&
|
|
node.declaration.declarations &&
|
|
node.declaration.declarations.length > 1
|
|
) {
|
|
// push a synthetic export declaration
|
|
const syntheticNode = {
|
|
type: 'ExportNamedDeclaration',
|
|
specifiers: node.declaration.declarations.map( declarator => {
|
|
const id = { name: declarator.id.name };
|
|
return {
|
|
local: id,
|
|
exported: id
|
|
};
|
|
}),
|
|
isSynthetic: true
|
|
};
|
|
|
|
const statement = new Statement( syntheticNode, this, node.start, node.start );
|
|
statements.push( statement );
|
|
|
|
this.magicString.remove( node.start, node.declaration.start );
|
|
node = node.declaration;
|
|
}
|
|
|
|
// 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 ) {
|
|
// remove the leading var/let/const... UNLESS the previous node
|
|
// was also a synthetic node, in which case it'll get removed anyway
|
|
const lastStatement = statements[ statements.length - 1 ];
|
|
if ( !lastStatement || !lastStatement.node.isSynthetic ) {
|
|
this.magicString.remove( node.start, node.declarations[0].start );
|
|
}
|
|
|
|
node.declarations.forEach( declarator => {
|
|
const { start, end } = declarator;
|
|
|
|
const syntheticNode = {
|
|
type: 'VariableDeclaration',
|
|
kind: node.kind,
|
|
start,
|
|
end,
|
|
declarations: [ declarator ],
|
|
isSynthetic: true
|
|
};
|
|
|
|
const statement = new Statement( syntheticNode, this, start, end );
|
|
statements.push( statement );
|
|
});
|
|
|
|
lastChar = node.end; // TODO account for trailing line comment
|
|
}
|
|
|
|
else {
|
|
let comment;
|
|
do {
|
|
comment = this.comments[ commentIndex ];
|
|
if ( !comment ) break;
|
|
if ( comment.start > node.start ) break;
|
|
commentIndex += 1;
|
|
} while ( comment.end < lastChar );
|
|
|
|
const start = comment ? Math.min( comment.start, node.start ) : node.start;
|
|
const end = node.end; // TODO account for trailing line comment
|
|
|
|
const statement = new Statement( node, this, start, end );
|
|
statements.push( statement );
|
|
|
|
lastChar = end;
|
|
}
|
|
});
|
|
|
|
let i = statements.length;
|
|
let next = this.code.length;
|
|
while ( i-- ) {
|
|
statements[i].next = next;
|
|
if ( !statements[i].isSynthetic ) next = statements[i].start;
|
|
}
|
|
|
|
return statements;
|
|
}
|
|
|
|
render ( es6 ) {
|
|
let magicString = this.magicString.clone();
|
|
|
|
this.statements.forEach( statement => {
|
|
if ( !statement.isIncluded ) {
|
|
magicString.remove( statement.start, statement.next );
|
|
return;
|
|
}
|
|
|
|
statement.stringLiteralRanges.forEach( range => magicString.indentExclusionRanges.push( range ) );
|
|
|
|
// skip `export { foo, bar, baz }`
|
|
if ( statement.node.type === 'ExportNamedDeclaration' ) {
|
|
if ( statement.node.isSynthetic ) return;
|
|
|
|
// skip `export { foo, bar, baz }`
|
|
if ( statement.node.specifiers.length ) {
|
|
magicString.remove( statement.start, statement.next );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// split up/remove var declarations as necessary
|
|
if ( statement.node.type === 'VariableDeclaration' ) {
|
|
const declarator = statement.node.declarations[0];
|
|
|
|
if ( declarator.id.type === 'Identifier' ) {
|
|
const declaration = this.declarations[ declarator.id.name ];
|
|
|
|
if ( declaration.exportName && declaration.isReassigned ) { // `var foo = ...` becomes `exports.foo = ...`
|
|
magicString.remove( statement.start, declarator.init ? declarator.start : statement.next );
|
|
if ( !declarator.init ) return;
|
|
}
|
|
}
|
|
|
|
else {
|
|
// we handle destructuring differently, because whereas we can rewrite
|
|
// `var foo = ...` as `exports.foo = ...`, in a case like `var { a, b } = c()`
|
|
// where `a` or `b` is exported and reassigned, we have to append
|
|
// `exports.a = a;` and `exports.b = b` instead
|
|
extractNames( declarator.id ).forEach( name => {
|
|
const declaration = this.declarations[ name ];
|
|
|
|
if ( declaration.exportName && declaration.isReassigned ) {
|
|
magicString.insertLeft( statement.end, `;\nexports.${name} = ${declaration.render( es6 )}` );
|
|
}
|
|
});
|
|
}
|
|
|
|
if ( statement.node.isSynthetic ) {
|
|
// insert `var/let/const` if necessary
|
|
magicString.insertRight( statement.start, `${statement.node.kind} ` );
|
|
magicString.insertLeft( statement.end, ';' );
|
|
magicString.overwrite( statement.end, statement.next, '\n' ); // TODO account for trailing newlines
|
|
}
|
|
}
|
|
|
|
let toDeshadow = blank();
|
|
|
|
statement.references.forEach( reference => {
|
|
const { start, end } = reference;
|
|
|
|
if ( reference.isUndefined ) {
|
|
magicString.overwrite( start, end, 'undefined', true );
|
|
}
|
|
|
|
const declaration = reference.declaration;
|
|
|
|
if ( declaration ) {
|
|
const name = declaration.render( es6 );
|
|
|
|
// the second part of this check is necessary because of
|
|
// namespace optimisation – name of `foo.bar` could be `bar`
|
|
if ( reference.name === name && name.length === end - start ) return;
|
|
|
|
reference.rewritten = true;
|
|
|
|
// prevent local variables from shadowing renamed references
|
|
const identifier = name.match( /[^\.]+/ )[0];
|
|
if ( reference.scope.contains( identifier ) ) {
|
|
toDeshadow[ identifier ] = `${identifier}$$`; // TODO more robust mechanism
|
|
}
|
|
|
|
if ( reference.isShorthandProperty ) {
|
|
magicString.insertLeft( end, `: ${name}` );
|
|
} else {
|
|
magicString.overwrite( start, end, name, true );
|
|
}
|
|
}
|
|
});
|
|
|
|
if ( keys( toDeshadow ).length ) {
|
|
statement.references.forEach( reference => {
|
|
if ( !reference.rewritten && reference.name in toDeshadow ) {
|
|
const replacement = toDeshadow[ reference.name ];
|
|
magicString.overwrite( reference.start, reference.end, reference.isShorthandProperty ? `${reference.name}: ${replacement}` : replacement, true );
|
|
}
|
|
});
|
|
}
|
|
|
|
// modify exports as necessary
|
|
if ( statement.isExportDeclaration ) {
|
|
// remove `export` from `export var foo = 42`
|
|
// TODO: can we do something simpler here?
|
|
// we just want to remove `export`, right?
|
|
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) {
|
|
const name = extractNames( statement.node.declaration.declarations[ 0 ].id )[ 0 ];
|
|
const declaration = this.declarations[ name ];
|
|
|
|
if ( !declaration ) throw new Error( `Missing declaration for ${name}!` );
|
|
|
|
const end = declaration.exportName && declaration.isReassigned ?
|
|
statement.node.declaration.declarations[0].start :
|
|
statement.node.declaration.start;
|
|
|
|
magicString.remove( statement.node.start, end );
|
|
}
|
|
|
|
else if ( statement.node.type === 'ExportAllDeclaration' ) {
|
|
// TODO: remove once `export * from 'external'` is supported.
|
|
magicString.remove( statement.start, statement.next );
|
|
}
|
|
|
|
// remove `export` from `export class Foo {...}` or `export default Foo`
|
|
// TODO default exports need different treatment
|
|
else if ( statement.node.declaration.id ) {
|
|
magicString.remove( statement.node.start, statement.node.declaration.start );
|
|
}
|
|
|
|
else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
|
|
const defaultDeclaration = this.declarations.default;
|
|
|
|
// prevent `var foo = foo`
|
|
if ( defaultDeclaration.original && !defaultDeclaration.original.isReassigned ) {
|
|
magicString.remove( statement.start, statement.next );
|
|
return;
|
|
}
|
|
|
|
const defaultName = defaultDeclaration.render();
|
|
|
|
// prevent `var undefined = sideEffectyDefault(foo)`
|
|
if ( !defaultDeclaration.exportName && !defaultDeclaration.isUsed ) {
|
|
magicString.remove( statement.start, statement.node.declaration.start );
|
|
return;
|
|
}
|
|
|
|
// anonymous functions should be converted into declarations
|
|
if ( statement.node.declaration.type === 'FunctionExpression' ) {
|
|
magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${defaultName}` );
|
|
} else {
|
|
magicString.overwrite( statement.node.start, statement.node.declaration.start, `${this.bundle.varOrConst} ${defaultName} = ` );
|
|
}
|
|
}
|
|
|
|
else {
|
|
throw new Error( 'Unhandled export' );
|
|
}
|
|
}
|
|
});
|
|
|
|
// add namespace block if necessary
|
|
const namespace = this.declarations['*'];
|
|
if ( namespace && namespace.needsNamespaceBlock ) {
|
|
magicString.append( '\n\n' + namespace.renderBlock( magicString.getIndentString() ) );
|
|
}
|
|
|
|
return magicString.trim();
|
|
}
|
|
|
|
/**
|
|
* Statically runs the module marking the top-level statements that must be
|
|
* included for the module to execute successfully.
|
|
*
|
|
* @param {boolean} treeshake - if we should tree-shake the module
|
|
* @return {boolean} marked - if any new statements were marked for inclusion
|
|
*/
|
|
run ( treeshake ) {
|
|
if ( !treeshake ) {
|
|
this.statements.forEach( statement => {
|
|
if ( statement.isImportDeclaration || ( statement.isExportDeclaration && statement.node.isSynthetic ) ) return;
|
|
|
|
statement.mark();
|
|
});
|
|
return false;
|
|
}
|
|
|
|
let marked = false;
|
|
|
|
this.statements.forEach( statement => {
|
|
marked = statement.run( this.strongDependencies ) || marked;
|
|
});
|
|
|
|
return marked;
|
|
}
|
|
|
|
toJSON () {
|
|
return {
|
|
id: this.id,
|
|
code: this.code,
|
|
originalCode: this.originalCode,
|
|
ast: this.ast,
|
|
sourceMapChain: this.sourceMapChain
|
|
};
|
|
}
|
|
|
|
trace ( name ) {
|
|
if ( name in this.declarations ) return this.declarations[ name ];
|
|
if ( name in this.imports ) {
|
|
const importDeclaration = this.imports[ name ];
|
|
const otherModule = importDeclaration.module;
|
|
|
|
if ( importDeclaration.name === '*' && !otherModule.isExternal ) {
|
|
return otherModule.namespace();
|
|
}
|
|
|
|
const declaration = otherModule.traceExport( importDeclaration.name );
|
|
|
|
if ( !declaration ) throw new Error( `Module ${otherModule.id} does not export ${importDeclaration.name} (imported by ${this.id})` );
|
|
return declaration;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
traceExport ( name ) {
|
|
// export { foo } from './other.js'
|
|
const reexportDeclaration = this.reexports[ name ];
|
|
if ( reexportDeclaration ) {
|
|
const declaration = reexportDeclaration.module.traceExport( reexportDeclaration.localName );
|
|
|
|
if ( !declaration ) {
|
|
const err = new Error( `'${reexportDeclaration.localName}' is not exported by '${reexportDeclaration.module.id}' (imported by '${this.id}')` );
|
|
err.file = this.id;
|
|
err.loc = getLocation( this.code, reexportDeclaration.start );
|
|
throw err;
|
|
}
|
|
|
|
return declaration;
|
|
}
|
|
|
|
const exportDeclaration = this.exports[ name ];
|
|
if ( exportDeclaration ) {
|
|
const name = exportDeclaration.localName;
|
|
const declaration = this.trace( name );
|
|
|
|
if ( declaration ) return declaration;
|
|
|
|
this.bundle.assumedGlobals[ name ] = true;
|
|
return ( this.declarations[ name ] = new SyntheticGlobalDeclaration( name ) );
|
|
}
|
|
|
|
for ( let i = 0; i < this.exportAllModules.length; i += 1 ) {
|
|
const module = this.exportAllModules[i];
|
|
const declaration = module.traceExport( name );
|
|
|
|
if ( declaration ) return declaration;
|
|
}
|
|
}
|
|
}
|
|
|