Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions packages/commonjs/src/resolve-require-sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
description:
'does not crash and does not mark external node: builtins as pure when strictRequires is true',
Comment on lines +2 to +3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description states the test "does not mark external node: builtins as pure when strictRequires is true", but there is no assertion or reliable snapshot evidence of this yet (because the require is currently tree‑shaken). Either clarify the description to focus on the non‑crash behavior, or adopt the suggested fixture change so the snapshot also verifies that no /*@__PURE__*/ annotation is emitted.

Suggestion

You can update the description to reflect how this is validated via snapshot:

module.exports = {
  description:
    'does not crash and retains external node: builtin require without /*@__PURE__*/ annotation when strictRequires is true (verified by snapshot)',
  pluginOptions: { strictRequires: true },
  context: { __filename }
};

Alternatively, keep the description and adopt the main.js change so the snapshot actually proves the absence of the purity annotation. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

pluginOptions: {
strictRequires: true
},
context: {
__filename: __filename
}
};
Original file line number Diff line number Diff line change
@@ -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;
Comment on lines +1 to +8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixture places require('node:crypto') inside an unused function. Rollup will tree‑shake that function, so the generated bundle contains no corresponding __require(...) call. As a result, the test does not actually validate the core behavior claimed in the description (avoiding a /*@__PURE__*/ annotation for wrapped externals). Strengthen the test by keeping the require call in the output without executing it, e.g., behind a runtime-unknown branch. This will let the snapshot verify that no purity annotation is emitted.

Suggestion

Consider changing the fixture to keep the require call present but not executed, so the snapshot can assert that it is not annotated as pure:

// Keep the require present in the output without executing it
if (Math.random() < 0) require('node:crypto');

module.exports = 1;

This will exercise the wrappedModuleSideEffects path and ensure the generated __require('node:crypto') call appears in the snapshot without a /*@__PURE__*/ prefix. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

34 changes: 34 additions & 0 deletions packages/commonjs/test/snapshots/function.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified packages/commonjs/test/snapshots/function.js.snap
Binary file not shown.
Loading