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