feat: support module.createRequire when mixedModules: false#558
feat: support module.createRequire when mixedModules: false#558styfle merged 11 commits intovercel:mainfrom
module.createRequire when mixedModules: false#558Conversation
module.createRequire when mixedModules: false
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive test coverage and fixes for edge cases around module.createRequire in ESM contexts, particularly when mixedModules: false. The changes address three key issues: null pointer exceptions when accessing knownBindings.require, incorrect blocking of variables named require that are created via module.createRequire, and missing support for named imports of createRequire.
- Added null safety checks for
knownBindings.requirewhich may be undefined in ESM withmixedModules: false - Modified
setKnownBindingto allow variables namedrequireif they're created bymodule.createRequire - Added support for detecting
createRequirewhen imported as a named export
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/module-create-require-no-mixed/test-opts.json | Configures test to run with mixedModules: false |
| test/unit/module-create-require-no-mixed/output.js | Expected output showing lib.node is traced with mixedModules: false |
| test/unit/module-create-require-no-mixed/lib.node | Dummy binary file for testing dependency tracing |
| test/unit/module-create-require-no-mixed/input.mjs | Test case using module.createRequire with namespace import |
| test/unit/module-create-require-named-require/test-opts.json | Configures test to run with mixedModules: false |
| test/unit/module-create-require-named-require/output.js | Expected output showing variable named 'require' from createRequire is properly traced |
| test/unit/module-create-require-named-require/lib.node | Dummy binary file for testing dependency tracing |
| test/unit/module-create-require-named-require/input.mjs | Test case where the createRequire result is named 'require' |
| test/unit/module-create-require-named-import/test-opts.json | Configures test to run with mixedModules: false |
| test/unit/module-create-require-named-import/output.js | Expected output showing named import of createRequire works correctly |
| test/unit/module-create-require-named-import/lib.node | Dummy binary file for testing dependency tracing |
| test/unit/module-create-require-named-import/input.mjs | Test case using named import syntax for createRequire |
| test/unit/module-create-require-block-other/test-opts.json | Configures test to run with mixedModules: false |
| test/unit/module-create-require-block-other/output.js | Expected output showing non-createRequire 'require' is not traced |
| test/unit/module-create-require-block-other/lib.node | Dummy binary file that should not be traced |
| test/unit/module-create-require-block-other/input.mjs | Test case verifying variables named 'require' that aren't from createRequire are blocked |
| test/unit.test.js | Updated test runner to use input.mjs and respect mixedModules setting from test-opts.json |
| src/analyze.ts | Adds CREATE_REQUIRE symbol, null safety checks, exception for BOUND_REQUIRE in setKnownBinding, and named import tracking for createRequire |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/analyze.ts
Outdated
| if (parent.type === 'VariableDeclarator') { | ||
| const requireName = parent.id.name; | ||
| setKnownBinding(requireName, { value: BOUND_REQUIRE }); |
There was a problem hiding this comment.
The code accesses parent.id.name without first verifying that parent.id.type === 'Identifier'. In JavaScript, variable declarators can use destructuring patterns (e.g., const { x } = createRequire(...)), where parent.id would be an ObjectPattern or ArrayPattern instead of an Identifier. This could cause a runtime error or unexpected behavior.
The existing code at line 1047 shows the correct pattern: it checks parent.id.type === 'Identifier' before accessing parent.id.name. The same check should be added here for consistency and safety.
There was a problem hiding this comment.
This seems like copilot found a bug
There was a problem hiding this comment.
Oh that's a rare one, I should've given those comments a glance.
I fixed it just now and added a couple of tests for it
Co-authored-by: Steven <[email protected]>
| @@ -0,0 +1,6 @@ | |||
| import * as module from 'node:module'; | |||
|
|
|||
| // Variable named 'require' - might conflict | |||
There was a problem hiding this comment.
This test doesn't seem to be actually testing anything different than the test above which uses
const req = createRequire(import.meta.url)I think instead of import.meta.url all the tests should be using a subdirectory instead like
module.createRequire(new URL('/sub', import.meta.url));That way we know the require() call is on the subdirectory and that we didn't accidentally resolve using the global require().
Does that make sense?
There was a problem hiding this comment.
This test doesn't seem to be actually testing anything different than the test above....
I wanted ensure that it doesn't ignore variables named require if created by module.createRequire, more of a sanity check. I could delete the other test since this can test both behaviors at the same time. What do you think?
I think instead of
import.meta.urlall the tests should be using a subdirectory instead like
Makes sense, I just pushed that and updated the tests.
|
|
||
| // Destructuring pattern - should not crash, but won't be traced | ||
| // since we can't bind a single identifier to the createRequire result | ||
| const { resolve } = module.createRequire(import.meta.url); |
There was a problem hiding this comment.
In a futre PR, this test should also call resolve() on a dependency to see if that still works
(preferably under the /sub directory)
module.createRequire when mixedModules: falsemodule.createRequire when mixedModules: false
|
π This PR is included in version 1.2.0 π The release is available on: Your semantic-release bot π¦π |
This PR adds several fixes and tests for various edge cases around
module.createRequire.Null Checks
When
mixedModules: falseand the file is ESM,knownBindings.requireisundefinedbecause it's only initialized for CommonJS or whenmixedModules: true. The code was accessingknownBindings.require.shadowDepthwithout checking ifrequireexists first.Blocking variables named
requireThis came up by @timfish in this comment, we seem to actively block variables named
require.This is marked as "known unknown" but I thought we should make an exception if the variable was created by
module.createRequire.Named import tracking
Using:
wasn't being detected, so I added a detection block to support it.
closes #543