From 281d5ca00ac28f62b06bb265987ba59cae7b2eff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 May 2015 16:42:23 -0400 Subject: [PATCH] support default exports from bundle --- src/Bundle.js | 70 ++++++++++++++++--- src/finalisers/amd.js | 4 +- src/finalisers/cjs.js | 26 ++++--- src/finalisers/es6.js | 4 +- src/finalisers/umd.js | 4 +- src/utils/object.js | 2 +- .../export-default-expression/_config.js | 8 +++ .../samples/export-default-expression/main.js | 1 + test/test.js | 12 ++-- 9 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 test/samples/export-default-expression/_config.js create mode 100644 test/samples/export-default-expression/main.js diff --git a/src/Bundle.js b/src/Bundle.js index 24510e7..153aeef 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,4 +1,4 @@ -import { resolve } from 'path'; +import { basename, extname, resolve } from 'path'; import { readFile } from 'sander'; import MagicString from 'magic-string'; import { keys, has } from './utils/object'; @@ -10,9 +10,13 @@ import replaceIdentifiers from './utils/replaceIdentifiers'; import makeLegalIdentifier from './utils/makeLegalIdentifier'; import { defaultResolver } from './utils/resolvePath'; +function badExports ( option, keys ) { + throw new Error( `'${option}' was specified for options.exports, but entry module has following exports: ${keys.join(', ')}` ); +} + export default class Bundle { constructor ( options ) { - this.base = options.base || process.cwd(); + this.base = resolve( options.base || process.cwd() ); this.entryPath = resolve( this.base, options.entry ).replace( /\.js$/, '' ) + '.js'; this.resolvePath = options.resolvePath || defaultResolver; @@ -20,6 +24,7 @@ export default class Bundle { this.modulePromises = {}; this.statements = []; this.externalModules = []; + this.defaultExportName = null; } fetchModule ( importee, importer ) { @@ -72,11 +77,29 @@ export default class Bundle { // Exclude imports if ( /^Import/.test( node.type ) ) return; - if ( node.type === 'ExportNamedDeclaration' ) { - // Exclude specifier exports - if ( node.specifiers.length ) return; + // Exclude default exports that proxy a name + // e.g. `export default foo` + if ( node.type === 'ExportDefaultDeclaration' && /Declaration$/.test( node.declaration.type ) ) return; + + // Exclude specifier exports + // e.g. `export { foo }` + if ( node.type === 'ExportNamedDeclaration' && node.specifiers.length ) return; + + // Include everything else... + + if ( node.type === 'ExportDefaultDeclaration' ) { + // TODO generic 'get deconflicted name' mechanism + let defaultExportName = makeLegalIdentifier( basename( this.entryPath ).slice( 0, -extname( this.entryPath ).length ) ); + while ( this.entryModule.ast._scope.contains( defaultExportName ) ) { + defaultExportName = `_${defaultExportName}`; + } - // Remove the `export` from everything else + this.defaultExportName = defaultExportName; + node._source.overwrite( node.start, node.declaration.start, `var ${defaultExportName} = ` ); + } + + if ( node.type === 'ExportNamedDeclaration' ) { + // Remove the `export` node._source.remove( node.start, node.declaration.start ); } @@ -136,6 +159,9 @@ export default class Bundle { generate ( options = {} ) { let magicString = new MagicString.Bundle({ separator: '' }); + // Determine export mode - 'default', 'named', 'none' + let exportMode = this.getExportMode( options.exports ); + // Apply new names and add to the output bundle this.statements.forEach( statement => { let replacements = {}; @@ -162,7 +188,7 @@ export default class Bundle { throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); } - magicString = finalise( this, magicString, options ); + magicString = finalise( this, magicString, exportMode, options ); return { code: magicString.toString(), @@ -173,4 +199,32 @@ export default class Bundle { }) }; } -} \ No newline at end of file + + getExportMode ( exportMode ) { + const exportKeys = keys( this.entryModule.exports ); + + if ( exportMode === 'default' ) { + if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) { + badExports( 'default', exportKeys ); + } + } else if ( exportMode === 'none' && exportKeys.length ) { + badExports( 'none', exportKeys ); + } + + if ( !exportMode || exportMode === 'auto' ) { + if ( exportKeys.length === 0 ) { + exportMode = 'none'; + } else if ( exportKeys.length === 1 && exportKeys[0] === 'default' ) { + exportMode = 'default'; + } else { + exportMode = 'named'; + } + } + + if ( !/(?:default|named|none)/.test( exportMode ) ) { + throw new Error( `options.exports must be 'default', 'named', 'none', 'auto', or left unspecified (defaults to 'auto')` ); + } + + return exportMode; + } +} diff --git a/src/finalisers/amd.js b/src/finalisers/amd.js index 3e4d3cb..9d9a4aa 100644 --- a/src/finalisers/amd.js +++ b/src/finalisers/amd.js @@ -1,3 +1,3 @@ -export default function amd ( bundle, magicString, options ) { +export default function amd ( bundle, magicString, exportMode, options ) { throw new Error( 'TODO' ); -} \ No newline at end of file +} diff --git a/src/finalisers/cjs.js b/src/finalisers/cjs.js index c12ad67..75aeeba 100644 --- a/src/finalisers/cjs.js +++ b/src/finalisers/cjs.js @@ -1,6 +1,6 @@ import { keys } from '../utils/object'; -export default function cjs ( bundle, magicString ) { +export default function cjs ( bundle, magicString, exportMode ) { let intro = `'use strict';\n\n`; // TODO handle ambiguous default imports @@ -24,19 +24,23 @@ export default function cjs ( bundle, magicString ) { magicString.prepend( intro ); - // TODO handle default exports - const exportBlock = keys( bundle.entryModule.exports ) - .map( key => { - const specifier = bundle.entryModule.exports[ key ]; - const name = bundle.entryModule.getCanonicalName( specifier.localName ); - - return `exports.${key} = ${name};`; - }) - .join( '\n' ); + let exportBlock; + if ( exportMode === 'default' && bundle.entryModule.exports.default ) { + exportBlock = `module.exports = ${bundle.defaultExportName};`; + } else if ( exportMode === 'named' ) { + exportBlock = keys( bundle.entryModule.exports ) + .map( key => { + const specifier = bundle.entryModule.exports[ key ]; + const name = bundle.entryModule.getCanonicalName( specifier.localName ); + + return `exports.${key} = ${name};`; + }) + .join( '\n' ); + } if ( exportBlock ) { magicString.append( '\n\n' + exportBlock ); } return magicString; -} \ No newline at end of file +} diff --git a/src/finalisers/es6.js b/src/finalisers/es6.js index fb9ba34..32b4c81 100644 --- a/src/finalisers/es6.js +++ b/src/finalisers/es6.js @@ -1,3 +1,3 @@ -export default function es6 ( bundle, magicString, options ) { +export default function es6 ( bundle, magicString, exportMode, options ) { throw new Error( 'TODO' ); -} \ No newline at end of file +} diff --git a/src/finalisers/umd.js b/src/finalisers/umd.js index eed7afa..a83e791 100644 --- a/src/finalisers/umd.js +++ b/src/finalisers/umd.js @@ -1,4 +1,4 @@ -export default function umd ( bundle, magicString, options ) { +export default function umd ( bundle, magicString, exportMode, options ) { const indentStr = magicString.getIndentString(); const intro = @@ -21,4 +21,4 @@ export default function umd ( bundle, magicString, options ) { .indent() .append( '\n\n}));' ) .prepend( intro ); -} \ No newline at end of file +} diff --git a/src/utils/object.js b/src/utils/object.js index 9ae1b20..82484c7 100644 --- a/src/utils/object.js +++ b/src/utils/object.js @@ -4,4 +4,4 @@ export const hasOwnProp = Object.prototype.hasOwnProperty; export function has ( obj, prop ) { return hasOwnProp.call( obj, prop ); -} \ No newline at end of file +} diff --git a/test/samples/export-default-expression/_config.js b/test/samples/export-default-expression/_config.js new file mode 100644 index 0000000..562b327 --- /dev/null +++ b/test/samples/export-default-expression/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'exports a default value as module.exports', + exports: function ( exports ) { + assert.equal( exports, 42 ); + } +}; diff --git a/test/samples/export-default-expression/main.js b/test/samples/export-default-expression/main.js new file mode 100644 index 0000000..7a4e8a7 --- /dev/null +++ b/test/samples/export-default-expression/main.js @@ -0,0 +1 @@ +export default 42; diff --git a/test/test.js b/test/test.js index 3cd685b..335c7c2 100644 --- a/test/test.js +++ b/test/test.js @@ -30,12 +30,14 @@ describe( 'rollup', function () { }); try { - var fn = new Function( 'require', 'exports', 'assert', result.code ); - var exports = {}; - fn( require, exports, assert ); + var fn = new Function( 'require', 'module', 'exports', 'assert', result.code ); + var module = { + exports: {} + }; + fn( require, module, module.exports, assert ); if ( config.exports ) { - config.exports( exports, assert ); + config.exports( module.exports, assert ); } } catch ( err ) { console.log( result.code ); @@ -48,4 +50,4 @@ describe( 'rollup', function () { }); }); }); -}); \ No newline at end of file +});