From 68f80dfb6975504ede977eb89c68b08e054ffa52 Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Sat, 6 Apr 2019 07:50:06 +0100 Subject: [PATCH] New mainfields option (#182) * feat: add `mainFields` api. Deprecate `browser`, `jsnext`, `module`, `main` * Fix remaining issues with mainfields * Add new option to types --- README.md | 17 ++++++-- index.d.ts | 22 +++++++--- package-lock.json | 3 +- src/index.js | 62 ++++++++++++++++++-------- test/test.js | 108 ++++++++++++++++++++++++++-------------------- 5 files changed, 137 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 75878b0..fbbd48d 100644 --- a/README.md +++ b/README.md @@ -25,24 +25,33 @@ export default { }, plugins: [ resolve({ + + // the fields to scan in a package.json to determine the entry point + // if this list contains "browser", overrides specified in "pkg.browser" + // will be used + mainFields: ['module', 'main'], // Default: ['module', 'main'] + + // DEPRECATED: use "mainFields" instead // use "module" field for ES6 module if possible module: true, // Default: true + // DEPRECATED: use "mainFields" instead // use "jsnext:main" if possible // legacy field pointing to ES6 module in third-party libraries, // deprecated in favor of "pkg.module": // - see: https://github.com/rollup/rollup/wiki/pkg.module jsnext: true, // Default: false + // DEPRECATED: use "mainFields" instead // use "main" field or index.js, even if it's not an ES6 module // (needs to be converted from CommonJS to ES6) // – see https://github.com/rollup/rollup-plugin-commonjs main: true, // Default: true - // some package.json files have a `browser` field which - // specifies alternative files to load for people bundling - // for the browser. If that's you, use this option, otherwise - // pkg.browser will be ignored + // some package.json files have a "browser" field which specifies + // alternative files to load for people bundling for the browser. If + // that's you, either use this option or add "browser" to the + // "mainfields" option, otherwise pkg.browser will be ignored browser: true, // Default: false // not all files you want to resolve are .js files diff --git a/index.d.ts b/index.d.ts index b1d3faa..dda9a21 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,13 +1,22 @@ -import { Plugin } from 'rollup'; -import { AsyncOpts } from 'resolve'; +import {Plugin} from 'rollup'; +import {AsyncOpts} from 'resolve'; interface RollupNodeResolveOptions { /** + * the fields to scan in a package.json to determine the entry point + * if this list contains "browser", overrides specified in "pkg.browser" + * will be used + * @default ['module', 'main'] + */ + mainFields?: ['browser', 'esnext', 'module', 'main'], + /** + * @deprecated use "mainFields" instead * use "module" field for ES6 module if possible * @default true */ module?: boolean; /** + * @deprecated use "mainFields" instead * use "jsnext:main" if possible * legacy field pointing to ES6 module in third-party libraries, * deprecated in favor of "pkg.module": @@ -16,6 +25,7 @@ interface RollupNodeResolveOptions { */ jsnext?: boolean; /** + * @deprecated use "mainFields" instead * use "main" field or index.js, even if it's not an ES6 module * (needs to be converted from CommonJS to ES6) * – see https://github.com/rollup/rollup-plugin-commonjs @@ -23,10 +33,10 @@ interface RollupNodeResolveOptions { */ main?: boolean; /** - * some package.json files have a `browser` field which - * specifies alternative files to load for people bundling - * for the browser. If that's you, use this option, otherwise - * pkg.browser will be ignored + * some package.json files have a "browser" field which specifies + * alternative files to load for people bundling for the browser. If + * that's you, either use this option or add "browser" to the + * "mainfields" option, otherwise pkg.browser will be ignored * @default false */ browser?: boolean; diff --git a/package-lock.json b/package-lock.json index 9e342e3..6463e01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,8 +33,7 @@ "@types/node": { "version": "11.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", - "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==", - "dev": true + "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==" }, "@types/resolve": { "version": "0.0.8", diff --git a/src/index.js b/src/index.js index 6dc093e..d973d7e 100644 --- a/src/index.js +++ b/src/index.js @@ -37,12 +37,41 @@ function cachedIsFile (file, cb) { isFileCache[file].then(contents => cb(null, contents), cb); } +function getMainFields (options) { + let mainFields; + if (options.mainFields) { + if ('module' in options || 'main' in options || 'jsnext' in options) { + throw new Error(`node-resolve: do not use deprecated 'module', 'main', 'jsnext' options with 'mainFields'`); + } + mainFields = options.mainFields; + } else { + mainFields = ['module', 'main']; + [['module', 'module'], ['jsnext', 'jsnext:main'], ['main', 'main']].forEach(([option, field]) => { + if (option in options) { + // eslint-disable-next-line no-console + console.warn(`node-resolve: setting options.${option} is deprecated, please override options.mainFields instead`); + if (options[option] === false) { + mainFields = mainFields.filter(mainField => mainField === field); + } else if (options[option] === true && mainFields.indexOf(field) === -1) { + mainFields.push(field); + } + } + }); + } + if (options.browser && mainFields.indexOf('browser') === -1) { + return ['browser'].concat(mainFields); + } + if ( !mainFields.length ) { + throw new Error( `Please ensure at least one 'mainFields' value is specified` ); + } + return mainFields; +} + const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId(file, opts, (err, contents) => err ? reject(err) : fulfil(contents))); export default function nodeResolve ( options = {} ) { - const useModule = options.module !== false; - const useMain = options.main !== false; - const useJsnext = options.jsnext === true; + const mainFields = getMainFields(options); + const useBrowserOverrides = mainFields.indexOf('browser') !== -1; const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false; const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true; const customResolveOptions = options.customResolveOptions || {}; @@ -59,10 +88,6 @@ export default function nodeResolve ( options = {} ) { throw new Error( 'options.skip is no longer supported — you should use the main Rollup `external` option instead' ); } - if ( !useModule && !useMain && !useJsnext ) { - throw new Error( `At least one of options.module, options.main or options.jsnext must be true` ); - } - let preserveSymlinks; return { @@ -83,7 +108,7 @@ export default function nodeResolve ( options = {} ) { const basedir = importer ? dirname( importer ) : process.cwd(); // https://github.com/defunctzombie/package-browser-field-spec - if (options.browser && browserMapCache[importer]) { + if (useBrowserOverrides && browserMapCache[importer]) { const resolvedImportee = resolve( basedir, importee ); const browser = browserMapCache[importer]; if (browser[importee] === false || browser[resolvedImportee] === false) { @@ -115,7 +140,7 @@ export default function nodeResolve ( options = {} ) { basedir, packageFilter ( pkg, pkgPath ) { const pkgRoot = dirname( pkgPath ); - if (options.browser && typeof pkg[ 'browser' ] === 'object') { + if (useBrowserOverrides && typeof pkg[ 'browser' ] === 'object') { packageBrowserField = Object.keys(pkg[ 'browser' ]).reduce((browser, key) => { let resolved = pkg[ 'browser' ][ key ]; if (resolved && resolved[0] === '.') { @@ -136,13 +161,16 @@ export default function nodeResolve ( options = {} ) { }, {}); } - if (options.browser && typeof pkg[ 'browser' ] === 'string') { - pkg[ 'main' ] = pkg[ 'browser' ]; - } else if ( useModule && pkg[ 'module' ] ) { - pkg[ 'main' ] = pkg[ 'module' ]; - } else if ( useJsnext && pkg[ 'jsnext:main' ] ) { - pkg[ 'main' ] = pkg[ 'jsnext:main' ]; - } else if ( ( useJsnext || useModule ) && !useMain ) { + let overriddenMain = false; + for ( let i = 0; i < mainFields.length; i++ ) { + const field = mainFields[i]; + if ( typeof pkg[ field ] === 'string' ) { + pkg[ 'main' ] = pkg[ field ]; + overriddenMain = true; + break; + } + } + if ( overriddenMain === false && mainFields.indexOf( 'main' ) === -1 ) { disregardResult = true; } return pkg; @@ -161,7 +189,7 @@ export default function nodeResolve ( options = {} ) { Object.assign( resolveOptions, customResolveOptions ) ) .then(resolved => { - if ( resolved && options.browser && packageBrowserField ) { + if ( resolved && useBrowserOverrides && packageBrowserField ) { if ( packageBrowserField.hasOwnProperty(resolved) ) { if (!packageBrowserField[resolved]) { browserMapCache[resolved] = packageBrowserField; diff --git a/test/test.js b/test/test.js index 75baa96..2b5ec60 100644 --- a/test/test.js +++ b/test/test.js @@ -54,6 +54,17 @@ describe( 'rollup-plugin-node-resolve', function () { return rollup.rollup({ input: 'samples/jsnext/main.js', onwarn: expectNoWarnings, + plugins: [ + nodeResolve({ mainFields: ['jsnext:main', 'module', 'main'] }) + ] + }).then( executeBundle ).then( module => { + assert.equal( module.exports, '2H' ); + }); + }); + + it( 'DEPRECATED: options.jsnext still works', function () { + return rollup.rollup({ + input: 'samples/jsnext/main.js', plugins: [ nodeResolve({ jsnext: true }) ] @@ -67,7 +78,7 @@ describe( 'rollup-plugin-node-resolve', function () { input: 'samples/commonjs/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ main: true }), + nodeResolve({ mainFields: ['main'] }), commonjs() ] }).then( executeBundle ).then( module => { @@ -80,7 +91,7 @@ describe( 'rollup-plugin-node-resolve', function () { input: 'samples/trailing-slash/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ main: true }), + nodeResolve({ mainFields: ['main'] }), commonjs() ] }).then( executeBundle ).then( module => { @@ -129,15 +140,12 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); - it( 'disregards top-level browser field by default', function () { + it( 'disregards top-level browser field', function () { return rollup.rollup({ input: 'samples/browser/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ - main: true, - browser: false - }) + nodeResolve() ] }).then( executeBundle ).then( module => { assert.equal( module.exports, 'node' ); @@ -150,8 +158,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -159,15 +166,12 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); - it( 'disregards object browser field by default', function () { + it( 'disregards object browser field', function () { return rollup.rollup({ input: 'samples/browser-object/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ - main: true, - browser: false - }) + nodeResolve() ] }).then( executeBundle ).then( module => { assert.equal( module.exports.env, 'node' ); @@ -182,8 +186,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -199,7 +202,21 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, + mainFields: [ 'browser', 'main' ] + }) + ] + }).then( executeBundle ).then( module => { + assert.equal( module.exports.env, 'browser' ); + assert.equal( module.exports.dep, 'browser-dep' ); + assert.equal( module.exports.test, 43 ); + }); + }); + + it( 'options.browser = true still works', function () { + return rollup.rollup({ + input: 'samples/browser-object-main/main.js', + plugins: [ + nodeResolve({ browser: true }) ] @@ -216,8 +233,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -231,8 +247,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -246,8 +261,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -263,8 +277,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -280,8 +293,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -295,8 +307,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -310,8 +321,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -327,8 +337,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -344,8 +353,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -359,8 +367,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -374,8 +381,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ).then( module => { @@ -406,8 +412,7 @@ describe( 'rollup-plugin-node-resolve', function () { onwarn: expectNoWarnings, plugins: [ nodeResolve({ - main: true, - browser: true + mainFields: [ 'browser', 'main' ] }) ] }).then( executeBundle ); @@ -495,12 +500,12 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); - it( 'prefers module field over jsnext:main and main', () => { + it( 'respects order if given module,jsnext:main,main', () => { return rollup.rollup({ input: 'samples/prefer-module/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ jsnext: true, preferBuiltins: false }) + nodeResolve({ mainFields: [ 'module', 'jsnext:main', 'main' ], preferBuiltins: false }) ] }).then( executeBundle ).then( module => { assert.equal( module.exports, 'MODULE-ENTRY' ); @@ -531,6 +536,17 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); + it( 'keeps the order of [browser, module, jsnext, main] with all enabled', function () { + return rollup.rollup({ + input: 'samples/browser/main.js', + plugins: [ + nodeResolve({ main: true, browser: true, jsnext: true, module: true }) + ] + }).then( executeBundle ).then( module => { + assert.equal( module.exports, 'browser' ); + }); + }); + describe( 'symlinks', () => { function createMissingDirectories () { createDirectory( './samples/symlinked/first/node_modules' ); @@ -591,12 +607,12 @@ describe( 'rollup-plugin-node-resolve', function () { }); }); - it( 'prefers jsnext:main field over main', () => { + it( 'respects order if given jsnext:main, main', () => { return rollup.rollup({ input: 'samples/prefer-jsnext/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ jsnext: true, module: false, preferBuiltins: false }) + nodeResolve({ mainFields: ['jsnext:main', 'main'], preferBuiltins: false }) ] }).then( executeBundle ).then( module => { assert.equal( module.exports, 'JSNEXT-ENTRY' ); @@ -608,7 +624,7 @@ describe( 'rollup-plugin-node-resolve', function () { input: './samples/jsnext/main.js', onwarn: expectNoWarnings, plugins: [ - nodeResolve({ jsnext: true }) + nodeResolve({}) ] }).then( executeBundle ).then( module => { assert.equal( module.exports, '2H' );