diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e512e..009ccd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # rollup changelog +## 0.25.1 + +* Throw error if namespace is called ([#446](https://github.com/rollup/rollup/issues/446)) +* Prevent shadowing bug in ES6 output ([#441](https://github.com/rollup/rollup/pull/441)) +* Prevent `var exports.foo` ([#426](https://github.com/rollup/rollup/issues/426)) +* Prevent double export of aliased symbols ([#438](https://github.com/rollup/rollup/issues/438)) +* Provide more informative error if Rollup is used in-browser without appropriate `resolveId`/`load` hooks ([#275](https://github.com/rollup/rollup/issues/275)) +* Use `_interopDefault` function to DRY out code with many external dependencies, in CommonJS output ([#458](https://github.com/rollup/rollup/pull/458)) + ## 0.25.0 * **breaking** Module order is determined according to spec, rather than in a way designed to prevent runtime errors. Rollup will warn if it detects runtime errors are likely ([#435](https://github.com/rollup/rollup/issues/435)) diff --git a/package.json b/package.json index 34e7ed8..1d8bdc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "0.25.0", + "version": "0.25.1", "description": "Next-generation ES6 module bundler", "main": "dist/rollup.js", "jsnext:main": "src/rollup.js", diff --git a/src/Bundle.js b/src/Bundle.js index 5f9a8a9..754ad8f 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -63,7 +63,7 @@ export default class Bundle { this.onwarn = options.onwarn || makeOnwarn(); // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles - [ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true ); + [ 'module', 'exports', '_interopDefault' ].forEach( global => this.assumedGlobals[ global ] = true ); } build () { @@ -360,7 +360,6 @@ export default class Bundle { }); } - return ordered; } } diff --git a/src/Declaration.js b/src/Declaration.js index 6b6996d..8c61bd2 100644 --- a/src/Declaration.js +++ b/src/Declaration.js @@ -135,6 +135,7 @@ export class SyntheticDefaultDeclaration { export class SyntheticNamespaceDeclaration { constructor ( module ) { + this.isNamespace = true; this.module = module; this.name = null; @@ -221,9 +222,10 @@ export class ExternalDeclaration { constructor ( module, name ) { this.module = module; this.name = name; + this.safeName = null; this.isExternal = true; - this.safeName = null; + this.isNamespace = name === '*'; } addAlias () { diff --git a/src/finalisers/cjs.js b/src/finalisers/cjs.js index 3a56ac9..c71f59d 100644 --- a/src/finalisers/cjs.js +++ b/src/finalisers/cjs.js @@ -3,17 +3,25 @@ import getExportBlock from './shared/getExportBlock.js'; export default function cjs ( bundle, magicString, { exportMode }, options ) { let intro = options.useStrict === false ? `` : `'use strict';\n\n`; + const hasDefaultImport = bundle.externalModules.some( mod => mod.declarations.default); + + if (hasDefaultImport) { + intro += `function _interopDefault (ex) { return 'default' in ex ? ex['default'] : ex; }\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.declarations.default ) { - requireStatement += '\n' + ( module.exportsNames ? `var ${module.name}__default = ` : `${module.name} = ` ) + - `'default' in ${module.name} ? ${module.name}['default'] : ${module.name};`; + if (module.exportsNames) { + return `var ${module.name} = require('${module.id}');` + + `\nvar ${module.name}__default = _interopDefault(${module.name});`; + } else { + return `var ${module.name} = _interopDefault(require('${module.id}'));`; + } + } else { + return `var ${module.name} = require('${module.id}');`; } - - return requireStatement; }) .join( '\n' ); diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 4753d1b..70c36fe 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -16,6 +16,8 @@ function addJsExtensionIfNecessary ( file ) { } export function resolveId ( importee, importer ) { + if ( typeof process === 'undefined' ) throw new Error( `It looks like you're using Rollup in a non-Node.js environment. This means you must supply a plugin with custom resolveId and load functions. See https://github.com/rollup/rollup/wiki/Plugins for more information` ); + // absolute paths are left untouched if ( isAbsolute( importee ) ) return addJsExtensionIfNecessary( importee ); diff --git a/src/utils/error.js b/src/utils/error.js new file mode 100644 index 0000000..5c0a703 --- /dev/null +++ b/src/utils/error.js @@ -0,0 +1,9 @@ +export default function error ( props ) { + const err = new Error( props.message ); + + Object.keys( props ).forEach( key => { + err[ key ] = props[ key ]; + }); + + throw err; +} diff --git a/src/utils/run.js b/src/utils/run.js index bf35a73..4470305 100644 --- a/src/utils/run.js +++ b/src/utils/run.js @@ -3,6 +3,8 @@ import modifierNodes, { isModifierNode } from '../ast/modifierNodes.js'; import isReference from '../ast/isReference.js'; import flatten from '../ast/flatten'; import pureFunctions from './pureFunctions.js'; +import getLocation from './getLocation.js'; +import error from './error.js'; function call ( callee, scope, statement, strongDependencies ) { while ( callee.type === 'ParenthesizedExpression' ) callee = callee.expression; @@ -11,7 +13,19 @@ function call ( callee, scope, statement, strongDependencies ) { const declaration = scope.findDeclaration( callee.name ) || statement.module.trace( callee.name ); - if ( declaration ) return declaration.run( strongDependencies ); + if ( declaration ) { + if ( declaration.isNamespace ) { + error({ + message: `Cannot call a namespace ('${callee.name}')`, + file: statement.module.id, + pos: callee.start, + loc: getLocation( statement.module.code, callee.start ) + }); + } + + return declaration.run( strongDependencies ); + } + return !pureFunctions[ callee.name ]; } diff --git a/test/form/external-imports-custom-names/_expected/cjs.js b/test/form/external-imports-custom-names/_expected/cjs.js index e83d17d..8f6506e 100644 --- a/test/form/external-imports-custom-names/_expected/cjs.js +++ b/test/form/external-imports-custom-names/_expected/cjs.js @@ -1,8 +1,9 @@ 'use strict'; -var $ = require('jquery'); -$ = 'default' in $ ? $['default'] : $; +function _interopDefault (ex) { return 'default' in ex ? ex['default'] : ex; } + +var $ = _interopDefault(require('jquery')); $( function () { $( 'body' ).html( '

hello world!

' ); -}); +}); \ No newline at end of file diff --git a/test/form/external-imports/_expected/cjs.js b/test/form/external-imports/_expected/cjs.js index 6c20bcc..a3ee9b0 100644 --- a/test/form/external-imports/_expected/cjs.js +++ b/test/form/external-imports/_expected/cjs.js @@ -1,14 +1,15 @@ 'use strict'; -var factory = require('factory'); -factory = 'default' in factory ? factory['default'] : factory; +function _interopDefault (ex) { return 'default' in ex ? ex['default'] : ex; } + +var factory = _interopDefault(require('factory')); var baz = require('baz'); var containers = require('shipping-port'); var alphabet = require('alphabet'); -var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet; +var alphabet__default = _interopDefault(alphabet); factory( null ); baz.foo( baz.bar, containers.port ); containers.forEach( console.log, console ); console.log( alphabet.a ); -console.log( alphabet__default.length ); +console.log( alphabet__default.length ); \ No newline at end of file diff --git a/test/function/cannot-call-external-namespace/_config.js b/test/function/cannot-call-external-namespace/_config.js new file mode 100644 index 0000000..263c14a --- /dev/null +++ b/test/function/cannot-call-external-namespace/_config.js @@ -0,0 +1,12 @@ +var path = require( 'path' ); +var assert = require( 'assert' ); + +module.exports = { + description: 'errors if code calls an external namespace', + error: function ( err ) { + assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); + assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); + assert.equal( err.pos, 28 ); + assert.deepEqual( err.loc, { line: 2, column: 0 }); + } +}; diff --git a/test/function/cannot-call-external-namespace/main.js b/test/function/cannot-call-external-namespace/main.js new file mode 100644 index 0000000..c1ad7c6 --- /dev/null +++ b/test/function/cannot-call-external-namespace/main.js @@ -0,0 +1,2 @@ +import * as foo from 'foo'; +foo(); diff --git a/test/function/cannot-call-internal-namespace/_config.js b/test/function/cannot-call-internal-namespace/_config.js new file mode 100644 index 0000000..ceb42c3 --- /dev/null +++ b/test/function/cannot-call-internal-namespace/_config.js @@ -0,0 +1,12 @@ +var path = require( 'path' ); +var assert = require( 'assert' ); + +module.exports = { + description: 'errors if code calls an internal namespace', + error: function ( err ) { + assert.equal( err.message, 'Cannot call a namespace (\'foo\')' ); + assert.equal( err.file.replace( /\//g, path.sep ), path.resolve( __dirname, 'main.js' ) ); + assert.equal( err.pos, 33 ); + assert.deepEqual( err.loc, { line: 2, column: 0 }); + } +}; diff --git a/test/function/cannot-call-internal-namespace/foo.js b/test/function/cannot-call-internal-namespace/foo.js new file mode 100644 index 0000000..cc798ff --- /dev/null +++ b/test/function/cannot-call-internal-namespace/foo.js @@ -0,0 +1 @@ +export const a = 1; diff --git a/test/function/cannot-call-internal-namespace/main.js b/test/function/cannot-call-internal-namespace/main.js new file mode 100644 index 0000000..74f0acb --- /dev/null +++ b/test/function/cannot-call-internal-namespace/main.js @@ -0,0 +1,2 @@ +import * as foo from './foo.js'; +foo(); diff --git a/test/function/cycles-stack-overflow/_config.js b/test/function/cycles-stack-overflow/_config.js new file mode 100644 index 0000000..ea40267 --- /dev/null +++ b/test/function/cycles-stack-overflow/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not stack overflow on crazy cyclical dependencies' +}; diff --git a/test/function/cycles-stack-overflow/b.js b/test/function/cycles-stack-overflow/b.js new file mode 100644 index 0000000..df7267a --- /dev/null +++ b/test/function/cycles-stack-overflow/b.js @@ -0,0 +1,11 @@ +import { C } from './c.js'; + +export function B () {}; + +B.prototype = { + c: function () { + return function () { + new C(); + }; + }() +}; diff --git a/test/function/cycles-stack-overflow/c.js b/test/function/cycles-stack-overflow/c.js new file mode 100644 index 0000000..5ee8efd --- /dev/null +++ b/test/function/cycles-stack-overflow/c.js @@ -0,0 +1,13 @@ +import { D } from './d.js'; + +export function C () { + this.x = 'x'; +} + +C.prototype = { + d: function () { + return function () { + new D(); + }; + }() +}; diff --git a/test/function/cycles-stack-overflow/d.js b/test/function/cycles-stack-overflow/d.js new file mode 100644 index 0000000..4abd410 --- /dev/null +++ b/test/function/cycles-stack-overflow/d.js @@ -0,0 +1,12 @@ +import { B } from './b.js'; +import { C } from './c.js'; + +export function D () {}; + +D.prototype = { + c: function () { + return function () { + new C(); + }; + }() +}; diff --git a/test/function/cycles-stack-overflow/main.js b/test/function/cycles-stack-overflow/main.js new file mode 100644 index 0000000..1f1d737 --- /dev/null +++ b/test/function/cycles-stack-overflow/main.js @@ -0,0 +1,3 @@ +import { C } from './c.js'; + +assert.equal( new C().x, 'x' );