diff --git a/packages/commonjs/src/index.js b/packages/commonjs/src/index.js index ed321dc1b..33dcda93e 100644 --- a/packages/commonjs/src/index.js +++ b/packages/commonjs/src/index.js @@ -25,7 +25,8 @@ import { getEntryProxy, getEsImportProxy, getStaticRequireProxy, - getUnknownRequireProxy + getUnknownRequireProxy, + getExternalBuiltinRequireProxy } from './proxies'; import getResolveId from './resolve-id'; import { getRequireResolver } from './resolve-require-sources'; @@ -262,6 +263,9 @@ export default function commonjs(options = {}) { if (isWrappedId(id, EXTERNAL_SUFFIX)) { const actualId = unwrapId(id, EXTERNAL_SUFFIX); + if (actualId.startsWith('node:')) { + return getExternalBuiltinRequireProxy(actualId); + } return getUnknownRequireProxy( actualId, isEsmExternal(actualId) ? getRequireReturnsDefault(actualId) : true diff --git a/packages/commonjs/src/proxies.js b/packages/commonjs/src/proxies.js index 2e8d9f270..72aae3640 100644 --- a/packages/commonjs/src/proxies.js +++ b/packages/commonjs/src/proxies.js @@ -85,3 +85,16 @@ export function getEsImportProxy(id, defaultIsModuleExports, moduleSideEffects) syntheticNamedExports: '__moduleExports' }; } + +// For external Node built-ins required from wrapped CommonJS modules, we must not +// hoist an ESM import of the built-in (which would eagerly load it). Instead, +// expose a lazy `__require()` that resolves the built-in at runtime via +// `createRequire(import.meta.url)`. +export function getExternalBuiltinRequireProxy(id) { + const stringifiedId = JSON.stringify(id); + return ( + `import { createRequire } from 'node:module';\n` + + `const require = createRequire(import.meta.url);\n` + + `export function __require() { return require(${stringifiedId}); }` + ); +} diff --git a/packages/commonjs/src/resolve-require-sources.js b/packages/commonjs/src/resolve-require-sources.js index 84b42fa66..6756a06c3 100644 --- a/packages/commonjs/src/resolve-require-sources.js +++ b/packages/commonjs/src/resolve-require-sources.js @@ -5,6 +5,7 @@ import { isWrappedId, PROXY_SUFFIX, wrapId, + unwrapId, WRAPPED_SUFFIX } from './helpers'; import { resolveExtensions } from './resolve-id'; @@ -190,8 +191,21 @@ export function getRequireResolver(extensions, detectCyclesAndConditional, curre fullyAnalyzedModules[parentId] = true; return requireTargets.map(({ id: dependencyId, allowProxy }, index) => { // eslint-disable-next-line no-multi-assign - const isCommonJS = (parentMeta.isRequiredCommonJS[dependencyId] = + let isCommonJS = (parentMeta.isRequiredCommonJS[dependencyId] = getTypeForFullyAnalyzedModule(dependencyId)); + // Special-case external Node built-ins to be handled via a lazy __require + // helper instead of hoisted ESM imports when strict wrapping is used. + if ( + parentMeta.initialCommonJSType === IS_WRAPPED_COMMONJS && + !allowProxy && + isWrappedId(dependencyId, EXTERNAL_SUFFIX) + ) { + const actualId = unwrapId(dependencyId, EXTERNAL_SUFFIX); + const isNodeBuiltin = actualId.startsWith('node:'); + if (isNodeBuiltin) { + isCommonJS = IS_WRAPPED_COMMONJS; + } + } const isWrappedCommonJS = isCommonJS === IS_WRAPPED_COMMONJS; fullyAnalyzedModules[dependencyId] = true; return { diff --git a/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/_config.js b/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/_config.js new file mode 100644 index 000000000..f56002724 --- /dev/null +++ b/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/_config.js @@ -0,0 +1,9 @@ +module.exports = { + description: "does not hoist external node built-in requires when strictRequires is true", + pluginOptions: { + strictRequires: true + }, + exports: (exports, t) => { + t.is(exports, 42); + } +}; diff --git a/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/main.js b/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/main.js new file mode 100644 index 000000000..347fad0c2 --- /dev/null +++ b/packages/commonjs/test/fixtures/function/strict-requires-external-node-builtin/main.js @@ -0,0 +1,4 @@ +if (false) { + require('node:sqlite'); +} +module.exports = 42; diff --git a/packages/commonjs/test/snapshots/function.js.md b/packages/commonjs/test/snapshots/function.js.md index e36cd7414..1c251e72d 100644 --- a/packages/commonjs/test/snapshots/function.js.md +++ b/packages/commonjs/test/snapshots/function.js.md @@ -9194,6 +9194,34 @@ Generated by [AVA](https://avajs.dev). `, } +## strict-requires-external-node-builtin + +> Snapshot 1 + + { + 'main.js': `'use strict';␊ + ␊ + function getDefaultExportFromCjs (x) {␊ + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;␊ + }␊ + ␊ + var main$1;␊ + var hasRequiredMain;␊ + ␊ + function requireMain () {␊ + if (hasRequiredMain) return main$1;␊ + hasRequiredMain = 1;␊ + main$1 = 42;␊ + return main$1;␊ + }␊ + ␊ + var mainExports = requireMain();␊ + var main = /*@__PURE__*/getDefaultExportFromCjs(mainExports);␊ + ␊ + module.exports = main;␊ + `, + } + ## strict-requires-file-without-module-type > Snapshot 1 diff --git a/packages/commonjs/test/snapshots/function.js.snap b/packages/commonjs/test/snapshots/function.js.snap index f04456243..2f11e8c91 100644 Binary files a/packages/commonjs/test/snapshots/function.js.snap and b/packages/commonjs/test/snapshots/function.js.snap differ