Socket
Socket
Sign inDemoInstall

rollup

Package Overview
Dependencies
Maintainers
3
Versions
820
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rollup - npm Package Compare versions

Comparing version 0.17.3 to 0.18.0

src/ast/attachScopes.js

1

bin/help.md

@@ -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:

3

bin/runRollup.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc