diff --git a/.gitignore b/.gitignore index 08625d3..7bb0182 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist .gobble* !test/node_modules +!test/node_modules/react-consumer/node_modules diff --git a/README.md b/README.md index fbbd48d..1e953dd 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,11 @@ export default { // ES2015 modules modulesOnly: true, // Default: false + // Force resolving for these modules to root's node_modules that helps + // to prevent bundling the same package multiple times if package is + // imported from dependencies. + dedupe: [ 'react', 'react-dom' ], // Default: [] + // Any additional options that should be passed through // to node-resolve customResolveOptions: { diff --git a/index.d.ts b/index.d.ts index dda9a21..9c4d2fe 100644 --- a/index.d.ts +++ b/index.d.ts @@ -70,6 +70,12 @@ interface RollupNodeResolveOptions { * @default false */ modulesOnly?: boolean; + /** + * Force resolving for these modules to root's node_modules that helps + * to prevent bundling the same package multiple times if package is + * imported from dependencies. + */ + dedupe?: string[]; /** * Any additional options that should be passed through * to node-resolve diff --git a/src/index.js b/src/index.js index d973d7e..37b9ed6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import {dirname, extname, normalize, resolve, sep} from 'path'; +import {dirname, extname, normalize, resolve, sep, join} from 'path'; import builtins from 'builtin-modules'; import resolveId from 'resolve'; import isModule from 'is-module'; @@ -72,6 +72,7 @@ const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId export default function nodeResolve ( options = {} ) { const mainFields = getMainFields(options); const useBrowserOverrides = mainFields.indexOf('browser') !== -1; + const dedupe = options.dedupe || []; const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false; const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true; const customResolveOptions = options.customResolveOptions || {}; @@ -107,6 +108,10 @@ export default function nodeResolve ( options = {} ) { const basedir = importer ? dirname( importer ) : process.cwd(); + if (dedupe.indexOf(importee) !== -1) { + importee = join(process.cwd(), 'node_modules', importee); + } + // https://github.com/defunctzombie/package-browser-field-spec if (useBrowserOverrides && browserMapCache[importer]) { const resolvedImportee = resolve( basedir, importee ); diff --git a/test/node_modules/react-consumer/index.js b/test/node_modules/react-consumer/index.js new file mode 100644 index 0000000..bfc70f0 --- /dev/null +++ b/test/node_modules/react-consumer/index.js @@ -0,0 +1,3 @@ +import React from 'react' + +export default 'react-consumer:' + React diff --git a/test/node_modules/react-consumer/node_modules/.gitkeep b/test/node_modules/react-consumer/node_modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/node_modules/react-consumer/node_modules/react/index.js b/test/node_modules/react-consumer/node_modules/react/index.js new file mode 100644 index 0000000..8bfd7ab --- /dev/null +++ b/test/node_modules/react-consumer/node_modules/react/index.js @@ -0,0 +1 @@ +export default 'react:child' \ No newline at end of file diff --git a/test/node_modules/react/index.js b/test/node_modules/react/index.js new file mode 100644 index 0000000..6fd06a9 --- /dev/null +++ b/test/node_modules/react/index.js @@ -0,0 +1 @@ +export default 'react:root' diff --git a/test/samples/react-app/main.js b/test/samples/react-app/main.js new file mode 100644 index 0000000..1b39a2a --- /dev/null +++ b/test/samples/react-app/main.js @@ -0,0 +1,4 @@ +import React from 'react' +import ReactConsumer from 'react-consumer' + +export { React, ReactConsumer } diff --git a/test/test.js b/test/test.js index 2b5ec60..102ef2b 100644 --- a/test/test.js +++ b/test/test.js @@ -777,4 +777,34 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); + it( 'single module version is bundle if dedupe is set', () => { + return rollup.rollup({ + input: 'samples/react-app/main.js', + plugins: [ + nodeResolve({ + dedupe: [ 'react' ] + }) + ] + }).then( executeBundle ).then( module => { + assert.deepEqual(module.exports, { + React: 'react:root', + ReactConsumer: 'react-consumer:react:root' + }); + }); + }); + + it( 'multiple module versions are bundled if dedupe is not set', () => { + return rollup.rollup({ + input: 'samples/react-app/main.js', + plugins: [ + nodeResolve() + ] + }).then( executeBundle ).then( module => { + assert.deepEqual(module.exports, { + React: 'react:root', + ReactConsumer: 'react-consumer:react:child' + }); + }); + }); + });