Comparing version 0.17.3 to 0.18.0
@@ -20,2 +20,3 @@ rollup version <%= version %> | ||
--no-strict Don't emit a `"use strict";` in the generated modules. | ||
--no-indent Don't indent result | ||
@@ -22,0 +23,0 @@ Examples: |
@@ -61,3 +61,4 @@ require( 'source-map-support' ).install(); | ||
moduleName: options.name, | ||
sourceMap: options.sourcemap | ||
sourceMap: options.sourcemap, | ||
indent: options.indent !== false | ||
}; | ||
@@ -64,0 +65,0 @@ |
# rollup changelog | ||
## 0.18.0 | ||
* Internal rewrite | ||
* Reinstate statically-analysable namespace imports | ||
* Avoid using getters in namespace blocks where possible ([#144](https://github.com/rollup/rollup/issues/144)) | ||
* Track variable aliases ([#96](https://github.com/rollup/rollup/issues/96)) | ||
* Prevent multiline strings being indented ([#164](https://github.com/rollup/rollup/issues/164)) | ||
## 0.17.4 | ||
* Allow imports from hidden directories (replay of [#133](https://github.com/rollup/rollup/issues/133)) | ||
## 0.17.3 | ||
@@ -4,0 +16,0 @@ |
{ | ||
"name": "rollup", | ||
"version": "0.17.3", | ||
"version": "0.18.0", | ||
"description": "Next-generation ES6 module bundler", | ||
@@ -62,2 +62,3 @@ "main": "dist/rollup.js", | ||
"chalk": "^1.0.0", | ||
"estree-walker": "^0.1.3", | ||
"magic-string": "^0.7.0", | ||
@@ -64,0 +65,0 @@ "minimist": "^1.1.1", |
@@ -132,4 +132,9 @@ # Rollup | ||
## How does this compare to JSPM/SystemJS? | ||
[JSPM](http://jspm.io/) is awesome! It's a little different to this project though – it combines a repository with a package manager and a client-side module loader, as opposed to simply bundling modules. JSPM allows you to use any module format and even develop without a build step, so it's a great choice for creating applications. Rollup generates smaller bundles that don't use the complex SystemJS format, and so is a better choice for creating libraries. A future version of JSPM [may use Rollup internally](https://github.com/systemjs/builder/pull/205), so you'll get the best of both worlds. | ||
## License | ||
Released under the [MIT license](TK). |
@@ -1,2 +0,2 @@ | ||
import { blank } from '../utils/object'; | ||
import { blank, keys } from '../utils/object'; | ||
@@ -36,2 +36,37 @@ const extractors = { | ||
class Declaration { | ||
constructor () { | ||
this.statement = null; | ||
this.name = null; | ||
this.isReassigned = false; | ||
this.aliases = []; | ||
} | ||
addAlias ( declaration ) { | ||
this.aliases.push( declaration ); | ||
} | ||
addReference ( reference ) { | ||
reference.declaration = this; | ||
this.name = reference.name; // TODO handle differences of opinion | ||
if ( reference.isReassignment ) this.isReassigned = true; | ||
} | ||
render ( es6 ) { | ||
if ( es6 ) return this.name; | ||
if ( !this.isReassigned || !this.isExported ) return this.name; | ||
return `exports.${this.name}`; | ||
} | ||
use () { | ||
this.isUsed = true; | ||
if ( this.statement ) this.statement.mark(); | ||
this.aliases.forEach( alias => alias.use() ); | ||
} | ||
} | ||
export default class Scope { | ||
@@ -42,7 +77,5 @@ constructor ( options ) { | ||
this.parent = options.parent; | ||
this.depth = this.parent ? this.parent.depth + 1 : 0; | ||
this.declarations = blank(); | ||
this.isBlockScope = !!options.block; | ||
this.varDeclarations = []; | ||
this.declarations = blank(); | ||
@@ -52,3 +85,3 @@ if ( options.params ) { | ||
extractNames( param ).forEach( name => { | ||
this.declarations[ name ] = true; | ||
this.declarations[ name ] = new Declaration( name ); | ||
}); | ||
@@ -59,11 +92,10 @@ }); | ||
addDeclaration ( declaration, isBlockDeclaration, isVar ) { | ||
addDeclaration ( node, isBlockDeclaration, isVar ) { | ||
if ( !isBlockDeclaration && this.isBlockScope ) { | ||
// it's a `var` or function node, and this | ||
// is a block scope, so we need to go up | ||
this.parent.addDeclaration( declaration, isBlockDeclaration, isVar ); | ||
this.parent.addDeclaration( node, isBlockDeclaration, isVar ); | ||
} else { | ||
extractNames( declaration.id ).forEach( name => { | ||
this.declarations[ name ] = true; | ||
if ( isVar ) this.varDeclarations.push( name ); | ||
extractNames( node.id ).forEach( name => { | ||
this.declarations[ name ] = new Declaration( name ); | ||
}); | ||
@@ -78,13 +110,12 @@ } | ||
findDefiningScope ( name ) { | ||
if ( this.declarations[ name ] ) { | ||
return this; | ||
} | ||
eachDeclaration ( fn ) { | ||
keys( this.declarations ).forEach( key => { | ||
fn( key, this.declarations[ key ] ); | ||
}); | ||
} | ||
if ( this.parent ) { | ||
return this.parent.findDefiningScope( name ); | ||
} | ||
return null; | ||
findDeclaration ( name ) { | ||
return this.declarations[ name ] || | ||
( this.parent && this.parent.findDeclaration( name ) ); | ||
} | ||
} |
@@ -7,3 +7,2 @@ import { Promise } from 'sander'; | ||
import finalisers from './finalisers/index'; | ||
import makeLegalIdentifier from './utils/makeLegalIdentifier'; | ||
import ensureArray from './utils/ensureArray'; | ||
@@ -33,4 +32,2 @@ import { defaultResolver, defaultExternalResolver } from './utils/resolveId'; | ||
this.toExport = null; | ||
this.pending = blank(); | ||
@@ -40,8 +37,9 @@ this.moduleById = blank(); | ||
this.statements = null; | ||
this.externalModules = []; | ||
this.internalNamespaceModules = []; | ||
this.internalNamespaces = []; | ||
this.assumedGlobals = blank(); | ||
this.assumedGlobals.exports = true; // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles | ||
// TODO strictly speaking, this only applies with non-ES6, non-default-only bundles | ||
[ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true ); | ||
} | ||
@@ -53,132 +51,60 @@ | ||
.then( entryModule => { | ||
entryModule.bindImportSpecifiers(); | ||
const defaultExport = entryModule.exports.default; | ||
this.entryModule = entryModule; | ||
if ( defaultExport ) { | ||
entryModule.needsDefault = true; | ||
this.modules.forEach( module => module.bindImportSpecifiers() ); | ||
this.modules.forEach( module => module.bindAliases() ); | ||
this.modules.forEach( module => module.bindReferences() ); | ||
// `export default function foo () {...}` - | ||
// use the declared name for the export | ||
if ( defaultExport.identifier ) { | ||
entryModule.suggestName( 'default', defaultExport.identifier ); | ||
} | ||
// mark all export statements | ||
entryModule.getExports().forEach( name => { | ||
const declaration = entryModule.traceExport( name ); | ||
declaration.isExported = true; | ||
// `export default a + b` - generate an export name | ||
// based on the id of the entry module | ||
else { | ||
let defaultExportName = this.entryModule.basename(); | ||
if ( declaration.statement ) declaration.use(); | ||
}); | ||
// deconflict | ||
let topLevelNames = []; | ||
entryModule.statements.forEach( statement => { | ||
keys( statement.defines ).forEach( name => topLevelNames.push( name ) ); | ||
}); | ||
let settled = false; | ||
while ( !settled ) { | ||
settled = true; | ||
while ( ~topLevelNames.indexOf( defaultExportName ) ) { | ||
defaultExportName = `_${defaultExportName}`; | ||
} | ||
entryModule.suggestName( 'default', defaultExportName ); | ||
} | ||
this.modules.forEach( module => { | ||
if ( module.markAllSideEffects() ) settled = false; | ||
}); | ||
} | ||
entryModule.markAllStatements( true ); | ||
this.markAllModifierStatements(); | ||
// Include all side-effects | ||
// TODO does this obviate the need for markAllStatements throughout? | ||
this.modules.forEach( module => { | ||
module.markAllSideEffects(); | ||
}); | ||
this.orderedModules = this.sort(); | ||
this.deconflict(); | ||
}); | ||
} | ||
// TODO would be better to deconflict once, rather than per-render | ||
deconflict ( es6 ) { | ||
let nameCount = blank(); | ||
deconflict () { | ||
let used = blank(); | ||
// ensure no conflicts with globals | ||
keys( this.assumedGlobals ).forEach( name => nameCount[ name ] = 0 ); | ||
keys( this.assumedGlobals ).forEach( name => used[ name ] = 1 ); | ||
let allReplacements = blank(); | ||
function getSafeName ( name ) { | ||
if ( used[ name ] ) { | ||
return `${name}$${used[name]++}`; | ||
} | ||
// Assign names to external modules | ||
used[ name ] = 1; | ||
return name; | ||
} | ||
this.externalModules.forEach( module => { | ||
// while we're here... | ||
allReplacements[ module.id ] = blank(); | ||
// TODO is this necessary in the ES6 case? | ||
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id ); | ||
module.name = getSafeName( name ); | ||
module.name = getSafeName( module.name ); | ||
}); | ||
// Discover conflicts (i.e. two statements in separate modules both define `foo`) | ||
let i = this.orderedModules.length; | ||
while ( i-- ) { | ||
const module = this.orderedModules[i]; | ||
this.modules.forEach( module => { | ||
keys( module.declarations ).forEach( originalName => { | ||
const declaration = module.declarations[ originalName ]; | ||
// while we're here... | ||
allReplacements[ module.id ] = blank(); | ||
keys( module.definitions ).forEach( name => { | ||
const safeName = getSafeName( name ); | ||
if ( safeName !== name ) { | ||
module.rename( name, safeName ); | ||
allReplacements[ module.id ][ name ] = safeName; | ||
if ( originalName === 'default' ) { | ||
if ( declaration.original && !declaration.original.isReassigned ) return; | ||
} | ||
}); | ||
} | ||
// Assign non-conflicting names to internal default/namespace export | ||
this.orderedModules.forEach( module => { | ||
if ( !module.needsDefault && !module.needsAll ) return; | ||
if ( module.needsAll ) { | ||
const namespaceName = getSafeName( module.suggestedNames[ '*' ] ); | ||
module.replacements[ '*' ] = namespaceName; | ||
} | ||
if ( module.needsDefault || module.needsAll && module.exports.default ) { | ||
const defaultExport = module.exports.default; | ||
// only create a new name if either | ||
// a) it's an expression (`export default 42`) or | ||
// b) it's a name that is reassigned to (`export var a = 1; a = 2`) | ||
if ( defaultExport && defaultExport.identifier && !defaultExport.isModified ) return; // TODO encapsulate check for whether we need synthetic default name | ||
const defaultName = getSafeName( module.suggestedNames.default ); | ||
module.replacements.default = defaultName; | ||
} | ||
}); | ||
this.orderedModules.forEach( module => { | ||
keys( module.imports ).forEach( localName => { | ||
if ( !module.imports[ localName ].isUsed ) return; | ||
const bundleName = this.trace( module, localName, es6 ); | ||
if ( bundleName !== localName ) { | ||
allReplacements[ module.id ][ localName ] = bundleName; | ||
} | ||
declaration.name = getSafeName( declaration.name ); | ||
}); | ||
}); | ||
function getSafeName ( name ) { | ||
if ( name in nameCount ) { | ||
nameCount[ name ] += 1; | ||
name = `${name}$${nameCount[ name ]}`; | ||
while ( name in nameCount ) name = `_${name}`; // just to avoid any crazy edge cases | ||
return name; | ||
} | ||
nameCount[ name ] = 0; | ||
return name; | ||
} | ||
return allReplacements; | ||
} | ||
@@ -200,8 +126,3 @@ | ||
const module = new Module({ | ||
id, | ||
source, | ||
ast, | ||
bundle: this | ||
}); | ||
const module = new Module({ id, source, ast, bundle: this }); | ||
@@ -243,52 +164,4 @@ this.modules.push( module ); | ||
markAllModifierStatements () { | ||
let settled = true; | ||
this.modules.forEach( module => { | ||
module.statements.forEach( statement => { | ||
if ( statement.isIncluded ) return; | ||
keys( statement.modifies ).forEach( name => { | ||
const definingStatement = module.definitions[ name ]; | ||
const exportDeclaration = module.exports[ name ] || module.reexports[ name ] || ( | ||
module.exports.default && module.exports.default.identifier === name && module.exports.default | ||
); | ||
const shouldMark = ( definingStatement && definingStatement.isIncluded ) || | ||
( exportDeclaration && exportDeclaration.isUsed ); | ||
if ( shouldMark ) { | ||
settled = false; | ||
statement.mark(); | ||
return; | ||
} | ||
// special case - https://github.com/rollup/rollup/pull/40 | ||
// TODO refactor this? it's a bit confusing | ||
const importDeclaration = module.imports[ name ]; | ||
if ( !importDeclaration || importDeclaration.module.isExternal ) return; | ||
if ( importDeclaration.name === '*' ) { | ||
importDeclaration.module.markAllExportStatements(); | ||
} else { | ||
const otherExportDeclaration = importDeclaration.module.exports[ importDeclaration.name ]; | ||
// TODO things like `export default a + b` don't apply here... right? | ||
const otherDefiningStatement = module.findDefiningStatement( otherExportDeclaration.localName ); | ||
if ( !otherDefiningStatement ) return; | ||
statement.mark(); | ||
} | ||
settled = false; | ||
}); | ||
}); | ||
}); | ||
if ( !settled ) this.markAllModifierStatements(); | ||
} | ||
render ( options = {} ) { | ||
const format = options.format || 'es6'; | ||
const allReplacements = this.deconflict( format === 'es6' ); | ||
@@ -298,57 +171,6 @@ // Determine export mode - 'default', 'named', 'none' | ||
// If we have named exports from the bundle, and those exports | ||
// are assigned to *within* the bundle, we may need to rewrite e.g. | ||
// | ||
// export let count = 0; | ||
// export function incr () { count++ } | ||
// | ||
// might become... | ||
// | ||
// exports.count = 0; | ||
// function incr () { | ||
// exports.count += 1; | ||
// } | ||
// exports.incr = incr; | ||
// | ||
// This doesn't apply if the bundle is exported as ES6! | ||
let allBundleExports = blank(); | ||
let isReassignedVarDeclaration = blank(); | ||
let varExports = blank(); | ||
let getterExports = []; | ||
this.orderedModules.forEach( module => { | ||
module.reassignments.forEach( name => { | ||
isReassignedVarDeclaration[ module.replacements[ name ] || name ] = true; | ||
}); | ||
}); | ||
if ( format !== 'es6' && exportMode === 'named' ) { | ||
keys( this.entryModule.exports ) | ||
.concat( keys( this.entryModule.reexports ) ) | ||
.forEach( name => { | ||
const canonicalName = this.traceExport( this.entryModule, name ); | ||
if ( isReassignedVarDeclaration[ canonicalName ] ) { | ||
varExports[ name ] = true; | ||
// if the same binding is exported multiple ways, we need to | ||
// use getters to keep all exports in sync | ||
if ( allBundleExports[ canonicalName ] ) { | ||
getterExports.push({ key: name, value: allBundleExports[ canonicalName ] }); | ||
} else { | ||
allBundleExports[ canonicalName ] = `exports.${name}`; | ||
} | ||
} | ||
}); | ||
} | ||
// since we're rewriting variable exports, we want to | ||
// ensure we don't try and export them again at the bottom | ||
this.toExport = this.entryModule.getExports() | ||
.filter( key => !varExports[ key ] ); | ||
let magicString = new MagicString.Bundle({ separator: '\n\n' }); | ||
this.orderedModules.forEach( module => { | ||
const source = module.render( allBundleExports, allReplacements[ module.id ], format ); | ||
const source = module.render( format === 'es6' ); | ||
if ( source.toString().length ) { | ||
@@ -359,34 +181,7 @@ magicString.addSource( source ); | ||
// prepend bundle with internal namespaces | ||
const indentString = getIndentString( magicString, options ); | ||
const namespaceBlock = this.internalNamespaceModules.map( module => { | ||
const exports = keys( module.exports ) | ||
.concat( keys( module.reexports ) ) | ||
.map( name => { | ||
const canonicalName = this.traceExport( module, name ); | ||
return `${indentString}get ${name} () { return ${canonicalName}; }`; | ||
}); | ||
return `var ${module.replacements['*']} = {\n` + | ||
exports.join( ',\n' ) + | ||
`\n};\n\n`; | ||
}).join( '' ); | ||
magicString.prepend( namespaceBlock ); | ||
if ( getterExports.length ) { | ||
// TODO offer ES3-safe (but not spec-compliant) alternative? | ||
const getterExportsBlock = `Object.defineProperties(exports, {\n` + | ||
getterExports.map( ({ key, value }) => indentString + `${key}: { get: function () { return ${value}; } }` ).join( ',\n' ) + | ||
`\n});`; | ||
magicString.append( '\n\n' + getterExportsBlock ); | ||
} | ||
const finalise = finalisers[ format ]; | ||
if ( !finalise ) throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); | ||
if ( !finalise ) { | ||
throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); | ||
} | ||
magicString = finalise( this, magicString.trim(), { exportMode, indentString }, options ); | ||
@@ -423,2 +218,3 @@ | ||
function visit ( module ) { | ||
if ( seen[ module.id ] ) return; | ||
seen[ module.id ] = true; | ||
@@ -472,3 +268,3 @@ | ||
visit( this.entryModule ); | ||
this.modules.forEach( visit ); | ||
@@ -499,40 +295,2 @@ if ( hasCycles ) { | ||
} | ||
trace ( module, localName, es6 ) { | ||
const importDeclaration = module.imports[ localName ]; | ||
// defined in this module | ||
if ( !importDeclaration ) return module.replacements[ localName ] || localName; | ||
// defined elsewhere | ||
return this.traceExport( importDeclaration.module, importDeclaration.name, es6 ); | ||
} | ||
traceExport ( module, name, es6 ) { | ||
if ( module.isExternal ) { | ||
if ( name === 'default' ) return module.needsNamed && !es6 ? `${module.name}__default` : module.name; | ||
if ( name === '*' ) return module.name; | ||
return es6 ? name : `${module.name}.${name}`; | ||
} | ||
const reexportDeclaration = module.reexports[ name ]; | ||
if ( reexportDeclaration ) { | ||
return this.traceExport( reexportDeclaration.module, reexportDeclaration.localName ); | ||
} | ||
if ( name === '*' ) return module.replacements[ '*' ]; | ||
if ( name === 'default' ) return module.defaultName(); | ||
const exportDeclaration = module.exports[ name ]; | ||
if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName ); | ||
for ( let i = 0; i < module.exportAlls.length; i += 1 ) { | ||
const declaration = module.exportAlls[i]; | ||
if ( declaration.module.exports[ name ] ) { | ||
return this.traceExport( declaration.module, name, es6 ); | ||
} | ||
} | ||
throw new Error( `Could not trace binding '${name}' from ${module.id}` ); | ||
} | ||
} |
import { blank } from './utils/object'; | ||
import makeLegalIdentifier from './utils/makeLegalIdentifier'; | ||
class ExternalDeclaration { | ||
constructor ( module, name ) { | ||
this.module = module; | ||
this.name = name; | ||
this.isExternal = true; | ||
} | ||
addAlias () { | ||
// noop | ||
} | ||
addReference ( reference ) { | ||
reference.declaration = this; | ||
if ( this.name === 'default' || this.name === '*' ) { | ||
this.module.suggestName( reference.name ); | ||
} | ||
} | ||
render ( es6 ) { | ||
if ( this.name === '*' ) { | ||
return this.module.name; | ||
} | ||
if ( this.name === 'default' ) { | ||
return !es6 && this.module.exportsNames ? | ||
`${this.module.name}__default` : | ||
this.module.name; | ||
} | ||
return es6 ? this.name : `${this.module.name}.${this.name}`; | ||
} | ||
use () { | ||
// noop? | ||
} | ||
} | ||
export default class ExternalModule { | ||
constructor ( id ) { | ||
this.id = id; | ||
this.name = null; | ||
this.name = makeLegalIdentifier( id ); | ||
this.nameSuggestions = blank(); | ||
this.mostCommonSuggestion = 0; | ||
this.isExternal = true; | ||
this.importedByBundle = []; | ||
this.declarations = blank(); | ||
this.suggestedNames = blank(); | ||
this.needsDefault = false; | ||
// Invariant: needsNamed and needsAll are never both true at once. | ||
// Because an import with both a namespace and named import is invalid: | ||
// | ||
// import * as ns, { a } from '...' | ||
// | ||
this.needsNamed = false; | ||
this.needsAll = false; | ||
this.exportsNames = false; | ||
} | ||
findDefiningStatement () { | ||
return null; | ||
} | ||
suggestName ( name ) { | ||
if ( !this.nameSuggestions[ name ] ) this.nameSuggestions[ name ] = 0; | ||
this.nameSuggestions[ name ] += 1; | ||
rename () { | ||
// noop | ||
if ( this.nameSuggestions[ name ] > this.mostCommonSuggestion ) { | ||
this.mostCommonSuggestion = this.nameSuggestions[ name ]; | ||
this.name = name; | ||
} | ||
} | ||
suggestName ( exportName, suggestion ) { | ||
if ( !this.suggestedNames[ exportName ] ) { | ||
this.suggestedNames[ exportName ] = suggestion; | ||
traceExport ( name ) { | ||
if ( name !== 'default' && name !== '*' ) { | ||
this.exportsNames = true; | ||
} | ||
return this.declarations[ name ] || ( | ||
this.declarations[ name ] = new ExternalDeclaration( this, name ) | ||
); | ||
} | ||
} |
@@ -25,3 +25,3 @@ import { getName, quoteId } from '../utils/map-helpers'; | ||
const exportBlock = getExportBlock( bundle, exportMode ); | ||
const exportBlock = getExportBlock( bundle.entryModule, exportMode ); | ||
if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); | ||
@@ -28,0 +28,0 @@ |
@@ -11,4 +11,4 @@ import getExportBlock from './shared/getExportBlock'; | ||
if ( module.needsDefault ) { | ||
requireStatement += '\n' + ( module.needsNamed ? `var ${module.name}__default = ` : `${module.name} = ` ) + | ||
if ( module.declarations.default ) { | ||
requireStatement += '\n' + ( module.exportsNames ? `var ${module.name}__default = ` : `${module.name} = ` ) + | ||
`'default' in ${module.name} ? ${module.name}['default'] : ${module.name};`; | ||
@@ -27,3 +27,3 @@ } | ||
const exportBlock = getExportBlock( bundle, exportMode, 'module.exports =' ); | ||
const exportBlock = getExportBlock( bundle.entryModule, exportMode, 'module.exports =' ); | ||
if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); | ||
@@ -30,0 +30,0 @@ |
@@ -1,13 +0,3 @@ | ||
import { blank, keys } from '../utils/object'; | ||
import { keys } from '../utils/object'; | ||
function uniqueNames ( declarations ) { | ||
let uniques = blank(); | ||
declarations | ||
.filter( declaration => !/^(default|\*)$/.test( declaration.name ) ) | ||
.forEach( declaration => uniques[ declaration.name ] = true ); | ||
return keys( uniques ); | ||
} | ||
function notDefault ( name ) { | ||
@@ -21,16 +11,15 @@ return name !== 'default'; | ||
const specifiers = []; | ||
const importedNames = keys( module.declarations ) | ||
.filter( name => name !== '*' && name !== 'default' ); | ||
if ( module.needsDefault ) { | ||
specifiers.push( module.importedByBundle.filter( declaration => | ||
declaration.name === 'default' )[0].localName ); | ||
if ( module.declarations.default ) { | ||
specifiers.push( module.name ); | ||
} | ||
if ( module.needsAll ) { | ||
specifiers.push( '* as ' + module.importedByBundle.filter( declaration => | ||
declaration.name === '*' )[0].localName ); | ||
if ( module.declarations['*'] ) { | ||
specifiers.push( `* as ${module.name}` ); | ||
} | ||
if ( module.needsNamed ) { | ||
specifiers.push( '{ ' + uniqueNames( module.importedByBundle ) | ||
.join( ', ' ) + ' }' ); | ||
if ( importedNames.length ) { | ||
specifiers.push( `{ ${importedNames.join( ', ' )} }` ); | ||
} | ||
@@ -50,8 +39,8 @@ | ||
const specifiers = bundle.toExport.filter( notDefault ).map( name => { | ||
const canonicalName = bundle.traceExport( module, name ); | ||
const specifiers = module.getExports().filter( notDefault ).map( name => { | ||
const declaration = module.traceExport( name ); | ||
return canonicalName === name ? | ||
return declaration.name === name ? | ||
name : | ||
`${canonicalName} as ${name}`; | ||
`${declaration.name} as ${name}`; | ||
}); | ||
@@ -63,3 +52,3 @@ | ||
if ( defaultExport ) { | ||
exportBlock += `export default ${bundle.traceExport(module,'default')};`; | ||
exportBlock += `export default ${module.traceExport( 'default' ).name};`; | ||
} | ||
@@ -66,0 +55,0 @@ |
@@ -36,3 +36,3 @@ import { blank } from '../utils/object'; | ||
const exportBlock = getExportBlock( bundle, exportMode ); | ||
const exportBlock = getExportBlock( bundle.entryModule, exportMode ); | ||
if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); | ||
@@ -39,0 +39,0 @@ |
@@ -1,18 +0,21 @@ | ||
export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) { | ||
export default function getExportBlock ( entryModule, exportMode, mechanism = 'return' ) { | ||
if ( exportMode === 'default' ) { | ||
const defaultExport = bundle.entryModule.exports.default; | ||
const defaultExportName = bundle.entryModule.replacements.default || | ||
defaultExport.identifier; | ||
return `${mechanism} ${defaultExportName};`; | ||
return `${mechanism} ${entryModule.declarations.default.render( false )};`; | ||
} | ||
return bundle.toExport | ||
return entryModule.getExports() | ||
.map( name => { | ||
const prop = name === 'default' ? `['default']` : `.${name}`; | ||
name = bundle.traceExport( bundle.entryModule, name ); | ||
return `exports${prop} = ${name};`; | ||
const declaration = entryModule.traceExport( name ); | ||
const lhs = `exports${prop}`; | ||
const rhs = declaration.render( false ); | ||
// prevent `exports.count = exports.count` | ||
if ( lhs === rhs ) return null; | ||
return `${lhs} = ${rhs};`; | ||
}) | ||
.filter( Boolean ) | ||
.join( '\n' ); | ||
} |
export default function getInteropBlock ( bundle ) { | ||
return bundle.externalModules | ||
.map( module => { | ||
return module.needsDefault ? | ||
( module.needsNamed ? | ||
return module.declarations.default ? | ||
( module.exportsNames ? | ||
`var ${module.name}__default = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` : | ||
@@ -7,0 +7,0 @@ `${module.name} = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` ) : |
@@ -51,3 +51,3 @@ import { blank } from '../utils/object'; | ||
const exportBlock = getExportBlock( bundle, exportMode ); | ||
const exportBlock = getExportBlock( bundle.entryModule, exportMode ); | ||
if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); | ||
@@ -54,0 +54,0 @@ |
import { parse } from 'acorn'; | ||
import MagicString from 'magic-string'; | ||
import { walk } from 'estree-walker'; | ||
import Statement from './Statement'; | ||
import walk from './ast/walk'; | ||
import { blank, keys } from './utils/object'; | ||
@@ -11,17 +11,111 @@ import { basename, extname } from './utils/path'; | ||
function deconflict ( name, names ) { | ||
while ( name in names ) { | ||
name = `_${name}`; | ||
class SyntheticDefaultDeclaration { | ||
constructor ( node, statement, name ) { | ||
this.node = node; | ||
this.statement = statement; | ||
this.name = name; | ||
this.original = null; | ||
this.isExported = false; | ||
this.aliases = []; | ||
} | ||
return name; | ||
addAlias ( declaration ) { | ||
this.aliases.push( declaration ); | ||
} | ||
addReference ( reference ) { | ||
reference.declaration = this; | ||
this.name = reference.name; | ||
} | ||
bind ( declaration ) { | ||
this.original = declaration; | ||
} | ||
render () { | ||
return !this.original || this.original.isReassigned ? | ||
this.name : | ||
this.original.render(); | ||
} | ||
use () { | ||
this.isUsed = true; | ||
this.statement.mark(); | ||
this.aliases.forEach( alias => alias.use() ); | ||
} | ||
} | ||
function isEmptyExportedVarDeclaration ( node, allBundleExports, moduleReplacements ) { | ||
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false; | ||
class SyntheticNamespaceDeclaration { | ||
constructor ( module ) { | ||
this.module = module; | ||
this.name = null; | ||
const name = node.declarations[0].id.name; | ||
const canonicalName = moduleReplacements[ name ] || name; | ||
this.needsNamespaceBlock = false; | ||
this.aliases = []; | ||
return canonicalName in allBundleExports; | ||
this.originals = blank(); | ||
module.getExports().forEach( name => { | ||
this.originals[ name ] = module.traceExport( name ); | ||
}); | ||
} | ||
addAlias ( declaration ) { | ||
this.aliases.push( declaration ); | ||
} | ||
addReference ( reference ) { | ||
// if we have e.g. `foo.bar`, we can optimise | ||
// the reference by pointing directly to `bar` | ||
if ( reference.parts.length ) { | ||
reference.name = reference.parts.shift(); | ||
reference.end += reference.name.length + 1; // TODO this is brittle | ||
const original = this.originals[ reference.name ]; | ||
original.addReference( reference ); | ||
return; | ||
} | ||
// otherwise we're accessing the namespace directly, | ||
// which means we need to mark all of this module's | ||
// exports and render a namespace block in the bundle | ||
if ( !this.needsNamespaceBlock ) { | ||
this.needsNamespaceBlock = true; | ||
this.module.bundle.internalNamespaces.push( this ); | ||
keys( this.originals ).forEach( name => { | ||
const original = this.originals[ name ]; | ||
original.use(); | ||
}); | ||
} | ||
reference.declaration = this; | ||
this.name = reference.name; | ||
} | ||
renderBlock ( indentString ) { | ||
const members = keys( this.originals ).map( name => { | ||
const original = this.originals[ name ]; | ||
if ( original.isReassigned ) { | ||
return `${indentString}get ${name} () { return ${original.render()}; }`; | ||
} | ||
return `${indentString}${name}: ${original.render()}`; | ||
}); | ||
return `var ${this.render()} = {\n${members.join( ',\n' )}\n};\n\n`; | ||
} | ||
render () { | ||
return this.name; | ||
} | ||
use () { | ||
// noop? | ||
this.aliases.forEach( alias => alias.use() ); | ||
} | ||
} | ||
@@ -32,10 +126,22 @@ | ||
this.source = source; | ||
this.bundle = bundle; | ||
this.id = id; | ||
// all dependencies | ||
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( source, { | ||
filename: id | ||
filename: id, | ||
indentExclusionRanges: [] | ||
}); | ||
@@ -50,29 +156,6 @@ | ||
this.suggestedNames = blank(); | ||
this.comments = []; | ||
this.statements = this.parse( ast ); | ||
// all dependencies | ||
this.dependencies = []; | ||
this.resolvedIds = blank(); | ||
this.boundImportSpecifiers = false; | ||
// imports and exports, indexed by local name | ||
this.imports = blank(); | ||
this.exports = blank(); | ||
this.reexports = blank(); | ||
this.exportDelegates = blank(); | ||
this.exportAlls = []; | ||
this.replacements = blank(); | ||
this.reassignments = []; | ||
this.marked = blank(); | ||
this.definitions = blank(); | ||
this.definitionPromises = blank(); | ||
this.modifications = blank(); | ||
this.declarations = blank(); | ||
this.analyse(); | ||
@@ -92,6 +175,3 @@ } | ||
// When an unknown import is encountered, we see if one of them can satisfy it. | ||
this.exportAlls.push({ | ||
statement, | ||
source | ||
}); | ||
this.exportAllSources.push( source ); | ||
} | ||
@@ -114,20 +194,11 @@ | ||
else if ( node.type === 'ExportDefaultDeclaration' ) { | ||
const isDeclaration = /Declaration$/.test( node.declaration.type ); | ||
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type ); | ||
const identifier = ( node.declaration.id && node.declaration.id.name ) || node.declaration.name; | ||
const identifier = isDeclaration ? | ||
node.declaration.id.name : | ||
node.declaration.type === 'Identifier' ? | ||
node.declaration.name : | ||
null; | ||
this.exports.default = { | ||
statement, | ||
name: 'default', | ||
localName: identifier || 'default', | ||
identifier, | ||
isDeclaration, | ||
isAnonymous, | ||
isModified: false // in case of `export default foo; foo = somethingElse` | ||
localName: 'default', | ||
identifier | ||
}; | ||
// create a synthetic declaration | ||
this.declarations.default = new SyntheticDefaultDeclaration( node, statement, identifier || this.basename() ); | ||
} | ||
@@ -145,7 +216,3 @@ | ||
this.exports[ exportedName ] = { | ||
statement, | ||
localName, | ||
exportedName | ||
}; | ||
this.exports[ exportedName ] = { localName }; | ||
}); | ||
@@ -167,7 +234,3 @@ } | ||
this.exports[ name ] = { | ||
statement, | ||
localName: name, | ||
expression: declaration | ||
}; | ||
this.exports[ name ] = { localName: name }; | ||
} | ||
@@ -184,7 +247,3 @@ } | ||
node.specifiers.forEach( specifier => { | ||
const isDefault = specifier.type === 'ImportDefaultSpecifier'; | ||
const isNamespace = specifier.type === 'ImportNamespaceSpecifier'; | ||
const localName = specifier.local.name; | ||
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name; | ||
@@ -198,7 +257,7 @@ if ( this.imports[ localName ] ) { | ||
this.imports[ localName ] = { | ||
source, | ||
name, | ||
localName | ||
}; | ||
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 }; | ||
}); | ||
@@ -215,40 +274,23 @@ } | ||
// consolidate names that are defined/modified in this module | ||
keys( statement.defines ).forEach( name => { | ||
this.definitions[ name ] = statement; | ||
statement.scope.eachDeclaration( ( name, declaration ) => { | ||
this.declarations[ name ] = declaration; | ||
}); | ||
keys( statement.modifies ).forEach( name => { | ||
( this.modifications[ name ] || ( this.modifications[ name ] = [] ) ).push( statement ); | ||
}); | ||
}); | ||
} | ||
// discover variables that are reassigned inside function | ||
// bodies, so we can keep bindings live, e.g. | ||
// | ||
// export var count = 0; | ||
// export function incr () { count += 1 } | ||
let reassigned = blank(); | ||
this.statements.forEach( statement => { | ||
keys( statement.reassigns ).forEach( name => { | ||
reassigned[ name ] = true; | ||
}); | ||
}); | ||
basename () { | ||
return makeLegalIdentifier( basename( this.id ).slice( 0, -extname( this.id ).length ) ); | ||
} | ||
// if names are referenced that are neither defined nor imported | ||
// in this module, we assume that they're globals | ||
this.statements.forEach( statement => { | ||
if ( statement.isReexportDeclaration ) return; | ||
bindAliases () { | ||
keys( this.declarations ).forEach( name => { | ||
const declaration = this.declarations[ name ]; | ||
const statement = declaration.statement; | ||
if ( statement.node.type !== 'VariableDeclaration' ) return; | ||
// while we're here, mark reassignments | ||
statement.scope.varDeclarations.forEach( name => { | ||
if ( reassigned[ name ] && !~this.reassignments.indexOf( name ) ) { | ||
this.reassignments.push( name ); | ||
} | ||
}); | ||
statement.references.forEach( reference => { | ||
if ( reference.name === name || !reference.isImmediatelyUsed ) return; | ||
keys( statement.dependsOn ).forEach( name => { | ||
if ( !this.definitions[ name ] && !this.imports[ name ] ) { | ||
this.bundle.assumedGlobals[ name ] = true; | ||
} | ||
const otherDeclaration = this.trace( reference.name ); | ||
if ( otherDeclaration ) otherDeclaration.addAlias( declaration ); | ||
}); | ||
@@ -258,10 +300,3 @@ }); | ||
basename () { | ||
return makeLegalIdentifier( basename( this.id ).slice( 0, -extname( this.id ).length ) ); | ||
} | ||
bindImportSpecifiers () { | ||
if ( this.boundImportSpecifiers ) return; | ||
this.boundImportSpecifiers = true; | ||
[ this.imports, this.reexports ].forEach( specifiers => { | ||
@@ -271,4 +306,2 @@ keys( specifiers ).forEach( name => { | ||
if ( specifier.module ) return; | ||
const id = this.resolvedIds[ specifier.source ]; | ||
@@ -279,22 +312,13 @@ specifier.module = this.bundle.moduleById[ id ]; | ||
this.exportAlls.forEach( delegate => { | ||
const id = this.resolvedIds[ delegate.source ]; | ||
delegate.module = this.bundle.moduleById[ id ]; | ||
}); | ||
this.dependencies.forEach( source => { | ||
this.exportAllModules = this.exportAllSources.map( source => { | ||
const id = this.resolvedIds[ source ]; | ||
const module = this.bundle.moduleById[ id ]; | ||
if ( !module.isExternal ) module.bindImportSpecifiers(); | ||
return this.bundle.moduleById[ id ]; | ||
}); | ||
} | ||
consolidateDependencies () { | ||
let strongDependencies = blank(); | ||
function addDependency ( dependencies, declaration ) { | ||
if ( declaration && declaration.module && !declaration.module.isExternal ) { | ||
dependencies[ declaration.module.id ] = declaration.module; | ||
return true; | ||
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 ); | ||
} | ||
@@ -304,85 +328,56 @@ } | ||
this.statements.forEach( statement => { | ||
if ( statement.isImportDeclaration && !statement.node.specifiers.length ) { | ||
// include module for its side-effects | ||
const id = this.resolvedIds[ statement.node.source.value ]; | ||
const module = this.bundle.moduleById[ id ]; | ||
if ( !module.isExternal ) strongDependencies[ module.id ] = module; | ||
// 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; | ||
} | ||
else if ( statement.isReexportDeclaration ) { | ||
if ( statement.node.specifiers ) { | ||
statement.node.specifiers.forEach( specifier => { | ||
let reexport; | ||
statement.references.forEach( reference => { | ||
const declaration = reference.scope.findDeclaration( reference.name ) || | ||
this.trace( reference.name ); | ||
let module = this; | ||
let name = specifier.exported.name; | ||
while ( !module.isExternal && module.reexports[ name ] && module.reexports[ name ].isUsed ) { | ||
reexport = module.reexports[ name ]; | ||
module = reexport.module; | ||
name = reexport.localName; | ||
} | ||
addDependency( strongDependencies, reexport ); | ||
}); | ||
if ( declaration ) { | ||
declaration.addReference( reference ); | ||
} else { | ||
// TODO handle globals | ||
this.bundle.assumedGlobals[ reference.name ] = true; | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
else { | ||
keys( statement.stronglyDependsOn ).forEach( name => { | ||
if ( statement.defines[ name ] ) return; | ||
consolidateDependencies () { | ||
let strongDependencies = blank(); | ||
let weakDependencies = blank(); | ||
addDependency( strongDependencies, this.exportDelegates[ name ] ) || | ||
addDependency( strongDependencies, this.imports[ name ] ); | ||
}); | ||
// 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[ dependency.id ] = dependency; | ||
} | ||
}); | ||
let weakDependencies = blank(); | ||
// identify strong dependencies to break ties in case of cycles | ||
this.statements.forEach( statement => { | ||
keys( statement.dependsOn ).forEach( name => { | ||
if ( statement.defines[ name ] ) return; | ||
statement.references.forEach( reference => { | ||
const declaration = reference.declaration; | ||
addDependency( weakDependencies, this.exportDelegates[ name ] ) || | ||
addDependency( weakDependencies, this.imports[ name ] ); | ||
if ( declaration && declaration.statement ) { | ||
const module = declaration.statement.module; | ||
if ( module === this ) return; | ||
// TODO disregard function declarations | ||
if ( reference.isImmediatelyUsed ) { | ||
strongDependencies[ module.id ] = module; | ||
} | ||
} | ||
}); | ||
}); | ||
// special case – `export { ... } from './other'` in entry module | ||
if ( this.exportAlls.length ) { | ||
this.exportAlls.forEach( ({ source }) => { | ||
const resolved = this.resolvedIds[ source ]; | ||
const otherModule = this.bundle.moduleById[ resolved ]; | ||
strongDependencies[ otherModule.id ] = otherModule; | ||
}); | ||
} | ||
return { strongDependencies, weakDependencies }; | ||
} | ||
defaultName () { | ||
const defaultExport = this.exports.default; | ||
if ( !defaultExport ) return null; | ||
const name = defaultExport.identifier && !defaultExport.isModified ? | ||
defaultExport.identifier : | ||
this.replacements.default; | ||
return this.replacements[ name ] || name; | ||
} | ||
findDefiningStatement ( name ) { | ||
if ( this.definitions[ name ] ) return this.definitions[ name ]; | ||
// TODO what about `default`/`*`? | ||
const importDeclaration = this.imports[ name ]; | ||
if ( !importDeclaration ) return null; | ||
return importDeclaration.module.findDefiningStatement( name ); | ||
} | ||
getExports () { | ||
@@ -399,3 +394,3 @@ let exports = blank(); | ||
this.exportAlls.forEach( ({ module }) => { | ||
this.exportAllModules.forEach( module => { | ||
module.getExports().forEach( name => { | ||
@@ -409,147 +404,18 @@ if ( name !== 'default' ) exports[ name ] = true; | ||
mark ( name ) { | ||
// shortcut cycles | ||
if ( this.marked[ name ] ) return; | ||
this.marked[ name ] = true; | ||
// The definition for this name is in a different module | ||
if ( this.imports[ name ] ) { | ||
const importDeclaration = this.imports[ name ]; | ||
importDeclaration.isUsed = true; | ||
const module = importDeclaration.module; | ||
// suggest names. TODO should this apply to non default/* imports? | ||
if ( importDeclaration.name === 'default' ) { | ||
// TODO this seems ropey | ||
const localName = importDeclaration.localName; | ||
let suggestion = this.suggestedNames[ localName ] || localName; | ||
// special case - the module has its own import by this name | ||
while ( !module.isExternal && module.imports[ suggestion ] ) { | ||
suggestion = `_${suggestion}`; | ||
} | ||
module.suggestName( 'default', suggestion ); | ||
} else if ( importDeclaration.name === '*' ) { | ||
const localName = importDeclaration.localName; | ||
const suggestion = this.suggestedNames[ localName ] || localName; | ||
module.suggestName( '*', suggestion ); | ||
module.suggestName( 'default', `${suggestion}__default` ); | ||
} | ||
if ( importDeclaration.name === 'default' ) { | ||
module.needsDefault = true; | ||
} else if ( importDeclaration.name === '*' ) { | ||
module.needsAll = true; | ||
} else { | ||
module.needsNamed = true; | ||
} | ||
if ( module.isExternal ) { | ||
module.importedByBundle.push( importDeclaration ); | ||
} | ||
else if ( importDeclaration.name === '*' ) { | ||
// we need to create an internal namespace | ||
if ( !~this.bundle.internalNamespaceModules.indexOf( module ) ) { | ||
this.bundle.internalNamespaceModules.push( module ); | ||
} | ||
module.markAllExportStatements(); | ||
} | ||
else { | ||
module.markExport( importDeclaration.name, name, this ); | ||
} | ||
} | ||
else { | ||
const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ]; | ||
if ( statement ) statement.mark(); | ||
} | ||
} | ||
markAllSideEffects () { | ||
this.statements.forEach( statement => { | ||
statement.markSideEffect(); | ||
}); | ||
} | ||
let hasSideEffect = false; | ||
markAllStatements ( isEntryModule ) { | ||
this.statements.forEach( statement => { | ||
if ( statement.isIncluded ) return; // TODO can this happen? probably not... | ||
// skip import declarations... | ||
if ( statement.isImportDeclaration ) { | ||
// ...unless they're empty, in which case assume we're importing them for the side-effects | ||
// THIS IS NOT FOOLPROOF. Probably need /*rollup: include */ or similar | ||
if ( !statement.node.specifiers.length ) { | ||
const id = this.resolvedIds[ statement.node.source.value ]; | ||
const otherModule = this.bundle.moduleById[ id ]; | ||
if ( !otherModule.isExternal ) otherModule.markAllStatements(); | ||
} | ||
} | ||
// skip `export { foo, bar, baz }`... | ||
else if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) { | ||
// ...but ensure they are defined, if this is the entry module | ||
if ( isEntryModule ) statement.mark(); | ||
} | ||
// include everything else | ||
else { | ||
statement.mark(); | ||
} | ||
if ( statement.markSideEffect() ) hasSideEffect = true; | ||
}); | ||
} | ||
markAllExportStatements () { | ||
this.statements.forEach( statement => { | ||
if ( statement.isExportDeclaration ) statement.mark(); | ||
}); | ||
return hasSideEffect; | ||
} | ||
markExport ( name, suggestedName, importer ) { | ||
const reexport = this.reexports[ name ]; | ||
const exportDeclaration = this.exports[ name ]; | ||
if ( reexport ) { | ||
reexport.isUsed = true; | ||
reexport.module.markExport( reexport.localName, suggestedName, this ); | ||
namespace () { | ||
if ( !this.declarations['*'] ) { | ||
this.declarations['*'] = new SyntheticNamespaceDeclaration( this ); | ||
} | ||
else if ( exportDeclaration ) { | ||
exportDeclaration.isUsed = true; | ||
if ( name === 'default' ) { | ||
this.needsDefault = true; | ||
this.suggestName( 'default', suggestedName ); | ||
return exportDeclaration.statement.mark(); | ||
} | ||
this.mark( exportDeclaration.localName ); | ||
} | ||
else { | ||
// See if there exists an export delegate that defines `name`. | ||
let i; | ||
for ( i = 0; i < this.exportAlls.length; i += 1 ) { | ||
const declaration = this.exportAlls[i]; | ||
if ( declaration.module.exports[ name ] ) { | ||
// It's found! This module exports `name` through declaration. | ||
// It is however not imported into this scope. | ||
this.exportDelegates[ name ] = declaration; | ||
declaration.module.markExport( name ); | ||
declaration.statement.dependsOn[ name ] = | ||
declaration.statement.stronglyDependsOn[ name ] = true; | ||
return; | ||
} | ||
} | ||
throw new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` ); | ||
} | ||
return this.declarations['*']; | ||
} | ||
@@ -646,7 +512,3 @@ | ||
rename ( name, replacement ) { | ||
this.replacements[ name ] = replacement; | ||
} | ||
render ( allBundleExports, moduleReplacements ) { | ||
render ( es6 ) { | ||
let magicString = this.magicString.clone(); | ||
@@ -660,2 +522,4 @@ | ||
statement.stringLiteralRanges.forEach( range => magicString.indentExclusionRanges.push( range ) ); | ||
// skip `export { foo, bar, baz }` | ||
@@ -668,21 +532,9 @@ if ( statement.node.type === 'ExportNamedDeclaration' ) { | ||
} | ||
// skip `export var foo;` if foo is exported | ||
if ( isEmptyExportedVarDeclaration( statement.node.declaration, allBundleExports, moduleReplacements ) ) { | ||
magicString.remove( statement.start, statement.next ); | ||
return; | ||
} | ||
} | ||
// skip empty var declarations for exported bindings | ||
// (otherwise we're left with `exports.foo;`, which is useless) | ||
if ( isEmptyExportedVarDeclaration( statement.node, allBundleExports, moduleReplacements ) ) { | ||
magicString.remove( statement.start, statement.next ); | ||
return; | ||
} | ||
// split up/remove var declarations as necessary | ||
if ( statement.node.isSynthetic ) { | ||
// insert `var/let/const` if necessary | ||
if ( !allBundleExports[ statement.node.declarations[0].id.name ] ) { | ||
const declaration = this.declarations[ statement.node.declarations[0].id.name ]; | ||
if ( !( declaration.isExported && declaration.isReassigned ) ) { // TODO encapsulate this | ||
magicString.insert( statement.start, `${statement.node.kind} ` ); | ||
@@ -694,31 +546,58 @@ } | ||
let replacements = blank(); | ||
let bundleExports = blank(); | ||
let toDeshadow = blank(); | ||
keys( statement.dependsOn ) | ||
.concat( keys( statement.defines ) ) | ||
.forEach( name => { | ||
const bundleName = moduleReplacements[ name ] || name; | ||
statement.references.forEach( reference => { | ||
const declaration = reference.declaration; | ||
if ( allBundleExports[ bundleName ] ) { | ||
bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ]; | ||
} else if ( bundleName !== name ) { // TODO weird structure | ||
replacements[ name ] = bundleName; | ||
if ( declaration ) { | ||
const { start, end } = reference; | ||
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 === reference.end - reference.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 | ||
} | ||
}); | ||
statement.replaceIdentifiers( magicString, replacements, bundleExports ); | ||
if ( reference.isShorthandProperty ) { | ||
magicString.insert( end, `: ${name}` ); | ||
} else { | ||
magicString.overwrite( start, end, name, true ); | ||
} | ||
} | ||
}); | ||
// modify exports as necessary | ||
if ( statement.isReexportDeclaration ) { | ||
// remove `export { foo } from './other'` and `export * from './other'` | ||
magicString.remove( statement.start, statement.next ); | ||
if ( keys( toDeshadow ).length ) { | ||
statement.references.forEach( reference => { | ||
if ( !reference.rewritten && reference.name in toDeshadow ) { | ||
magicString.overwrite( reference.start, reference.end, toDeshadow[ reference.name ], true ); | ||
} | ||
}); | ||
} | ||
else if ( statement.isExportDeclaration ) { | ||
// modify exports as necessary | ||
if ( statement.isExportDeclaration ) { | ||
// remove `export` from `export var foo = 42` | ||
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) { | ||
magicString.remove( statement.node.start, statement.node.declaration.start ); | ||
const name = statement.node.declaration.declarations[0].id.name; | ||
const declaration = this.declarations[ name ]; | ||
const end = declaration.isExported && 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` | ||
@@ -731,5 +610,6 @@ // TODO default exports need different treatment | ||
else if ( statement.node.type === 'ExportDefaultDeclaration' ) { | ||
const canonicalName = this.defaultName(); | ||
const defaultDeclaration = this.declarations.default; | ||
if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) { | ||
// prevent `var foo = foo` | ||
if ( defaultDeclaration.original && !defaultDeclaration.original.isReassigned ) { | ||
magicString.remove( statement.start, statement.next ); | ||
@@ -739,4 +619,6 @@ return; | ||
const defaultName = defaultDeclaration.render(); | ||
// prevent `var undefined = sideEffectyDefault(foo)` | ||
if ( canonicalName === undefined ) { | ||
if ( !defaultDeclaration.isExported && !defaultDeclaration.isUsed ) { | ||
magicString.remove( statement.start, statement.node.declaration.start ); | ||
@@ -748,5 +630,5 @@ return; | ||
if ( statement.node.declaration.type === 'FunctionExpression' ) { | ||
magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${canonicalName}` ); | ||
magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${defaultName}` ); | ||
} else { | ||
magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${canonicalName} = ` ); | ||
magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${defaultName} = ` ); | ||
} | ||
@@ -761,15 +643,51 @@ } | ||
// add namespace block if necessary | ||
const namespace = this.declarations['*']; | ||
if ( namespace && namespace.needsNamespaceBlock ) { | ||
magicString.append( '\n\n' + namespace.renderBlock( magicString.getIndentString() ) ); | ||
} | ||
return magicString.trim(); | ||
} | ||
suggestName ( defaultOrBatch, suggestion ) { | ||
// deconflict anonymous default exports with this module's definitions | ||
const shouldDeconflict = this.exports.default && this.exports.default.isAnonymous; | ||
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 ( shouldDeconflict ) suggestion = deconflict( suggestion, this.definitions ); | ||
if ( importDeclaration.name === '*' && !otherModule.isExternal ) { | ||
return otherModule.namespace(); | ||
} | ||
if ( !this.suggestedNames[ defaultOrBatch ] ) { | ||
this.suggestedNames[ defaultOrBatch ] = makeLegalIdentifier( suggestion ); | ||
return otherModule.traceExport( importDeclaration.name, this ); | ||
} | ||
return null; | ||
} | ||
traceExport ( name, importer ) { | ||
// export { foo } from './other' | ||
const reexportDeclaration = this.reexports[ name ]; | ||
if ( reexportDeclaration ) { | ||
return reexportDeclaration.module.traceExport( reexportDeclaration.localName, this ); | ||
} | ||
const exportDeclaration = this.exports[ name ]; | ||
if ( exportDeclaration ) { | ||
return this.trace( exportDeclaration.localName ); | ||
} | ||
for ( let i = 0; i < this.exportAllModules.length; i += 1 ) { | ||
const module = this.exportAllModules[i]; | ||
const declaration = module.traceExport( name, this ); | ||
if ( declaration ) return declaration; | ||
} | ||
let errorMessage = `Module ${this.id} does not export ${name}`; | ||
if ( importer ) errorMessage += ` (imported by ${importer.id})`; | ||
throw new Error( errorMessage ); | ||
} | ||
} |
@@ -1,11 +0,6 @@ | ||
import { blank, keys } from './utils/object'; | ||
import { walk } from 'estree-walker'; | ||
import Scope from './ast/Scope'; | ||
import attachScopes from './ast/attachScopes'; | ||
import getLocation from './utils/getLocation'; | ||
import walk from './ast/walk'; | ||
import Scope from './ast/Scope'; | ||
const blockDeclarations = { | ||
'const': true, | ||
'let': true | ||
}; | ||
const modifierNodes = { | ||
@@ -20,10 +15,47 @@ AssignmentExpression: 'left', | ||
function isFunctionDeclaration ( node, parent ) { | ||
// `function foo () {}` | ||
if ( node.type === 'FunctionDeclaration' ) return true; | ||
function isReference ( node, parent ) { | ||
if ( node.type === 'MemberExpression' ) { | ||
return !node.computed && isReference( node.object, node ); | ||
} | ||
// `var foo = function () {}` - same thing for present purposes | ||
if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true; | ||
if ( node.type === 'Identifier' ) { | ||
// TODO is this right? | ||
if ( parent.type === 'MemberExpression' ) return parent.computed || node === parent.object; | ||
// disregard the `bar` in { bar: foo } | ||
if ( parent.type === 'Property' && node !== parent.value ) return false; | ||
// disregard the `bar` in `class Foo { bar () {...} }` | ||
if ( parent.type === 'MethodDefinition' ) return false; | ||
// disregard the `bar` in `export { foo as bar }` | ||
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return; | ||
return true; | ||
} | ||
} | ||
class Reference { | ||
constructor ( node, scope ) { | ||
this.node = node; | ||
this.scope = scope; | ||
this.declaration = null; // bound later | ||
this.parts = []; | ||
let root = node; | ||
while ( root.type === 'MemberExpression' ) { | ||
this.parts.unshift( root.property.name ); | ||
root = root.object; | ||
} | ||
this.name = root.name; | ||
this.start = node.start; | ||
this.end = node.start + this.name.length; // can be overridden in the case of namespace members | ||
this.rewritten = false; | ||
} | ||
} | ||
export default class Statement { | ||
@@ -38,10 +70,6 @@ constructor ( node, module, start, end ) { | ||
this.scope = new Scope(); | ||
this.defines = blank(); | ||
this.modifies = blank(); | ||
this.dependsOn = blank(); | ||
this.stronglyDependsOn = blank(); | ||
this.reassigns = blank(); | ||
this.references = []; | ||
this.stringLiteralRanges = []; | ||
this.hasSideEffects = false; | ||
this.isIncluded = false; | ||
@@ -57,217 +85,82 @@ | ||
let scope = this.scope; | ||
// attach scopes | ||
attachScopes( this ); | ||
walk( this.node, { | ||
enter ( node, parent ) { | ||
let newScope; | ||
switch ( node.type ) { | ||
case 'FunctionDeclaration': | ||
scope.addDeclaration( node, false, false ); | ||
break; | ||
case 'BlockStatement': | ||
if ( parent && /Function/.test( parent.type ) ) { | ||
newScope = new Scope({ | ||
parent: scope, | ||
block: false, | ||
params: parent.params | ||
}); | ||
// named function expressions - the name is considered | ||
// part of the function's scope | ||
if ( parent.type === 'FunctionExpression' && parent.id ) { | ||
newScope.addDeclaration( parent, false, false ); | ||
} | ||
} else { | ||
newScope = new Scope({ | ||
parent: scope, | ||
block: true | ||
}); | ||
} | ||
break; | ||
case 'CatchClause': | ||
newScope = new Scope({ | ||
parent: scope, | ||
params: [ node.param ], | ||
block: true | ||
}); | ||
break; | ||
case 'VariableDeclaration': | ||
node.declarations.forEach( declarator => { | ||
const isBlockDeclaration = node.type === 'VariableDeclaration' && blockDeclarations[ node.kind ]; | ||
scope.addDeclaration( declarator, isBlockDeclaration, true ); | ||
}); | ||
break; | ||
case 'ClassDeclaration': | ||
scope.addDeclaration( node, false, false ); | ||
break; | ||
} | ||
if ( newScope ) { | ||
Object.defineProperty( node, '_scope', { | ||
value: newScope, | ||
configurable: true | ||
}); | ||
scope = newScope; | ||
} | ||
}, | ||
leave ( node ) { | ||
if ( node._scope ) { | ||
scope = scope.parent; | ||
} | ||
} | ||
// attach statement to each top-level declaration, | ||
// so we can mark statements easily | ||
this.scope.eachDeclaration( ( name, declaration ) => { | ||
declaration.statement = this; | ||
}); | ||
// This allows us to track whether we're looking at code that will | ||
// be executed immediately (either outside a function, or immediately | ||
// inside an IIFE), for the purposes of determining whether dependencies | ||
// are strong or weak. It's not bulletproof, since it wouldn't catch... | ||
// | ||
// var calledImmediately = function () { | ||
// doSomethingWith( strongDependency ); | ||
// } | ||
// calledImmediately(); | ||
// | ||
// ...but it's better than nothing | ||
// find references | ||
let { module, references, scope, stringLiteralRanges } = this; | ||
let readDepth = 0; | ||
// This allows us to track whether a modifying statement (i.e. assignment | ||
// /update expressions) need to be captured | ||
let writeDepth = 0; | ||
walk( this.node, { | ||
enter ( node, parent ) { | ||
const isStringLiteral = node.type === 'TemplateElement' || ( node.type === 'Literal' && typeof node.value === 'string' ); | ||
if ( isStringLiteral ) stringLiteralRanges.push([ node.start, node.end ]); | ||
if ( !this.isImportDeclaration ) { | ||
walk( this.node, { | ||
enter: ( node, parent ) => { | ||
if ( isFunctionDeclaration( node, parent ) ) writeDepth += 1; | ||
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1; | ||
if ( node._scope ) scope = node._scope; | ||
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1; | ||
if ( node._scope ) scope = node._scope; | ||
this.checkForReads( scope, node, parent, !readDepth ); | ||
this.checkForWrites( scope, node, writeDepth ); | ||
}, | ||
leave: ( node, parent ) => { | ||
if ( isFunctionDeclaration( node, parent ) ) writeDepth -= 1; | ||
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1; | ||
if ( node._scope ) scope = scope.parent; | ||
// special case – shorthand properties. because node.key === node.value, | ||
// we can't differentiate once we've descended into the node | ||
if ( node.type === 'Property' && node.shorthand ) { | ||
const reference = new Reference( node.key, scope ); | ||
reference.isShorthandProperty = true; // TODO feels a bit kludgy | ||
references.push( reference ); | ||
return this.skip(); | ||
} | ||
}); | ||
} | ||
keys( scope.declarations ).forEach( name => { | ||
this.defines[ name ] = true; | ||
}); | ||
} | ||
let isReassignment; | ||
checkForReads ( scope, node, parent, strong ) { | ||
if ( node.type === 'Identifier' ) { | ||
// disregard the `bar` in `foo.bar` - these appear as Identifier nodes | ||
if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) { | ||
return; | ||
} | ||
if ( parent && parent.type in modifierNodes ) { | ||
let subject = parent[ modifierNodes[ parent.type ] ]; | ||
let depth = 0; | ||
// disregard the `bar` in { bar: foo } | ||
if ( parent.type === 'Property' && node !== parent.value ) { | ||
return; | ||
} | ||
while ( subject.type === 'MemberExpression' ) { | ||
subject = subject.object; | ||
depth += 1; | ||
} | ||
// disregard the `bar` in `class Foo { bar () {...} }` | ||
if ( parent.type === 'MethodDefinition' ) return; | ||
const importDeclaration = module.imports[ subject.name ]; | ||
// disregard the `bar` in `export { foo as bar }` | ||
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return; | ||
if ( !scope.contains( subject.name ) && importDeclaration ) { | ||
const minDepth = importDeclaration.name === '*' ? | ||
2 : // cannot do e.g. `namespace.foo = bar` | ||
1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine | ||
const definingScope = scope.findDefiningScope( node.name ); | ||
if ( !definingScope || definingScope.depth === 0 ) { | ||
this.dependsOn[ node.name ] = true; | ||
if ( strong ) this.stronglyDependsOn[ node.name ] = true; | ||
} | ||
} | ||
} | ||
checkForWrites ( scope, node, writeDepth ) { | ||
const addNode = ( node, isAssignment ) => { | ||
let depth = 0; // determine whether we're illegally modifying a binding or namespace | ||
while ( node.type === 'MemberExpression' ) { | ||
node = node.object; | ||
depth += 1; | ||
} | ||
// disallow assignments/updates to imported bindings and namespaces | ||
if ( isAssignment ) { | ||
const importSpecifier = this.module.imports[ node.name ]; | ||
if ( importSpecifier && !scope.contains( node.name ) ) { | ||
const minDepth = importSpecifier.name === '*' ? | ||
2 : // cannot do e.g. `namespace.foo = bar` | ||
1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine | ||
if ( depth < minDepth ) { | ||
const err = new Error( `Illegal reassignment to import '${node.name}'` ); | ||
err.file = this.module.id; | ||
err.loc = getLocation( this.module.magicString.toString(), node.start ); | ||
throw err; | ||
if ( depth < minDepth ) { | ||
const err = new Error( `Illegal reassignment to import '${subject.name}'` ); | ||
err.file = module.id; | ||
err.loc = getLocation( module.magicString.toString(), subject.start ); | ||
throw err; | ||
} | ||
} | ||
} | ||
// special case = `export default foo; foo += 1;` - we'll | ||
// need to assign a new variable so that the exported | ||
// value is not updated by the second statement | ||
if ( this.module.exports.default && depth === 0 && this.module.exports.default.identifier === node.name ) { | ||
// but only if this is a) inside a function body or | ||
// b) after the export declaration | ||
if ( !!scope.parent || node.start > this.module.exports.default.statement.node.start ) { | ||
this.module.exports.default.isModified = true; | ||
} | ||
isReassignment = !depth; | ||
} | ||
// we track updates/reassignments to variables, to know whether we | ||
// need to rewrite it later from `foo` to `exports.foo` to keep | ||
// bindings live | ||
if ( | ||
depth === 0 && | ||
writeDepth > 0 && | ||
!scope.contains( node.name ) | ||
) { | ||
this.reassigns[ node.name ] = true; | ||
} | ||
} | ||
if ( isReference( node, parent ) ) { | ||
// function declaration IDs are a special case – they're associated | ||
// with the parent scope | ||
const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ? | ||
scope.parent : | ||
scope; | ||
// we only care about writes that happen a) at the top level, | ||
// or b) inside a function that could be immediately invoked. | ||
// Writes inside named functions are only relevant if the | ||
// function is called, in which case we don't need to do | ||
// anything (but we still need to call checkForWrites to | ||
// catch illegal reassignments to imported bindings) | ||
if ( writeDepth === 0 && node.type === 'Identifier' ) { | ||
this.modifies[ node.name ] = true; | ||
} | ||
}; | ||
const reference = new Reference( node, referenceScope ); | ||
references.push( reference ); | ||
if ( node.type === 'AssignmentExpression' ) { | ||
addNode( node.left, true ); | ||
} | ||
reference.isImmediatelyUsed = !readDepth; | ||
reference.isReassignment = isReassignment; | ||
else if ( node.type === 'UpdateExpression' ) { | ||
addNode( node.argument, true ); | ||
} | ||
else if ( node.type === 'CallExpression' ) { | ||
node.arguments.forEach( arg => addNode( arg, false ) ); | ||
// `foo.bar()` is assumed to mutate foo | ||
if ( node.callee.type === 'MemberExpression' ) { | ||
addNode( node.callee ); | ||
this.skip(); // don't descend from `foo.bar.baz` into `foo.bar` | ||
} | ||
}, | ||
leave ( node, parent ) { | ||
if ( node._scope ) scope = scope.parent; | ||
if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1; | ||
} | ||
} | ||
}); | ||
} | ||
@@ -279,30 +172,4 @@ | ||
// `export { name } from './other'` is a special case | ||
if ( this.isReexportDeclaration ) { | ||
const id = this.module.resolvedIds[ this.node.source.value ]; | ||
const otherModule = this.module.bundle.moduleById[ id ]; | ||
if ( this.node.specifiers ) { | ||
this.node.specifiers.forEach( specifier => { | ||
const reexport = this.module.reexports[ specifier.exported.name ]; | ||
reexport.isUsed = true; | ||
reexport.module = otherModule; // TODO still necessary? | ||
if ( !otherModule.isExternal ) otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); | ||
}); | ||
} else { | ||
otherModule.needsAll = true; | ||
otherModule.getExports().forEach( name => { | ||
if ( name !== 'default' ) otherModule.markExport( name, name, this.module ); | ||
}); | ||
} | ||
return; | ||
} | ||
Object.keys( this.dependsOn ).forEach( name => { | ||
if ( this.defines[ name ] ) return; // TODO maybe exclude from `this.dependsOn` in the first place? | ||
this.module.mark( name ); | ||
this.references.forEach( reference => { | ||
if ( reference.declaration ) reference.declaration.use(); | ||
}); | ||
@@ -312,3 +179,6 @@ } | ||
markSideEffect () { | ||
if ( this.isIncluded ) return; | ||
const statement = this; | ||
let hasSideEffect = false; | ||
@@ -321,4 +191,4 @@ walk( this.node, { | ||
// this statement will need to be marked | ||
if ( node.type === 'CallExpression' ) { | ||
statement.mark(); | ||
if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) { | ||
hasSideEffect = true; | ||
} | ||
@@ -330,134 +200,15 @@ | ||
const bundle = statement.module.bundle; | ||
const canonicalName = bundle.trace( statement.module, subject.name ); | ||
const declaration = statement.module.trace( subject.name ); | ||
if ( bundle.assumedGlobals[ canonicalName ] ) statement.mark(); | ||
} | ||
} | ||
}); | ||
} | ||
replaceIdentifiers ( magicString, names, bundleExports ) { | ||
const replacementStack = [ names ]; | ||
const nameList = keys( names ); | ||
let deshadowList = []; | ||
nameList.forEach( name => { | ||
const replacement = names[ name ]; | ||
deshadowList.push( replacement.split( '.' )[0] ); | ||
}); | ||
let topLevel = true; | ||
let depth = 0; | ||
walk( this.node, { | ||
enter ( node, parent ) { | ||
if ( node._skip ) return this.skip(); | ||
if ( /^Function/.test( node.type ) ) depth += 1; | ||
// `this` is undefined at the top level of ES6 modules | ||
if ( node.type === 'ThisExpression' && depth === 0 ) { | ||
magicString.overwrite( node.start, node.end, 'undefined', true ); | ||
} | ||
// special case - variable declarations that need to be rewritten | ||
// as bundle exports | ||
if ( topLevel ) { | ||
if ( node.type === 'VariableDeclaration' ) { | ||
// if this contains a single declarator, and it's one that | ||
// needs to be rewritten, we replace the whole lot | ||
const name = node.declarations[0].id.name; | ||
if ( node.declarations.length === 1 && bundleExports[ name ] ) { | ||
magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ], true ); | ||
node.declarations[0].id._skip = true; | ||
} | ||
// otherwise, we insert the `exports.foo = foo` after the declaration | ||
else { | ||
const exportInitialisers = node.declarations | ||
.map( declarator => declarator.id.name ) | ||
.filter( name => !!bundleExports[ name ] ) | ||
.map( name => `\n${bundleExports[name]} = ${name};` ) | ||
.join( '' ); | ||
if ( exportInitialisers ) { | ||
// TODO clean this up | ||
try { | ||
magicString.insert( node.end, exportInitialisers ); | ||
} catch ( err ) { | ||
magicString.append( exportInitialisers ); | ||
} | ||
} | ||
} | ||
if ( !declaration || declaration.statement.isIncluded ) { | ||
hasSideEffect = true; | ||
} | ||
} | ||
const scope = node._scope; | ||
if ( scope ) { | ||
topLevel = false; | ||
let newNames = blank(); | ||
let hasReplacements; | ||
keys( names ).forEach( name => { | ||
if ( !scope.declarations[ name ] ) { | ||
newNames[ name ] = names[ name ]; | ||
hasReplacements = true; | ||
} | ||
}); | ||
deshadowList.forEach( name => { | ||
if ( scope.declarations[ name ] ) { | ||
newNames[ name ] = name + '$$'; // TODO better mechanism | ||
hasReplacements = true; | ||
} | ||
}); | ||
if ( !hasReplacements && depth > 0 ) { | ||
return this.skip(); | ||
} | ||
names = newNames; | ||
replacementStack.push( newNames ); | ||
} | ||
if ( node.type !== 'Identifier' ) return; | ||
// if there's no replacement, or it's the same, there's nothing more to do | ||
const name = names[ node.name ]; | ||
if ( !name || name === node.name ) return; | ||
// shorthand properties (`obj = { foo }`) need to be expanded | ||
if ( parent.type === 'Property' && parent.shorthand ) { | ||
magicString.insert( node.end, `: ${name}` ); | ||
parent.key._skip = true; | ||
parent.value._skip = true; // redundant, but defensive | ||
return; | ||
} | ||
// property names etc can be disregarded | ||
if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return; | ||
if ( parent.type === 'Property' && node !== parent.value ) return; | ||
if ( parent.type === 'MethodDefinition' && node === parent.key ) return; | ||
if ( parent.type === 'FunctionExpression' ) return; | ||
if ( /Function/.test( parent.type ) && ~parent.params.indexOf( node ) ) return; | ||
// TODO others...? | ||
// all other identifiers should be overwritten | ||
magicString.overwrite( node.start, node.end, name, true ); | ||
}, | ||
leave ( node ) { | ||
if ( /^Function/.test( node.type ) ) depth -= 1; | ||
if ( node._scope ) { | ||
replacementStack.pop(); | ||
names = replacementStack[ replacementStack.length - 1 ]; | ||
} | ||
if ( hasSideEffect ) this.skip(); | ||
} | ||
}); | ||
return magicString; | ||
if ( hasSideEffect ) statement.mark(); | ||
return hasSideEffect; | ||
} | ||
@@ -464,0 +215,0 @@ |
@@ -10,3 +10,3 @@ import { keys } from './object'; | ||
.concat( keys( bundle.entryModule.reexports ) ) | ||
.concat( bundle.entryModule.exportAlls ); // not keys, but makes our job easier this way | ||
.concat( bundle.entryModule.exportAllSources ); // not keys, but makes our job easier this way | ||
@@ -13,0 +13,0 @@ if ( exportMode === 'default' ) { |
@@ -61,8 +61,6 @@ // TODO does this all work on windows? | ||
while ( parts[0] && parts[0][0] === '.' ) { | ||
while ( parts[0] === '.' || parts[0] === '..' ) { | ||
const part = parts.shift(); | ||
if ( part === '..' ) { | ||
resolvedParts.pop(); | ||
} else if ( part !== '.' ) { | ||
throw new Error( `Unexpected path part (${part})` ); | ||
} | ||
@@ -69,0 +67,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
140
911038
7
10875
+ Addedestree-walker@^0.1.3
+ Addedestree-walker@0.1.3(transitive)