From f2b6084f3f7687116c818657a5bc791f5cee0b2c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Aug 2016 14:28:39 -0400 Subject: [PATCH 01/11] interop, take 2 --- .eslintrc | 43 +-- package.json | 3 +- rollup.config.js | 9 +- src/defaultResolver.js | 39 +++ src/helpers.js | 19 ++ src/index.js | 322 +++++++++------------- src/transform.js | 172 ++++++++++++ test/samples/react-apollo/commonjs-bar.js | 6 + test/samples/react-apollo/commonjs-foo.js | 4 + test/samples/react-apollo/main.js | 3 + test/test.js | 15 +- 11 files changed, 412 insertions(+), 223 deletions(-) create mode 100644 src/defaultResolver.js create mode 100644 src/helpers.js create mode 100644 src/transform.js create mode 100644 test/samples/react-apollo/commonjs-bar.js create mode 100644 test/samples/react-apollo/commonjs-foo.js create mode 100644 test/samples/react-apollo/main.js 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..3d12dc4 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,19 @@ +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 interopDefault(x) { + if ( !x || typeof x !== 'object' || !x.default ) return x; + return x['default']; +} + +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-required:'; diff --git a/src/index.js b/src/index.js index bdacf61..46016f8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,28 +1,17 @@ -import { statSync } from 'fs'; +import { readFileSync, statSync } from 'fs'; import { basename, dirname, extname, resolve, sep } from 'path'; import { sync as nodeResolveSync } from 'resolve'; -import acorn from 'acorn'; -import { walk } from 'estree-walker'; +import { createFilter, makeLegalIdentifier } 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]*))?$/; +import { PREFIX, HELPERS_ID, HELPERS } from './helpers.js'; +import defaultResolver from './defaultResolver.js'; +import transform from './transform.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 ); -function getName ( id ) { - const base = basename( id ); - const ext = extname( base ); - - return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); -} - function getCandidatesForExtension ( resolved, extension ) { return [ resolved + extension, @@ -37,33 +26,30 @@ function getCandidates ( resolved, extensions ) { ); } -function deconflict ( identifier, code ) { - let i = 1; - let deconflicted = identifier; +function getName ( id ) { + const base = basename( id ); + const ext = extname( base ); - while ( ~code.indexOf( deconflicted ) ) deconflicted = `${identifier}_${i++}`; - return deconflicted; + return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); } -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; +// 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() ); + }; } -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 +66,144 @@ export default function commonjs ( options = {} ) { }); } - return { - name: 'commonjs', - - resolveId ( importee, importer ) { - if ( importee === HELPERS_ID ) return importee; - if ( importee[0] !== '.' || !importer ) return; // not our problem - - const resolved = resolve( dirname( importer ), importee ); - const candidates = getCandidates( resolved, extensions ); + function resolveId ( importee, importer ) { + if ( importee === HELPERS_ID ) return importee; - for ( let i = 0; i < candidates.length; i += 1 ) { - try { - const stats = statSync( candidates[i] ); - if ( stats.isFile() ) return candidates[i]; - } catch ( err ) { /* noop */ } - } - }, - - load ( id ) { - if ( id === HELPERS_ID ) return HELPERS; - }, + if ( importer ) importer = importer.replace( PREFIX, '' ); - transform ( code, id ) { - if ( !filter( id ) ) return null; - if ( extensions.indexOf( extname( id ) ) === -1 ) return null; - if ( !firstpass.test( code ) ) return null; + const isImportedByCommonJsModule = importee.startsWith( PREFIX ); + importee = importee.replace( PREFIX, '' ); - let ast; + return resolveUsingOtherResolvers( importee, importer ).then( resolved => { + if ( resolved ) return isImportedByCommonJsModule ? PREFIX + resolved : resolved; - try { - ast = acorn.parse( code, { - ecmaVersion: 6, - sourceType: 'module' - }); - } catch ( err ) { - err.message += ` in ${id}`; - throw err; + if ( isImportedByCommonJsModule ) { + // standard resolution procedure + const resolved = defaultResolver( importee, importer ); + if ( resolved ) return PREFIX + resolved; } + }); + } - 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 ); - } - - 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; + let commonjsModules = new Map(); + function getCommonjsModule ( code, id ) { + if ( !commonjsModules.has( id ) ) { + commonjsModules.set( id, transform( code, id, ignoreGlobal, customNamedExports[ id ] ) ); + } - const match = exportsPattern.exec( flattened.keypath ); - if ( !match || flattened.keypath === 'exports' ) return; + return commonjsModules.get( id ); + } - 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; - }); - } + let resolveUsingOtherResolvers; - if ( match[1] ) namedExports[ match[1] ] = true; + return { + name: 'commonjs', - return; + 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 */ } + } + }; } - // 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; + return plugin.resolveId; + }) + .filter( Boolean ); - if ( name === 'require' && !scope.contains( name ) ) { - magicString.overwrite( node.start, node.end, `'function'` ); - return; - } - } + resolveUsingOtherResolvers = first( resolvers ); + }, - 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; - } + resolveId, - if ( node.type === 'ThisExpression' && scopeDepth === 0 && !ignoreGlobal ) { - uses.global = true; - magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal`, true ); - return; - } + load ( id ) { + if ( id === HELPERS_ID ) return HELPERS; - 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? + if ( id.startsWith( PREFIX ) ) { + const actualId = id.slice( PREFIX.length ); + return readFileSync( actualId, 'utf-8' ); + } + }, - const source = node.arguments[0].value; + transform ( code, id ) { + const isImportedByCommonJsModule = id.startsWith( PREFIX ); + id = id.replace( PREFIX, '' ); - let existing = required[ source ]; - if ( existing === undefined ) { - sources.unshift(source); - } - let name; + let transformed; + if ( filter( id ) && extensions.indexOf( extname( id ) ) !== -1 ) transformed = getCommonjsModule( code, id ); + const isCommonJsModule = !!transformed; - if ( !existing ) { - name = `require$$${uid++}`; - required[ source ] = { source, name, importsDefault: false }; - } else { - name = required[ source ].name; - } + const name = getName( id ); - 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 ); - } - }, + if ( isImportedByCommonJsModule ) { + if ( !isCommonJsModule ) { + // CJS importing ES – need to import a namespace and re-export as default + const code = `import * as ${name} from '${id}'; export default ${name}['default'] || ${name};`; - leave ( node ) { - if ( node.scope ) scope = scope.parent; - if ( /^Function/.test( node.type ) ) scopeDepth -= 1; + return { + code, + map: { mappings: '' } + }; } - }); - 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 + // CJS importing CJS – pass module.exports through unmolested + return transformed; } - 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' ); + // ES importing CJS – do the interop dance + if ( isCommonJsModule ) { + const HELPERS_NAME = 'commonjsHelpers'; + + let proxy = `import * as ${HELPERS_NAME} from '${HELPERS_ID}';\nimport ${name} from '${PREFIX}${id}';\n\n`; + proxy += /__esModule/.test( code ) ? `export default ${HELPERS_NAME}.unwrapExports(${name});\n` : `export default ${name};\n`; + proxy += Object.keys( transformed.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' ); - const args = `module${uses.exports ? ', exports' : ''}`; + return { + code: proxy, + map: { mappings: '' } + }; + } - const intro = `\n\nvar ${name} = ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`; - let outro = `\n});\n\nexport default ${HELPERS_NAME}.interopDefault(${name});\n`; + return null; + }, - 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..e5641a1 --- /dev/null +++ b/src/transform.js @@ -0,0 +1,172 @@ +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'; + +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, ignoreGlobal, customNamedExports ) { + 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; + + // 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 => { + const { name, importsDefault } = required[ source ]; + return `import ${importsDefault ? `${name} from ` : ``}'${PREFIX}${source}';`; + }) + ).join( '\n' ); + + const args = `module${uses.exports ? ', exports' : ''}`; + + const wrapperStart = `\n\nexport default ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`; + const wrapperEnd = `\n});`; + + magicString.trim() + .prepend( importBlock + wrapperStart ) + .trim() + .append( wrapperEnd ); + + code = magicString.toString(); + + return { code, map: { mappings: '' }, namedExports }; +} 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..c699b8d 100644 --- a/test/test.js +++ b/test/test.js @@ -5,14 +5,16 @@ 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 ); + const fn = new Function( 'module', 'exports', 'require', 'assert', code ); const module = { exports: {} }; @@ -70,7 +72,7 @@ describe( 'rollup-plugin-commonjs', () => { }); }); - it( 'generates a sourcemap', () => { + it.skip( 'generates a sourcemap', () => { return rollup({ entry: 'samples/sourcemap/main.js', plugins: [ commonjs({ sourceMap: true }) ] @@ -370,4 +372,11 @@ 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 ); + }); }); From a5caa8144af5a78cd69efc5757790ddc1743a062 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Aug 2016 14:34:49 -0400 Subject: [PATCH 02/11] remove unused helper --- src/helpers.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index 3d12dc4..65eae61 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,12 +1,7 @@ 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 interopDefault(x) { - if ( !x || typeof x !== 'object' || !x.default ) return x; - return x['default']; -} +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; From 68c36c4c81887d960b0a9d29b38082fff21474e4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Aug 2016 15:09:32 -0400 Subject: [PATCH 03/11] 0.12 --- src/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 46016f8..0abb24b 100644 --- a/src/index.js +++ b/src/index.js @@ -45,6 +45,10 @@ function first ( candidates ) { }; } +function startsWith ( str, prefix ) { + return str.slice( 0, prefix.length ) === prefix; +} + export default function commonjs ( options = {} ) { const extensions = options.extensions || ['.js']; @@ -71,7 +75,7 @@ export default function commonjs ( options = {} ) { if ( importer ) importer = importer.replace( PREFIX, '' ); - const isImportedByCommonJsModule = importee.startsWith( PREFIX ); + const isImportedByCommonJsModule = startsWith( importee, PREFIX ); importee = importee.replace( PREFIX, '' ); return resolveUsingOtherResolvers( importee, importer ).then( resolved => { @@ -131,14 +135,14 @@ export default function commonjs ( options = {} ) { load ( id ) { if ( id === HELPERS_ID ) return HELPERS; - if ( id.startsWith( PREFIX ) ) { + if ( startsWith( id, PREFIX ) ) { const actualId = id.slice( PREFIX.length ); return readFileSync( actualId, 'utf-8' ); } }, transform ( code, id ) { - const isImportedByCommonJsModule = id.startsWith( PREFIX ); + const isImportedByCommonJsModule = startsWith( id, PREFIX ); id = id.replace( PREFIX, '' ); let transformed; From 9fc983ab7061491b60c81b7273261b8126e271c2 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 30 Aug 2016 23:03:50 -0400 Subject: [PATCH 04/11] use virtual modules for proxies, not the real ones --- src/index.js | 79 +++++++++++++++++++++--------------------------- src/transform.js | 43 +++++++++++++++++++++++--- test/test.js | 2 +- 3 files changed, 74 insertions(+), 50 deletions(-) diff --git a/src/index.js b/src/index.js index 0abb24b..d1c986d 100644 --- a/src/index.js +++ b/src/index.js @@ -70,29 +70,34 @@ export default function commonjs ( options = {} ) { }); } + let entryModuleIdPromise = null; + let entryModuleId = null; + function resolveId ( importee, importer ) { if ( importee === HELPERS_ID ) return importee; if ( importer ) importer = importer.replace( PREFIX, '' ); - const isImportedByCommonJsModule = startsWith( importee, PREFIX ); + const isProxyModule = startsWith( importee, PREFIX ); importee = importee.replace( PREFIX, '' ); return resolveUsingOtherResolvers( importee, importer ).then( resolved => { - if ( resolved ) return isImportedByCommonJsModule ? PREFIX + resolved : resolved; + if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; - if ( isImportedByCommonJsModule ) { - // standard resolution procedure - const resolved = defaultResolver( importee, importer ); - if ( resolved ) return PREFIX + resolved; - } + resolved = defaultResolver( importee, importer ); + if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; }); } + const sourceMap = options.sourceMap !== false; + let commonjsModules = new Map(); function getCommonjsModule ( code, id ) { if ( !commonjsModules.has( id ) ) { - commonjsModules.set( id, transform( code, id, ignoreGlobal, customNamedExports[ id ] ) ); + const promise = entryModuleIdPromise.then( () => { + return transform( code, id, id === entryModuleId, ignoreGlobal, customNamedExports[ id ], sourceMap ); + }); + commonjsModules.set( id, promise ); } return commonjsModules.get( id ); @@ -128,6 +133,10 @@ export default function commonjs ( options = {} ) { .filter( Boolean ); resolveUsingOtherResolvers = first( resolvers ); + + entryModuleIdPromise = resolveId( options.entry ).then( resolved => { + entryModuleId = resolved; + }); }, resolveId, @@ -142,19 +151,22 @@ export default function commonjs ( options = {} ) { }, transform ( code, id ) { - const isImportedByCommonJsModule = startsWith( id, PREFIX ); + const isProxyModule = startsWith( id, PREFIX ); id = id.replace( PREFIX, '' ); - let transformed; - if ( filter( id ) && extensions.indexOf( extname( id ) ) !== -1 ) transformed = getCommonjsModule( code, id ); - const isCommonJsModule = !!transformed; + const promise = ( filter( id ) && extensions.indexOf( extname( id ) ) !== -1 ) ? + getCommonjsModule( code, id ) : + Promise.resolve( null ); + + return promise.then( transformed => { + const isCommonJsModule = !!transformed; - const name = getName( id ); + const name = getName( id ); - if ( isImportedByCommonJsModule ) { - if ( !isCommonJsModule ) { - // CJS importing ES – need to import a namespace and re-export as default - const code = `import * as ${name} from '${id}'; export default ${name}['default'] || ${name};`; + if ( isProxyModule ) { + const code = isCommonJsModule ? + `import { __moduleExports } from '${id}'; export default __moduleExports;` : + `import * as ${name} from '${id}'; export default ${name}['default'] || ${name};`; return { code, @@ -162,34 +174,13 @@ export default function commonjs ( options = {} ) { }; } - // CJS importing CJS – pass module.exports through unmolested - return transformed; - } - - // ES importing CJS – do the interop dance - if ( isCommonJsModule ) { - const HELPERS_NAME = 'commonjsHelpers'; - - let proxy = `import * as ${HELPERS_NAME} from '${HELPERS_ID}';\nimport ${name} from '${PREFIX}${id}';\n\n`; - proxy += /__esModule/.test( code ) ? `export default ${HELPERS_NAME}.unwrapExports(${name});\n` : `export default ${name};\n`; - proxy += Object.keys( transformed.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' ); - - return { - code: proxy, - map: { mappings: '' } - }; - } + // ES importing CJS + if ( isCommonJsModule ) { + return transformed; + } - return null; + return null; + }); }, transformBundle ( code ) { diff --git a/src/transform.js b/src/transform.js index e5641a1..6307594 100644 --- a/src/transform.js +++ b/src/transform.js @@ -1,10 +1,15 @@ import acorn from 'acorn'; +import { basename, extname } from 'path'; 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'; +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/; @@ -30,7 +35,14 @@ function tryParse ( code, id ) { } } -export default function transform ( code, id, ignoreGlobal, customNamedExports ) { +function getName ( id ) { + const base = basename( id ); + const ext = extname( base ); + + return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); +} + +export default function transform ( code, id, isEntry, ignoreGlobal, customNamedExports, sourceMap ) { const firstpass = ignoreGlobal ? firstpassNoGlobal : firstpassGlobal; if ( !firstpass.test( code ) ) return null; @@ -59,6 +71,11 @@ export default function transform ( code, id, ignoreGlobal, customNamedExports ) 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; @@ -158,15 +175,31 @@ export default function transform ( code, id, ignoreGlobal, customNamedExports ) const args = `module${uses.exports ? ', exports' : ''}`; - const wrapperStart = `\n\nexport default ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`; - const wrapperEnd = `\n});`; + 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 ); + .append( wrapperEnd + exportBlock ); code = magicString.toString(); + const map = sourceMap ? magicString.generateMap() : null; - return { code, map: { mappings: '' }, namedExports }; + return { code, map }; } diff --git a/test/test.js b/test/test.js index c699b8d..f53351d 100644 --- a/test/test.js +++ b/test/test.js @@ -72,7 +72,7 @@ describe( 'rollup-plugin-commonjs', () => { }); }); - it.skip( 'generates a sourcemap', () => { + it( 'generates a sourcemap', () => { return rollup({ entry: 'samples/sourcemap/main.js', plugins: [ commonjs({ sourceMap: true }) ] From e8bf186333883019fb1aebbc644d99bdd75e636c Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 30 Aug 2016 23:14:35 -0400 Subject: [PATCH 05/11] failing test --- test/samples/other-transforms/bar.js | 1 + test/samples/other-transforms/foo.js | 3 +++ test/samples/other-transforms/main.js | 3 +++ test/test.js | 15 +++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 test/samples/other-transforms/bar.js create mode 100644 test/samples/other-transforms/foo.js create mode 100644 test/samples/other-transforms/main.js 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/test.js b/test/test.js index f53351d..456b9f2 100644 --- a/test/test.js +++ b/test/test.js @@ -379,4 +379,19 @@ describe( 'rollup-plugin-commonjs', () => { 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 ); + }); }); From f13c1e4c0719b00aa339a041ec776a60976dc2af Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 31 Aug 2016 08:52:51 -0400 Subject: [PATCH 06/11] handle external modules --- src/helpers.js | 3 ++- src/index.js | 68 ++++++++++++++++++++---------------------------- src/transform.js | 5 ++++ 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index 65eae61..cd78d33 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -11,4 +11,5 @@ export function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; }`; -export const PREFIX = '\0commonjs-required:'; +export const PREFIX = '\0commonjs-proxy:'; +export const EXTERNAL = '\0commonjs-external:'; diff --git a/src/index.js b/src/index.js index d1c986d..bf544ec 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,9 @@ -import { readFileSync, statSync } from 'fs'; +import { statSync } from 'fs'; import { basename, dirname, extname, resolve, sep } from 'path'; import { sync as nodeResolveSync } from 'resolve'; import { createFilter, makeLegalIdentifier } from 'rollup-pluginutils'; import MagicString from 'magic-string'; -import { PREFIX, HELPERS_ID, HELPERS } from './helpers.js'; +import { EXTERNAL, PREFIX, HELPERS_ID, HELPERS } from './helpers.js'; import defaultResolver from './defaultResolver.js'; import transform from './transform.js'; @@ -85,24 +85,19 @@ export default function commonjs ( options = {} ) { if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; resolved = defaultResolver( importee, importer ); - if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; + + if ( isProxyModule ) { + if ( resolved ) return PREFIX + resolved; + return EXTERNAL + importee; // external + } + + return resolved; }); } const sourceMap = options.sourceMap !== false; let commonjsModules = new Map(); - function getCommonjsModule ( code, id ) { - if ( !commonjsModules.has( id ) ) { - const promise = entryModuleIdPromise.then( () => { - return transform( code, id, id === entryModuleId, ignoreGlobal, customNamedExports[ id ], sourceMap ); - }); - commonjsModules.set( id, promise ); - } - - return commonjsModules.get( id ); - } - let resolveUsingOtherResolvers; return { @@ -144,42 +139,35 @@ export default function commonjs ( options = {} ) { load ( id ) { if ( id === HELPERS_ID ) return HELPERS; + // generate proxy modules + if ( startsWith( id, EXTERNAL ) ) { + const actualId = id.slice( EXTERNAL.length ); + const name = getName( actualId ); + + return `import ${name} from '${actualId}'; export default ${name};`; + } + if ( startsWith( id, PREFIX ) ) { const actualId = id.slice( PREFIX.length ); - return readFileSync( actualId, 'utf-8' ); + const name = getName( actualId ); + + return commonjsModules.has( actualId ) ? + `import { __moduleExports } from '${actualId}'; export default __moduleExports;` : + `import * as ${name} from '${actualId}'; export default ( ${name} && ${name}['default'] ) || ${name};`; } }, transform ( code, id ) { - const isProxyModule = startsWith( id, PREFIX ); - id = id.replace( PREFIX, '' ); - - const promise = ( filter( id ) && extensions.indexOf( extname( id ) ) !== -1 ) ? - getCommonjsModule( code, id ) : - Promise.resolve( null ); - - return promise.then( transformed => { - const isCommonJsModule = !!transformed; + if ( !filter( id ) ) return null; + if ( extensions.indexOf( extname( id ) ) === -1 ) return null; - const name = getName( id ); + return entryModuleIdPromise.then( () => { + const transformed = transform( code, id, id === entryModuleId, ignoreGlobal, customNamedExports[ id ], sourceMap ); - if ( isProxyModule ) { - const code = isCommonJsModule ? - `import { __moduleExports } from '${id}'; export default __moduleExports;` : - `import * as ${name} from '${id}'; export default ${name}['default'] || ${name};`; - - return { - code, - map: { mappings: '' } - }; - } - - // ES importing CJS - if ( isCommonJsModule ) { + if ( transformed ) { + commonjsModules.set( id, true ); return transformed; } - - return null; }); }, diff --git a/src/transform.js b/src/transform.js index 6307594..8bed7d0 100644 --- a/src/transform.js +++ b/src/transform.js @@ -167,6 +167,11 @@ export default function transform ( code, id, isEntry, ignoreGlobal, customNamed } 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}';`; From e7128c92375069565df87993f987a6e98d58a835 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 31 Aug 2016 09:03:20 -0400 Subject: [PATCH 07/11] log code with syntax error, to try and debug windows via appveyor... --- test/test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index 456b9f2..c3ecbd4 100644 --- a/test/test.js +++ b/test/test.js @@ -14,7 +14,15 @@ function executeBundle ( bundle ) { format: 'cjs' }); - const fn = new Function( 'module', 'exports', 'require', 'assert', code ); + let fn; + + try { + fn = new Function( 'module', 'exports', 'require', 'assert', code ); + } catch ( err ) { + // syntax error + console.log( code ); + throw err; + } const module = { exports: {} }; From 827b12fdc003bee672d1d6e6eadf3af07a4a0224 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 31 Aug 2016 09:11:30 -0400 Subject: [PATCH 08/11] split on /, not path.sep --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index bf544ec..25997c3 100644 --- a/src/index.js +++ b/src/index.js @@ -27,7 +27,7 @@ function getCandidates ( resolved, extensions ) { } function getName ( id ) { - const base = basename( id ); + const base = id.split( '/' ).pop(); const ext = extname( base ); return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); From d0849ee750237cac277b86040c1171ba36d21384 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 31 Aug 2016 09:32:26 -0400 Subject: [PATCH 09/11] wtf windows --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 25997c3..64d795c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ 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 { createFilter, makeLegalIdentifier } from 'rollup-pluginutils'; import MagicString from 'magic-string'; @@ -27,7 +27,7 @@ function getCandidates ( resolved, extensions ) { } function getName ( id ) { - const base = id.split( '/' ).pop(); + const base = id.split( /\/\\/ ).pop(); const ext = extname( base ); return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); From aa7857f446907403c03d6e45bb162ad8ac02fb9a Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 31 Aug 2016 23:43:07 +0300 Subject: [PATCH 10/11] Fix windows test with escaping backslash --- src/index.js | 25 +++++++------------------ src/transform.js | 9 +-------- src/utils.js | 7 +++++++ 3 files changed, 15 insertions(+), 26 deletions(-) create mode 100644 src/utils.js diff --git a/src/index.js b/src/index.js index 64d795c..583becc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,12 @@ import { statSync } from 'fs'; import { dirname, extname, resolve, sep } from 'path'; import { sync as nodeResolveSync } from 'resolve'; -import { createFilter, makeLegalIdentifier } from 'rollup-pluginutils'; +import { createFilter } from 'rollup-pluginutils'; import MagicString from 'magic-string'; import { EXTERNAL, PREFIX, HELPERS_ID, HELPERS } from './helpers.js'; import defaultResolver from './defaultResolver.js'; import transform from './transform.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 ); +import { getName } from './utils.js'; function getCandidatesForExtension ( resolved, extension ) { return [ @@ -26,13 +22,6 @@ function getCandidates ( resolved, extensions ) { ); } -function getName ( id ) { - const base = id.split( /\/\\/ ).pop(); - const ext = extname( base ); - - return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); -} - // Return the first non-falsy result from an array of // maybe-sync, maybe-promise-returning functions function first ( candidates ) { @@ -76,10 +65,10 @@ export default function commonjs ( options = {} ) { function resolveId ( importee, importer ) { if ( importee === HELPERS_ID ) return importee; - if ( importer ) importer = importer.replace( PREFIX, '' ); + if ( importer && startsWith( importer, PREFIX ) ) importer = importer.slice( PREFIX.length ); const isProxyModule = startsWith( importee, PREFIX ); - importee = importee.replace( PREFIX, '' ); + if (isProxyModule) importee = importee.slice( PREFIX.length ); return resolveUsingOtherResolvers( importee, importer ).then( resolved => { if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; @@ -144,7 +133,7 @@ export default function commonjs ( options = {} ) { const actualId = id.slice( EXTERNAL.length ); const name = getName( actualId ); - return `import ${name} from '${actualId}'; export default ${name};`; + return `import ${name} from ${JSON.stringify(actualId)}; export default ${name};`; } if ( startsWith( id, PREFIX ) ) { @@ -152,8 +141,8 @@ export default function commonjs ( options = {} ) { const name = getName( actualId ); return commonjsModules.has( actualId ) ? - `import { __moduleExports } from '${actualId}'; export default __moduleExports;` : - `import * as ${name} from '${actualId}'; export default ( ${name} && ${name}['default'] ) || ${name};`; + `import { __moduleExports } from ${JSON.stringify(actualId)}; export default __moduleExports;` : + `import * as ${name} from ${JSON.stringify(actualId)}; export default ( ${name} && ${name}['default'] ) || ${name};`; } }, diff --git a/src/transform.js b/src/transform.js index 8bed7d0..87b897a 100644 --- a/src/transform.js +++ b/src/transform.js @@ -1,10 +1,10 @@ import acorn from 'acorn'; -import { basename, extname } from 'path'; 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 }; @@ -35,13 +35,6 @@ function tryParse ( code, id ) { } } -function getName ( id ) { - const base = basename( id ); - const ext = extname( base ); - - return makeLegalIdentifier( ext.length ? base.slice( 0, -ext.length ) : base ); -} - export default function transform ( code, id, isEntry, ignoreGlobal, customNamedExports, sourceMap ) { const firstpass = ignoreGlobal ? firstpassNoGlobal : firstpassGlobal; if ( !firstpass.test( code ) ) return null; 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 ) ) ); +} + From 4c216c136de414d5dadd444f57edc1e200589f32 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 31 Aug 2016 23:57:59 +0300 Subject: [PATCH 11/11] Fix codestyle --- src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 583becc..5fcdd04 100644 --- a/src/index.js +++ b/src/index.js @@ -68,7 +68,7 @@ export default function commonjs ( options = {} ) { if ( importer && startsWith( importer, PREFIX ) ) importer = importer.slice( PREFIX.length ); const isProxyModule = startsWith( importee, PREFIX ); - if (isProxyModule) importee = importee.slice( PREFIX.length ); + if ( isProxyModule ) importee = importee.slice( PREFIX.length ); return resolveUsingOtherResolvers( importee, importer ).then( resolved => { if ( resolved ) return isProxyModule ? PREFIX + resolved : resolved; @@ -133,7 +133,7 @@ export default function commonjs ( options = {} ) { const actualId = id.slice( EXTERNAL.length ); const name = getName( actualId ); - return `import ${name} from ${JSON.stringify(actualId)}; export default ${name};`; + return `import ${name} from ${JSON.stringify( actualId )}; export default ${name};`; } if ( startsWith( id, PREFIX ) ) { @@ -141,8 +141,8 @@ export default function commonjs ( options = {} ) { const name = getName( actualId ); 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};`; + `import { __moduleExports } from ${JSON.stringify( actualId )}; export default __moduleExports;` : + `import * as ${name} from ${JSON.stringify( actualId )}; export default ( ${name} && ${name}['default'] ) || ${name};`; } },