Browse Source

Merge pull request #79 from rollup/0.14.0

0.14.0
contingency-plan v0.14.0
Rich Harris 9 years ago
parent
commit
7fdea10b36
  1. 19
      .eslintrc
  2. 10
      .jshintrc
  3. 7
      CHANGELOG.md
  4. 8
      package.json
  5. 194
      src/Bundle.js
  6. 15
      src/ExternalModule.js
  7. 211
      src/Module.js
  8. 7
      src/Statement.js
  9. 2
      src/ast/Scope.js
  10. 21
      src/finalisers/es6.js
  11. 10
      src/finalisers/shared/getExportBlock.js
  12. 6
      test/form/dedupes-external-imports/_config.js
  13. 28
      test/form/dedupes-external-imports/_expected/amd.js
  14. 28
      test/form/dedupes-external-imports/_expected/cjs.js
  15. 26
      test/form/dedupes-external-imports/_expected/es6.js
  16. 28
      test/form/dedupes-external-imports/_expected/iife.js
  17. 32
      test/form/dedupes-external-imports/_expected/umd.js
  18. 8
      test/form/dedupes-external-imports/bar.js
  19. 8
      test/form/dedupes-external-imports/baz.js
  20. 8
      test/form/dedupes-external-imports/foo.js
  21. 7
      test/form/dedupes-external-imports/main.js
  22. 3
      test/form/unused-default-exports/_config.js
  23. 14
      test/form/unused-default-exports/_expected/amd.js
  24. 12
      test/form/unused-default-exports/_expected/cjs.js
  25. 10
      test/form/unused-default-exports/_expected/es6.js
  26. 14
      test/form/unused-default-exports/_expected/iife.js
  27. 18
      test/form/unused-default-exports/_expected/umd.js
  28. 8
      test/form/unused-default-exports/foo.js
  29. 2
      test/form/unused-default-exports/main.js
  30. 3
      test/function/deconflicts-generated-default-names/_config.js
  31. 9
      test/function/deconflicts-generated-default-names/foo.js
  32. 3
      test/function/deconflicts-generated-default-names/main.js
  33. 3
      test/function/export-default-as-b/_config.js
  34. 1
      test/function/export-default-as-b/bar.js
  35. 1
      test/function/export-default-as-b/baz.js
  36. 3
      test/function/export-default-as-b/main.js
  37. 8
      test/function/export-default-as-c/_config.js
  38. 1
      test/function/export-default-as-c/baz.js
  39. 5
      test/function/export-default-as-c/main.js
  40. 1
      test/function/export-default-as-c/namespace.js
  41. 8
      test/function/export-default-as/_config.js
  42. 1
      test/function/export-default-as/foo.js
  43. 1
      test/function/export-default-as/main.js
  44. 4
      test/test.js

19
.eslintrc

@ -0,0 +1,19 @@
{
"rules": {
"indent": [ 2, "tab", { "SwitchCase": 1}],
"quotes": [ 2, "single" ],
"linebreak-style": [ 2, "unix" ],
"semi": [ 2, "always" ],
"no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
"no-cond-assign": [ 0 ]
},
"env": {
"es6": true,
"browser": true,
"node": true
},
"extends": "eslint:recommended",
"ecmaFeatures": {
"modules": true
}
}

10
.jshintrc

@ -1,10 +0,0 @@
{
"esnext": true,
"undef": true,
"unused": true,
"globals": {
"process": true,
"module": true,
"assert": true
}
}

7
CHANGELOG.md

@ -1,5 +1,12 @@
# rollup changelog
## 0.14.0
* Internal refactoring
* Correctly deconflict generated default export names ([#72](https://github.com/rollup/rollup/issues/72))
* Handle `export { x } from 'y'` declarations ([#74](https://github.com/rollup/rollup/issues/74))
* Dedupe named imports from external modules in ES6 bundles ([#77](https://github.com/rollup/rollup/issues/77))
## 0.13.0
* Support `banner` and `footer` options ([#66](https://github.com/rollup/rollup/pull/66))

8
package.json

@ -1,6 +1,6 @@
{
"name": "rollup",
"version": "0.13.0",
"version": "0.14.0",
"description": "Next-generation ES6 module bundler",
"main": "dist/rollup.js",
"jsnext:main": "src/rollup.js",
@ -11,7 +11,8 @@
"test": "mocha",
"pretest": "npm run build",
"build": "gobble build -f dist",
"prepublish": "npm test"
"prepublish": "npm test",
"lint": "eslint src"
},
"repository": {
"type": "git",
@ -33,12 +34,13 @@
"devDependencies": {
"babel-core": "^5.5.8",
"console-group": "^0.1.2",
"eslint": "^1.1.0",
"gobble": "^0.10.1",
"gobble-babel": "^5.5.8",
"gobble-browserify": "^0.6.1",
"gobble-cli": "^0.4.2",
"gobble-esperanto-bundle": "^0.2.0",
"gobble-rollup": "^0.5.0",
"gobble-rollup": "^0.6.0",
"gobble-rollup-babel": "^0.1.0",
"mocha": "^2.2.4",
"source-map": "^0.1.40"

194
src/Bundle.js

@ -30,7 +30,6 @@ export default class Bundle {
transform: ensureArray( options.transform )
};
this.varExports = blank();
this.toExport = null;
this.modulePromises = blank();
@ -52,10 +51,12 @@ export default class Bundle {
this.entryModule = entryModule;
if ( defaultExport ) {
entryModule.needsDefault = true;
// `export default function foo () {...}` -
// use the declared name for the export
if ( defaultExport.declaredName ) {
entryModule.suggestName( 'default', defaultExport.declaredName );
if ( defaultExport.identifier ) {
entryModule.suggestName( 'default', defaultExport.identifier );
}
// `export default a + b` - generate an export name
@ -87,89 +88,85 @@ export default class Bundle {
});
}
// TODO would be better to deconflict once, rather than per-render
deconflict ( es6 ) {
let definers = blank();
let conflicts = blank();
let usedNames = blank();
// ensure no conflicts with globals
keys( this.assumedGlobals ).forEach( name => usedNames[ name ] = true );
let allReplacements = blank();
// Assign names to external modules
this.externalModules.forEach( module => {
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
while ( definers[ name ] ) {
conflicts[ name ] = true;
name = `_${name}`;
}
// while we're here...
allReplacements[ module.id ] = blank();
definers[ name ] = [ module ];
module.name = name;
this.assumedGlobals[ name ] = true;
// TODO is this necessary in the ES6 case?
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
module.name = getSafeName( name );
});
// Discover conflicts (i.e. two statements in separate modules both define `foo`)
this.orderedModules.forEach( module => {
module.statements.forEach( statement => {
const names = keys( statement.defines );
// with default exports that are expressions (`export default 42`),
// we need to ensure that the name chosen for the expression does
// not conflict
if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const name = module.getCanonicalName( 'default', es6 );
let i = this.orderedModules.length;
while ( i-- ) {
const module = this.orderedModules[i];
const isProxy = statement.node.declaration && statement.node.declaration.type === 'Identifier';
const shouldDeconflict = !isProxy || ( module.getCanonicalName( statement.node.declaration.name, es6 ) !== name );
// while we're here...
allReplacements[ module.id ] = blank();
if ( shouldDeconflict && !~names.indexOf( name ) ) {
names.push( name );
keys( module.definitions ).forEach( name => {
const safeName = getSafeName( name );
if ( safeName !== name ) {
module.rename( name, safeName );
allReplacements[ module.id ][ name ] = safeName;
}
});
}
names.forEach( name => {
if ( definers[ name ] ) {
conflicts[ name ] = true;
} else {
definers[ name ] = [];
// 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;
}
// TODO in good js, there shouldn't be duplicate definitions
// per module... but some people write bad js
definers[ name ].push( module );
});
});
});
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
// Ensure we don't conflict with globals
keys( this.assumedGlobals ).forEach( name => {
if ( definers[ name ] ) {
conflicts[ name ] = true;
const defaultName = getSafeName( module.suggestedNames.default );
module.replacements.default = defaultName;
}
});
// Rename conflicting identifiers so they can live in the same scope
keys( conflicts ).forEach( name => {
const modules = definers[ name ];
this.orderedModules.forEach( module => {
keys( module.imports ).forEach( localName => {
if ( !module.imports[ localName ].isUsed ) return;
if ( !this.assumedGlobals[ name ] ) {
// the module closest to the entryModule gets away with
// keeping things as they are, unless we have a conflict
// with a global name
modules.pop();
const bundleName = this.trace( module, localName, es6 );
if ( bundleName !== localName ) {
allReplacements[ module.id ][ localName ] = bundleName;
}
modules.forEach( module => {
const replacement = getSafeName( name );
module.rename( name, replacement );
});
});
function getSafeName ( name ) {
while ( conflicts[ name ] ) {
while ( usedNames[ name ] ) {
name = `_${name}`;
}
conflicts[ name ] = true;
usedNames[ name ] = true;
return name;
}
return allReplacements;
}
fetchModule ( importee, importer ) {
@ -272,7 +269,10 @@ export default class Bundle {
render ( options = {} ) {
const format = options.format || 'es6';
this.deconflict( format === 'es6' );
const allReplacements = this.deconflict( format === 'es6' );
// Determine export mode - 'default', 'named', 'none'
const exportMode = getExportMode( this, options.exports );
// If we have named exports from the bundle, and those exports
// are assigned to *within* the bundle, we may need to rewrite e.g.
@ -290,18 +290,19 @@ export default class Bundle {
//
// This doesn't apply if the bundle is exported as ES6!
let allBundleExports = blank();
let varExports = blank();
if ( format !== 'es6' ) {
if ( format !== 'es6' && exportMode === 'named' ) {
keys( this.entryModule.exports ).forEach( key => {
const exportDeclaration = this.entryModule.exports[ key ];
const originalDeclaration = this.entryModule.findDeclaration( exportDeclaration.localName );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.entryModule.getCanonicalName( exportDeclaration.localName, false );
const canonicalName = this.trace( this.entryModule, exportDeclaration.localName, false );
allBundleExports[ canonicalName ] = `exports.${key}`;
this.varExports[ key ] = true;
varExports[ key ] = true;
}
});
}
@ -309,13 +310,13 @@ export default class Bundle {
// since we're rewriting variable exports, we want to
// ensure we don't try and export them again at the bottom
this.toExport = keys( this.entryModule.exports )
.filter( key => !this.varExports[ key ] );
.filter( key => !varExports[ key ] );
let magicString = new MagicString.Bundle({ separator: '\n\n' });
this.orderedModules.forEach( module => {
const source = module.render( allBundleExports, format );
const source = module.render( allBundleExports, allReplacements[ module.id ], format );
if ( source.toString().length ) {
magicString.addSource( source );
}
@ -326,10 +327,20 @@ export default class Bundle {
const namespaceBlock = this.internalNamespaceModules.map( module => {
const exportKeys = keys( module.exports );
return `var ${module.getCanonicalName('*', format === 'es6')} = {\n` +
return `var ${module.replacements['*']} = {\n` +
exportKeys.map( key => {
const localName = module.exports[ key ].localName;
return `${indentString}get ${key} () { return ${module.getCanonicalName(localName, format === 'es6')}; }`;
let actualModule = module;
let exportDeclaration = module.exports[ key ];
// special case - `export { default as foo } from './foo'`
while ( exportDeclaration.linkedImport ) {
actualModule = exportDeclaration.linkedImport.module;
exportDeclaration = actualModule.exports[ exportDeclaration.linkedImport.name ];
}
let localName = exportDeclaration.localName;
localName = actualModule.replacements[ localName ] || localName;
return `${indentString}get ${key} () { return ${localName}; }`; // TODO...
}).join( ',\n' ) +
`\n};\n\n`;
}).join( '' );
@ -344,7 +355,7 @@ export default class Bundle {
magicString = finalise( this, magicString.trim(), {
// Determine export mode - 'default', 'named', 'none'
exportMode: getExportMode( this, options.exports ),
exportMode,
// Determine indentation
indentString: getIndentString( magicString, options )
@ -453,4 +464,55 @@ export default class Bundle {
return ordered;
}
trace ( module, localName, es6 ) {
const importDeclaration = module.imports[ localName ];
// defined in this module
if ( !importDeclaration ) {
if ( localName === 'default' ) return module.defaultName();
return module.replacements[ localName ] || localName;
}
// defined elsewhere
const otherModule = importDeclaration.module;
if ( otherModule.isExternal ) {
if ( importDeclaration.name === 'default' ) {
return otherModule.needsNamed && !es6 ?
`${otherModule.name}__default` :
otherModule.name;
}
if ( importDeclaration.name === '*' ) {
return otherModule.name;
}
return es6 ?
importDeclaration.name :
`${otherModule.name}.${importDeclaration.name}`;
}
if ( importDeclaration.name === '*' ) {
return otherModule.replacements[ '*' ];
}
if ( importDeclaration.name === 'default' ) {
return otherModule.defaultName();
}
const exportDeclaration = otherModule.exports[ importDeclaration.name ];
if ( exportDeclaration ) return this.trace( otherModule, exportDeclaration.localName );
for ( let i = 0; i < otherModule.exportDelegates.length; i += 1 ) {
const delegate = otherModule.exportDelegates[i];
const delegateExportDeclaration = delegate.module.exports[ importDeclaration.name ];
if ( delegateExportDeclaration ) {
return this.trace( delegate.module, delegateExportDeclaration.localName );
}
}
throw new Error( 'Could not trace binding' );
}
}

15
src/ExternalModule.js

@ -8,7 +8,6 @@ export default class ExternalModule {
this.isExternal = true;
this.importedByBundle = [];
this.canonicalNames = blank();
this.suggestedNames = blank();
this.needsDefault = false;
@ -26,20 +25,8 @@ export default class ExternalModule {
return null;
}
getCanonicalName ( name, es6 ) {
if ( name === 'default' ) {
return this.needsNamed && !es6 ? `${this.name}__default` : this.name;
}
if ( name === '*' ) {
return this.name; // TODO is this correct in ES6?
}
return es6 ? ( this.canonicalNames[ name ] || name ) : `${this.name}.${name}`;
}
rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement;
// noop
}
suggestName ( exportName, suggestion ) {

211
src/Module.js

@ -9,7 +9,7 @@ import { first, sequence } from './utils/promise';
import getLocation from './utils/getLocation';
import makeLegalIdentifier from './utils/makeLegalIdentifier';
const emptyArrayPromise = Promise.resolve([]);
const emptyPromise = Promise.resolve();
function deconflict ( name, names ) {
while ( name in names ) {
@ -19,11 +19,11 @@ function deconflict ( name, names ) {
return name;
}
function isEmptyExportedVarDeclaration ( node, module, allBundleExports, es6 ) {
function isEmptyExportedVarDeclaration ( node, module, allBundleExports, moduleReplacements, es6 ) {
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false;
const name = node.declarations[0].id.name;
const canonicalName = module.getCanonicalName( name, es6 );
const canonicalName = moduleReplacements[ name ] || name;
return canonicalName in allBundleExports;
}
@ -51,7 +51,7 @@ export default class Module {
this.suggestedNames = blank();
this.comments = [];
this.statements = this._parse( ast );
this.statements = this.parse( ast );
// imports and exports, indexed by ID
this.imports = blank();
@ -62,7 +62,7 @@ export default class Module {
// array of all-export sources
this.exportDelegates = [];
this.canonicalNames = blank();
this.replacements = blank();
this.definitions = blank();
this.definitionPromises = blank();
@ -82,14 +82,16 @@ export default class Module {
const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type );
const declaredName = isDeclaration && node.declaration.id.name;
const identifier = node.declaration.type === 'Identifier' && 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: declaredName || 'default',
declaredName,
localName: identifier || 'default',
identifier,
isDeclaration,
isAnonymous,
@ -107,19 +109,21 @@ export default class Module {
const localName = specifier.local.name;
const exportedName = specifier.exported.name;
this.exports[ exportedName ] = {
localName,
exportedName
};
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName,
localName: exportedName,
name: localName
};
}
this.exports[ exportedName ] = {
statement,
localName,
exportedName,
linkedImport: source ? this.imports[ localName ] : null
};
});
}
@ -253,6 +257,18 @@ export default class Module {
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 ];
@ -276,6 +292,8 @@ export default class Module {
const module = importDeclaration.module;
if ( module.isExternal ) return null;
if ( importDeclaration.name === '*' ) return null;
if ( importDeclaration.name === 'default' ) return null;
const exportDeclaration = module.exports[ importDeclaration.name ];
return module.findDeclaration( exportDeclaration.localName );
@ -293,62 +311,10 @@ export default class Module {
return null;
}
getCanonicalName ( localName, es6 ) {
// Special case
if ( localName === 'default' && ( this.exports.default.isModified || !this.suggestedNames.default ) ) {
let canonicalName = makeLegalIdentifier( this.id.replace( dirname( this.bundle.entryModule.id ) + '/', '' ).replace( /\.js$/, '' ) );
return deconflict( canonicalName, this.definitions );
}
if ( this.suggestedNames[ localName ] ) {
localName = this.suggestedNames[ localName ];
}
const id = localName + ( es6 ? '-es6' : '' ); // TODO ugh this seems like a terrible hack
if ( !this.canonicalNames[ id ] ) {
let canonicalName;
if ( this.imports[ localName ] ) {
const importDeclaration = this.imports[ localName ];
const module = importDeclaration.module;
if ( importDeclaration.name === '*' ) {
canonicalName = module.suggestedNames[ '*' ];
} else {
let exporterLocalName;
if ( module.isExternal ) {
exporterLocalName = importDeclaration.name;
} else {
const exportDeclaration = module.exports[ importDeclaration.name ];
// The export declaration of the particular name is known.
if (exportDeclaration) {
exporterLocalName = exportDeclaration.localName;
} else { // export * from '...'
exporterLocalName = importDeclaration.name;
}
}
canonicalName = module.getCanonicalName( exporterLocalName, es6 );
}
}
else {
canonicalName = localName;
}
this.canonicalNames[ id ] = canonicalName;
}
return this.canonicalNames[ id ];
}
mark ( name ) {
// shortcut cycles. TODO this won't work everywhere...
// shortcut cycles
if ( this.definitionPromises[ name ] ) {
return emptyArrayPromise;
return emptyPromise;
}
let promise;
@ -356,6 +322,7 @@ export default class Module {
// The definition for this name is in a different module
if ( this.imports[ name ] ) {
const importDeclaration = this.imports[ name ];
importDeclaration.isUsed = true;
promise = this.bundle.fetchModule( importDeclaration.source, this.id )
.then( module => {
@ -380,8 +347,7 @@ export default class Module {
module.suggestName( 'default', `${suggestion}__default` );
}
if ( module.isExternal ) {
if ( importDeclaration.name === 'default' ) {
if ( importDeclaration.name === 'default' && ( module.isExternal || !module.exports.default.linkedImport ) ) { // special case - exclude `export { default } from ...`
module.needsDefault = true;
} else if ( importDeclaration.name === '*' ) {
module.needsAll = true;
@ -389,8 +355,9 @@ export default class Module {
module.needsNamed = true;
}
if ( module.isExternal ) {
module.importedByBundle.push( importDeclaration );
return emptyArrayPromise;
return emptyPromise;
}
if ( importDeclaration.name === '*' ) {
@ -399,7 +366,7 @@ export default class Module {
this.bundle.internalNamespaceModules.push( module );
}
return module.markAllStatements();
return module.markAllExportStatements();
}
const exportDeclaration = module.exports[ importDeclaration.name ];
@ -429,53 +396,21 @@ export default class Module {
}
exportDeclaration.isUsed = true;
return module.mark( exportDeclaration.localName );
});
}
// The definition is in this module
else if ( name === 'default' && this.exports.default.isDeclaration ) {
// We have something like `export default foo` - so we just start again,
// searching for `foo` instead of default
promise = this.mark( this.exports.default.name );
}
else {
let statement;
statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
promise = statement && !statement.isIncluded ? statement.mark() : emptyArrayPromise;
// Special case - `export default foo; foo += 1` - need to be
// vigilant about maintaining the correct order of the export
// declaration. Otherwise, the export declaration will always
// go at the end of the expansion, because the expansion of
// `foo` will include statements *after* the declaration
if ( name === 'default' && this.exports.default.identifier && this.exports.default.isModified ) {
const defaultExportStatement = this.exports.default.statement;
promise = promise.then( statements => {
// remove the default export statement...
// TODO could this be statements.pop()?
statements.splice( statements.indexOf( defaultExportStatement ), 1 );
let i = statements.length;
let inserted = false;
while ( i-- ) {
if ( statements[i].module === this && statements[i].index < defaultExportStatement.index ) {
statements.splice( i + 1, 0, defaultExportStatement );
inserted = true;
break;
}
if ( importDeclaration.name === 'default' ) {
return exportDeclaration.statement.mark();
}
if ( !inserted ) statements.push( statement );
return statements;
return module.mark( exportDeclaration.localName );
});
}
else {
const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
promise = statement && statement.mark();
}
this.definitionPromises[ name ] = promise || emptyArrayPromise;
this.definitionPromises[ name ] = promise || emptyPromise;
return this.definitionPromises[ name ];
}
@ -516,8 +451,15 @@ export default class Module {
});
}
// TODO rename this to parse, once https://github.com/rollup/rollup/issues/42 is fixed
_parse ( ast ) {
markAllExportStatements () {
return sequence( this.statements, statement => {
return statement.isExportDeclaration ?
statement.mark() :
null;
});
}
parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be)
if ( !ast ) {
// Try to extract a list of top-level statements/declarations. If
@ -554,7 +496,7 @@ export default class Module {
// remove the leading var/let/const
this.magicString.remove( node.start, node.declarations[0].start );
node.declarations.forEach( ( declarator, i ) => {
node.declarations.forEach( declarator => {
const { start, end } = declarator;
const syntheticNode = {
@ -601,17 +543,13 @@ export default class Module {
}
rename ( name, replacement ) {
// TODO again, hacky...
this.canonicalNames[ name ] = this.canonicalNames[ name + '-es6' ] = replacement;
this.replacements[ name ] = replacement;
}
render ( allBundleExports, format ) {
render ( allBundleExports, moduleReplacements, format ) {
let magicString = this.magicString.clone();
let previousIndex = -1;
let previousMargin = 0;
this.statements.forEach( ( statement, i ) => {
this.statements.forEach( statement => {
if ( !statement.isIncluded ) {
magicString.remove( statement.start, statement.next );
return;
@ -623,10 +561,10 @@ export default class Module {
if ( statement.node.specifiers.length ) {
magicString.remove( statement.start, statement.next );
return;
};
}
// skip `export var foo;` if foo is exported
if ( isEmptyExportedVarDeclaration( statement.node.declaration, statement.module, allBundleExports, format === 'es6' ) ) {
if ( isEmptyExportedVarDeclaration( statement.node.declaration, this, allBundleExports, moduleReplacements, format === 'es6' ) ) {
magicString.remove( statement.start, statement.next );
return;
}
@ -634,7 +572,7 @@ export default class Module {
// skip empty var declarations for exported bindings
// (otherwise we're left with `exports.foo;`, which is useless)
if ( isEmptyExportedVarDeclaration( statement.node, statement.module, allBundleExports, format === 'es6' ) ) {
if ( isEmptyExportedVarDeclaration( statement.node, this, allBundleExports, moduleReplacements, format === 'es6' ) ) {
magicString.remove( statement.start, statement.next );
return;
}
@ -655,12 +593,12 @@ export default class Module {
keys( statement.dependsOn )
.concat( keys( statement.defines ) )
.forEach( name => {
const canonicalName = statement.module.getCanonicalName( name, format === 'es6' );
const bundleName = moduleReplacements[ name ] || name;
if ( allBundleExports[ canonicalName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ canonicalName ];
} else if ( name !== canonicalName ) {
replacements[ name ] = canonicalName;
if ( allBundleExports[ bundleName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ];
} else if ( bundleName !== name ) { // TODO weird structure
replacements[ name ] = bundleName;
}
});
@ -680,14 +618,19 @@ export default class Module {
}
else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const module = statement.module;
const canonicalName = module.getCanonicalName( 'default', format === 'es6' );
const canonicalName = this.defaultName();
if ( statement.node.declaration.type === 'Identifier' && canonicalName === module.getCanonicalName( statement.node.declaration.name, format === 'es6' ) ) {
if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) {
magicString.remove( statement.start, statement.next );
return;
}
// prevent `var undefined = sideEffectyDefault(foo)`
if ( canonicalName === undefined ) {
magicString.remove( statement.start, statement.node.declaration.start );
return;
}
// anonymous functions should be converted into declarations
if ( statement.node.declaration.type === 'FunctionExpression' ) {
magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${canonicalName}` );

7
src/Statement.js

@ -158,6 +158,9 @@ export default class Statement {
// disregard the `bar` in `class Foo { bar () {...} }`
if ( parent.type === 'MethodDefinition' ) return;
// disregard the `bar` in `export { foo as bar }`
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return;
const definingScope = scope.findDefiningScope( node.name );
if ( ( !definingScope || definingScope.depth === 0 ) && !this.defines[ node.name ] ) {
@ -367,4 +370,8 @@ export default class Statement {
return magicString;
}
toString () {
return this.module.magicString.slice( this.start, this.end );
}
}

2
src/ast/Scope.js

@ -48,7 +48,7 @@ export default class Scope {
}
findDefiningScope ( name ) {
if ( !!this.declarations[ name ] ) {
if ( this.declarations[ name ] ) {
return this;
}

21
src/finalisers/es6.js

@ -1,6 +1,16 @@
import { keys } from '../utils/object';
import { blank, keys } from '../utils/object';
export default function es6 ( bundle, magicString, { exportMode }, options ) {
function uniqueNames ( declarations ) {
let uniques = blank();
declarations
.filter( declaration => !/^(default|\*)$/.test( declaration.name ) )
.forEach( declaration => uniques[ declaration.name ] = true );
return keys( uniques );
}
export default function es6 ( bundle, magicString ) {
const importBlock = bundle.externalModules
.map( module => {
const specifiers = [];
@ -16,10 +26,7 @@ export default function es6 ( bundle, magicString, { exportMode }, options ) {
}
if ( module.needsNamed ) {
specifiers.push( '{ ' + module.importedByBundle
.filter( declaration => !/^(default|\*)$/.test( declaration.name ) )
.map( ({ name, localName }) =>
name === localName ? name : `${name} as ${localName}` )
specifiers.push( '{ ' + uniqueNames( module.importedByBundle )
.join( ', ' ) + ' }' );
}
@ -37,7 +44,7 @@ export default function es6 ( bundle, magicString, { exportMode }, options ) {
const exportBlock = keys( exports ).map( exportedName => {
const specifier = exports[ exportedName ];
const canonicalName = bundle.entryModule.getCanonicalName( specifier.localName );
const canonicalName = bundle.entryModule.replacements[ specifier.localName ] || specifier.localName;
if ( exportedName === 'default' ) {
return `export default ${canonicalName};`;

10
src/finalisers/shared/getExportBlock.js

@ -1,12 +1,18 @@
export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) {
if ( exportMode === 'default' ) {
return `${mechanism} ${bundle.entryModule.getCanonicalName('default')};`;
const defaultExport = bundle.entryModule.exports.default;
const defaultExportName = bundle.entryModule.replacements.default ||
defaultExport.identifier;
return `${mechanism} ${defaultExportName};`;
}
return bundle.toExport
.map( name => {
const prop = name === 'default' ? `['default']` : `.${name}`;
return `exports${prop} = ${bundle.entryModule.getCanonicalName(name)};`;
name = bundle.trace( bundle.entryModule, name );
return `exports${prop} = ${name};`;
})
.join( '\n' );
}

6
test/form/dedupes-external-imports/_config.js

@ -0,0 +1,6 @@
module.exports = {
description: 'dedupes external imports',
options: {
external: [ 'external' ]
}
};

28
test/form/dedupes-external-imports/_expected/amd.js

@ -0,0 +1,28 @@
define(['external'], function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
});

28
test/form/dedupes-external-imports/_expected/cjs.js

@ -0,0 +1,28 @@
'use strict';
var external = require('external');
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

26
test/form/dedupes-external-imports/_expected/es6.js

@ -0,0 +1,26 @@
import { Component } from 'external';
class Foo extends Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

28
test/form/dedupes-external-imports/_expected/iife.js

@ -0,0 +1,28 @@
(function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
})(external);

32
test/form/dedupes-external-imports/_expected/umd.js

@ -0,0 +1,32 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('external')) :
typeof define === 'function' && define.amd ? define(['external'], factory) :
factory(global.external);
}(this, function (external) { 'use strict';
class Foo extends external.Component {
constructor () {
super();
this.isFoo = true;
}
}
class Bar extends external.Component {
constructor () {
super();
this.isBar = true;
}
}
class Baz extends external.Component {
constructor () {
super();
this.isBaz = true;
}
}
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();
}));

8
test/form/dedupes-external-imports/bar.js

@ -0,0 +1,8 @@
import { Component } from 'external';
export default class Bar extends Component {
constructor () {
super();
this.isBar = true;
}
}

8
test/form/dedupes-external-imports/baz.js

@ -0,0 +1,8 @@
import { Component as Comp } from 'external';
export default class Baz extends Comp {
constructor () {
super();
this.isBaz = true;
}
}

8
test/form/dedupes-external-imports/foo.js

@ -0,0 +1,8 @@
import { Component } from 'external';
export default class Foo extends Component {
constructor () {
super();
this.isFoo = true;
}
}

7
test/form/dedupes-external-imports/main.js

@ -0,0 +1,7 @@
import Foo from './foo';
import Bar from './bar';
import Baz from './baz';
const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

3
test/form/unused-default-exports/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'does not name unused-but-included default export'
};

14
test/form/unused-default-exports/_expected/amd.js

@ -0,0 +1,14 @@
define(function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
});

12
test/form/unused-default-exports/_expected/cjs.js

@ -0,0 +1,12 @@
'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );

10
test/form/unused-default-exports/_expected/es6.js

@ -0,0 +1,10 @@
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );

14
test/form/unused-default-exports/_expected/iife.js

@ -0,0 +1,14 @@
(function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
})();

18
test/form/unused-default-exports/_expected/umd.js

@ -0,0 +1,18 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
mutate( foo );
assert.equal( foo.value, 2 );
}));

8
test/form/unused-default-exports/foo.js

@ -0,0 +1,8 @@
export var foo = { value: 1 };
function mutate ( obj ) {
obj.value += 1;
return obj;
}
export default mutate( foo );

2
test/form/unused-default-exports/main.js

@ -0,0 +1,2 @@
import { foo } from './foo';
assert.equal( foo.value, 2 );

3
test/function/deconflicts-generated-default-names/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'deconflicts generated default export names'
};

9
test/function/deconflicts-generated-default-names/foo.js

@ -0,0 +1,9 @@
export default notActuallyFoo;
function notActuallyFoo () {
return 'not ' + foo();
}
function foo () {
return 'actually foo';
}

3
test/function/deconflicts-generated-default-names/main.js

@ -0,0 +1,3 @@
import foo from './foo';
assert.equal( foo(), 'not actually foo' );

3
test/function/export-default-as-b/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'exports default-as-named from sibling module (b)'
};

1
test/function/export-default-as-b/bar.js

@ -0,0 +1 @@
export { default as baz } from './baz';

1
test/function/export-default-as-b/baz.js

@ -0,0 +1 @@
export default 'BAZ';

3
test/function/export-default-as-b/main.js

@ -0,0 +1,3 @@
import { baz } from './bar';
assert.equal( baz, 'BAZ' );

8
test/function/export-default-as-c/_config.js

@ -0,0 +1,8 @@
var assert = require( 'assert' );
module.exports = {
description: 'exports default-as-named from sibling module (c)',
exports: function ( exports ) {
assert.equal( exports.namespace.baz, 'BAZ' );
}
};

1
test/function/export-default-as-c/baz.js

@ -0,0 +1 @@
export default 'BAZ';

5
test/function/export-default-as-c/main.js

@ -0,0 +1,5 @@
import * as namespace from './namespace';
assert.equal( namespace.baz, 'BAZ' );
export { namespace };

1
test/function/export-default-as-c/namespace.js

@ -0,0 +1 @@
export { default as baz } from './baz';

8
test/function/export-default-as/_config.js

@ -0,0 +1,8 @@
var assert = require( 'assert' );
module.exports = {
description: 'exports default-as-named from sibling module',
exports: function ( exports ) {
assert.equal( exports.foo, 'FOO' );
}
};

1
test/function/export-default-as/foo.js

@ -0,0 +1 @@
export default 'FOO';

1
test/function/export-default-as/main.js

@ -0,0 +1 @@
export { default as foo } from './foo';

4
test/test.js

@ -308,8 +308,12 @@ describe( 'rollup', function () {
else {
var expected = sander.readFileSync( '_expected.js' ).toString();
try {
assert.equal( code.trim(), expected.trim() );
done();
} catch ( err ) {
done( err );
}
}
});
});

Loading…
Cancel
Save