diff --git a/.eslintrc b/.eslintrc index 6e5fb1a..f6499f1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,24 @@ { - "rules": { - "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", - "ecmaFeatures": { - "modules": true - } + "rules": { + "indent": [ 2, "tab", { "SwitchCase": 1 } ], + "quotes": [ 2, "single", { "allowTemplateLiterals": true } ], + "linebreak-style": [ 2, "unix" ], + "semi": [ 2, "always" ], + "keyword-spacing": [ 2, { "before": true, "after": true } ], + "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", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + } } diff --git a/package.json b/package.json index b90e4d6..0d04cc9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "rollup": "^0.33.0", "rollup-plugin-buble": "^0.12.1", "rollup-plugin-node-resolve": "^1.7.1", - "source-map": "^0.5.6" + "source-map": "^0.5.6", + "source-map-support": "^0.4.2" }, "main": "dist/rollup-plugin-commonjs.cjs.js", "jsnext:main": "dist/rollup-plugin-commonjs.es.js", diff --git a/rollup.config.js b/rollup.config.js index 5c78ba1..7d08e28 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -4,6 +4,11 @@ var external = Object.keys( require( './package.json' ).dependencies ).concat([ export default { entry: 'src/index.js', - plugins: [ buble() ], - external: external + plugins: [ + buble({ + transforms: { dangerousForOf: true } + }) + ], + external: external, + sourceMap: true }; diff --git a/src/defaultResolver.js b/src/defaultResolver.js new file mode 100644 index 0000000..45a282f --- /dev/null +++ b/src/defaultResolver.js @@ -0,0 +1,39 @@ +import * as fs from 'fs'; +import { dirname, resolve } from 'path'; + +function isFile ( file ) { + try { + const stats = fs.statSync( file ); + return stats.isFile(); + } catch ( err ) { + return false; + } +} + +function addJsExtensionIfNecessary ( file ) { + if ( isFile( file ) ) return file; + + file += '.js'; + if ( isFile( file ) ) return file; + + return null; +} + +const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/; + +function isAbsolute ( path ) { + return absolutePath.test( path ); +} + +export default function defaultResolver ( importee, importer ) { + // absolute paths are left untouched + if ( isAbsolute( importee ) ) return addJsExtensionIfNecessary( resolve( importee ) ); + + // if this is the entry point, resolve against cwd + if ( importer === undefined ) return addJsExtensionIfNecessary( resolve( process.cwd(), importee ) ); + + // external modules are skipped at this stage + if ( importee[0] !== '.' ) return null; + + return addJsExtensionIfNecessary( resolve( dirname( importer ), importee ) ); +} diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..cd78d33 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,15 @@ +export const HELPERS_ID = '\0commonjsHelpers'; + +export const HELPERS = ` +export var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +export function unwrapExports (x) { + return x && x.__esModule ? x['default'] : x; +} + +export function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +}`; + +export const PREFIX = '\0commonjs-proxy:'; +export const EXTERNAL = '\0commonjs-external:'; diff --git a/src/index.js b/src/index.js index bdacf61..5fcdd04 100644 --- a/src/index.js +++ b/src/index.js @@ -1,27 +1,12 @@ import { statSync } from 'fs'; -import { basename, dirname, extname, resolve, sep } from 'path'; +import { dirname, extname, resolve, sep } from 'path'; import { sync as nodeResolveSync } from 'resolve'; -import acorn from 'acorn'; -import { walk } from 'estree-walker'; +import { createFilter } from 'rollup-pluginutils'; import MagicString from 'magic-string'; -import { attachScopes, createFilter, makeLegalIdentifier } from 'rollup-pluginutils'; -import { flatten, isReference } from './ast-utils.js'; - -var firstpassGlobal = /\b(?:require|module|exports|global)\b/; -var firstpassNoGlobal = /\b(?:require|module|exports)\b/; -var exportsPattern = /^(?:module\.)?exports(?:\.([a-zA-Z_$][a-zA-Z_$0-9]*))?$/; - -const reserved = 'abstract arguments boolean break byte case catch char class const continue debugger default delete do double else enum eval export extends false final finally float for function goto if implements import in instanceof int interface let long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var void volatile while with yield'.split( ' ' ); - -var blacklistedExports = { __esModule: true }; -reserved.forEach( word => blacklistedExports[ word ] = true ); - -function getName ( id ) { - const base = basename( id ); - const ext = extname( base ); - - return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); -} +import { EXTERNAL, PREFIX, HELPERS_ID, HELPERS } from './helpers.js'; +import defaultResolver from './defaultResolver.js'; +import transform from './transform.js'; +import { getName } from './utils.js'; function getCandidatesForExtension ( resolved, extension ) { return [ @@ -37,33 +22,27 @@ function getCandidates ( resolved, extensions ) { ); } -function deconflict ( identifier, code ) { - let i = 1; - let deconflicted = identifier; - - while ( ~code.indexOf( deconflicted ) ) deconflicted = `${identifier}_${i++}`; - return deconflicted; +// Return the first non-falsy result from an array of +// maybe-sync, maybe-promise-returning functions +function first ( candidates ) { + return function ( ...args ) { + return candidates.reduce( ( promise, candidate ) => { + return promise.then( result => result != null ? + result : + Promise.resolve( candidate( ...args ) ) ); + }, Promise.resolve() ); + }; } -const HELPERS_ID = '\0commonjsHelpers'; -const HELPERS = ` -export var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {} - -export function interopDefault(ex) { - return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; +function startsWith ( str, prefix ) { + return str.slice( 0, prefix.length ) === prefix; } -export function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -}`; export default function commonjs ( options = {} ) { const extensions = options.extensions || ['.js']; const filter = createFilter( options.include, options.exclude ); const ignoreGlobal = options.ignoreGlobal; - const firstpass = ignoreGlobal ? firstpassNoGlobal : firstpassGlobal; - - const sourceMap = options.sourceMap !== false; let customNamedExports = {}; if ( options.namedExports ) { @@ -80,200 +59,123 @@ export default function commonjs ( options = {} ) { }); } - return { - name: 'commonjs', + let entryModuleIdPromise = null; + let entryModuleId = null; - resolveId ( importee, importer ) { - if ( importee === HELPERS_ID ) return importee; - if ( importee[0] !== '.' || !importer ) return; // not our problem + function resolveId ( importee, importer ) { + if ( importee === HELPERS_ID ) return importee; - const resolved = resolve( dirname( importer ), importee ); - const candidates = getCandidates( resolved, extensions ); + if ( importer && startsWith( importer, PREFIX ) ) importer = importer.slice( PREFIX.length ); - for ( let i = 0; i < candidates.length; i += 1 ) { - try { - const stats = statSync( candidates[i] ); - if ( stats.isFile() ) return candidates[i]; - } catch ( err ) { /* noop */ } - } - }, + const isProxyModule = startsWith( importee, PREFIX ); + if ( isProxyModule ) importee = importee.slice( PREFIX.length ); - load ( id ) { - if ( id === HELPERS_ID ) return HELPERS; - }, + return resolveUsingOtherResolvers( importee, importer ).then( resolved => { + if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; - transform ( code, id ) { - if ( !filter( id ) ) return null; - if ( extensions.indexOf( extname( id ) ) === -1 ) return null; - if ( !firstpass.test( code ) ) return null; + resolved = defaultResolver( importee, importer ); - let ast; - - try { - ast = acorn.parse( code, { - ecmaVersion: 6, - sourceType: 'module' - }); - } catch ( err ) { - err.message += ` in ${id}`; - throw err; + if ( isProxyModule ) { + if ( resolved ) return PREFIX + resolved; + return EXTERNAL + importee; // external } - const magicString = new MagicString( code ); - - let required = {}; - // Because objects have no guaranteed ordering, yet we need it, - // we need to keep track of the order in a array - let sources = []; - - let uid = 0; - - let scope = attachScopes( ast, 'scope' ); - let uses = { module: false, exports: false, global: false }; - - let namedExports = {}; - if ( customNamedExports[ id ] ) { - customNamedExports[ id ].forEach( name => namedExports[ name ] = true ); - } + return resolved; + }); + } - let scopeDepth = 0; + const sourceMap = options.sourceMap !== false; - const HELPERS_NAME = deconflict( 'commonjsHelpers', code ); + let commonjsModules = new Map(); + let resolveUsingOtherResolvers; - walk( ast, { - enter ( node, parent ) { - if ( node.scope ) scope = node.scope; - if ( /^Function/.test( node.type ) ) scopeDepth += 1; + return { + name: 'commonjs', - if ( sourceMap ) { - magicString.addSourcemapLocation( node.start ); - magicString.addSourcemapLocation( node.end ); + options ( options ) { + const resolvers = ( options.plugins || [] ) + .map( plugin => { + if ( plugin.resolveId === resolveId ) { + // substitute CommonJS resolution logic + return ( importee, importer ) => { + if ( importee[0] !== '.' || !importer ) return; // not our problem + + const resolved = resolve( dirname( importer ), importee ); + const candidates = getCandidates( resolved, extensions ); + + for ( let i = 0; i < candidates.length; i += 1 ) { + try { + const stats = statSync( candidates[i] ); + if ( stats.isFile() ) return candidates[i]; + } catch ( err ) { /* noop */ } + } + }; } - // Is this an assignment to exports or module.exports? - if ( node.type === 'AssignmentExpression' ) { - if ( node.left.type !== 'MemberExpression' ) return; - - const flattened = flatten( node.left ); - if ( !flattened ) return; - - if ( scope.contains( flattened.name ) ) return; - - const match = exportsPattern.exec( flattened.keypath ); - if ( !match || flattened.keypath === 'exports' ) return; - - if ( flattened.keypath === 'module.exports' && node.right.type === 'ObjectExpression' ) { - return node.right.properties.forEach( prop => { - if ( prop.computed || prop.key.type !== 'Identifier' ) return; - const name = prop.key.name; - if ( name === makeLegalIdentifier( name ) ) namedExports[ name ] = true; - }); - } + return plugin.resolveId; + }) + .filter( Boolean ); - if ( match[1] ) namedExports[ match[1] ] = true; + resolveUsingOtherResolvers = first( resolvers ); - return; - } - - // To allow consumption of UMD modules, transform `typeof require` to `'function'` - if ( node.type === 'UnaryExpression' && node.operator === 'typeof' && node.argument.type === 'Identifier' ) { - const name = node.argument.name; + entryModuleIdPromise = resolveId( options.entry ).then( resolved => { + entryModuleId = resolved; + }); + }, - if ( name === 'require' && !scope.contains( name ) ) { - magicString.overwrite( node.start, node.end, `'function'` ); - return; - } - } + resolveId, - if ( node.type === 'Identifier' ) { - if ( ( node.name in uses ) && isReference( node, parent ) && !scope.contains( node.name ) ) { - uses[ node.name ] = true; - if ( node.name === 'global' ) magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal` ); - } - return; - } + load ( id ) { + if ( id === HELPERS_ID ) return HELPERS; - if ( node.type === 'ThisExpression' && scopeDepth === 0 && !ignoreGlobal ) { - uses.global = true; - magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal`, true ); - return; - } + // generate proxy modules + if ( startsWith( id, EXTERNAL ) ) { + const actualId = id.slice( EXTERNAL.length ); + const name = getName( actualId ); - if ( node.type !== 'CallExpression' ) return; - if ( node.callee.name !== 'require' || scope.contains( 'require' ) ) return; - if ( node.arguments.length !== 1 || node.arguments[0].type !== 'Literal' ) return; // TODO handle these weird cases? + return `import ${name} from ${JSON.stringify( actualId )}; export default ${name};`; + } - const source = node.arguments[0].value; + if ( startsWith( id, PREFIX ) ) { + const actualId = id.slice( PREFIX.length ); + const name = getName( actualId ); - let existing = required[ source ]; - if ( existing === undefined ) { - sources.unshift(source); - } - let name; + return commonjsModules.has( actualId ) ? + `import { __moduleExports } from ${JSON.stringify( actualId )}; export default __moduleExports;` : + `import * as ${name} from ${JSON.stringify( actualId )}; export default ( ${name} && ${name}['default'] ) || ${name};`; + } + }, - if ( !existing ) { - name = `require$$${uid++}`; - required[ source ] = { source, name, importsDefault: false }; - } else { - name = required[ source ].name; - } + transform ( code, id ) { + if ( !filter( id ) ) return null; + if ( extensions.indexOf( extname( id ) ) === -1 ) return null; - if ( parent.type !== 'ExpressionStatement' ) { - required[ source ].importsDefault = true; - magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.interopDefault(${name})` ); - } else { - // is a bare import, e.g. `require('foo');` - magicString.remove( parent.start, parent.end ); - } - }, + return entryModuleIdPromise.then( () => { + const transformed = transform( code, id, id === entryModuleId, ignoreGlobal, customNamedExports[ id ], sourceMap ); - leave ( node ) { - if ( node.scope ) scope = scope.parent; - if ( /^Function/.test( node.type ) ) scopeDepth -= 1; + if ( transformed ) { + commonjsModules.set( id, true ); + return transformed; } }); + }, - if ( !sources.length && !uses.module && !uses.exports && !uses.global ) { - if ( Object.keys( namedExports ).length ) { - throw new Error( `Custom named exports were specified for ${id} but it does not appear to be a CommonJS module` ); - } - return null; // not a CommonJS module - } - - const name = getName( id ); - - const importBlock = [ `import * as ${HELPERS_NAME} from '${HELPERS_ID}';` ].concat( - sources.map( source => { - const { name, importsDefault } = required[ source ]; - return `import ${importsDefault ? `* as ${name} from ` : ``}'${source}';`; - }) - ).join( '\n' ); - - const args = `module${uses.exports ? ', exports' : ''}`; - - const intro = `\n\nvar ${name} = ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`; - let outro = `\n});\n\nexport default ${HELPERS_NAME}.interopDefault(${name});\n`; - - outro += Object.keys( namedExports ) - .filter( key => !blacklistedExports[ key ] ) - .map( x => { - if (x === name) { - return `var ${x}$$1 = ${name}.${x};\nexport { ${x}$$1 as ${x} };`; - } else { - return `export var ${x} = ${name}.${x};`; - } - }) - .join( '\n' ); + transformBundle ( code ) { + // prevent external dependencies from having the prefix + const magicString = new MagicString( code ); + const pattern = new RegExp( PREFIX, 'g' ); - magicString.trim() - .prepend( importBlock + intro ) - .trim() - .append( outro ); + if ( !pattern.test( code ) ) return null; - code = magicString.toString(); - const map = sourceMap ? magicString.generateMap() : null; + let match; + while ( match = pattern.exec( code ) ) { + magicString.remove( match.index, match[0].length ); + } - return { code, map }; + return { + code: magicString.toString(), + map: magicString.generateMap({ hires: true }) + }; } }; } diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 0000000..87b897a --- /dev/null +++ b/src/transform.js @@ -0,0 +1,203 @@ +import acorn from 'acorn'; +import { walk } from 'estree-walker'; +import MagicString from 'magic-string'; +import { attachScopes, makeLegalIdentifier } from 'rollup-pluginutils'; +import { flatten, isReference } from './ast-utils.js'; +import { PREFIX, HELPERS_ID } from './helpers.js'; +import { getName } from './utils.js'; + +const reserved = 'abstract arguments boolean break byte case catch char class const continue debugger default delete do double else enum eval export extends false final finally float for function goto if implements import in instanceof int interface let long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var void volatile while with yield'.split( ' ' ); +var blacklistedExports = { __esModule: true }; +reserved.forEach( word => blacklistedExports[ word ] = true ); + +var exportsPattern = /^(?:module\.)?exports(?:\.([a-zA-Z_$][a-zA-Z_$0-9]*))?$/; + +var firstpassGlobal = /\b(?:require|module|exports|global)\b/; +var firstpassNoGlobal = /\b(?:require|module|exports)\b/; + +function deconflict ( identifier, code ) { + let i = 1; + let deconflicted = identifier; + + while ( ~code.indexOf( deconflicted ) ) deconflicted = `${identifier}_${i++}`; + return deconflicted; +} + +function tryParse ( code, id ) { + try { + return acorn.parse( code, { + ecmaVersion: 6, + sourceType: 'module' + }); + } catch ( err ) { + err.message += ` in ${id}`; + throw err; + } +} + +export default function transform ( code, id, isEntry, ignoreGlobal, customNamedExports, sourceMap ) { + const firstpass = ignoreGlobal ? firstpassNoGlobal : firstpassGlobal; + if ( !firstpass.test( code ) ) return null; + + let namedExports = {}; + if ( customNamedExports ) customNamedExports.forEach( name => namedExports[ name ] = true ); + + const ast = tryParse( code, id ); + const magicString = new MagicString( code ); + + let required = {}; + // Because objects have no guaranteed ordering, yet we need it, + // we need to keep track of the order in a array + let sources = []; + + let uid = 0; + + let scope = attachScopes( ast, 'scope' ); + let uses = { module: false, exports: false, global: false }; + + let scopeDepth = 0; + + const HELPERS_NAME = deconflict( 'commonjsHelpers', code ); + + walk( ast, { + enter ( node, parent ) { + if ( node.scope ) scope = node.scope; + if ( /^Function/.test( node.type ) ) scopeDepth += 1; + + if ( sourceMap ) { + magicString.addSourcemapLocation( node.start ); + magicString.addSourcemapLocation( node.end ); + } + + // Is this an assignment to exports or module.exports? + if ( node.type === 'AssignmentExpression' ) { + if ( node.left.type !== 'MemberExpression' ) return; + + const flattened = flatten( node.left ); + if ( !flattened ) return; + + if ( scope.contains( flattened.name ) ) return; + + const match = exportsPattern.exec( flattened.keypath ); + if ( !match || flattened.keypath === 'exports' ) return; + + if ( flattened.keypath === 'module.exports' && node.right.type === 'ObjectExpression' ) { + return node.right.properties.forEach( prop => { + if ( prop.computed || prop.key.type !== 'Identifier' ) return; + const name = prop.key.name; + if ( name === makeLegalIdentifier( name ) ) namedExports[ name ] = true; + }); + } + + if ( match[1] ) namedExports[ match[1] ] = true; + + return; + } + + // To allow consumption of UMD modules, transform `typeof require` to `'function'` + if ( node.type === 'UnaryExpression' && node.operator === 'typeof' && node.argument.type === 'Identifier' ) { + const name = node.argument.name; + + if ( name === 'require' && !scope.contains( name ) ) { + magicString.overwrite( node.start, node.end, `'function'` ); + return; + } + } + + if ( node.type === 'Identifier' ) { + if ( ( node.name in uses ) && isReference( node, parent ) && !scope.contains( node.name ) ) { + uses[ node.name ] = true; + if ( node.name === 'global' ) magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal` ); + } + return; + } + + if ( node.type === 'ThisExpression' && scopeDepth === 0 && !ignoreGlobal ) { + uses.global = true; + magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal`, true ); + return; + } + + if ( node.type !== 'CallExpression' ) return; + if ( node.callee.name !== 'require' || scope.contains( 'require' ) ) return; + if ( node.arguments.length !== 1 || node.arguments[0].type !== 'Literal' ) return; // TODO handle these weird cases? + + const source = node.arguments[0].value; + + let existing = required[ source ]; + if ( existing === undefined ) { + sources.unshift(source); + } + let name; + + if ( !existing ) { + name = `require$$${uid++}`; + required[ source ] = { source, name, importsDefault: false }; + } else { + name = required[ source ].name; + } + + if ( parent.type !== 'ExpressionStatement' ) { + required[ source ].importsDefault = true; + magicString.overwrite( node.start, node.end, name ); + } else { + // is a bare import, e.g. `require('foo');` + magicString.remove( parent.start, parent.end ); + } + }, + + leave ( node ) { + if ( node.scope ) scope = scope.parent; + if ( /^Function/.test( node.type ) ) scopeDepth -= 1; + } + }); + + if ( !sources.length && !uses.module && !uses.exports && !uses.global ) { + if ( Object.keys( namedExports ).length ) { + throw new Error( `Custom named exports were specified for ${id} but it does not appear to be a CommonJS module` ); + } + return null; // not a CommonJS module + } + + const importBlock = [ `import * as ${HELPERS_NAME} from '${HELPERS_ID}';` ].concat( + sources.map( source => { + // import the actual module before the proxy, so that we know + // what kind of proxy to build + return `import '${source}';`; + }), + sources.map( source => { + const { name, importsDefault } = required[ source ]; + return `import ${importsDefault ? `${name} from ` : ``}'${PREFIX}${source}';`; + }) + ).join( '\n' ); + + const args = `module${uses.exports ? ', exports' : ''}`; + + const name = getName( id ); + + const wrapperStart = `\n\nvar ${name} = ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`; + const wrapperEnd = `\n});\n\n`; + + let exportBlock = ( isEntry ? [] : [ `export { ${name} as __moduleExports };` ] ).concat( + /__esModule/.test( code ) ? `export default ${HELPERS_NAME}.unwrapExports(${name});\n` : `export default ${name};\n`, + Object.keys( namedExports ) + .filter( key => !blacklistedExports[ key ] ) + .map( x => { + if (x === name) { + return `var ${x}$$1 = ${name}.${x};\nexport { ${x}$$1 as ${x} };`; + } else { + return `export var ${x} = ${name}.${x};`; + } + }) + ).join( '\n' ); + + magicString.trim() + .prepend( importBlock + wrapperStart ) + .trim() + .append( wrapperEnd + exportBlock ); + + code = magicString.toString(); + const map = sourceMap ? magicString.generateMap() : null; + + return { code, map }; +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..a98362e --- /dev/null +++ b/src/utils.js @@ -0,0 +1,7 @@ +import { basename, extname } from 'path'; +import { makeLegalIdentifier } from 'rollup-pluginutils'; + +export function getName ( id ) { + return makeLegalIdentifier( basename( id, extname( id ) ) ); +} + diff --git a/test/samples/other-transforms/bar.js b/test/samples/other-transforms/bar.js new file mode 100644 index 0000000..f967812 --- /dev/null +++ b/test/samples/other-transforms/bar.js @@ -0,0 +1 @@ +module.exports = 40; diff --git a/test/samples/other-transforms/foo.js b/test/samples/other-transforms/foo.js new file mode 100644 index 0000000..c52d053 --- /dev/null +++ b/test/samples/other-transforms/foo.js @@ -0,0 +1,3 @@ +var bar = require( './bar.js' ); + +module.exports = bar + 1; diff --git a/test/samples/other-transforms/main.js b/test/samples/other-transforms/main.js new file mode 100644 index 0000000..c01e9a1 --- /dev/null +++ b/test/samples/other-transforms/main.js @@ -0,0 +1,3 @@ +import foo from './foo.js'; + +assert.equal( foo, 42 ); diff --git a/test/samples/react-apollo/commonjs-bar.js b/test/samples/react-apollo/commonjs-bar.js new file mode 100644 index 0000000..d371ae5 --- /dev/null +++ b/test/samples/react-apollo/commonjs-bar.js @@ -0,0 +1,6 @@ +function Bar () { + this.x = 42; +} + +exports.__esModule = true; +exports.default = Bar; diff --git a/test/samples/react-apollo/commonjs-foo.js b/test/samples/react-apollo/commonjs-foo.js new file mode 100644 index 0000000..ea021a0 --- /dev/null +++ b/test/samples/react-apollo/commonjs-foo.js @@ -0,0 +1,4 @@ +var Bar = require( './commonjs-bar' ); + +exports.__esModule = true; +exports.Bar = Bar.default; diff --git a/test/samples/react-apollo/main.js b/test/samples/react-apollo/main.js new file mode 100644 index 0000000..b3979e6 --- /dev/null +++ b/test/samples/react-apollo/main.js @@ -0,0 +1,3 @@ +import { Bar } from './commonjs-foo.js'; + +assert.equal( new Bar().x, 42 ); diff --git a/test/test.js b/test/test.js index 976da8f..c3ecbd4 100644 --- a/test/test.js +++ b/test/test.js @@ -5,14 +5,24 @@ const { rollup } = require( 'rollup' ); const nodeResolve = require( 'rollup-plugin-node-resolve' ); const commonjs = require( '..' ); +require( 'source-map-support' ).install(); + process.chdir( __dirname ); function executeBundle ( bundle ) { - const generated = bundle.generate({ + const { code } = bundle.generate({ format: 'cjs' }); - const fn = new Function( 'module', 'exports', 'require', 'assert', generated.code ); + let fn; + + try { + fn = new Function( 'module', 'exports', 'require', 'assert', code ); + } catch ( err ) { + // syntax error + console.log( code ); + throw err; + } const module = { exports: {} }; @@ -370,4 +380,26 @@ describe( 'rollup-plugin-commonjs', () => { plugins: [ commonjs() ] }).then( executeBundle ); }); + + it( 'does not remove .default properties', () => { + return rollup({ + entry: 'samples/react-apollo/main.js', + plugins: [ commonjs() ] + }).then( executeBundle ); + }); + + it( 'respects other plugins', () => { + return rollup({ + entry: 'samples/other-transforms/main.js', + plugins: [ + { + transform ( code, id ) { + if ( id[0] === '\0' ) return null; + return code.replace( '40', '41' ); + } + }, + commonjs() + ] + }).then( executeBundle ); + }); });