From 98cf636e2ec28961132b1cc2efb6e350060e2030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Segersv=C3=A4rd?= Date: Mon, 14 Sep 2015 17:31:24 +0200 Subject: [PATCH 1/3] Implemented `export * from "internal";` --- src/Bundle.js | 18 +++++------ src/Module.js | 81 ++++++++++++++++++++++++++++++++---------------- src/Scope.js | 4 +-- src/Statement.js | 4 +-- 4 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 6f0857b..b985c14 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -49,7 +49,6 @@ export default class Bundle { this.statements = null; this.externalModules = []; - this.internalNamespaceModules = []; } build () { @@ -60,13 +59,10 @@ export default class Bundle { this.exports = entryModule.exports; entryModule.markAllStatements( true ); - this.orderedModules = this.sort(); + entryModule.markAllExports(); - this.exports.localIds().forEach( ([ , id ]) => { - // If the export is a module (namespace), we need - // all its exports dynamically accessible. - if ( id.module === id ) id.dynamicAccess(); - }); + // Sort the modules. + this.orderedModules = this.sort(); // As a last step, deconflict all identifier names, once. this.scope.deconflict(); @@ -213,9 +209,11 @@ export default class Bundle { // prepend bundle with internal namespaces const indentString = getIndentString( magicString, options ); - const namespaceBlock = this.internalNamespaceModules.map( module => { - const exports = module.exports.localIds().map( ( [ name, id ] ) => - `${indentString}get ${name} () { return ${id.name}; }`); + const namespaceBlock = this.modules.filter( module => module.needsDynamicAccess ).map( module => { + const exports = module.exports.getNames().map( name => { + const id = module.exports.lookup( name ); + return `${indentString}get ${name} () { return ${id.name}; }`; + }); return `var ${module.name} = {\n` + exports.join( ',\n' ) + diff --git a/src/Module.js b/src/Module.js index 36607ec..18f9628 100644 --- a/src/Module.js +++ b/src/Module.js @@ -26,7 +26,7 @@ function removeSourceMappingURLComments ( source, magicString ) { } function assign ( target, source ) { - for ( var key in source ) target[ key ] = source[ key ]; + for ( let key in source ) target[ key ] = source[ key ]; } class Id { @@ -92,9 +92,9 @@ export default class Module { return reference.call( this.exports, name ); } - // ... otherwise search exportAlls - for ( let i = 0; i < this.exportAlls.length; i += 1 ) { - const module = this.exportAlls[i]; + // ... otherwise search allExportsFrom + for ( let i = 0; i < this.allExportsFrom.length; i += 1 ) { + const module = this.allExportsFrom[i]; if ( module.exports.inScope( name ) ) { return module.exports.reference( name ); } @@ -107,7 +107,7 @@ export default class Module { this.exports.inScope = name => { if ( inScope.call( this.exports, name ) ) return true; - return this.exportAlls.some( module => module.exports.inScope( name ) ); + return this.allExportsFrom.some( module => module.exports.inScope( name ) ); }; // Create a unique virtual scope for references to the module. @@ -115,7 +115,9 @@ export default class Module { // unique.define( this.name, this ); // this.reference = unique.reference( this.name ); - this.exportAlls = []; + // As far as we know, all our exported bindings have been resolved. + this.allExportsResolved = true; + this.allExportsFrom = []; this.reassignments = []; @@ -138,10 +140,18 @@ export default class Module { // When an unknown import is encountered, we see if one of them can satisfy it. if ( module.isExternal ) { - throw new Error( `Cannot trace 'export *' references through external modules.` ); + let err = new Error( `Cannot trace 'export *' references through external modules.` ); + err.file = this.id; + err.loc = getLocation( this.source, node.start ); + throw err; } - this.exportAlls.push( module ); + // It seems like we must re-export all exports from another module... + this.allExportsResolved = false; + + if ( !~this.allExportsFrom.indexOf( module ) ) { + this.allExportsFrom.push( module ); + } } else { @@ -266,6 +276,23 @@ export default class Module { }); }); + // If all exports aren't resolved, but all our delegate modules are... + if ( !this.allExportsResolved && this.allExportsFrom.every( module => module.allExportsResolved )) { + // .. then all our exports should be as well. + this.allExportsResolved = true; + + // For all modules we export all from, iterate through its exported names. + // If we don't already define the binding 'name', + // bind the name to the other module's reference. + this.allExportsFrom.forEach( module => { + module.exports.getNames().forEach( name => { + if ( !this.exports.defines( name ) ) { + this.exports.bind( name, module.exports.reference( name ) ); + } + }); + }); + } + // discover variables that are reassigned inside function // bodies, so we can keep bindings live, e.g. // @@ -375,8 +402,12 @@ export default class Module { }); }); - this.locals.getNames().forEach( name => { - const id = this.locals.lookup( name ); + // Go through all our local and exported ids and make us depend on + // the defining modules as well as + this.exports.getIds().concat(this.locals.getIds()).forEach( id => { + if ( id.module && !id.module.isExternal ) { + weakDependencies[ id.module.id ] = id.module; + } if ( !id.modifierStatements ) return; @@ -394,24 +425,16 @@ export default class Module { return { strongDependencies, weakDependencies }; } - // Enforce dynamic access of the module's properties. - dynamicAccess () { - if ( this.needsDynamicAccess ) return; - - this.needsDynamicAccess = true; - this.markAllExportStatements(); - - if ( !~this.bundle.internalNamespaceModules.indexOf( this ) ) { - this.bundle.internalNamespaceModules.push( this ); - } - } - getModule ( source ) { return this.bundle.moduleById[ this.resolvedIds[ source ] ]; } + // If a module is marked, enforce dynamic access of its properties. mark () { - this.dynamicAccess(); + if ( this.needsDynamicAccess ) return; + this.needsDynamicAccess = true; + + this.markAllExports(); } markAllStatements ( isEntryModule ) { @@ -447,10 +470,9 @@ export default class Module { }); } - markAllExportStatements () { - this.statements.forEach( statement => { - if ( statement.isExportDeclaration ) statement.mark(); - }); + // Marks all exported identifiers. + markAllExports () { + this.exports.getIds().forEach( id => id.mark() ); } parse ( ast ) { @@ -621,6 +643,11 @@ export default class Module { magicString.remove( statement.node.start, statement.node.declaration.start ); } + else if ( statement.node.type === 'ExportAllDeclaration' ) { + // TODO: remove once `export * from 'external'` is supported. + magicString.remove( statement.start, statement.next ); + } + // remove `export` from `export class Foo {...}` or `export default Foo` // TODO default exports need different treatment else if ( statement.node.declaration.id ) { diff --git a/src/Scope.js b/src/Scope.js index 1c04fae..9d644ef 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -112,8 +112,8 @@ export default class Scope { } // Returns a list of `[ name, identifier ]` tuples. - localIds () { - return keys( this.names ).map( name => [ name, this.lookup( name ) ] ); + getIds () { + return keys( this.names ).map( name => this.lookup( name ) ); } // Lookup the identifier referred to by `name`. diff --git a/src/Statement.js b/src/Statement.js index 790c3a1..260d4f5 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -200,7 +200,7 @@ export default class Statement { ( node.property.type === 'Literal' ? String( node.property.value ) : null ); // If we can't resolve the name being accessed statically, - // we require the namespace to be dynamically accessible. + // we mark the whole namespace for inclusion in the bundle. // // // resolvable // console.log( javascript.keywords.for ) @@ -211,7 +211,7 @@ export default class Statement { // console.log( javascript.keywords[ index ] ) // console.log( javascript.keywords[ 1 + 5 ] ) if ( name === null ) { - namespace.dynamicAccess(); + namespace.mark(); namespace = null; currentMemberExpression = null; From 9531335f1c5956e9c267b70e2ce2c907ce3e7181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Segersv=C3=A4rd?= Date: Mon, 14 Sep 2015 17:32:08 +0200 Subject: [PATCH 2/3] When configuring a form test to be solo, execute all tests in that group. --- test/test.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/test.js b/test/test.js index 4adad98..a14f1e0 100644 --- a/test/test.js +++ b/test/test.js @@ -156,17 +156,15 @@ describe( 'rollup', function () { sander.readdirSync( FORM ).sort().forEach( function ( dir ) { if ( dir[0] === '.' ) return; // .DS_Store... - describe( dir, function () { - var config = require( FORM + '/' + dir + '/_config' ); + var config = require( FORM + '/' + dir + '/_config' ); - var options = extend( {}, config.options, { - entry: FORM + '/' + dir + '/main.js' - }); + var options = extend( {}, config.options, { + entry: FORM + '/' + dir + '/main.js' + }); + ( config.skip ? describe.skip : config.solo ? describe.only : describe)( dir, function () { PROFILES.forEach( function ( profile ) { - ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { - if ( config.solo ) console.group( dir ); - + it( 'generates ' + profile.format, function () { return rollup.rollup( options ).then( function ( bundle ) { var options = extend( {}, config.options, { dest: FORM + '/' + dir + '/_actual/' + profile.format + '.js', @@ -195,8 +193,6 @@ describe( 'rollup', function () { assert.equal( actualCode, expectedCode ); assert.deepEqual( actualMap, expectedMap ); - - if ( config.solo ) console.groupEnd(); }); }); }); From 886e6f7edc647db4fe57e27a5b76af2aebd5ca8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Segersv=C3=A4rd?= Date: Mon, 14 Sep 2015 17:32:52 +0200 Subject: [PATCH 3/3] Added tests --- test/form/export-all-from-internal/_config.js | 7 +++++++ test/form/export-all-from-internal/_expected/amd.js | 9 +++++++++ test/form/export-all-from-internal/_expected/cjs.js | 7 +++++++ test/form/export-all-from-internal/_expected/es6.js | 4 ++++ .../form/export-all-from-internal/_expected/iife.js | 9 +++++++++ test/form/export-all-from-internal/_expected/umd.js | 13 +++++++++++++ test/form/export-all-from-internal/internal.js | 2 ++ test/form/export-all-from-internal/main.js | 1 + 8 files changed, 52 insertions(+) create mode 100644 test/form/export-all-from-internal/_config.js create mode 100644 test/form/export-all-from-internal/_expected/amd.js create mode 100644 test/form/export-all-from-internal/_expected/cjs.js create mode 100644 test/form/export-all-from-internal/_expected/es6.js create mode 100644 test/form/export-all-from-internal/_expected/iife.js create mode 100644 test/form/export-all-from-internal/_expected/umd.js create mode 100644 test/form/export-all-from-internal/internal.js create mode 100644 test/form/export-all-from-internal/main.js diff --git a/test/form/export-all-from-internal/_config.js b/test/form/export-all-from-internal/_config.js new file mode 100644 index 0000000..09cf4bf --- /dev/null +++ b/test/form/export-all-from-internal/_config.js @@ -0,0 +1,7 @@ +module.exports = { + // solo: true, + description: 'should be able to export * from the bundle', + options: { + moduleName: 'exposedInternals' + } +}; diff --git a/test/form/export-all-from-internal/_expected/amd.js b/test/form/export-all-from-internal/_expected/amd.js new file mode 100644 index 0000000..6b0c47e --- /dev/null +++ b/test/form/export-all-from-internal/_expected/amd.js @@ -0,0 +1,9 @@ +define(['exports'], function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +}); diff --git a/test/form/export-all-from-internal/_expected/cjs.js b/test/form/export-all-from-internal/_expected/cjs.js new file mode 100644 index 0000000..869bba0 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +const a = 1; +const b = 2; + +exports.a = a; +exports.b = b; diff --git a/test/form/export-all-from-internal/_expected/es6.js b/test/form/export-all-from-internal/_expected/es6.js new file mode 100644 index 0000000..aafcac6 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/es6.js @@ -0,0 +1,4 @@ +const a = 1; +const b = 2; + +export { a, b }; diff --git a/test/form/export-all-from-internal/_expected/iife.js b/test/form/export-all-from-internal/_expected/iife.js new file mode 100644 index 0000000..238e208 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/iife.js @@ -0,0 +1,9 @@ +(function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +})((this.exposedInternals = {})); diff --git a/test/form/export-all-from-internal/_expected/umd.js b/test/form/export-all-from-internal/_expected/umd.js new file mode 100644 index 0000000..50c5cc4 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + factory((global.exposedInternals = {})); +}(this, function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +})); diff --git a/test/form/export-all-from-internal/internal.js b/test/form/export-all-from-internal/internal.js new file mode 100644 index 0000000..72ab60e --- /dev/null +++ b/test/form/export-all-from-internal/internal.js @@ -0,0 +1,2 @@ +export const a = 1; +export const b = 2; diff --git a/test/form/export-all-from-internal/main.js b/test/form/export-all-from-internal/main.js new file mode 100644 index 0000000..676c1ba --- /dev/null +++ b/test/form/export-all-from-internal/main.js @@ -0,0 +1 @@ +export * from './internal.js';