diff --git a/packages/commonjs/src/resolve-require-sources.js b/packages/commonjs/src/resolve-require-sources.js index 6756a06c3..db1603dd5 100644 --- a/packages/commonjs/src/resolve-require-sources.js +++ b/packages/commonjs/src/resolve-require-sources.js @@ -195,22 +195,35 @@ export function getRequireResolver(extensions, detectCyclesAndConditional, curre 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. + const isExternalWrapped = isWrappedId(dependencyId, EXTERNAL_SUFFIX); if ( parentMeta.initialCommonJSType === IS_WRAPPED_COMMONJS && !allowProxy && - isWrappedId(dependencyId, EXTERNAL_SUFFIX) + isExternalWrapped ) { - const actualId = unwrapId(dependencyId, EXTERNAL_SUFFIX); - const isNodeBuiltin = actualId.startsWith('node:'); - if (isNodeBuiltin) { + const actualExternalId = unwrapId(dependencyId, EXTERNAL_SUFFIX); + if (actualExternalId.startsWith('node:')) { isCommonJS = IS_WRAPPED_COMMONJS; + parentMeta.isRequiredCommonJS[dependencyId] = isCommonJS; } } const isWrappedCommonJS = isCommonJS === IS_WRAPPED_COMMONJS; fullyAnalyzedModules[dependencyId] = true; + const moduleInfo = + isWrappedCommonJS && !isExternalWrapped + ? rollupContext.getModuleInfo(dependencyId) + : null; + // For wrapped dependencies, annotate the generated require call as pure only + // when Rollup has module info and it explicitly reports no side effects. + // Note: For external Node built-ins (handled via EXTERNAL_SUFFIX), the module + // has not been loaded yet at this point and getModuleInfo returns null. + // Default to side effects = true in that case to be safe. + // Preserve Rollup's tri-state semantics (true | false | 'no-treeshake') when available. + const wrappedModuleSideEffects = !isWrappedCommonJS + ? false + : moduleInfo?.moduleSideEffects ?? true; return { - wrappedModuleSideEffects: - isWrappedCommonJS && rollupContext.getModuleInfo(dependencyId).moduleSideEffects, + wrappedModuleSideEffects, source: sources[index].source, id: allowProxy ? wrapId(dependencyId, isWrappedCommonJS ? WRAPPED_SUFFIX : PROXY_SUFFIX) diff --git a/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/_config.js b/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/_config.js new file mode 100644 index 000000000..6bc57ba61 --- /dev/null +++ b/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/_config.js @@ -0,0 +1,10 @@ +module.exports = { + description: + 'does not crash and does not mark external node: builtins as pure when strictRequires is true', + pluginOptions: { + strictRequires: true + }, + context: { + __filename: __filename + } +}; diff --git a/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/main.js b/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/main.js new file mode 100644 index 000000000..b6c0d07c2 --- /dev/null +++ b/packages/commonjs/test/fixtures/function/module-side-effects-external-node-builtin-wrapped/main.js @@ -0,0 +1,8 @@ +// Top-level require of a Node builtin ensures the transform computes +// wrappedModuleSideEffects for an external wrapped dependency. +function unused() { + // External Node builtin require; not executed at runtime + require('node:crypto'); +} + +module.exports = 1; diff --git a/packages/commonjs/test/snapshots/function.js.md b/packages/commonjs/test/snapshots/function.js.md index 1c251e72d..d42cefb65 100644 --- a/packages/commonjs/test/snapshots/function.js.md +++ b/packages/commonjs/test/snapshots/function.js.md @@ -6697,6 +6697,40 @@ Generated by [AVA](https://avajs.dev). `, } +## module-side-effects-external-node-builtin-wrapped + +> Snapshot 1 + + { + 'main.js': `'use strict';␊ + ␊ + var node_module = require('node:module');␊ + ␊ + var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;␊ + function getDefaultExportFromCjs (x) {␊ + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;␊ + }␊ + ␊ + node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('main.js', document.baseURI).href)));␊ + ␊ + var main$1;␊ + var hasRequiredMain;␊ + ␊ + function requireMain () {␊ + if (hasRequiredMain) return main$1;␊ + hasRequiredMain = 1;␊ + ␊ + main$1 = 1;␊ + return main$1;␊ + }␊ + ␊ + var mainExports = requireMain();␊ + var main = /*@__PURE__*/getDefaultExportFromCjs(mainExports);␊ + ␊ + module.exports = main;␊ + `, + } + ## module-side-effects-import-wrapped > Snapshot 1 diff --git a/packages/commonjs/test/snapshots/function.js.snap b/packages/commonjs/test/snapshots/function.js.snap index 2f11e8c91..591ed4029 100644 Binary files a/packages/commonjs/test/snapshots/function.js.snap and b/packages/commonjs/test/snapshots/function.js.snap differ