From 032055b9ab555155e9828ba30a27e67949351f7c Mon Sep 17 00:00:00 2001 From: younggglcy Date: Tue, 24 Sep 2024 01:34:32 +0800 Subject: [PATCH] feat(node-resolve): allow preferBuiltins to be a function (#1694) * feat: allow `options.preferBuiltins` to be function * docs: update readme * feat: add types in dts * tests: add testcase about passing function to preferBuiltins --- packages/node-resolve/README.md | 10 +++++- packages/node-resolve/src/index.js | 17 +++++---- .../prefer-builtin-local-and-builtin.js | 4 +++ packages/node-resolve/test/prefer-builtins.js | 36 +++++++++++++++---- packages/node-resolve/types/index.d.ts | 4 ++- 5 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 packages/node-resolve/test/fixtures/prefer-builtin-local-and-builtin.js diff --git a/packages/node-resolve/README.md b/packages/node-resolve/README.md index 40425d2d7..6866a6853 100755 --- a/packages/node-resolve/README.md +++ b/packages/node-resolve/README.md @@ -133,11 +133,19 @@ Specifies the properties to scan within a `package.json`, used to determine the ### `preferBuiltins` -Type: `Boolean`
+Type: `Boolean | (module: string) => boolean`
Default: `true` (with warnings if a builtin module is used over a local version. Set to `true` to disable warning.) If `true`, the plugin will prefer built-in modules (e.g. `fs`, `path`). If `false`, the plugin will look for locally installed modules of the same name. +Alternatively, you may pass in a function that returns a boolean to confirm whether the plugin should prefer built-in modules. e.g. + +```js +preferBuiltins: (module) => module !== 'punycode'; +``` + +will not treat `punycode` as a built-in module + ### `modulesOnly` Type: `Boolean`
diff --git a/packages/node-resolve/src/index.js b/packages/node-resolve/src/index.js index dbf301c80..a675247aa 100644 --- a/packages/node-resolve/src/index.js +++ b/packages/node-resolve/src/index.js @@ -58,7 +58,7 @@ export function nodeResolve(opts = {}) { const idToPackageInfo = new Map(); const mainFields = getMainFields(options); const useBrowserOverrides = mainFields.indexOf('browser') !== -1; - const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false; + const isPreferBuiltinsSet = Object.prototype.hasOwnProperty.call(options, 'preferBuiltins'); const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true; const rootDir = resolve(options.rootDir || process.cwd()); let { dedupe } = options; @@ -194,8 +194,10 @@ export function nodeResolve(opts = {}) { }); const importeeIsBuiltin = builtinModules.includes(importee.replace(nodeImportPrefix, '')); + const preferImporteeIsBuiltin = + typeof preferBuiltins === 'function' ? preferBuiltins(importee) : preferBuiltins; const resolved = - importeeIsBuiltin && preferBuiltins + importeeIsBuiltin && preferImporteeIsBuiltin ? { packageInfo: undefined, hasModuleSideEffects: () => null, @@ -230,11 +232,14 @@ export function nodeResolve(opts = {}) { idToPackageInfo.set(location, packageInfo); if (hasPackageEntry) { - if (importeeIsBuiltin && preferBuiltins) { + if (importeeIsBuiltin && preferImporteeIsBuiltin) { if (!isPreferBuiltinsSet && resolvedWithoutBuiltins && resolved !== importee) { - context.warn( - `preferring built-in module '${importee}' over local alternative at '${resolvedWithoutBuiltins.location}', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning` - ); + context.warn({ + message: + `preferring built-in module '${importee}' over local alternative at '${resolvedWithoutBuiltins.location}', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning.` + + `or passing a function to 'preferBuiltins' to provide more fine-grained control over which built-in modules to prefer.`, + pluginCode: 'PREFER_BUILTINS' + }); } return false; } else if (jail && location.indexOf(normalize(jail.trim(sep))) !== 0) { diff --git a/packages/node-resolve/test/fixtures/prefer-builtin-local-and-builtin.js b/packages/node-resolve/test/fixtures/prefer-builtin-local-and-builtin.js new file mode 100644 index 000000000..de1bf9637 --- /dev/null +++ b/packages/node-resolve/test/fixtures/prefer-builtin-local-and-builtin.js @@ -0,0 +1,4 @@ +import { sep } from 'path'; +import events from 'events'; + +export default { sep, events }; diff --git a/packages/node-resolve/test/prefer-builtins.js b/packages/node-resolve/test/prefer-builtins.js index 27d69d771..9f9998629 100644 --- a/packages/node-resolve/test/prefer-builtins.js +++ b/packages/node-resolve/test/prefer-builtins.js @@ -30,13 +30,12 @@ test('handles importing builtins', async (t) => { }); test('warning when preferring a builtin module, no explicit configuration', async (t) => { - let warning = null; + let warning = ''; await rollup({ input: 'prefer-builtin.js', - onwarn({ message }) { - // eslint-disable-next-line no-bitwise - if (~message.indexOf('preferring')) { - warning = message; + onwarn({ message, pluginCode }) { + if (pluginCode === 'PREFER_BUILTINS') { + warning += message; } }, plugins: [nodeResolve()] @@ -47,7 +46,8 @@ test('warning when preferring a builtin module, no explicit configuration', asyn warning, `preferring built-in module 'events' over local alternative ` + `at '${localPath}', pass 'preferBuiltins: false' to disable this behavior ` + - `or 'preferBuiltins: true' to disable this warning` + `or 'preferBuiltins: true' to disable this warning.` + + `or passing a function to 'preferBuiltins' to provide more fine-grained control over which built-in modules to prefer.` ); }); @@ -134,3 +134,27 @@ test('detects builtins imported with node: protocol', async (t) => { t.is(warnings.length, 0); }); + +test('accpet passing a function to determine which builtins to prefer', async (t) => { + const warnings = []; + const bundle = await rollup({ + input: 'prefer-builtin-local-and-builtin.js', + onwarn({ message }) { + warnings.push(message); + }, + plugins: [ + nodeResolve({ + preferBuiltins: (id) => id !== 'events' + }) + ] + }); + + const { + module: { exports } + } = await testBundle(t, bundle); + + t.is(warnings.length, 0); + t.is(exports.sep, require('node:path').sep); + t.not(exports.events, require('node:events')); + t.is(exports.events, 'not the built-in events module'); +}); diff --git a/packages/node-resolve/types/index.d.ts b/packages/node-resolve/types/index.d.ts index 6bd5db3c2..f27b456cf 100755 --- a/packages/node-resolve/types/index.d.ts +++ b/packages/node-resolve/types/index.d.ts @@ -79,9 +79,11 @@ export interface RollupNodeResolveOptions { /** * If `true`, the plugin will prefer built-in modules (e.g. `fs`, `path`). If `false`, * the plugin will look for locally installed modules of the same name. + * + * If a function is provided, it will be called to determine whether to prefer built-ins. * @default true */ - preferBuiltins?: boolean; + preferBuiltins?: boolean | ((module: string) => boolean); /** * An `Array` which instructs the plugin to limit module resolution to those whose