diff --git a/.babelrc b/.babelrc index 6ce65b2..ed25dc0 100644 --- a/.babelrc +++ b/.babelrc @@ -5,6 +5,7 @@ "es6.classes", "es6.constants", "es6.destructuring", + "es6.modules", "es6.parameters", "es6.properties.shorthand", "es6.spread", diff --git a/.eslintrc b/.eslintrc index 11f6d54..6e5fb1a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,15 +1,19 @@ { "rules": { - "indent": [ 2, "tab", { "SwitchCase": 1}], + "indent": [ 2, "tab", { "SwitchCase": 1 } ], "quotes": [ 2, "single" ], "linebreak-style": [ 2, "unix" ], "semi": [ 2, "always" ], + "space-after-keywords": [ 2, "always" ], + "space-before-blocks": [ 2, "always" ], + "space-before-function-paren": [ 2, "always" ], "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], "no-cond-assign": [ 0 ] }, "env": { "es6": true, "browser": true, + "mocha": true, "node": true }, "extends": "eslint:recommended", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cfd6d3..937c2e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # rollup changelog +## 0.16.1 + +* Handle assignment patterns, and destructured/rest parameters, when analysing scopes +* Fix bug preventing project from self-building (make base `Identifier` class markable) + +## 0.16.0 + +* Internal refactoring ([#99](https://github.com/rollup/rollup/pull/99)) +* Optimisation for statically-analysable namespace imports ([#99](https://github.com/rollup/rollup/pull/99)) +* Windows support (theoretically!) ([#117](https://github.com/rollup/rollup/pull/117) / [#119](https://github.com/rollup/rollup/pull/119)) + +## 0.15.0 + +* Load all modules specified by `import` statements, and do tree-shaking synchronously once loading is complete. This results in simpler and faster code, and enables future improvements ([#97](https://github.com/rollup/rollup/pull/97)) +* Only rewrite `foo` as `exports.foo` when it makes sense to ([#92](https://github.com/rollup/rollup/issues/92)) +* Fix bug with shadowed variables that are eventually exported ([#91](https://github.com/rollup/rollup/issues/91)) +* Exclude unused function declarations that happen to modify a used name ([#90](https://github.com/rollup/rollup/pull/90)) +* Simplify internal `Scope` model – scopes always attach to blocks, never function expressions/declarations + ## 0.14.1 * `export { name } from './other'` does not create a local binding ([#16](https://github.com/rollup/rollup/issues/16)) diff --git a/README.md b/README.md index ecc5c01..978fded 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,24 @@ # Rollup +

+ + build status + + + npm version + + + license + + + dependency status + +

+ > *I roll up, I roll up, I roll up, Shawty I roll up* > > *I roll up, I roll up, I roll up* diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..cd3fab4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,33 @@ +# http://www.appveyor.com/docs/appveyor-yml + +version: "{build}" + +clone_depth: 10 + +init: + - git config --global core.autocrlf false + +environment: + matrix: + # node.js + - nodejs_version: 0.10 + - nodejs_version: 0.12 + # io.js + - nodejs_version: 1 + +install: + - ps: Install-Product node $env:nodejs_version + - npm install + +build: off + +test_script: + - node --version && npm --version + - npm test + +matrix: + fast_finish: false + +# cache: +# - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache +# - node_modules -> package.json # local npm modules diff --git a/package.json b/package.json index b8c9890..991c290 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "0.14.1", + "version": "0.16.1", "description": "Next-generation ES6 module bundler", "main": "dist/rollup.js", "jsnext:main": "src/rollup.js", @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/rich-harris/rollup" + "url": "https://github.com/rollup/rollup" }, "keywords": [ "modules", @@ -26,12 +26,16 @@ "optimizer" ], "author": "Rich Harris", + "contributors": [ + "Oskar Segersvärd " + ], "license": "MIT", "bugs": { - "url": "https://github.com/rich-harris/rollup/issues" + "url": "https://github.com/rollup/rollup/issues" }, - "homepage": "https://github.com/rich-harris/rollup", + "homepage": "https://github.com/rollup/rollup", "devDependencies": { + "babel": "^5.8.21", "babel-core": "^5.5.8", "console-group": "^0.1.2", "eslint": "^1.1.0", @@ -40,15 +44,15 @@ "gobble-browserify": "^0.6.1", "gobble-cli": "^0.4.2", "gobble-esperanto-bundle": "^0.2.0", - "gobble-rollup": "^0.7.0", + "gobble-rollup": "^0.8.0", "gobble-rollup-babel": "^0.1.0", "mocha": "^2.2.4", - "source-map": "^0.1.40" + "source-map": "^0.4.4" }, "dependencies": { - "acorn": "^1.1.0", + "acorn": "^2.3.0", "chalk": "^1.0.0", - "magic-string": "^0.6.5", + "magic-string": "^0.7.0", "minimist": "^1.1.1", "sander": "^0.3.3", "source-map-support": "^0.3.1" diff --git a/src/Bundle.js b/src/Bundle.js index 0fd7656..28347b1 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,17 +1,16 @@ -import { basename, extname } from './utils/path'; import { Promise } from 'sander'; import MagicString from 'magic-string'; import { blank, keys } from './utils/object'; import Module from './Module'; import ExternalModule from './ExternalModule'; import finalisers from './finalisers/index'; -import makeLegalIdentifier from './utils/makeLegalIdentifier'; import ensureArray from './utils/ensureArray'; import { defaultResolver, defaultExternalResolver } from './utils/resolveId'; import { defaultLoader } from './utils/load'; import getExportMode from './utils/getExportMode'; import getIndentString from './utils/getIndentString'; import { unixizePath } from './utils/normalizePlatform.js'; +import Scope from './Scope'; export default class Bundle { constructor ( options ) { @@ -30,246 +29,131 @@ export default class Bundle { transform: ensureArray( options.transform ) }; + // The global scope, and the bundle's internal scope. + this.globals = new Scope(); + this.scope = new Scope( this.globals ); + + // Strictly speaking, these globals only apply to non-ES6, non-default-only bundles. + // However, the deconfliction logic is greatly simplified by being the same for all formats. + // * CommonJS needs `module` and `exports` ( and `require`? ) to be in scope. + // * SystemJS needs a reference to a function for its `exports`, + // and another one for any `module` it imports. These global names can be reused! + [ 'exports', 'module' ] + .forEach( name => { + this.globals.define( name ); + this.scope.bind( name, this.globals.reference( name ) ); + }); + + // Alias for entryModule.exports. + this.exports = null; + this.toExport = null; - this.modulePromises = blank(); + this.pending = blank(); + this.moduleById = blank(); this.modules = []; this.statements = null; this.externalModules = []; - this.internalNamespaceModules = []; - - this.assumedGlobals = blank(); - this.assumedGlobals.exports = true; // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles } build () { - return this.fetchModule( this.entry, undefined ) + return Promise.resolve( this.resolveId( this.entry, undefined, this.resolveOptions ) ) + .then( id => this.fetchModule( id ) ) .then( entryModule => { - const defaultExport = entryModule.exports.default; - this.entryModule = entryModule; + this.exports = entryModule.exports; - if ( defaultExport ) { - entryModule.needsDefault = true; + entryModule.markAllStatements( true ); + entryModule.markAllExports(); - // `export default function foo () {...}` - - // use the declared name for the export - if ( defaultExport.identifier ) { - entryModule.suggestName( 'default', defaultExport.identifier ); - } + // Include all side-effects + this.modules.forEach( module => { + module.markAllSideEffects(); + }); - // `export default a + b` - generate an export name - // based on the id of the entry module - else { - let defaultExportName = makeLegalIdentifier( basename( this.entryModule.id ).slice( 0, -extname( this.entryModule.id ).length ) ); + // Sort the modules. + this.orderedModules = this.sort(); - // deconflict - let topLevelNames = []; - entryModule.statements.forEach( statement => { - keys( statement.defines ).forEach( name => topLevelNames.push( name ) ); - }); + // As a last step, deconflict all identifier names, once. + this.scope.deconflict(); - while ( ~topLevelNames.indexOf( defaultExportName ) ) { - defaultExportName = `_${defaultExportName}`; - } + // Alias the default import to the external module named + // for external modules that don't need named imports. + this.externalModules.forEach( module => { + const externalDefault = module.exports.lookup( 'default' ); - entryModule.suggestName( 'default', defaultExportName ); + if ( externalDefault && !( module.needsNamed || module.needsAll ) ) { + externalDefault.name = module.name; } - } - - return entryModule.markAllStatements( true ); - }) - .then( () => { - return this.markAllModifierStatements(); - }) - .then( () => { - this.orderedModules = this.sort(); + }); }); } - // TODO would be better to deconflict once, rather than per-render - deconflict ( es6 ) { - let usedNames = blank(); + fetchModule ( id ) { + // short-circuit cycles + if ( this.pending[ id ] ) return null; + this.pending[ id ] = true; - // ensure no conflicts with globals - keys( this.assumedGlobals ).forEach( name => usedNames[ name ] = true ); + return Promise.resolve( this.load( id, this.loadOptions ) ) + .then( source => { + let ast; - let allReplacements = blank(); - - // Assign names to external modules - 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 ); - }); - - // Discover conflicts (i.e. two statements in separate modules both define `foo`) - let i = this.orderedModules.length; - while ( i-- ) { - const module = this.orderedModules[i]; - - // 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 ( typeof source === 'object' ) { + ast = source.ast; + source = source.code; } - }); - } - // 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; - } - }); + const module = new Module({ + id, + source, + ast, + bundle: this + }); - this.orderedModules.forEach( module => { - keys( module.imports ).forEach( localName => { - if ( !module.imports[ localName ].isUsed ) return; + this.modules.push( module ); + this.moduleById[ id ] = module; - const bundleName = this.trace( module, localName, es6 ); - if ( bundleName !== localName ) { - allReplacements[ module.id ][ localName ] = bundleName; - } + return this.fetchAllDependencies( module ).then( () => { + // Analyze the module once all its dependencies have been resolved. + // This means that any dependencies of a module has already been + // analysed when it's time for the module itself. + module.analyse(); + return module; + }); }); - }); - - function getSafeName ( name ) { - while ( usedNames[ name ] ) { - name = `_${name}`; - } - - usedNames[ name ] = true; - return name; - } - - return allReplacements; } - fetchModule ( importee, importer ) { - return Promise.resolve( this.resolveId( importee, importer, this.resolveOptions ) ) - .then( id => { - if ( !id ) { + fetchAllDependencies ( module ) { + const promises = module.dependencies.map( source => { + return Promise.resolve( this.resolveId( source, module.id, this.resolveOptions ) ) + .then( resolvedId => { + module.resolvedIds[ source ] = resolvedId || source; + // external module - if ( !this.modulePromises[ importee ] ) { - const module = new ExternalModule( importee ); - this.externalModules.push( module ); - this.modulePromises[ importee ] = Promise.resolve( module ); + if ( !resolvedId ) { + if ( !this.moduleById[ source ] ) { + const module = new ExternalModule( { id: source, bundle: this } ); + this.externalModules.push( module ); + this.moduleById[ source ] = module; + } } - return this.modulePromises[ importee ]; - } - - if ( id === importer ) { - throw new Error( `A module cannot import itself (${id})` ); - } - - if ( !this.modulePromises[ id ] ) { - this.modulePromises[ id ] = Promise.resolve( this.load( id, this.loadOptions ) ) - .then( source => { - let ast; - - if ( typeof source === 'object' ) { - ast = source.ast; - source = source.code; - } - - const module = new Module({ - id, - source, - ast, - bundle: this - }); - - this.modules.push( module ); - - return module; - }); - } - - return this.modulePromises[ id ]; - }); - } - - markAllModifierStatements () { - let settled = true; - let promises = []; - - 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; - promises.push( statement.mark() ); - return; + else if ( resolvedId === module.id ) { + throw new Error( `A module cannot import itself (${resolvedId})` ); } - // special case - https://github.com/rollup/rollup/pull/40 - const importDeclaration = module.imports[ name ]; - if ( !importDeclaration ) return; - - const promise = Promise.resolve( importDeclaration.module || this.fetchModule( importDeclaration.source, module.id ) ) - .then( module => { - if ( module.isExternal ) return null; - - importDeclaration.module = module; - const exportDeclaration = module.exports[ importDeclaration.name ]; - // TODO things like `export default a + b` don't apply here... right? - return module.findDefiningStatement( exportDeclaration.localName ); - }) - .then( definingStatement => { - if ( !definingStatement ) return; - - settled = false; - return statement.mark(); - }); - - promises.push( promise ); + else { + return this.fetchModule( resolvedId ); + } }); - }); }); - return Promise.all( promises ).then( () => { - if ( !settled ) return this.markAllModifierStatements(); - }); + return Promise.all( promises ); } render ( options = {} ) { const format = options.format || 'es6'; - const allReplacements = this.deconflict( format === 'es6' ); // Determine export mode - 'default', 'named', 'none' const exportMode = getExportMode( this, options.exports ); @@ -290,23 +174,22 @@ export default class Bundle { // // This doesn't apply if the bundle is exported as ES6! let allBundleExports = blank(); - let isVarDeclaration = blank(); + let isReassignedVarDeclaration = blank(); let varExports = blank(); let getterExports = []; this.orderedModules.forEach( module => { - module.varDeclarations.forEach( name => { - isVarDeclaration[ module.replacements[ name ] || name ] = true; + module.reassignments.forEach( name => { + isReassignedVarDeclaration[ module.locals.lookup( name ).name ] = true; }); }); if ( format !== 'es6' && exportMode === 'named' ) { - keys( this.entryModule.exports ) - .concat( keys( this.entryModule.reexports ) ) + this.exports.getNames() .forEach( name => { - const canonicalName = this.traceExport( this.entryModule, name ); + const canonicalName = this.exports.lookup( name ).name; - if ( isVarDeclaration[ canonicalName ] ) { + if ( isReassignedVarDeclaration[ canonicalName ] ) { varExports[ name ] = true; // if the same binding is exported multiple ways, we need to @@ -322,14 +205,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 ) - .concat( keys( this.entryModule.reexports ) ) + this.toExport = this.exports.getNames() .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( allBundleExports, format === 'es6' ); if ( source.toString().length ) { magicString.addSource( source ); } @@ -337,15 +219,14 @@ export default class Bundle { // 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` + + 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' ) + `\n};\n\n`; }).join( '' ); @@ -390,12 +271,17 @@ export default class Bundle { } sort () { - let seen = {}; + // Set of visited module ids. + let seen = blank(); + let ordered = []; let hasCycles; - let strongDeps = {}; - let stronglyDependsOn = {}; + // Map from module id to list of modules. + let strongDeps = blank(); + + // Map from module id to boolean. + let stronglyDependsOn = blank(); function visit ( module ) { seen[ module.id ] = true; @@ -472,44 +358,4 @@ export default class Bundle { return ordered; } - - 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.exportDelegates.length; i += 1 ) { - const delegate = module.exportDelegates[i]; - const delegateExportDeclaration = delegate.module.exports[ name ]; - - if ( delegateExportDeclaration ) { - return this.trace( delegate.module, delegateExportDeclaration.localName, es6 ); - } - } - - throw new Error( `Could not trace binding '${name}' from ${module.id}` ); - } } diff --git a/src/ExternalModule.js b/src/ExternalModule.js index aabf9df..4b49a2e 100644 --- a/src/ExternalModule.js +++ b/src/ExternalModule.js @@ -1,16 +1,36 @@ import { blank } from './utils/object'; +import makeLegalIdentifier from './utils/makeLegalIdentifier'; + +// An external identifier. +class Id { + constructor ( module, name ) { + this.originalName = this.name = name; + this.module = module; + + this.modifierStatements = []; + } + + // Flags the identifier as imported by the bundle when marked. + mark () { + this.module.importedByBundle[ this.originalName ] = true; + this.modifierStatements.forEach( stmt => stmt.mark() ); + } +} export default class ExternalModule { - constructor ( id ) { + constructor ( { id, bundle } ) { this.id = id; - this.name = null; - this.isExternal = true; - this.importedByBundle = []; + // Implement `Identifier` interface. + this.originalName = this.name = makeLegalIdentifier( id ); + this.module = this; + this.isModule = true; - this.suggestedNames = blank(); + // Define the external module's name in the bundle scope. + bundle.scope.define( id, this ); - this.needsDefault = false; + this.isExternal = true; + this.importedByBundle = blank(); // Invariant: needsNamed and needsAll are never both true at once. // Because an import with both a namespace and named import is invalid: @@ -19,19 +39,28 @@ export default class ExternalModule { // this.needsNamed = false; this.needsAll = false; - } - findDefiningStatement () { - return null; - } + this.exports = bundle.scope.virtual( false ); + + const { reference } = this.exports; + + // Override reference. + this.exports.reference = name => { + if ( name !== 'default' ) { + this.needsNamed = true; + } + + if ( !this.exports.defines( name ) ) { + this.exports.define( name, new Id( this, name ) ); + } - rename () { - // noop + return reference.call( this.exports, name ); + }; } - suggestName ( exportName, suggestion ) { - if ( !this.suggestedNames[ exportName ] ) { - this.suggestedNames[ exportName ] = suggestion; - } + // External modules are always marked for inclusion in the bundle. + // Marking an external module signals its use as a namespace. + mark () { + this.needsAll = true; } } diff --git a/src/Module.js b/src/Module.js index d47601e..01b461a 100644 --- a/src/Module.js +++ b/src/Module.js @@ -1,30 +1,51 @@ -import { Promise } from 'sander'; -import { parse } from 'acorn/src/index'; +import { basename, extname } from './utils/path'; +import { parse } from 'acorn'; import MagicString from 'magic-string'; import Statement from './Statement'; import walk from './ast/walk'; import { blank, keys } from './utils/object'; -import { first, sequence } from './utils/promise'; import getLocation from './utils/getLocation'; import makeLegalIdentifier from './utils/makeLegalIdentifier'; -const emptyPromise = Promise.resolve(); +function isEmptyExportedVarDeclaration ( node, exports, toExport ) { + if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false; + + const name = node.declarations[0].id.name; -function deconflict ( name, names ) { - while ( name in names ) { - name = `_${name}`; + const id = exports.lookup( name ); + + return id && id.name in toExport; +} + +function removeSourceMappingURLComments ( source, magicString ) { + const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g; + let match; + while ( match = pattern.exec( source ) ) { + magicString.remove( match.index, match.index + match[0].length ); } +} - return name; +function assign ( target, source ) { + for ( let key in source ) target[ key ] = source[ key ]; } -function isEmptyExportedVarDeclaration ( node, allBundleExports, moduleReplacements ) { - if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false; +class Id { + constructor ( module, name, statement ) { + this.originalName = this.name = name; + this.module = module; + this.statement = statement; - const name = node.declarations[0].id.name; - const canonicalName = moduleReplacements[ name ] || name; + this.modifierStatements = []; - return canonicalName in allBundleExports; + // modifiers + this.isUsed = false; + } + + mark () { + this.isUsed = true; + this.statement.mark(); + this.modifierStatements.forEach( stmt => stmt.mark() ); + } } export default class Module { @@ -33,6 +54,16 @@ export default class Module { this.bundle = bundle; this.id = id; + this.module = this; + this.isModule = true; + + // Implement Identifier interface. + this.name = makeLegalIdentifier( basename( id ).slice( 0, -extname( id ).length ) ); + + // HACK: If `id` isn't a path, the above code yields the empty string. + if ( !this.name ) { + this.name = makeLegalIdentifier( id ); + } // By default, `id` is the filename. Custom resolvers and loaders // can change that, but it makes sense to use it for the source filename @@ -40,37 +71,60 @@ export default class Module { filename: id }); - // remove existing sourceMappingURL comments - const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g; - let match; - while ( match = pattern.exec( source ) ) { - this.magicString.remove( match.index, match.index + match[0].length ); - } + removeSourceMappingURLComments( source, this.magicString ); - this.suggestedNames = blank(); this.comments = []; this.statements = this.parse( ast ); - // imports and exports, indexed by ID - this.imports = blank(); - this.exports = blank(); - this.reexports = blank(); + // all dependencies + this.resolvedIds = blank(); - this.exportAlls = blank(); + // Virtual scopes for the local and exported names. + this.locals = bundle.scope.virtual( true ); + this.exports = bundle.scope.virtual( false ); - // array of all-export sources - this.exportDelegates = []; + const { reference, inScope } = this.exports; - this.replacements = blank(); + this.exports.reference = name => { + // If we have it, grab it. + if ( inScope.call( this.exports, name ) ) { + return reference.call( this.exports, name ); + } - this.varDeclarations = []; + // ... 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 ); + } + } + + // throw new Error( `The name "${name}" is never exported (from ${this.id})!` ); + return reference.call( this.exports, name ); + }; + + this.exports.inScope = name => { + if ( inScope.call( this.exports, name ) ) return true; + + return this.allExportsFrom.some( module => module.exports.inScope( name ) ); + }; + + // Create a unique virtual scope for references to the module. + // const unique = bundle.scope.virtual(); + // unique.define( this.name, this ); + // this.reference = unique.reference( this.name ); - this.definitions = blank(); - this.definitionPromises = blank(); - this.modifications = blank(); + // As far as we know, all our exported bindings have been resolved. + this.allExportsResolved = true; + this.allExportsFrom = []; - this.analyse(); + this.reassignments = []; + + // TODO: change to false, and detect when it's necessary. + this.needsDynamicAccess = false; + + this.dependencies = this.collectDependencies(); } addExport ( statement ) { @@ -79,22 +133,32 @@ export default class Module { // export { name } from './other' if ( source ) { + const module = this.getModule( source ); + if ( node.type === 'ExportAllDeclaration' ) { // Store `export * from '...'` statements in an array of delegates. // When an unknown import is encountered, we see if one of them can satisfy it. - this.exportDelegates.push({ - statement, - source - }); + + if ( module.isExternal ) { + let err = new Error( `Cannot trace 'export *' references through external modules.` ); + err.file = this.id; + err.loc = getLocation( this.source, node.start ); + throw err; + } + + // 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 { node.specifiers.forEach( specifier => { - this.reexports[ specifier.exported.name ] = { - source, - localName: specifier.local.name, - module: null // filled in later - }; + // Bind the export of this module, to the export of the other. + this.exports.bind( specifier.exported.name, + module.exports.reference( specifier.local.name ) ); }); } } @@ -111,16 +175,18 @@ export default class Module { node.declaration.type === 'Identifier' ? node.declaration.name : null; + const name = identifier || this.name; + + // Always define a new `Identifier` for the default export. + const id = new Id( this, name, statement ); + + // Keep the identifier name, if one exists. + // We can optimize the newly created default `Identifier` away, + // if it is never modified. + // in case of `export default foo; foo = somethingElse` + assign( id, { isDeclaration, isAnonymous, identifier } ); - this.exports.default = { - statement, - name: 'default', - localName: identifier || 'default', - identifier, - isDeclaration, - isAnonymous, - isModified: false // in case of `export default foo; foo = somethingElse` - }; + this.exports.define( 'default', id ); } // export { foo, bar, baz } @@ -133,11 +199,7 @@ export default class Module { const localName = specifier.local.name; const exportedName = specifier.exported.name; - this.exports[ exportedName ] = { - statement, - localName, - exportedName - }; + this.exports.bind( exportedName, this.locals.reference( localName ) ); }); } @@ -154,38 +216,49 @@ export default class Module { name = declaration.id.name; } - this.exports[ name ] = { - statement, - localName: name, - expression: declaration - }; + this.locals.define( name, new Id( this, name, statement ) ); + this.exports.bind( name, this.locals.reference( name ) ); } } } addImport ( statement ) { const node = statement.node; - const source = node.source.value; + const module = this.getModule( node.source.value ); 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; - if ( this.imports[ localName ] ) { + if ( this.locals.defines( localName ) ) { const err = new Error( `Duplicated import '${localName}'` ); err.file = this.id; err.loc = getLocation( this.source, specifier.start ); throw err; } - this.imports[ localName ] = { - source, - name, - localName - }; + if ( isNamespace ) { + // If it's a namespace import, we bind the localName to the module itself. + module.needsAll = true; + module.name = localName; + this.locals.bind( localName, module ); + } else { + const name = isDefault ? 'default' : specifier.imported.name; + + this.locals.bind( localName, module.exports.reference( name ) ); + + // For compliance with earlier Rollup versions. + // If the module is external, and we access the default. + // Rewrite the module name, and the default name to the + // `localName` we use for it. + if ( module.isExternal && isDefault ) { + const id = module.exports.lookup( name ); + module.name = id.name = localName; + id.name += '__default'; + } + } }); } @@ -199,15 +272,36 @@ export default class Module { // consolidate names that are defined/modified in this module keys( statement.defines ).forEach( name => { - this.definitions[ name ] = statement; + this.locals.define( name, new Id( this, name, statement ) ); }); + }); - statement.scope.varDeclarations.forEach( name => { - this.varDeclarations.push( name ); + // 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 ) ); + } + }); }); + } - 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; }); }); @@ -216,12 +310,47 @@ export default class Module { this.statements.forEach( statement => { if ( statement.isReexportDeclaration ) return; + // while we're here, mark reassignments + statement.scope.varDeclarations.forEach( name => { + if ( reassigned[ name ] && !~this.reassignments.indexOf( name ) ) { + this.reassignments.push( name ); + } + }); + keys( statement.dependsOn ).forEach( name => { - if ( !this.definitions[ name ] && !this.imports[ name ] ) { - this.bundle.assumedGlobals[ name ] = true; + // For each name we depend on that isn't in scope, + // add a new global and bind the local name to it. + if ( !this.locals.inScope( name ) ) { + this.bundle.globals.define( name, { + originalName: name, + name, + mark () {} + }); + this.locals.bind( name, this.bundle.globals.reference( name ) ); } }); }); + + // OPTIMIZATION! + // If we have a default export and it's value is never modified, + // bind to it directly. + const def = this.exports.lookup( 'default' ); + if ( def && !def.isModified && def.identifier ) { + this.exports.bind( 'default', this.locals.reference( def.identifier ) ); + } + } + + // Returns the set of imported module ids by going through all import/exports statements. + collectDependencies () { + const importedModules = blank(); + + this.statements.forEach( statement => { + if ( statement.isImportDeclaration || ( statement.isExportDeclaration && statement.node.source ) ) { + importedModules[ statement.node.source.value ] = true; + } + }); + + return keys( importedModules ); } consolidateDependencies () { @@ -235,25 +364,21 @@ export default class Module { } this.statements.forEach( statement => { - if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) { + if ( statement.isImportDeclaration && !statement.node.specifiers.length ) { // include module for its side-effects - strongDependencies[ statement.module.id ] = statement.module; // TODO is this right? `statement.module` should be `this`, surely? + const module = this.getModule( statement.node.source.value ); + + if ( !module.isExternal ) strongDependencies[ module.id ] = module; } else if ( statement.isReexportDeclaration ) { if ( statement.node.specifiers ) { statement.node.specifiers.forEach( specifier => { - let reexport; - - 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 ); + let id = this.exports.lookup( name ); + + addDependency( strongDependencies, id ); }); } } @@ -262,8 +387,7 @@ export default class Module { keys( statement.stronglyDependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; - addDependency( strongDependencies, this.exportAlls[ name ] ) || - addDependency( strongDependencies, this.imports[ name ] ); + addDependency( strongDependencies, this.locals.lookup( name ) ); }); } }); @@ -274,114 +398,53 @@ export default class Module { keys( statement.dependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; - addDependency( weakDependencies, this.exportAlls[ name ] ) || - addDependency( weakDependencies, this.imports[ name ] ); + addDependency( weakDependencies, this.locals.lookup( name ) ); }); }); - return { strongDependencies, weakDependencies }; - } + // 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; + } - defaultName () { - const defaultExport = this.exports.default; + if ( !id.modifierStatements ) return; - if ( !defaultExport ) return null; + id.modifierStatements.forEach( statement => { + const module = statement.module; + weakDependencies[ module.id ] = module; + }); + }); - const name = defaultExport.identifier && !defaultExport.isModified ? - defaultExport.identifier : - this.replacements.default; + // `Bundle.sort` gets stuck in an infinite loop if a module has + // `strongDependencies` to itself. Make sure it doesn't happen. + delete strongDependencies[ this.id ]; + delete weakDependencies[ this.id ]; - return this.replacements[ name ] || name; + return { strongDependencies, weakDependencies }; } - findDefiningStatement ( name ) { - if ( this.definitions[ name ] ) return this.definitions[ name ]; - - // TODO what about `default`/`*`? - - const importDeclaration = this.imports[ name ]; - if ( !importDeclaration ) return null; - - return Promise.resolve( importDeclaration.module || this.bundle.fetchModule( importDeclaration.source, this.id ) ) - .then( module => { - importDeclaration.module = module; - return module.findDefiningStatement( name ); - }); + getModule ( source ) { + return this.bundle.moduleById[ this.resolvedIds[ source ] ]; } - mark ( name ) { - // shortcut cycles - if ( this.definitionPromises[ name ] ) { - return emptyPromise; - } - - let promise; - - // 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 => { - importDeclaration.module = 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 ); - return emptyPromise; - } - - if ( importDeclaration.name === '*' ) { - // we need to create an internal namespace - if ( !~this.bundle.internalNamespaceModules.indexOf( module ) ) { - this.bundle.internalNamespaceModules.push( module ); - } + // If a module is marked, enforce dynamic access of its properties. + mark () { + if ( this.needsDynamicAccess ) return; + this.needsDynamicAccess = true; - return module.markAllExportStatements(); - } - - return module.markExport( importDeclaration.name, name, this ); - }); - } - - else { - const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ]; - promise = statement && statement.mark(); - } + this.markAllExports(); + } - this.definitionPromises[ name ] = promise || emptyPromise; - return this.definitionPromises[ name ]; + markAllSideEffects () { + this.statements.forEach( statement => { + statement.markSideEffect(); + }); } markAllStatements ( isEntryModule ) { - return sequence( this.statements, statement => { + this.statements.forEach( statement => { if ( statement.isIncluded ) return; // TODO can this happen? probably not... // skip import declarations... @@ -389,87 +452,33 @@ export default class Module { // ...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 ) { - return this.bundle.fetchModule( statement.node.source.value, this.id ) - .then( module => { - statement.module = module; - if ( module.isExternal ) { - return; - } - return module.markAllStatements(); - }); - } + const otherModule = this.getModule( statement.node.source.value ); - return; + if ( !otherModule.isExternal ) otherModule.markAllStatements(); + } } // skip `export { foo, bar, baz }`... - if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) { + else if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) { // ...but ensure they are defined, if this is the entry module - if ( isEntryModule ) { - return statement.mark(); - } - - return; + if ( isEntryModule ) statement.mark(); } // include everything else - return statement.mark(); - }); - } + else { + // Be sure to mark the default export for the entry module. + if ( isEntryModule && statement.node.type === 'ExportDefaultDeclaration' ) { + this.exports.lookup( 'default' ).mark(); + } - markAllExportStatements () { - return sequence( this.statements, statement => { - return statement.isExportDeclaration ? - statement.mark() : - null; + statement.mark(); + } }); } - markExport ( name, suggestedName, importer ) { - const reexportDeclaration = this.reexports[ name ]; - if ( reexportDeclaration ) { - reexportDeclaration.isUsed = true; - - return this.bundle.fetchModule( reexportDeclaration.source, this.id ) - .then( otherModule => { - reexportDeclaration.module = otherModule; - return otherModule.markExport( reexportDeclaration.localName, suggestedName, this ); - }); - } - - const exportDeclaration = this.exports[ name ]; - if ( exportDeclaration ) { - exportDeclaration.isUsed = true; - if ( name === 'default' ) { - this.needsDefault = true; - this.suggestName( 'default', suggestedName ); - return exportDeclaration.statement.mark(); - } - - return this.mark( exportDeclaration.localName ); - } - - const noExport = new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` ); - - // See if there exists an export delegate that defines `name`. - return first( this.exportDelegates, noExport, declaration => { - return this.bundle.fetchModule( declaration.source, this.id ).then( submodule => { - declaration.module = submodule; - - return submodule.mark( name ).then( result => { - if ( !result.length ) throw noExport; - - // It's found! This module exports `name` through declaration. - // It is however not imported into this scope. - this.exportAlls[ name ] = declaration; - - declaration.statement.dependsOn[ name ] = - declaration.statement.stronglyDependsOn[ name ] = result; - - return result; - }); - }); - }); + // Marks all exported identifiers. + markAllExports () { + this.exports.getIds().forEach( id => id.mark() ); } parse ( ast ) { @@ -555,11 +564,7 @@ export default class Module { return statements; } - rename ( name, replacement ) { - this.replacements[ name ] = replacement; - } - - render ( allBundleExports, moduleReplacements ) { + render ( toExport, direct ) { let magicString = this.magicString.clone(); this.statements.forEach( statement => { @@ -577,7 +582,7 @@ export default class Module { } // skip `export var foo;` if foo is exported - if ( isEmptyExportedVarDeclaration( statement.node.declaration, allBundleExports, moduleReplacements ) ) { + if ( isEmptyExportedVarDeclaration( statement.node.declaration, this.exports, toExport ) ) { magicString.remove( statement.start, statement.next ); return; } @@ -585,7 +590,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, allBundleExports, moduleReplacements ) ) { + if ( isEmptyExportedVarDeclaration( statement.node, this.exports, toExport ) ) { magicString.remove( statement.start, statement.next ); return; } @@ -593,7 +598,7 @@ export default class Module { // 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 ] ) { + if ( !toExport[ statement.node.declarations[0].id.name ] ) { magicString.insert( statement.start, `${statement.node.kind} ` ); } @@ -603,14 +608,34 @@ export default class Module { let replacements = blank(); let bundleExports = blank(); + // Indirect identifier access. + if ( !direct ) { + keys( statement.dependsOn ) + .forEach( name => { + const id = this.locals.lookup( name ); + + // We shouldn't create a replacement for `id` if + // 1. `id` is a Global, in which case it has no module property + // 2. `id.module` isn't external, which means we have direct access + // 3. `id` is its own module, in the case of namespace imports + if ( id.module && id.module.isExternal && id.module !== id ) { + replacements[ name ] = id.originalName === 'default' ? + // default names are always directly accessed + id.name : + // other names are indirectly accessed + `${id.module.name}.${id.originalName}`; + } + }); + } + keys( statement.dependsOn ) .concat( keys( statement.defines ) ) .forEach( name => { - const bundleName = moduleReplacements[ name ] || name; + const bundleName = this.locals.lookup( name ).name; - if ( allBundleExports[ bundleName ] ) { - bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ]; - } else if ( bundleName !== name ) { // TODO weird structure + if ( toExport[ bundleName ] ) { + bundleExports[ name ] = replacements[ name ] = toExport[ bundleName ]; + } else if ( bundleName !== name && !replacements[ name ] ) { // TODO weird structure replacements[ name ] = bundleName; } }); @@ -624,6 +649,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 ) { @@ -631,24 +661,25 @@ export default class Module { } else if ( statement.node.type === 'ExportDefaultDeclaration' ) { - const canonicalName = this.defaultName(); + const def = this.exports.lookup( 'default' ); - if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) { + // FIXME: dunno what to do here yet. + if ( statement.node.declaration.type === 'Identifier' && def.name === ( replacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) { magicString.remove( statement.start, statement.next ); return; } // prevent `var undefined = sideEffectyDefault(foo)` - if ( canonicalName === undefined ) { + if ( !def.isUsed ) { 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}` ); + magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${def.name}` ); } else { - magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${canonicalName} = ` ); + magicString.overwrite( statement.node.start, statement.node.declaration.start, `var ${def.name} = ` ); } } @@ -660,15 +691,4 @@ export default class Module { return magicString.trim(); } - - suggestName ( defaultOrBatch, suggestion ) { - // deconflict anonymous default exports with this module's definitions - const shouldDeconflict = this.exports.default && this.exports.default.isAnonymous; - - if ( shouldDeconflict ) suggestion = deconflict( suggestion, this.definitions ); - - if ( !this.suggestedNames[ defaultOrBatch ] ) { - this.suggestedNames[ defaultOrBatch ] = makeLegalIdentifier( suggestion ); - } - } } diff --git a/src/Scope.js b/src/Scope.js new file mode 100644 index 0000000..e5f5967 --- /dev/null +++ b/src/Scope.js @@ -0,0 +1,158 @@ +import { blank, keys } from './utils/object'; + +// A minimal `Identifier` implementation. Anything that has an `originalName`, +// and a mutable `name` property can be used as an `Identifier`. +class Identifier { + constructor ( name ) { + this.originalName = this.name = name; + } + + mark () { + // noop + } +} + +// A reference to an `Identifier`. +function Reference ( scope, index ) { + this.scope = scope; + this.index = index; +} + +// Dereferences a `Reference`. +function dereference ( ref ) { + return ref.scope.ids[ ref.index ]; +} + +function isntReference ( id ) { + return !( id instanceof Reference ); +} + +// Prefix the argument with '_'. +function underscorePrefix ( x ) { + return '_' + x; +} + +// ## Scope +// A Scope is a mapping from string names to `Identifiers`. +export default class Scope { + constructor ( parent ) { + this.ids = []; + this.names = blank(); + + this.parent = parent || null; + this.used = blank(); + } + + // Binds the `name` to the given reference `ref`. + bind ( name, ref ) { + this.ids[ this.index( name ) ] = ref; + } + + // Deconflict all names within the scope, + // using the given renaming function. + // If no function is supplied, `underscorePrefix` is used. + deconflict ( rename = underscorePrefix ) { + const names = this.used; + + this.ids.filter( ref => ref instanceof Reference ).forEach( ref => { + // Same scope. + if ( ref.scope.ids === this.ids ) return; + + // Another scope! + while ( ref instanceof Reference ) { + ref = dereference( ref ); + } + + names[ ref.name ] = ref; + }); + + this.ids.filter( isntReference ).forEach( id => { + if ( typeof id === 'string' ) { + throw new Error( `Required name "${id}" is undefined!` ); + } + + let name = id.name; + + while ( name in names && names[ name ] !== id ) { + name = rename( name ); + } + names[ name ] = id; + + id.name = name; + }); + } + + // Defines `name` in the scope to be `id`. + // If no `id` is supplied, a plain `Identifier` is created. + define ( name, id ) { + this.ids[ this.index( name ) ] = id || new Identifier( name ); + } + + // TODO: rename! Too similar to `define`. + defines ( name ) { + return name in this.names; + } + + // Return the names referenced to in the scope. + getNames () { + return keys( this.names ); + } + + // *private, don't use* + // + // Return `name`'s index in the `ids` array if it exists, + // otherwise returns the index to a new placeholder slot. + index ( name ) { + if ( !( name in this.names ) ) { + return this.names[ name ] = this.ids.push( name ) - 1; + } + + return this.names[ name ]; + } + + // Returns true if `name` is in Scope. + inScope ( name ) { + if ( name in this.names ) return true; + + return this.parent ? this.parent.inScope( name ) : false; + } + + // Returns a list of `[ name, identifier ]` tuples. + getIds () { + return keys( this.names ).map( name => this.lookup( name ) ); + } + + // Lookup the identifier referred to by `name`. + lookup ( name ) { + if ( !( name in this.names ) && this.parent ) { + return this.parent.lookup( name ); + } + + let id = this.ids[ this.names[ name ] ]; + + while ( id instanceof Reference ) { + id = dereference( id ); + } + + return id; + } + + // Get a reference to the identifier `name` in this scope. + reference ( name ) { + return new Reference( this, this.index( name ) ); + } + + // Return the used names of the scope. + // Names aren't considered used unless they're deconflicted. + usedNames () { + return keys( this.used ).sort(); + } + + // Create and return a virtual `Scope` instance, bound to + // the actual scope of `this`, optionally inherit the parent scope. + virtual ( inheritParent ) { + const scope = new Scope( inheritParent ? this.parent : null ); + scope.ids = this.ids; + return scope; + } +} diff --git a/src/Statement.js b/src/Statement.js index b68d216..1a165b4 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -1,9 +1,18 @@ import { blank, keys } from './utils/object'; -import { sequence } from './utils/promise'; import getLocation from './utils/getLocation'; import walk from './ast/walk'; import Scope from './ast/Scope'; +const blockDeclarations = { + 'const': true, + 'let': true +}; + +const modifierNodes = { + AssignmentExpression: 'left', + UpdateExpression: 'argument' +}; + function isIife ( node, parent ) { return parent && parent.type === 'CallExpression' && node === parent.callee; } @@ -16,6 +25,14 @@ function isFunctionDeclaration ( node, parent ) { if ( node.type === 'FunctionExpression' && parent.type === 'VariableDeclarator' ) return true; } +function chainedMemberExpression ( node ) { + if ( node.object.type === 'MemberExpression' ) { + return chainedMemberExpression( node.object ) + '.' + node.property.name; + } + + return node.object.name + '.' + node.property.name; +} + export default class Statement { constructor ( node, module, start, end ) { this.node = node; @@ -26,10 +43,15 @@ export default class Statement { this.scope = new Scope(); this.defines = blank(); - this.modifies = blank(); this.dependsOn = blank(); this.stronglyDependsOn = blank(); + this.reassigns = blank(); + + // TODO: make this more efficient + this.dependantIds = []; + this.namespaceReplacements = []; + this.isIncluded = false; this.isImportDeclaration = node.type === 'ImportDeclaration'; @@ -40,6 +62,19 @@ export default class Statement { analyse () { if ( this.isImportDeclaration ) return; // nothing to analyse + // `export { name } from './other'` is a special case + if ( this.isReexportDeclaration ) { + this.node.specifiers && this.node.specifiers.forEach( specifier => { + const id = this.module.exports.lookup( specifier.exported.name ); + + if ( !~this.dependantIds.indexOf( id ) ) { + this.dependantIds.push( id ); + } + }); + + return; + } + let scope = this.scope; walk( this.node, { @@ -47,29 +82,23 @@ export default class Statement { let newScope; switch ( node.type ) { - case 'FunctionExpression': case 'FunctionDeclaration': - case 'ArrowFunctionExpression': - if ( node.type === 'FunctionDeclaration' ) { - scope.addDeclaration( node.id.name, node, false ); - } - - newScope = new Scope({ - parent: scope, - params: node.params, // TODO rest params? - block: false - }); - - // named function expressions - the name is considered - // part of the function's scope - if ( node.type === 'FunctionExpression' && node.id ) { - newScope.addDeclaration( node.id.name, node, false ); - } - - break; + scope.addDeclaration( node, false, false ); case 'BlockStatement': - if ( !/Function/.test( parent.type ) ) { + 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 @@ -89,12 +118,13 @@ export default class Statement { case 'VariableDeclaration': node.declarations.forEach( declarator => { - scope.addDeclaration( declarator.id.name, node, true ); + const isBlockDeclaration = node.type === 'VariableDeclaration' && blockDeclarations[ node.kind ]; + scope.addDeclaration( declarator, isBlockDeclaration, true ); }); break; case 'ClassDeclaration': - scope.addDeclaration( node.id.name, node, false ); + scope.addDeclaration( node, false, false ); break; } @@ -131,29 +161,105 @@ export default class Statement { // /update expressions) need to be captured let writeDepth = 0; + // Used to track + let topName; + let currentMemberExpression = null; + let namespace = null; + if ( !this.isImportDeclaration ) { walk( this.node, { enter: ( node, parent ) => { - if ( node._scope ) { - if ( !scope.isBlockScope ) { - if ( !isIife( node, parent ) ) readDepth += 1; - if ( isFunctionDeclaration( node, parent ) ) writeDepth += 1; - } + if ( isFunctionDeclaration( node, parent ) ) writeDepth += 1; + if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1; - scope = node._scope; - } + if ( node._scope ) scope = node._scope; this.checkForReads( scope, node, parent, !readDepth ); this.checkForWrites( scope, node, writeDepth ); }, leave: ( node, parent ) => { - if ( node._scope ) { - if ( !scope.isBlockScope ) { - if ( !isIife( node, parent ) ) readDepth -= 1; - if ( isFunctionDeclaration( node, parent ) ) writeDepth -= 1; + if ( isFunctionDeclaration( node, parent ) ) writeDepth -= 1; + if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1; + + if ( node._scope ) scope = scope.parent; + + // Optimize namespace lookups, which manifest as MemberExpressions. + if ( node.type === 'MemberExpression' && ( !currentMemberExpression || node.object === currentMemberExpression ) ) { + currentMemberExpression = node; + + if ( !namespace ) { + topName = node.object.name; + const id = this.module.locals.lookup( topName ); + + if ( !id || !id.isModule || id.isExternal ) return; + + namespace = id; + } + + // If a namespace is the left hand side of an assignment, throw an error. + if ( parent.type === 'AssignmentExpression' && parent.left === node || + parent.type === 'UpdateExpression' && parent.argument === node ) { + const err = new Error( `Illegal reassignment to import '${chainedMemberExpression( node )}'` ); + err.file = this.module.id; + err.loc = getLocation( this.module.magicString.toString(), node.start ); + throw err; + } + + // Extract the name of the accessed property, from and Identifier or Literal. + // Any eventual Literal value is converted to a string. + const name = !node.computed ? node.property.name : + ( node.property.type === 'Literal' ? String( node.property.value ) : null ); + + // If we can't resolve the name being accessed statically, + // we mark the whole namespace for inclusion in the bundle. + // + // // resolvable + // console.log( javascript.keywords.for ) + // console.log( javascript.keywords[ 'for' ] ) + // console.log( javascript.keywords[ 6 ] ) + // + // // unresolvable + // console.log( javascript.keywords[ index ] ) + // console.log( javascript.keywords[ 1 + 5 ] ) + if ( name === null ) { + namespace.mark(); + + namespace = null; + currentMemberExpression = null; + return; + } + + const id = namespace.exports.lookup( name ); + + // If the namespace doesn't define the given name, + // we can throw an error (even for nested namespaces). + if ( !id ) { + throw new Error( `Module doesn't define "${name}"!` ); + } + + // We can't resolve deeper. Replace the member chain. + if ( parent.type !== 'MemberExpression' || !( id.isModule && !id.isExternal ) ) { + if ( !~this.dependantIds.indexOf( id ) ) { + this.dependantIds.push( id ); + } + + // FIXME: do this better + // If we depend on this name... + if ( this.dependsOn[ topName ] ) { + // ... decrement the count... + if ( !--this.dependsOn[ topName ] ) { + // ... and remove it if the count is 0. + delete this.dependsOn[ topName ]; + } + } + + this.namespaceReplacements.push( [ node, id ] ); + namespace = null; + currentMemberExpression = null; + return; } - scope = scope.parent; + namespace = id; } } }); @@ -185,7 +291,11 @@ export default class Statement { const definingScope = scope.findDefiningScope( node.name ); if ( !definingScope || definingScope.depth === 0 ) { - this.dependsOn[ node.name ] = true; + if ( !( node.name in this.dependsOn ) ) { + this.dependsOn[ node.name ] = 0; + } + + this.dependsOn[ node.name ]++; if ( strong ) this.stronglyDependsOn[ node.name ] = true; } } @@ -202,9 +312,9 @@ export default class Statement { // disallow assignments/updates to imported bindings and namespaces if ( isAssignment ) { - const importSpecifier = this.module.imports[ node.name ]; + const importSpecifier = this.module.locals.lookup( node.name ); - if ( importSpecifier && !scope.contains( node.name ) ) { + if ( importSpecifier && importSpecifier.module !== this.module && !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 @@ -220,13 +330,25 @@ export default class Statement { // 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 ) { + const def = this.module.exports.lookup( 'default' ); + if ( def && depth === 0 && def.name === 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; + if ( !!scope.parent || node.start > def.statement.node.start ) { + def.isModified = true; } } + + // 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; + } } // we only care about writes that happen a) at the top level, @@ -236,7 +358,11 @@ export default class Statement { // 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 id = this.module.locals.lookup( node.name ); + + if ( id && id.modifierStatements && !~id.modifierStatements.indexOf( this ) ) { + id.modifierStatements.push( this ); + } } }; @@ -262,33 +388,42 @@ export default class Statement { if ( this.isIncluded ) return; // prevent infinite loops this.isIncluded = true; - // `export { name } from './other'` is a special case - if ( this.isReexportDeclaration ) { - return this.module.bundle.fetchModule( this.node.source.value, this.module.id ) - .then( otherModule => { - return sequence( this.node.specifiers, specifier => { - const reexport = this.module.reexports[ specifier.exported.name ]; + this.dependantIds.forEach( id => id.mark() ); - reexport.isUsed = true; - reexport.module = otherModule; + // TODO: perhaps these could also be added? + keys( this.dependsOn ).forEach( name => { + if ( this.defines[ name ] ) return; // TODO maybe exclude from `this.dependsOn` in the first place? + this.module.locals.lookup( name ).mark(); + }); + } - return otherModule.isExternal ? - null : - otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); - }); - }); - } + markSideEffect () { + const statement = this; + + walk( this.node, { + enter ( node, parent ) { + if ( /Function/.test( node.type ) && !isIife( node, parent ) ) return this.skip(); - const dependencies = Object.keys( this.dependsOn ); + // If this is a top-level call expression, or an assignment to a global, + // this statement will need to be marked + if ( node.type === 'CallExpression' ) { + statement.mark(); + } - return sequence( dependencies, name => { - if ( this.defines[ name ] ) return; // TODO maybe exclude from `this.dependsOn` in the first place? - return this.module.mark( name ); + else if ( node.type in modifierNodes ) { + let subject = node[ modifierNodes[ node.type ] ]; + while ( subject.type === 'MemberExpression' ) subject = subject.object; + + if ( statement.module.bundle.globals.defines( subject.name ) ) statement.mark(); + } + } }); } replaceIdentifiers ( magicString, names, bundleExports ) { - const replacementStack = [ names ]; + const statement = this; + + const replacementStack = []; const nameList = keys( names ); let deshadowList = []; @@ -308,7 +443,7 @@ export default class Statement { // `this` is undefined at the top level of ES6 modules if ( node.type === 'ThisExpression' && depth === 0 ) { - magicString.overwrite( node.start, node.end, 'undefined' ); + magicString.overwrite( node.start, node.end, 'undefined', true ); } // special case - variable declarations that need to be rewritten @@ -317,10 +452,12 @@ export default class Statement { 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; + const id = node.declarations[0].id; + const name = id.name; + if ( node.declarations.length === 1 && bundleExports[ name ] ) { - magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ] ); - node.declarations[0].id._skip = true; + magicString.overwrite( node.start, id.end, bundleExports[ name ], true ); + id._skip = true; } // otherwise, we insert the `exports.foo = foo` after the declaration @@ -351,11 +488,6 @@ export default class Statement { let newNames = blank(); let hasReplacements; - // special case = function foo ( foo ) {...} - if ( node.id && names[ node.id.name ] && scope.declarations[ node.id.name ] ) { - magicString.overwrite( node.id.start, node.id.end, names[ node.id.name ] ); - } - keys( names ).forEach( name => { if ( !scope.declarations[ name ] ) { newNames[ name ] = names[ name ]; @@ -378,6 +510,18 @@ export default class Statement { replacementStack.push( newNames ); } + if ( node.type === 'MemberExpression' ) { + const replacements = statement.namespaceReplacements; + for ( let i = 0; i < replacements.length; i += 1 ) { + const [ top, id ] = replacements[ i ]; + + if ( node === top ) { + magicString.overwrite( node.start, node.end, id.name ); + return this.skip(); + } + } + } + if ( node.type !== 'Identifier' ) return; // if there's no replacement, or it's the same, there's nothing more to do @@ -396,18 +540,19 @@ export default class Statement { 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 ); + 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 ]; + names = replacementStack.pop(); } } }); diff --git a/src/ast/Scope.js b/src/ast/Scope.js index 6cbc6ce..7bd79bf 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -1,10 +1,38 @@ import { blank } from '../utils/object'; -const blockDeclarations = { - 'const': true, - 'let': true +const extractors = { + Identifier ( names, param ) { + names.push( param.name ); + }, + + ObjectPattern ( names, param ) { + param.properties.forEach( prop => { + extractors[ prop.key.type ]( names, prop.key ); + }); + }, + + ArrayPattern ( names, param ) { + param.elements.forEach( element => { + if ( element ) extractors[ element.type ]( names, element ); + }); + }, + + RestElement ( names, param ) { + extractors[ param.argument.type ]( names, param.argument ); + }, + + AssignmentPattern ( names, param ) { + return extractors[ param.left.type ]( names, param.left ); + } }; +function extractNames ( param ) { + let names = []; + + extractors[ param.type ]( names, param ); + return names; +} + export default class Scope { constructor ( options ) { options = options || {}; @@ -18,26 +46,29 @@ export default class Scope { if ( options.params ) { options.params.forEach( param => { - this.declarations[ param.name ] = param; + extractNames( param ).forEach( name => { + this.declarations[ name ] = true; + }); }); } } - addDeclaration ( name, declaration, isVar ) { - const isBlockDeclaration = declaration.type === 'VariableDeclaration' && blockDeclarations[ declaration.kind ]; - + addDeclaration ( declaration, isBlockDeclaration, isVar ) { if ( !isBlockDeclaration && this.isBlockScope ) { - // it's a `var` or function declaration, and this + // it's a `var` or function node, and this // is a block scope, so we need to go up - this.parent.addDeclaration( name, declaration, isVar ); + this.parent.addDeclaration( declaration, isBlockDeclaration, isVar ); } else { - this.declarations[ name ] = declaration; - if ( isVar ) this.varDeclarations.push( name ) + extractNames( declaration.id ).forEach( name => { + this.declarations[ name ] = true; + if ( isVar ) this.varDeclarations.push( name ); + }); } } contains ( name ) { - return !!this.getDeclaration( name ); + return this.declarations[ name ] || + ( this.parent ? this.parent.contains( name ) : false ); } findDefiningScope ( name ) { @@ -51,9 +82,4 @@ export default class Scope { return null; } - - getDeclaration ( name ) { - return this.declarations[ name ] || - this.parent && this.parent.getDeclaration( name ); - } } diff --git a/src/finalisers/cjs.js b/src/finalisers/cjs.js index 034273d..8e065d2 100644 --- a/src/finalisers/cjs.js +++ b/src/finalisers/cjs.js @@ -1,21 +1,19 @@ +import getInteropBlock from './shared/getInteropBlock'; import getExportBlock from './shared/getExportBlock'; export default function cjs ( bundle, magicString, { exportMode }, options ) { let intro = options.useStrict === false ? `` : `'use strict';\n\n`; // TODO handle empty imports, once they're supported - const importBlock = bundle.externalModules - .map( module => { - let requireStatement = `var ${module.name} = require('${module.id}');`; - - if ( module.needsDefault ) { - requireStatement += '\n' + ( module.needsNamed ? `var ${module.name}__default = ` : `${module.name} = ` ) + - `'default' in ${module.name} ? ${module.name}['default'] : ${module.name};`; - } - - return requireStatement; - }) - .join( '\n' ); + let importBlock = bundle.externalModules + .map( module => `var ${module.name} = require('${module.id}');`) + .join('\n'); + + const interopBlock = getInteropBlock( bundle ); + + if ( interopBlock ) { + importBlock += '\n' + interopBlock; + } if ( importBlock ) { intro += importBlock + '\n\n'; diff --git a/src/finalisers/es6.js b/src/finalisers/es6.js index 0ed804c..3cdd71c 100644 --- a/src/finalisers/es6.js +++ b/src/finalisers/es6.js @@ -1,13 +1,14 @@ -import { blank, keys } from '../utils/object'; +import { keys } from '../utils/object'; -function uniqueNames ( declarations ) { - let uniques = blank(); +function specifiersFor ( externalModule ) { + return keys( externalModule.importedByBundle ) + .filter( notDefault ) + .sort() + .map( name => { + const id = externalModule.exports.lookup( name ); - declarations - .filter( declaration => !/^(default|\*)$/.test( declaration.name ) ) - .forEach( declaration => uniques[ declaration.name ] = true ); - - return keys( uniques ); + return name !== id.name ? `${name} as ${id.name}` : name; + }); } function notDefault ( name ) { @@ -19,19 +20,18 @@ export default function es6 ( bundle, magicString ) { .map( module => { const specifiers = []; - if ( module.needsDefault ) { - specifiers.push( module.importedByBundle.filter( declaration => - declaration.name === 'default' )[0].localName ); + const id = module.exports.lookup( 'default' ); + + if ( id ) { + specifiers.push( id.name ); } if ( module.needsAll ) { - specifiers.push( '* as ' + module.importedByBundle.filter( declaration => - declaration.name === '*' )[0].localName ); + specifiers.push( '* as ' + module.name ); } if ( module.needsNamed ) { - specifiers.push( '{ ' + uniqueNames( module.importedByBundle ) - .join( ', ' ) + ' }' ); + specifiers.push( '{ ' + specifiersFor( module ).join( ', ' ) + ' }' ); } return specifiers.length ? @@ -47,18 +47,18 @@ export default function es6 ( bundle, magicString ) { const module = bundle.entryModule; const specifiers = bundle.toExport.filter( notDefault ).map( name => { - const canonicalName = bundle.traceExport( module, name ); + const id = bundle.exports.lookup( name ); - return canonicalName === name ? + return id.name === name ? name : - `${canonicalName} as ${name}`; + `${id.name} as ${name}`; }); let exportBlock = specifiers.length ? `export { ${specifiers.join(', ')} };` : ''; - const defaultExport = module.exports.default || module.reexports.default; + const defaultExport = module.exports.lookup( 'default' ); if ( defaultExport ) { - exportBlock += `export default ${bundle.traceExport(module,'default')};`; + exportBlock += `\nexport default ${ defaultExport.name };`; } if ( exportBlock ) { diff --git a/src/finalisers/shared/getExportBlock.js b/src/finalisers/shared/getExportBlock.js index 24eeb61..6c53a3d 100644 --- a/src/finalisers/shared/getExportBlock.js +++ b/src/finalisers/shared/getExportBlock.js @@ -1,18 +1,24 @@ +function wrapAccess ( id ) { + return ( id.originalName !== 'default' && id.module && id.module.isExternal ) ? + id.module.name + propertyAccess( id.originalName ) : id.name; +} + +function propertyAccess ( name ) { + return name === 'default' ? `['default']` : `.${name}`; +} + export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) { if ( exportMode === 'default' ) { - const defaultExport = bundle.entryModule.exports.default; - - const defaultExportName = bundle.entryModule.replacements.default || - defaultExport.identifier; + const id = bundle.exports.lookup( 'default' ); - return `${mechanism} ${defaultExportName};`; + return `${mechanism} ${wrapAccess( id )};`; } return bundle.toExport .map( name => { - const prop = name === 'default' ? `['default']` : `.${name}`; - name = bundle.traceExport( bundle.entryModule, name ); - return `exports${prop} = ${name};`; + const id = bundle.exports.lookup( name ); + + return `exports${propertyAccess( name )} = ${wrapAccess( id )};`; }) .join( '\n' ); } diff --git a/src/finalisers/shared/getInteropBlock.js b/src/finalisers/shared/getInteropBlock.js index 8060e00..684af72 100644 --- a/src/finalisers/shared/getInteropBlock.js +++ b/src/finalisers/shared/getInteropBlock.js @@ -1,11 +1,12 @@ export default function getInteropBlock ( bundle ) { return bundle.externalModules .map( module => { - return module.needsDefault ? - ( module.needsNamed ? - `var ${module.name}__default = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` : - `${module.name} = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` ) : - null; + const def = module.exports.lookup( 'default' ); + + if ( !def ) return; + + return ( module.needsNamed ? 'var ' : '' ) + + `${def.name} = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};`; }) .filter( Boolean ) .join( '\n' ); diff --git a/src/utils/getExportMode.js b/src/utils/getExportMode.js index 86ed4d4..439c33c 100644 --- a/src/utils/getExportMode.js +++ b/src/utils/getExportMode.js @@ -5,7 +5,7 @@ function badExports ( option, keys ) { } export default function getExportMode ( bundle, exportMode ) { - const exportKeys = keys( bundle.entryModule.exports ).concat( keys( bundle.entryModule.reexports ) ); + const exportKeys = keys( bundle.entryModule.exports.names ); if ( exportMode === 'default' ) { if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) { diff --git a/src/utils/makeLegalIdentifier.js b/src/utils/makeLegalIdentifier.js index 7e0a846..1dd4454 100644 --- a/src/utils/makeLegalIdentifier.js +++ b/src/utils/makeLegalIdentifier.js @@ -8,7 +8,10 @@ reservedWords.concat( builtins ).forEach( word => blacklisted[ word ] = true ); export default function makeLegalIdentifier ( str ) { - str = str.replace( /[^$_a-zA-Z0-9]/g, '_' ); + str = str + .replace( /-(\w)/g, ( _, letter ) => letter.toUpperCase() ) + .replace( /[^$_a-zA-Z0-9]/g, '_' ); + if ( /\d/.test( str[0] ) || blacklisted[ str ] ) str = `_${str}`; return str; diff --git a/src/utils/path.js b/src/utils/path.js index b5db7c1..4f55ba8 100644 --- a/src/utils/path.js +++ b/src/utils/path.js @@ -1,6 +1,6 @@ // TODO does this all work on windows? -export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?\\)/; +export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/; export function isAbsolute ( path ) { return absolutePath.test( path ); diff --git a/src/utils/promise.js b/src/utils/promise.js deleted file mode 100644 index e37489d..0000000 --- a/src/utils/promise.js +++ /dev/null @@ -1,42 +0,0 @@ -import { Promise } from 'sander'; - -export function sequence ( arr, callback ) { - const len = arr.length; - let results = new Array( len ); - - let promise = Promise.resolve(); - - function next ( i ) { - return promise - .then( () => callback( arr[i], i ) ) - .then( result => results[i] = result ); - } - - let i; - - for ( i = 0; i < len; i += 1 ) { - promise = next( i ); - } - - return promise.then( () => results ); -} - - -export function first ( arr, fail, callback ) { - const len = arr.length; - - let promise = Promise.reject( fail ); - - function next ( i ) { - return promise - .catch(() => callback( arr[i], i )); - } - - let i; - - for ( i = 0; i < len; i += 1 ) { - promise = next( i ); - } - - return promise; -} diff --git a/src/utils/resolveId.js b/src/utils/resolveId.js index 1af9378..3090001 100644 --- a/src/utils/resolveId.js +++ b/src/utils/resolveId.js @@ -10,15 +10,6 @@ function dirExists ( dir ) { } } -function fileExists ( dir ) { - try { - readFileSync( dir ); - return true; - } catch ( err ) { - return false; - } -} - export function defaultResolver ( importee, importer, options ) { // absolute paths are left untouched if ( isAbsolute( importee ) ) return importee; @@ -47,7 +38,7 @@ export function defaultExternalResolver ( id, importer ) { // `foo` should use jsnext:main, but `foo/src/bar` shouldn't const parts = id.split( /[\/\\]/ ); - while ( dir !== root ) { + while ( dir !== root && dir !== '.' ) { const modulePath = resolve( dir, 'node_modules', parts[0] ); if ( dirExists( modulePath ) ) { @@ -58,7 +49,6 @@ export function defaultExternalResolver ( id, importer ) { // `foo` const pkgPath = resolve( modulePath, 'package.json' ); - let pkgJson; let pkg; try { diff --git a/test/cli/external-modules/main.js b/test/cli/external-modules/main.js index 03f1b74..abaa454 100644 --- a/test/cli/external-modules/main.js +++ b/test/cli/external-modules/main.js @@ -1,5 +1,5 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; import { format } from 'util'; assert.equal( format( 'it %s', 'works' ), 'it works' ); -assert.equal( relative( 'a/b/c', 'a/c/b' ), '../../c/b' ); +assert.equal( relative( 'a/b/c', 'a/c/b' ), normalize('../../c/b') ); 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..83871ca --- /dev/null +++ b/test/form/export-all-from-internal/_config.js @@ -0,0 +1,6 @@ +module.exports = { + 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..89f5652 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/amd.js @@ -0,0 +1,11 @@ +define(['exports'], function (exports) { 'use strict'; + + const a = 1; + const b = 2; + var internal = 42; + + exports.a = a; + exports.b = b; + exports['default'] = internal; + +}); 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..cadb160 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/cjs.js @@ -0,0 +1,9 @@ +'use strict'; + +const a = 1; +const b = 2; +var internal = 42; + +exports.a = a; +exports.b = b; +exports['default'] = internal; 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..d68e7f9 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/es6.js @@ -0,0 +1,6 @@ +const a = 1; +const b = 2; +var internal = 42; + +export { a, b }; +export default internal; 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..c093623 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/iife.js @@ -0,0 +1,11 @@ +(function (exports) { 'use strict'; + + const a = 1; + const b = 2; + var internal = 42; + + exports.a = a; + exports.b = b; + exports['default'] = internal; + +})((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..45a93fa --- /dev/null +++ b/test/form/export-all-from-internal/_expected/umd.js @@ -0,0 +1,15 @@ +(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; + var internal = 42; + + exports.a = a; + exports.b = b; + exports['default'] = internal; + +})); 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..8f791b6 --- /dev/null +++ b/test/form/export-all-from-internal/internal.js @@ -0,0 +1,3 @@ +export const a = 1; +export const b = 2; +export default 42; 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'; diff --git a/test/form/exported-empty-vars/_config.js b/test/form/exported-empty-vars/_config.js deleted file mode 100644 index fa0287c..0000000 --- a/test/form/exported-empty-vars/_config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - description: 'removes empty var declarations that are exported', - options: { - moduleName: 'myBundle' - } -}; diff --git a/test/form/exported-empty-vars/_expected/amd.js b/test/form/exported-empty-vars/_expected/amd.js deleted file mode 100644 index c0f1472..0000000 --- a/test/form/exported-empty-vars/_expected/amd.js +++ /dev/null @@ -1,8 +0,0 @@ -define(['exports'], function (exports) { 'use strict'; - - exports.foo = 42; - - exports.bar = 43; - exports.baz = 44; - -}); diff --git a/test/form/exported-empty-vars/_expected/cjs.js b/test/form/exported-empty-vars/_expected/cjs.js deleted file mode 100644 index 4011bd7..0000000 --- a/test/form/exported-empty-vars/_expected/cjs.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -exports.foo = 42; - -exports.bar = 43; -exports.baz = 44; diff --git a/test/form/exported-empty-vars/_expected/es6.js b/test/form/exported-empty-vars/_expected/es6.js deleted file mode 100644 index 4b76753..0000000 --- a/test/form/exported-empty-vars/_expected/es6.js +++ /dev/null @@ -1,9 +0,0 @@ -var foo; -foo = 42; - -var bar; -var baz; -bar = 43; -baz = 44; - -export { foo, bar, baz }; diff --git a/test/form/exported-empty-vars/_expected/iife.js b/test/form/exported-empty-vars/_expected/iife.js deleted file mode 100644 index 63c56b2..0000000 --- a/test/form/exported-empty-vars/_expected/iife.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (exports) { 'use strict'; - - exports.foo = 42; - - exports.bar = 43; - exports.baz = 44; - -})((this.myBundle = {})); diff --git a/test/form/exported-empty-vars/bar.js b/test/form/exported-empty-vars/bar.js deleted file mode 100644 index b8818da..0000000 --- a/test/form/exported-empty-vars/bar.js +++ /dev/null @@ -1,6 +0,0 @@ -var bar, baz; - -bar = 43; -baz = 44; - -export { bar, baz }; diff --git a/test/form/exported-empty-vars/foo.js b/test/form/exported-empty-vars/foo.js deleted file mode 100644 index 2c71d30..0000000 --- a/test/form/exported-empty-vars/foo.js +++ /dev/null @@ -1,2 +0,0 @@ -export var foo; -foo = 42; diff --git a/test/form/exported-empty-vars/main.js b/test/form/exported-empty-vars/main.js deleted file mode 100644 index 21ee721..0000000 --- a/test/form/exported-empty-vars/main.js +++ /dev/null @@ -1,2 +0,0 @@ -export { foo } from './foo'; -export { bar, baz } from './bar'; diff --git a/test/form/exports-at-end-if-possible/_config.js b/test/form/exports-at-end-if-possible/_config.js new file mode 100644 index 0000000..a898bc7 --- /dev/null +++ b/test/form/exports-at-end-if-possible/_config.js @@ -0,0 +1,7 @@ +module.exports = { + description: 'exports variables at end, if possible', + options: { + moduleName: 'myBundle' + }, + // solo: true +}; diff --git a/test/form/exports-at-end-if-possible/_expected/amd.js b/test/form/exports-at-end-if-possible/_expected/amd.js new file mode 100644 index 0000000..2612901 --- /dev/null +++ b/test/form/exports-at-end-if-possible/_expected/amd.js @@ -0,0 +1,11 @@ +define(['exports'], function (exports) { 'use strict'; + + var FOO = 'foo'; + + console.log( FOO ); + console.log( FOO ); + console.log( FOO ); + + exports.FOO = FOO; + +}); diff --git a/test/form/exports-at-end-if-possible/_expected/cjs.js b/test/form/exports-at-end-if-possible/_expected/cjs.js new file mode 100644 index 0000000..57d55d3 --- /dev/null +++ b/test/form/exports-at-end-if-possible/_expected/cjs.js @@ -0,0 +1,9 @@ +'use strict'; + +var FOO = 'foo'; + +console.log( FOO ); +console.log( FOO ); +console.log( FOO ); + +exports.FOO = FOO; diff --git a/test/form/exports-at-end-if-possible/_expected/es6.js b/test/form/exports-at-end-if-possible/_expected/es6.js new file mode 100644 index 0000000..b42b4e4 --- /dev/null +++ b/test/form/exports-at-end-if-possible/_expected/es6.js @@ -0,0 +1,7 @@ +var FOO = 'foo'; + +console.log( FOO ); +console.log( FOO ); +console.log( FOO ); + +export { FOO }; diff --git a/test/form/exports-at-end-if-possible/_expected/iife.js b/test/form/exports-at-end-if-possible/_expected/iife.js new file mode 100644 index 0000000..d1b29c8 --- /dev/null +++ b/test/form/exports-at-end-if-possible/_expected/iife.js @@ -0,0 +1,11 @@ +(function (exports) { 'use strict'; + + var FOO = 'foo'; + + console.log( FOO ); + console.log( FOO ); + console.log( FOO ); + + exports.FOO = FOO; + +})((this.myBundle = {})); diff --git a/test/form/exported-empty-vars/_expected/umd.js b/test/form/exports-at-end-if-possible/_expected/umd.js similarity index 72% rename from test/form/exported-empty-vars/_expected/umd.js rename to test/form/exports-at-end-if-possible/_expected/umd.js index 9cde4bd..f30f365 100644 --- a/test/form/exported-empty-vars/_expected/umd.js +++ b/test/form/exports-at-end-if-possible/_expected/umd.js @@ -4,9 +4,12 @@ factory((global.myBundle = {})); }(this, function (exports) { 'use strict'; - exports.foo = 42; + var FOO = 'foo'; - exports.bar = 43; - exports.baz = 44; + console.log( FOO ); + console.log( FOO ); + console.log( FOO ); + + exports.FOO = FOO; })); diff --git a/test/form/exports-at-end-if-possible/main.js b/test/form/exports-at-end-if-possible/main.js new file mode 100644 index 0000000..ee6a75a --- /dev/null +++ b/test/form/exports-at-end-if-possible/main.js @@ -0,0 +1,5 @@ +export var FOO = 'foo'; + +console.log( FOO ); +console.log( FOO ); +console.log( FOO ); diff --git a/test/form/external-imports/_expected/cjs.js b/test/form/external-imports/_expected/cjs.js index ac9f79d..b6f5d31 100644 --- a/test/form/external-imports/_expected/cjs.js +++ b/test/form/external-imports/_expected/cjs.js @@ -1,10 +1,10 @@ 'use strict'; var factory = require('factory'); -factory = 'default' in factory ? factory['default'] : factory; var baz = require('baz'); var containers = require('shipping-port'); var alphabet = require('alphabet'); +factory = 'default' in factory ? factory['default'] : factory; var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet; factory( null ); diff --git a/test/form/external-imports/_expected/es6.js b/test/form/external-imports/_expected/es6.js index cd41111..4d05b46 100644 --- a/test/form/external-imports/_expected/es6.js +++ b/test/form/external-imports/_expected/es6.js @@ -1,10 +1,10 @@ import factory from 'factory'; import { bar, foo } from 'baz'; import * as containers from 'shipping-port'; -import alphabet, { a } from 'alphabet'; +import alphabet__default, { a } from 'alphabet'; factory( null ); foo( bar ); containers.forEach( console.log, console ); console.log( a ); -console.log( alphabet.length ); +console.log( alphabet__default.length ); diff --git a/test/form/internal-conflict-resolution/_expected/amd.js b/test/form/internal-conflict-resolution/_expected/amd.js index f2b3c4a..62ff65b 100644 --- a/test/form/internal-conflict-resolution/_expected/amd.js +++ b/test/form/internal-conflict-resolution/_expected/amd.js @@ -1,15 +1,15 @@ define(function () { 'use strict'; - var _bar = 42; + var bar = 42; function foo () { - return _bar; + return bar; } - function bar () { + function _bar () { alert( foo() ); } - bar(); + _bar(); }); diff --git a/test/form/internal-conflict-resolution/_expected/cjs.js b/test/form/internal-conflict-resolution/_expected/cjs.js index 74ae2f8..cbc7af9 100644 --- a/test/form/internal-conflict-resolution/_expected/cjs.js +++ b/test/form/internal-conflict-resolution/_expected/cjs.js @@ -1,13 +1,13 @@ 'use strict'; -var _bar = 42; +var bar = 42; function foo () { - return _bar; + return bar; } -function bar () { +function _bar () { alert( foo() ); } -bar(); +_bar(); diff --git a/test/form/internal-conflict-resolution/_expected/es6.js b/test/form/internal-conflict-resolution/_expected/es6.js index e1fc1f7..7bb41e3 100644 --- a/test/form/internal-conflict-resolution/_expected/es6.js +++ b/test/form/internal-conflict-resolution/_expected/es6.js @@ -1,11 +1,11 @@ -var _bar = 42; +var bar = 42; function foo () { - return _bar; + return bar; } -function bar () { +function _bar () { alert( foo() ); } -bar(); +_bar(); diff --git a/test/form/internal-conflict-resolution/_expected/iife.js b/test/form/internal-conflict-resolution/_expected/iife.js index efe296f..76687ff 100644 --- a/test/form/internal-conflict-resolution/_expected/iife.js +++ b/test/form/internal-conflict-resolution/_expected/iife.js @@ -1,15 +1,15 @@ (function () { 'use strict'; - var _bar = 42; + var bar = 42; function foo () { - return _bar; + return bar; } - function bar () { + function _bar () { alert( foo() ); } - bar(); + _bar(); })(); diff --git a/test/form/internal-conflict-resolution/_expected/umd.js b/test/form/internal-conflict-resolution/_expected/umd.js index 979f229..d9d801b 100644 --- a/test/form/internal-conflict-resolution/_expected/umd.js +++ b/test/form/internal-conflict-resolution/_expected/umd.js @@ -4,16 +4,16 @@ factory(); }(this, function () { 'use strict'; - var _bar = 42; + var bar = 42; function foo () { - return _bar; + return bar; } - function bar () { + function _bar () { alert( foo() ); } - bar(); + _bar(); })); diff --git a/test/form/multiple-exports/_expected/amd.js b/test/form/multiple-exports/_expected/amd.js index 5460f4f..2ebff3b 100644 --- a/test/form/multiple-exports/_expected/amd.js +++ b/test/form/multiple-exports/_expected/amd.js @@ -1,6 +1,9 @@ define(['exports'], function (exports) { 'use strict'; - exports.foo = 1; - exports.bar = 2; + var foo = 1; + var bar = 2; + + exports.foo = foo; + exports.bar = bar; }); diff --git a/test/form/multiple-exports/_expected/cjs.js b/test/form/multiple-exports/_expected/cjs.js index 0bbfe60..1968b8b 100644 --- a/test/form/multiple-exports/_expected/cjs.js +++ b/test/form/multiple-exports/_expected/cjs.js @@ -1,4 +1,7 @@ 'use strict'; -exports.foo = 1; -exports.bar = 2; +var foo = 1; +var bar = 2; + +exports.foo = foo; +exports.bar = bar; diff --git a/test/form/multiple-exports/_expected/iife.js b/test/form/multiple-exports/_expected/iife.js index 09461cb..8ed8290 100644 --- a/test/form/multiple-exports/_expected/iife.js +++ b/test/form/multiple-exports/_expected/iife.js @@ -1,6 +1,9 @@ (function (exports) { 'use strict'; - exports.foo = 1; - exports.bar = 2; + var foo = 1; + var bar = 2; + + exports.foo = foo; + exports.bar = bar; })((this.myBundle = {})); diff --git a/test/form/multiple-exports/_expected/umd.js b/test/form/multiple-exports/_expected/umd.js index 2fd0eb7..687b75c 100644 --- a/test/form/multiple-exports/_expected/umd.js +++ b/test/form/multiple-exports/_expected/umd.js @@ -4,7 +4,10 @@ factory((global.myBundle = {})); }(this, function (exports) { 'use strict'; - exports.foo = 1; - exports.bar = 2; + var foo = 1; + var bar = 2; + + exports.foo = foo; + exports.bar = bar; })); diff --git a/test/form/namespace-optimization/_config.js b/test/form/namespace-optimization/_config.js new file mode 100644 index 0000000..ec88243 --- /dev/null +++ b/test/form/namespace-optimization/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'it does static lookup optimization of internal namespaces' +}; diff --git a/test/form/namespace-optimization/_expected/amd.js b/test/form/namespace-optimization/_expected/amd.js new file mode 100644 index 0000000..a244c47 --- /dev/null +++ b/test/form/namespace-optimization/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + function a () {} + + a(); + +}); diff --git a/test/form/namespace-optimization/_expected/cjs.js b/test/form/namespace-optimization/_expected/cjs.js new file mode 100644 index 0000000..b52a7e5 --- /dev/null +++ b/test/form/namespace-optimization/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +function a () {} + +a(); diff --git a/test/form/namespace-optimization/_expected/es6.js b/test/form/namespace-optimization/_expected/es6.js new file mode 100644 index 0000000..8bee044 --- /dev/null +++ b/test/form/namespace-optimization/_expected/es6.js @@ -0,0 +1,3 @@ +function a () {} + +a(); diff --git a/test/form/namespace-optimization/_expected/iife.js b/test/form/namespace-optimization/_expected/iife.js new file mode 100644 index 0000000..aac8ff9 --- /dev/null +++ b/test/form/namespace-optimization/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { 'use strict'; + + function a () {} + + a(); + +})(); diff --git a/test/form/namespace-optimization/_expected/umd.js b/test/form/namespace-optimization/_expected/umd.js new file mode 100644 index 0000000..38f7835 --- /dev/null +++ b/test/form/namespace-optimization/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}(this, function () { 'use strict'; + + function a () {} + + a(); + +})); diff --git a/test/form/namespace-optimization/bar.js b/test/form/namespace-optimization/bar.js new file mode 100644 index 0000000..aa96676 --- /dev/null +++ b/test/form/namespace-optimization/bar.js @@ -0,0 +1,3 @@ +import * as quux from './quux'; + +export { quux }; diff --git a/test/form/namespace-optimization/foo.js b/test/form/namespace-optimization/foo.js new file mode 100644 index 0000000..42a93ae --- /dev/null +++ b/test/form/namespace-optimization/foo.js @@ -0,0 +1,3 @@ +import * as bar from './bar'; + +export { bar }; diff --git a/test/form/namespace-optimization/main.js b/test/form/namespace-optimization/main.js new file mode 100644 index 0000000..e902244 --- /dev/null +++ b/test/form/namespace-optimization/main.js @@ -0,0 +1,3 @@ +import * as foo from './foo'; + +foo.bar.quux.a(); diff --git a/test/form/namespace-optimization/quux.js b/test/form/namespace-optimization/quux.js new file mode 100644 index 0000000..103a9f0 --- /dev/null +++ b/test/form/namespace-optimization/quux.js @@ -0,0 +1 @@ +export function a () {} diff --git a/test/form/preserves-comments-after-imports/_expected/amd.js b/test/form/preserves-comments-after-imports/_expected/amd.js index fd4591b..82a0a52 100644 --- a/test/form/preserves-comments-after-imports/_expected/amd.js +++ b/test/form/preserves-comments-after-imports/_expected/amd.js @@ -4,6 +4,8 @@ define(['exports'], function (exports) { 'use strict'; var number = 5; /** A comment for obj */ - exports.obj = { number }; + var obj = { number }; + + exports.obj = obj; }); diff --git a/test/form/preserves-comments-after-imports/_expected/cjs.js b/test/form/preserves-comments-after-imports/_expected/cjs.js index 86e64c1..8b15cd1 100644 --- a/test/form/preserves-comments-after-imports/_expected/cjs.js +++ b/test/form/preserves-comments-after-imports/_expected/cjs.js @@ -4,4 +4,6 @@ var number = 5; /** A comment for obj */ -exports.obj = { number }; +var obj = { number }; + +exports.obj = obj; diff --git a/test/form/preserves-comments-after-imports/_expected/iife.js b/test/form/preserves-comments-after-imports/_expected/iife.js index 02f5255..9da8252 100644 --- a/test/form/preserves-comments-after-imports/_expected/iife.js +++ b/test/form/preserves-comments-after-imports/_expected/iife.js @@ -4,6 +4,8 @@ var number = 5; /** A comment for obj */ - exports.obj = { number }; + var obj = { number }; + + exports.obj = obj; })((this.myBundle = {})); diff --git a/test/form/preserves-comments-after-imports/_expected/umd.js b/test/form/preserves-comments-after-imports/_expected/umd.js index 6993070..96fec25 100644 --- a/test/form/preserves-comments-after-imports/_expected/umd.js +++ b/test/form/preserves-comments-after-imports/_expected/umd.js @@ -8,6 +8,8 @@ var number = 5; /** A comment for obj */ - exports.obj = { number }; + var obj = { number }; + + exports.obj = obj; })); diff --git a/test/form/sourcemaps-inline/_expected/amd.js b/test/form/sourcemaps-inline/_expected/amd.js index 9861ddd..e6aaf48 100644 --- a/test/form/sourcemaps-inline/_expected/amd.js +++ b/test/form/sourcemaps-inline/_expected/amd.js @@ -14,4 +14,4 @@ define(function () { 'use strict'; bar(); }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW1kLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxDQUFlLFNBQVMsR0FBRyxJQUFJO0FBQS9CLENBQ0EsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLG1CQUFtQixFQUFFO0FBRG5DLENBRUE7O0FDRkEsQ0FBZSxTQUFTLEdBQUcsSUFBSTtBQUEvQixDQUNBLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTtBQURuQyxDQUVBOztBQ0ZBLENBR0EsT0FBTyxDQUFDLEdBQUcsRUFBRSxvQkFBb0IsRUFBRTs7QUFIbkMsQ0FLQSxHQUFHLEVBQUU7QUFMTCxDQU1BLEdBQUcsRUFBRSw7OyJ9 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW1kLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Q0FBZSxTQUFTLEdBQUcsSUFBSTtFQUM5QixPQUFPLENBQUMsR0FBRyxFQUFFLG1CQUFtQixFQUFFOzs7Q0NEcEIsU0FBUyxHQUFHLElBQUk7RUFDOUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTs7O0NDRW5DLE9BQU8sQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUU7O0NBRW5DLEdBQUcsRUFBRTtDQUNMLEdBQUcsRUFBRSw7OyJ9 diff --git a/test/form/sourcemaps-inline/_expected/cjs.js b/test/form/sourcemaps-inline/_expected/cjs.js index e71035b..1f995ee 100644 --- a/test/form/sourcemaps-inline/_expected/cjs.js +++ b/test/form/sourcemaps-inline/_expected/cjs.js @@ -12,4 +12,4 @@ console.log( 'hello from main.js' ); foo(); bar(); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2pzLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBZSxTQUFTLEdBQUcsSUFBSTtBQUMvQixDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7QUFDbkM7O0FDRmUsU0FBUyxHQUFHLElBQUk7QUFDL0IsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLG1CQUFtQixFQUFFO0FBQ25DOztBQ0NBLE9BQU8sQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUU7O0FBRW5DLEdBQUcsRUFBRTtBQUNMLEdBQUcsRUFBRSJ9 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2pzLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBZSxTQUFTLEdBQUcsSUFBSTtDQUM5QixPQUFPLENBQUMsR0FBRyxFQUFFLG1CQUFtQixFQUFFOzs7QUNEcEIsU0FBUyxHQUFHLElBQUk7Q0FDOUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTs7O0FDRW5DLE9BQU8sQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUU7O0FBRW5DLEdBQUcsRUFBRTtBQUNMLEdBQUcsRUFBRSJ9 diff --git a/test/form/sourcemaps-inline/_expected/es6.js b/test/form/sourcemaps-inline/_expected/es6.js index 9258b3e..3374dfb 100644 --- a/test/form/sourcemaps-inline/_expected/es6.js +++ b/test/form/sourcemaps-inline/_expected/es6.js @@ -10,4 +10,4 @@ console.log( 'hello from main.js' ); foo(); bar(); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXM2LmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQWUsU0FBUyxHQUFHLElBQUk7QUFDL0IsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLG1CQUFtQixFQUFFO0FBQ25DOztBQ0ZlLFNBQVMsR0FBRyxJQUFJO0FBQy9CLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTtBQUNuQzs7QUNDQSxPQUFPLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFOztBQUVuQyxHQUFHLEVBQUU7QUFDTCxHQUFHLEVBQUUifQ== +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXM2LmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQWUsU0FBUyxHQUFHLElBQUk7Q0FDOUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTs7O0FDRHBCLFNBQVMsR0FBRyxJQUFJO0NBQzlCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7OztBQ0VuQyxPQUFPLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFOztBQUVuQyxHQUFHLEVBQUU7QUFDTCxHQUFHLEVBQUUifQ== diff --git a/test/form/sourcemaps-inline/_expected/iife.js b/test/form/sourcemaps-inline/_expected/iife.js index 50dbeb3..f38c6ef 100644 --- a/test/form/sourcemaps-inline/_expected/iife.js +++ b/test/form/sourcemaps-inline/_expected/iife.js @@ -14,4 +14,4 @@ bar(); })(); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWlmZS5qcyIsInNvdXJjZXMiOlsiLi4vZm9vLmpzIiwiLi4vYmFyLmpzIiwiLi4vbWFpbi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBmb28gKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gZm9vLmpzJyApO1xufVxuIiwiZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gYmFyICgpIHtcblx0Y29uc29sZS5sb2coICdoZWxsbyBmcm9tIGJhci5qcycgKTtcbn1cbiIsImltcG9ydCBmb28gZnJvbSAnLi9mb28nO1xuaW1wb3J0IGJhciBmcm9tICcuL2Jhcic7XG5cbmNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBtYWluLmpzJyApO1xuXG5mb28oKTtcbmJhcigpO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsQ0FBZSxTQUFTLEdBQUcsSUFBSTtBQUEvQixDQUNBLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTtBQURuQyxDQUVBOztBQ0ZBLENBQWUsU0FBUyxHQUFHLElBQUk7QUFBL0IsQ0FDQSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7QUFEbkMsQ0FFQTs7QUNGQSxDQUdBLE9BQU8sQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUU7O0FBSG5DLENBS0EsR0FBRyxFQUFFO0FBTEwsQ0FNQSxHQUFHLEVBQUUsOzsifQ== +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWlmZS5qcyIsInNvdXJjZXMiOlsiLi4vZm9vLmpzIiwiLi4vYmFyLmpzIiwiLi4vbWFpbi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBmb28gKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gZm9vLmpzJyApO1xufVxuIiwiZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gYmFyICgpIHtcblx0Y29uc29sZS5sb2coICdoZWxsbyBmcm9tIGJhci5qcycgKTtcbn1cbiIsImltcG9ydCBmb28gZnJvbSAnLi9mb28nO1xuaW1wb3J0IGJhciBmcm9tICcuL2Jhcic7XG5cbmNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBtYWluLmpzJyApO1xuXG5mb28oKTtcbmJhcigpO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0NBQWUsU0FBUyxHQUFHLElBQUk7RUFDOUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTs7O0NDRHBCLFNBQVMsR0FBRyxJQUFJO0VBQzlCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7OztDQ0VuQyxPQUFPLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFOztDQUVuQyxHQUFHLEVBQUU7Q0FDTCxHQUFHLEVBQUUsOzsifQ== diff --git a/test/form/sourcemaps-inline/_expected/umd.js b/test/form/sourcemaps-inline/_expected/umd.js index e213a79..a63cd7a 100644 --- a/test/form/sourcemaps-inline/_expected/umd.js +++ b/test/form/sourcemaps-inline/_expected/umd.js @@ -18,4 +18,4 @@ bar(); })); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW1kLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsQ0FBZSxTQUFTLEdBQUcsSUFBSTtBQUEvQixDQUNBLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTtBQURuQyxDQUVBOztBQ0ZBLENBQWUsU0FBUyxHQUFHLElBQUk7QUFBL0IsQ0FDQSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7QUFEbkMsQ0FFQTs7QUNGQSxDQUdBLE9BQU8sQ0FBQyxHQUFHLEVBQUUsb0JBQW9CLEVBQUU7O0FBSG5DLENBS0EsR0FBRyxFQUFFO0FBTEwsQ0FNQSxHQUFHLEVBQUUsOzsifQ== +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW1kLmpzIiwic291cmNlcyI6WyIuLi9mb28uanMiLCIuLi9iYXIuanMiLCIuLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGZvbyAoKSB7XG5cdGNvbnNvbGUubG9nKCAnaGVsbG8gZnJvbSBmb28uanMnICk7XG59XG4iLCJleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBiYXIgKCkge1xuXHRjb25zb2xlLmxvZyggJ2hlbGxvIGZyb20gYmFyLmpzJyApO1xufVxuIiwiaW1wb3J0IGZvbyBmcm9tICcuL2Zvbyc7XG5pbXBvcnQgYmFyIGZyb20gJy4vYmFyJztcblxuY29uc29sZS5sb2coICdoZWxsbyBmcm9tIG1haW4uanMnICk7XG5cbmZvbygpO1xuYmFyKCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0NBQWUsU0FBUyxHQUFHLElBQUk7RUFDOUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsRUFBRTs7O0NDRHBCLFNBQVMsR0FBRyxJQUFJO0VBQzlCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUU7OztDQ0VuQyxPQUFPLENBQUMsR0FBRyxFQUFFLG9CQUFvQixFQUFFOztDQUVuQyxHQUFHLEVBQUU7Q0FDTCxHQUFHLEVBQUUsOzsifQ== diff --git a/test/form/sourcemaps/_expected/amd.js.map b/test/form/sourcemaps/_expected/amd.js.map index f596c11..2b2f6e2 100644 --- a/test/form/sourcemaps/_expected/amd.js.map +++ b/test/form/sourcemaps/_expected/amd.js.map @@ -1 +1 @@ -{"version":3,"file":"amd.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;AAAA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAGA,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAHnC,CAKA,GAAG,EAAE;AALL,CAMA,GAAG,EAAE,;;"} +{"version":3,"file":"amd.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;CAAe,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCDpB,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCEnC,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;CAEnC,GAAG,EAAE;CACL,GAAG,EAAE,;;"} diff --git a/test/form/sourcemaps/_expected/cjs.js.map b/test/form/sourcemaps/_expected/cjs.js.map index 99d230e..452d9f3 100644 --- a/test/form/sourcemaps/_expected/cjs.js.map +++ b/test/form/sourcemaps/_expected/cjs.js.map @@ -1 +1 @@ -{"version":3,"file":"cjs.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;AAAe,SAAS,GAAG,IAAI;AAC/B,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AACnC;;ACFe,SAAS,GAAG,IAAI;AAC/B,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AACnC;;ACCA,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAEnC,GAAG,EAAE;AACL,GAAG,EAAE"} +{"version":3,"file":"cjs.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;AAAe,SAAS,GAAG,IAAI;CAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;ACDpB,SAAS,GAAG,IAAI;CAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;ACEnC,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAEnC,GAAG,EAAE;AACL,GAAG,EAAE"} diff --git a/test/form/sourcemaps/_expected/es6.js.map b/test/form/sourcemaps/_expected/es6.js.map index ea62cf4..6489227 100644 --- a/test/form/sourcemaps/_expected/es6.js.map +++ b/test/form/sourcemaps/_expected/es6.js.map @@ -1 +1 @@ -{"version":3,"file":"es6.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":"AAAe,SAAS,GAAG,IAAI;AAC/B,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AACnC;;ACFe,SAAS,GAAG,IAAI;AAC/B,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AACnC;;ACCA,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAEnC,GAAG,EAAE;AACL,GAAG,EAAE"} +{"version":3,"file":"es6.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":"AAAe,SAAS,GAAG,IAAI;CAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;ACDpB,SAAS,GAAG,IAAI;CAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;ACEnC,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAEnC,GAAG,EAAE;AACL,GAAG,EAAE"} diff --git a/test/form/sourcemaps/_expected/iife.js.map b/test/form/sourcemaps/_expected/iife.js.map index 8584fd5..6b91b81 100644 --- a/test/form/sourcemaps/_expected/iife.js.map +++ b/test/form/sourcemaps/_expected/iife.js.map @@ -1 +1 @@ -{"version":3,"file":"iife.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;AAAA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAGA,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAHnC,CAKA,GAAG,EAAE;AALL,CAMA,GAAG,EAAE,;;"} +{"version":3,"file":"iife.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;CAAe,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCDpB,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCEnC,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;CAEnC,GAAG,EAAE;CACL,GAAG,EAAE,;;"} diff --git a/test/form/sourcemaps/_expected/umd.js.map b/test/form/sourcemaps/_expected/umd.js.map index 396895e..08e949a 100644 --- a/test/form/sourcemaps/_expected/umd.js.map +++ b/test/form/sourcemaps/_expected/umd.js.map @@ -1 +1 @@ -{"version":3,"file":"umd.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;;;;;AAAA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAAe,SAAS,GAAG,IAAI;AAA/B,CACA,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;AADnC,CAEA;;ACFA,CAGA,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;AAHnC,CAKA,GAAG,EAAE;AALL,CAMA,GAAG,EAAE,;;"} +{"version":3,"file":"umd.js","sources":["../foo.js","../bar.js","../main.js"],"sourcesContent":["export default function foo () {\n\tconsole.log( 'hello from foo.js' );\n}\n","export default function bar () {\n\tconsole.log( 'hello from bar.js' );\n}\n","import foo from './foo';\nimport bar from './bar';\n\nconsole.log( 'hello from main.js' );\n\nfoo();\nbar();\n"],"names":[],"mappings":";;;;;;CAAe,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCDpB,SAAS,GAAG,IAAI;EAC9B,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE;;;CCEnC,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE;;CAEnC,GAAG,EAAE;CACL,GAAG,EAAE,;;"} diff --git a/test/form/unused-side-effect/_config.js b/test/form/unused-side-effect/_config.js new file mode 100644 index 0000000..0431077 --- /dev/null +++ b/test/form/unused-side-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'side-effects to non-globals are not blindly included' +}; diff --git a/test/form/unused-side-effect/_expected/amd.js b/test/form/unused-side-effect/_expected/amd.js new file mode 100644 index 0000000..c82bf4f --- /dev/null +++ b/test/form/unused-side-effect/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var foo = 42; + + assert.equal( foo, 42 ); + +}); diff --git a/test/form/unused-side-effect/_expected/cjs.js b/test/form/unused-side-effect/_expected/cjs.js new file mode 100644 index 0000000..0d2573f --- /dev/null +++ b/test/form/unused-side-effect/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var foo = 42; + +assert.equal( foo, 42 ); diff --git a/test/form/unused-side-effect/_expected/es6.js b/test/form/unused-side-effect/_expected/es6.js new file mode 100644 index 0000000..e50ecda --- /dev/null +++ b/test/form/unused-side-effect/_expected/es6.js @@ -0,0 +1,3 @@ +var foo = 42; + +assert.equal( foo, 42 ); diff --git a/test/form/unused-side-effect/_expected/iife.js b/test/form/unused-side-effect/_expected/iife.js new file mode 100644 index 0000000..a3f7fc8 --- /dev/null +++ b/test/form/unused-side-effect/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { 'use strict'; + + var foo = 42; + + assert.equal( foo, 42 ); + +})(); diff --git a/test/form/unused-side-effect/_expected/umd.js b/test/form/unused-side-effect/_expected/umd.js new file mode 100644 index 0000000..f96fd30 --- /dev/null +++ b/test/form/unused-side-effect/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}(this, function () { 'use strict'; + + var foo = 42; + + assert.equal( foo, 42 ); + +})); diff --git a/test/form/unused-side-effect/foo.js b/test/form/unused-side-effect/foo.js new file mode 100644 index 0000000..7953f6e --- /dev/null +++ b/test/form/unused-side-effect/foo.js @@ -0,0 +1,13 @@ +var uid = 0; +uid = 1; +uid += 1; +uid++; + +// ensure identifiers aren't treated as globals just because +// var declaration hasn't been encountered yet... +uid2 = 1; +uid2 += 1; +uid2++; +var uid2; + +export var foo = 42; diff --git a/test/form/unused-side-effect/main.js b/test/form/unused-side-effect/main.js new file mode 100644 index 0000000..ef12cae --- /dev/null +++ b/test/form/unused-side-effect/main.js @@ -0,0 +1,2 @@ +import { foo } from './foo'; +assert.equal( foo, 42 ); diff --git a/test/function/allows-external-modules-from-nested-module/main.js b/test/function/allows-external-modules-from-nested-module/main.js index 8bacdc9..bdd4f85 100644 --- a/test/function/allows-external-modules-from-nested-module/main.js +++ b/test/function/allows-external-modules-from-nested-module/main.js @@ -1,8 +1,8 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; import foo from './foo'; var path = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( relative( path, path2 ), '../../baz/bar' ); -assert.equal( foo, '../../c/b' ); \ No newline at end of file +assert.equal( relative( path, path2 ), normalize('../../baz/bar') ); +assert.equal( foo, normalize('../../c/b') ); \ No newline at end of file diff --git a/test/function/assignment-patterns/_config.js b/test/function/assignment-patterns/_config.js new file mode 100644 index 0000000..d4afdc3 --- /dev/null +++ b/test/function/assignment-patterns/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'allows reassigments to default parameters that shadow imports', + babel: true +}; diff --git a/test/function/assignment-patterns/main.js b/test/function/assignment-patterns/main.js new file mode 100644 index 0000000..844c908 --- /dev/null +++ b/test/function/assignment-patterns/main.js @@ -0,0 +1,21 @@ +import { bar, baz, x, items, p, q, r, s } from './other'; + +function foo ( bar = 1, { baz } = { baz: 2 }, [[[,x = 3] = []] = []] = [], ...items ) { + bar += 1; + baz += 1; + x += 1; + + let { p, q } = { p: 4, q: 5 }; + let [ r, s ] = [ 6, 7 ]; + + p++; + q += 1; + r = 7; + s = 6; + + return bar + baz + x + items.length + p + q + r + s; +} + +assert.equal( foo(), 33 ); +assert.equal( foo( 2 ), 34 ); +assert.equal( foo( 2, { baz: 3 }, [[[99,10]]], 'a', 'b', 'c' ), 45 ); diff --git a/test/function/assignment-patterns/other.js b/test/function/assignment-patterns/other.js new file mode 100644 index 0000000..840584c --- /dev/null +++ b/test/function/assignment-patterns/other.js @@ -0,0 +1,8 @@ +export const bar = 'bar'; +export const baz = 'baz'; +export const x = 'x'; +export const items = 'items'; +export const p = 'p'; +export const q = 'q'; +export const r = 'r'; +export const s = 's'; diff --git a/test/function/assignment-to-exports/_config.js b/test/function/assignment-to-exports/_config.js index c958adb..fac0cb3 100644 --- a/test/function/assignment-to-exports/_config.js +++ b/test/function/assignment-to-exports/_config.js @@ -6,5 +6,6 @@ module.exports = { assert.equal( exports.count, 0 ); exports.incr(); assert.equal( exports.count, 1 ); - } + }, + // solo: true }; diff --git a/test/function/custom-path-resolver-async/_config.js b/test/function/custom-path-resolver-async/_config.js index 287f536..c336d67 100644 --- a/test/function/custom-path-resolver-async/_config.js +++ b/test/function/custom-path-resolver-async/_config.js @@ -8,7 +8,7 @@ module.exports = { var Promise = require( 'sander' ).Promise; var resolved; - if ( importee === path.resolve( __dirname, 'main.js' ) ) return importee; + if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee; if ( importee === 'foo' ) { resolved = path.resolve( __dirname, 'bar.js' ); diff --git a/test/function/custom-path-resolver-sync/_config.js b/test/function/custom-path-resolver-sync/_config.js index 9a755e5..ee993e1 100644 --- a/test/function/custom-path-resolver-sync/_config.js +++ b/test/function/custom-path-resolver-sync/_config.js @@ -5,7 +5,7 @@ module.exports = { description: 'uses a custom path resolver (synchronous)', options: { resolveId: function ( importee, importer ) { - if ( importee === path.resolve( __dirname, 'main.js' ) ) return importee; + if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee; if ( importee === 'foo' ) return path.resolve( __dirname, 'bar.js' ); return false; diff --git a/test/function/duplicate-import-fails/_config.js b/test/function/duplicate-import-fails/_config.js index 4280c53..4083690 100644 --- a/test/function/duplicate-import-fails/_config.js +++ b/test/function/duplicate-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate imports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 9 }); assert.ok( /Duplicated import/.test( err.message ) ); } diff --git a/test/function/duplicate-import-specifier-fails/_config.js b/test/function/duplicate-import-specifier-fails/_config.js index b935a69..e3957b5 100644 --- a/test/function/duplicate-import-specifier-fails/_config.js +++ b/test/function/duplicate-import-specifier-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate import specifiers', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 1, column: 12 }); assert.ok( /Duplicated import/.test( err.message ) ); } diff --git a/test/function/dynamic-namespace-lookup/_config.js b/test/function/dynamic-namespace-lookup/_config.js new file mode 100644 index 0000000..3c2fe54 --- /dev/null +++ b/test/function/dynamic-namespace-lookup/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does namespace optimization when possible, but not for dynamic lookups' +}; diff --git a/test/function/dynamic-namespace-lookup/foo.js b/test/function/dynamic-namespace-lookup/foo.js new file mode 100644 index 0000000..a727aee --- /dev/null +++ b/test/function/dynamic-namespace-lookup/foo.js @@ -0,0 +1,2 @@ +export var bar = 'bar'; +export var baz = 'baz'; diff --git a/test/function/dynamic-namespace-lookup/main.js b/test/function/dynamic-namespace-lookup/main.js new file mode 100644 index 0000000..ee1ce10 --- /dev/null +++ b/test/function/dynamic-namespace-lookup/main.js @@ -0,0 +1,8 @@ +import * as foo from './foo'; + +var bar = 'baz'; + +assert.equal( foo.bar, 'bar' ); +assert.equal( foo.baz, 'baz' ); + +assert.equal( foo[ bar ], 'baz' ); diff --git a/test/function/export-all/_config.js b/test/function/export-all/_config.js index 24639c6..8060316 100644 --- a/test/function/export-all/_config.js +++ b/test/function/export-all/_config.js @@ -1,5 +1,3 @@ -var assert = require( 'assert' ); - module.exports = { description: 'allows export *' }; diff --git a/test/function/export-from-default-renamed/_config.js b/test/function/export-from-default-renamed/_config.js new file mode 100644 index 0000000..2c9c52a --- /dev/null +++ b/test/function/export-from-default-renamed/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'export from works with multiple renamed default exports' +}; diff --git a/test/function/export-from-default-renamed/a.js b/test/function/export-from-default-renamed/a.js new file mode 100644 index 0000000..0ed5c30 --- /dev/null +++ b/test/function/export-from-default-renamed/a.js @@ -0,0 +1 @@ +export default 'a'; diff --git a/test/function/export-from-default-renamed/b.js b/test/function/export-from-default-renamed/b.js new file mode 100644 index 0000000..a68ac28 --- /dev/null +++ b/test/function/export-from-default-renamed/b.js @@ -0,0 +1 @@ +export default 'b'; diff --git a/test/function/export-from-default-renamed/foo.js b/test/function/export-from-default-renamed/foo.js new file mode 100644 index 0000000..c69fcf2 --- /dev/null +++ b/test/function/export-from-default-renamed/foo.js @@ -0,0 +1,2 @@ +export { default as a } from './a'; +export { default as b } from './b'; diff --git a/test/function/export-from-default-renamed/main.js b/test/function/export-from-default-renamed/main.js new file mode 100644 index 0000000..3a9fb68 --- /dev/null +++ b/test/function/export-from-default-renamed/main.js @@ -0,0 +1,3 @@ +import { a, b } from './foo'; +assert.equal( a, 'a' ); +assert.equal( b, 'b' ); diff --git a/test/function/export-not-at-top-level-fails/_config.js b/test/function/export-not-at-top-level-fails/_config.js index c68ca42..2dadeab 100644 --- a/test/function/export-not-at-top-level-fails/_config.js +++ b/test/function/export-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 2 }); assert.ok( /may only appear at the top level/.test( err.message ) ); } diff --git a/test/function/functions-renamed-correctly/_config.js b/test/function/functions-renamed-correctly/_config.js new file mode 100644 index 0000000..b0719a0 --- /dev/null +++ b/test/function/functions-renamed-correctly/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'renames function expression IDs correctly' +}; diff --git a/test/function/functions-renamed-correctly/after.js b/test/function/functions-renamed-correctly/after.js new file mode 100644 index 0000000..549d2db --- /dev/null +++ b/test/function/functions-renamed-correctly/after.js @@ -0,0 +1,5 @@ +function x () { + return 'after'; +} + +export { x as after }; diff --git a/test/function/functions-renamed-correctly/before.js b/test/function/functions-renamed-correctly/before.js new file mode 100644 index 0000000..56fba5f --- /dev/null +++ b/test/function/functions-renamed-correctly/before.js @@ -0,0 +1,5 @@ +function x () { + return 'before'; +} + +export { x as before }; diff --git a/test/function/functions-renamed-correctly/factorial.js b/test/function/functions-renamed-correctly/factorial.js new file mode 100644 index 0000000..9f5e752 --- /dev/null +++ b/test/function/functions-renamed-correctly/factorial.js @@ -0,0 +1,7 @@ +var x = (function () { + return function x ( num ) { + return num <= 2 ? num : num * x( num - 1 ); + }; +})(); + +export { x }; diff --git a/test/function/functions-renamed-correctly/main.js b/test/function/functions-renamed-correctly/main.js new file mode 100644 index 0000000..d2e616a --- /dev/null +++ b/test/function/functions-renamed-correctly/main.js @@ -0,0 +1,7 @@ +import { before } from './before'; +import { x } from './factorial'; +import { after } from './after'; + +before(); // before and after ensure x is renamed +assert.equal( x( 5 ), 120 ); +after(); diff --git a/test/function/globally-called-modifying-function/_config.js b/test/function/globally-called-modifying-function/_config.js new file mode 100644 index 0000000..86b2f62 --- /dev/null +++ b/test/function/globally-called-modifying-function/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'globally called function should be included if it modifies an exported value (#112)' +}; diff --git a/test/function/globally-called-modifying-function/main.js b/test/function/globally-called-modifying-function/main.js new file mode 100644 index 0000000..3b2061e --- /dev/null +++ b/test/function/globally-called-modifying-function/main.js @@ -0,0 +1,3 @@ +import value from './module.js'; + +assert.equal( value, 3 ); diff --git a/test/function/globally-called-modifying-function/module.js b/test/function/globally-called-modifying-function/module.js new file mode 100644 index 0000000..fc50ee5 --- /dev/null +++ b/test/function/globally-called-modifying-function/module.js @@ -0,0 +1,17 @@ +var value = 1; + +function change () { + value = 2; +} + +function changeAgain () { + value += 1; +} + +change(); + +if ( true ) { + changeAgain(); +} + +export default value; diff --git a/test/function/import-default-from-external/main.js b/test/function/import-default-from-external/main.js index 5d5cd6d..aacce04 100644 --- a/test/function/import-default-from-external/main.js +++ b/test/function/import-default-from-external/main.js @@ -4,4 +4,4 @@ import path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( path.relative( path1, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( path.relative( path1, path2 ), path.normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-named-from-external/main.js b/test/function/import-named-from-external/main.js index 49027f6..1030a82 100644 --- a/test/function/import-named-from-external/main.js +++ b/test/function/import-named-from-external/main.js @@ -1,6 +1,6 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; var path = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( relative( path, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( relative( path, path2 ), normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-namespace-from-external-module-renamed/main.js b/test/function/import-namespace-from-external-module-renamed/main.js index 2208cd4..947231e 100644 --- a/test/function/import-namespace-from-external-module-renamed/main.js +++ b/test/function/import-namespace-from-external-module-renamed/main.js @@ -3,4 +3,4 @@ import * as node_path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( node_path.relative( path1, path2 ), '../../baz/bar' ); +assert.equal( node_path.relative( path1, path2 ), node_path.normalize('../../baz/bar') ); diff --git a/test/function/import-namespace-from-external-module/main.js b/test/function/import-namespace-from-external-module/main.js index 000a219..10f9d36 100644 --- a/test/function/import-namespace-from-external-module/main.js +++ b/test/function/import-namespace-from-external-module/main.js @@ -3,4 +3,4 @@ import * as path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( path.relative( path1, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( path.relative( path1, path2 ), path.normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-not-at-top-level-fails/_config.js b/test/function/import-not-at-top-level-fails/_config.js index c75e986..4f873aa 100644 --- a/test/function/import-not-at-top-level-fails/_config.js +++ b/test/function/import-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level imports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 2 }); assert.ok( /may only appear at the top level/.test( err.message ) ); } diff --git a/test/function/imports-are-deconflicted-b/main.js b/test/function/imports-are-deconflicted-b/main.js index fa81bd0..cf917fc 100644 --- a/test/function/imports-are-deconflicted-b/main.js +++ b/test/function/imports-are-deconflicted-b/main.js @@ -1,5 +1,6 @@ import foo from './foo'; import bar from './bar'; +import { normalize } from 'path'; assert.equal( foo, 'foo' ); -assert.equal( bar(), '../../baz/bar' ); +assert.equal( bar(), normalize('../../baz/bar') ); diff --git a/test/function/imports-are-deconflicted/main.js b/test/function/imports-are-deconflicted/main.js index fa81bd0..cf917fc 100644 --- a/test/function/imports-are-deconflicted/main.js +++ b/test/function/imports-are-deconflicted/main.js @@ -1,5 +1,6 @@ import foo from './foo'; import bar from './bar'; +import { normalize } from 'path'; assert.equal( foo, 'foo' ); -assert.equal( bar(), '../../baz/bar' ); +assert.equal( bar(), normalize('../../baz/bar') ); diff --git a/test/function/modify-assumed-global/_config.js b/test/function/modify-assumed-global/_config.js new file mode 100644 index 0000000..ee89a52 --- /dev/null +++ b/test/function/modify-assumed-global/_config.js @@ -0,0 +1,14 @@ +var assert = require( 'assert' ); + +var Math = {}; + +module.exports = { + description: 'side-effects to assumed globals are included', + context: { + Math: Math + }, + exports: function ( exports ) { + assert.equal( Math.square( 3 ), 9 ); + assert.equal( Math.cube( 3 ), 27 ); + } +}; diff --git a/test/function/modify-assumed-global/main.js b/test/function/modify-assumed-global/main.js new file mode 100644 index 0000000..cc4f217 --- /dev/null +++ b/test/function/modify-assumed-global/main.js @@ -0,0 +1,3 @@ +import { square } from './math'; + +assert.equal( square( 2 ), 4 ); diff --git a/test/function/modify-assumed-global/math.js b/test/function/modify-assumed-global/math.js new file mode 100644 index 0000000..469df59 --- /dev/null +++ b/test/function/modify-assumed-global/math.js @@ -0,0 +1,15 @@ +function square ( x ) { + return x * x; +} + +function cube ( x ) { + return x * x * x; +} + +Math.square = square; + +if ( true ) { + Math.cube = cube; +} + +export { square }; diff --git a/test/function/namespace-reassign-import-fails/_config.js b/test/function/namespace-reassign-import-fails/_config.js index 7e2d7a8..2d606f4 100644 --- a/test/function/namespace-reassign-import-fails/_config.js +++ b/test/function/namespace-reassign-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows reassignments to namespace exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/namespace-update-import-fails/_config.js b/test/function/namespace-update-import-fails/_config.js index 116f4d0..4cff5c0 100644 --- a/test/function/namespace-update-import-fails/_config.js +++ b/test/function/namespace-update-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to namespace exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/reassign-import-fails/_config.js b/test/function/reassign-import-fails/_config.js index c73c36d..e01d090 100644 --- a/test/function/reassign-import-fails/_config.js +++ b/test/function/reassign-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 8, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/reassign-import-not-at-top-level-fails/_config.js b/test/function/reassign-import-not-at-top-level-fails/_config.js index d17af29..6e00786 100644 --- a/test/function/reassign-import-not-at-top-level-fails/_config.js +++ b/test/function/reassign-import-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings not at the top level', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 7, column: 2 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/shadowed-external-export/main.js b/test/function/shadowed-external-export/main.js index d8a080a..37ce4be 100644 --- a/test/function/shadowed-external-export/main.js +++ b/test/function/shadowed-external-export/main.js @@ -1,4 +1,4 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; var paths = {}; function getRelativePath ( path, path2 ) { @@ -6,5 +6,5 @@ function getRelativePath ( path, path2 ) { return relative( path, path2 ); } -assert.equal( getRelativePath( 'foo/bar/baz', 'foo/baz/bar' ), '../../baz/bar' ); +assert.equal( getRelativePath( 'foo/bar/baz', 'foo/baz/bar' ), normalize('../../baz/bar') ); assert.deepEqual( paths, { 'foo/bar/baz': true }); diff --git a/test/function/shorthand-properties/baz.js b/test/function/shorthand-properties/baz.js new file mode 100644 index 0000000..d826bfa --- /dev/null +++ b/test/function/shorthand-properties/baz.js @@ -0,0 +1,3 @@ +export default function bar () { + return 'main-bar'; +} diff --git a/test/function/shorthand-properties/foo.js b/test/function/shorthand-properties/foo.js index 25941fc..831360e 100644 --- a/test/function/shorthand-properties/foo.js +++ b/test/function/shorthand-properties/foo.js @@ -1,7 +1,10 @@ +import baz from './baz.js'; + function bar () { return 'foo-bar'; } export var foo = { - bar + bar, + baz }; diff --git a/test/function/shorthand-properties/main.js b/test/function/shorthand-properties/main.js index 5118a11..754fba8 100644 --- a/test/function/shorthand-properties/main.js +++ b/test/function/shorthand-properties/main.js @@ -1,8 +1,5 @@ +import bar from './baz.js'; import { foo } from './foo'; -function bar () { - return 'main-bar'; -} - assert.equal( bar(), 'main-bar' ); assert.equal( foo.bar(), 'foo-bar' ); diff --git a/test/function/tracks-alias-mutations/_config.js b/test/function/tracks-alias-mutations/_config.js new file mode 100644 index 0000000..c3a0619 --- /dev/null +++ b/test/function/tracks-alias-mutations/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'tracks mutations of aliased objects', + skip: true +}; diff --git a/test/function/tracks-alias-mutations/bar.js b/test/function/tracks-alias-mutations/bar.js new file mode 100644 index 0000000..4ec5140 --- /dev/null +++ b/test/function/tracks-alias-mutations/bar.js @@ -0,0 +1,6 @@ +import { foo } from './foo'; + +var f = foo; +f.wasMutated = true; + +export var bar = 'whatever'; diff --git a/test/function/tracks-alias-mutations/foo.js b/test/function/tracks-alias-mutations/foo.js new file mode 100644 index 0000000..386f265 --- /dev/null +++ b/test/function/tracks-alias-mutations/foo.js @@ -0,0 +1 @@ +export var foo = {}; diff --git a/test/function/tracks-alias-mutations/main.js b/test/function/tracks-alias-mutations/main.js new file mode 100644 index 0000000..7cf7ee2 --- /dev/null +++ b/test/function/tracks-alias-mutations/main.js @@ -0,0 +1,4 @@ +import { foo } from './foo'; +import { bar } from './bar'; + +assert.ok( foo.wasMutated ); diff --git a/test/function/update-expression-of-import-fails/_config.js b/test/function/update-expression-of-import-fails/_config.js index f2f1390..5f5c27c 100644 --- a/test/function/update-expression-of-import-fails/_config.js +++ b/test/function/update-expression-of-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to imported bindings', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/sourcemaps/names/_config.js b/test/sourcemaps/names/_config.js new file mode 100644 index 0000000..36523ef --- /dev/null +++ b/test/sourcemaps/names/_config.js @@ -0,0 +1,28 @@ +var assert = require( 'assert' ); +var getLocation = require( '../../utils/getLocation' ); +var SourceMapConsumer = require( 'source-map' ).SourceMapConsumer; + +module.exports = { + description: 'names are recovered (https://github.com/rollup/rollup/issues/101)', + options: { + moduleName: 'myModule' + }, + test: function ( code, map ) { + var match = /Object\.create\( ([^\.]+)\.prototype/.exec( code ); + + var deconflictedName = match[1]; + if ( deconflictedName !== 'Foo' ) throw new Error( 'Need to update this test!' ); + + var smc = new SourceMapConsumer( map ); + + var index = code.indexOf( deconflictedName ); + var generatedLoc = getLocation( code, index ); + var originalLoc = smc.originalPositionFor( generatedLoc ); + assert.equal( originalLoc.name, null ); + + index = code.indexOf( deconflictedName, index + 1 ); + generatedLoc = getLocation( code, index ); + originalLoc = smc.originalPositionFor( generatedLoc ); + assert.equal( originalLoc.name, 'Foo' ); + } +}; diff --git a/test/sourcemaps/names/bar.js b/test/sourcemaps/names/bar.js new file mode 100644 index 0000000..b074af8 --- /dev/null +++ b/test/sourcemaps/names/bar.js @@ -0,0 +1 @@ +export default function Foo () {} diff --git a/test/sourcemaps/names/foo.js b/test/sourcemaps/names/foo.js new file mode 100644 index 0000000..82616f3 --- /dev/null +++ b/test/sourcemaps/names/foo.js @@ -0,0 +1,5 @@ +import Bar from './bar'; + +export default function Foo () {} + +Foo.prototype = Object.create( Bar.prototype ); diff --git a/test/sourcemaps/names/main.js b/test/sourcemaps/names/main.js new file mode 100644 index 0000000..a03af97 --- /dev/null +++ b/test/sourcemaps/names/main.js @@ -0,0 +1 @@ +export { default as Foo } from './foo'; diff --git a/test/test.js b/test/test.js index 0ea9e27..4e2bd25 100644 --- a/test/test.js +++ b/test/test.js @@ -2,11 +2,11 @@ require( 'source-map-support' ).install(); require( 'console-group' ).install(); var path = require( 'path' ); +var os = require( 'os' ); var sander = require( 'sander' ); var assert = require( 'assert' ); var exec = require( 'child_process' ).exec; var babel = require( 'babel-core' ); -var sequence = require( './utils/promiseSequence' ); var rollup = require( '../dist/rollup' ); var FUNCTION = path.resolve( __dirname, 'function' ); @@ -32,6 +32,10 @@ function extend ( target ) { return target; } +function normaliseOutput ( code ) { + return code.toString().trim().replace( /\r\n/g, '\n' ); +} + describe( 'rollup', function () { describe( 'sanity checks', function () { it( 'exists', function () { @@ -157,49 +161,45 @@ 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 bundlePromise = rollup.rollup( options ); + 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 ); - - return bundlePromise.then( function ( bundle ) { + it( 'generates ' + profile.format, function () { + return rollup.rollup( options ).then( function ( bundle ) { var options = extend( {}, config.options, { dest: FORM + '/' + dir + '/_actual/' + profile.format + '.js', format: profile.format }); return bundle.write( options ).then( function () { - var actualCode = sander.readFileSync( FORM, dir, '_actual', profile.format + '.js' ).toString().trim(); + var actualCode = normaliseOutput( sander.readFileSync( FORM, dir, '_actual', profile.format + '.js' ) ); var expectedCode; var actualMap; var expectedMap; try { - expectedCode = sander.readFileSync( FORM, dir, '_expected', profile.format + '.js' ).toString().trim(); + expectedCode = normaliseOutput( sander.readFileSync( FORM, dir, '_expected', profile.format + '.js' ) ); } catch ( err ) { expectedCode = 'missing file'; } try { actualMap = JSON.parse( sander.readFileSync( FORM, dir, '_actual', profile.format + '.js.map' ).toString() ); + actualMap.sourcesContent = actualMap.sourcesContent.map( normaliseOutput ); } catch ( err ) {} try { expectedMap = JSON.parse( sander.readFileSync( FORM, dir, '_expected', profile.format + '.js.map' ).toString() ); + expectedMap.sourcesContent = expectedMap.sourcesContent.map( normaliseOutput ); } catch ( err ) {} assert.equal( actualCode, expectedCode ); assert.deepEqual( actualMap, expectedMap ); - - if ( config.solo ) console.groupEnd(); }); }); }); @@ -219,17 +219,16 @@ describe( 'rollup', function () { entry: SOURCEMAPS + '/' + dir + '/main.js' }); - var bundlePromise = rollup.rollup( options ); - PROFILES.forEach( function ( profile ) { ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { - return bundlePromise.then( function ( bundle ) { - var result = bundle.generate({ + return rollup.rollup( options ).then( function ( bundle ) { + var options = extend( {}, config.options, { format: profile.format, sourceMap: true, sourceMapFile: 'bundle.js' }); + var result = bundle.generate( options ); config.test( result.code, result.map ); }); }); @@ -248,9 +247,13 @@ describe( 'rollup', function () { ( config.skip ? it.skip : config.solo ? it.only : it )( dir, function ( done ) { process.chdir( path.resolve( CLI, dir ) ); + if (os.platform() === 'win32') { + config.command = "node " + path.resolve( __dirname, '../bin' ) + path.sep + config.command; + } + exec( config.command, { env: { - PATH: path.resolve( __dirname, '../bin' ) + ':' + process.env.PATH + PATH: path.resolve( __dirname, '../bin' ) + path.delimiter + process.env.PATH } }, function ( err, code, stderr ) { if ( err ) return done( err ); @@ -309,7 +312,7 @@ describe( 'rollup', function () { else { var expected = sander.readFileSync( '_expected.js' ).toString(); try { - assert.equal( code.trim(), expected.trim() ); + assert.equal( normaliseOutput( code ), normaliseOutput( expected ) ); done(); } catch ( err ) { done( err ); diff --git a/test/testScope.js b/test/testScope.js new file mode 100644 index 0000000..2658774 --- /dev/null +++ b/test/testScope.js @@ -0,0 +1,120 @@ +require('babel/register'); +var assert = require( 'assert' ); + +var Scope = require( '../src/Scope' ); + +describe( 'Scope', function () { + it( 'can define and bind names', function () { + const scope = new Scope(); + + // If I define 'a'... + scope.define( 'a' ); + + // ... and bind 'b' to a reference to 'a'... + scope.bind( 'b', scope.reference( 'a' ) ); + + // ... lookups for 'a' and 'b' should both + // resolve to the same identifier. + assert.equal( scope.lookup( 'b' ), scope.lookup( 'a' ) ); + }); + + describe( 'parent:', function () { + var parent = new Scope(), + child = new Scope( parent ); + + it( 'allows children access to its names', function () { + parent.define( 'a' ); + + assert.equal( child.lookup( 'a' ), parent.lookup( 'a' ) ); + }); + + it( 'names in the child scope shadows the parent', function () { + child.define( 'a' ); + + assert.notEqual( child.lookup( 'a' ), parent.lookup( 'a' ) ); + + child.define( 'b' ); + + assert.equal( parent.lookup( 'b' ), undefined ); + }); + }); + + describe( 'virtual scope:', function () { + var real, a, b; + + beforeEach(function () { + real = new Scope(); + a = real.virtual(); + b = real.virtual(); + }); + + it( 'is created within another scope', function () { + // The actual ids are the same. + assert.equal( real.ids, a.ids ); + assert.equal( real.ids, b.ids ); + }); + + it( 'lookups different identifiers', function () { + // If I define 'a' in both scopes... + a.define( 'a' ); + b.define( 'a' ); + + // ... the name 'a' should lookup different identifiers. + assert.notEqual( a.lookup( 'a' ), b.lookup( 'b' ) ); + }); + + it( 'can deconflict names', function () { + a.define( 'a' ); + b.define( 'a' ); + + // Deconflicting the actual scope should make all identifiers unique. + real.deconflict(); + + assert.deepEqual( real.usedNames(), [ '_a', 'a' ] ); + }); + + it( 'deconflicts with a custom function, if provided', function () { + for (var i = 0; i < 26; i++) { + // Create 26 scopes, all of which define 'a'. + real.virtual().define( 'a' ); + } + + // Custom deconfliction function which ignores the current name. + var num = 10; + real.deconflict( function () { + return (num++).toString(36); + }); + + assert.deepEqual( real.usedNames(), 'abcdefghijklmnopqrstuvwxyz'.split('') ); + + // Deconflicting twice has no additional effect. + real.deconflict(); + assert.deepEqual( real.usedNames(), 'abcdefghijklmnopqrstuvwxyz'.split('') ); + }); + }); + + it( 'dedupes-external-imports', function () { + var real = new Scope(); + + var external = real.virtual(), + locals = real.virtual(), + exports = real.virtual(); + + external.define( 'Component' ); + + locals.bind( 'Comp', external.reference( 'Component' ) ); + + exports.bind( 'default', locals.reference( 'Foo' ) ); + + try { + real.deconflict(); + assert.ok( false, 'Scope.deconflict should throw with "Foo" undefined' ); + } catch ( ignore ) { + // as expected + } + + locals.define( 'Foo' ); + + real.deconflict(); + }); +});