-
-
Notifications
You must be signed in to change notification settings - Fork 32k
module: add support for node:
‑prefixed require(…)
calls
#37246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,7 +110,8 @@ let hasLoadedAnyUserCJSModule = false; | |
const { | ||
ERR_INVALID_ARG_VALUE, | ||
ERR_INVALID_MODULE_SPECIFIER, | ||
ERR_REQUIRE_ESM | ||
ERR_REQUIRE_ESM, | ||
ERR_UNKNOWN_BUILTIN_MODULE, | ||
} = require('internal/errors').codes; | ||
const { validateString } = require('internal/validators'); | ||
const pendingDeprecation = getOptionValue('--pending-deprecation'); | ||
|
@@ -770,6 +771,17 @@ Module._load = function(request, parent, isMain) { | |
} | ||
|
||
const filename = Module._resolveFilename(request, parent, isMain); | ||
if (StringPrototypeStartsWith(filename, 'node:')) { | ||
// Slice 'node:' prefix | ||
const id = StringPrototypeSlice(filename, 5); | ||
|
||
const module = loadNativeModule(id, request); | ||
if (!module?.canBeRequiredByUsers) { | ||
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); | ||
} | ||
|
||
return module.exports; | ||
} | ||
Comment on lines
+774
to
+784
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the motivation for short-circuiting the cache here, something that has never been done previously for native modules? I don't see why this scheme should be any different to any other in this regard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this topic, should we throw or defer to cache on unknown builtins? |
||
|
||
const cachedModule = Module._cache[filename]; | ||
if (cachedModule !== undefined) { | ||
|
@@ -841,7 +853,8 @@ Module._load = function(request, parent, isMain) { | |
}; | ||
|
||
Module._resolveFilename = function(request, parent, isMain, options) { | ||
if (NativeModule.canBeRequiredByUsers(request)) { | ||
if (StringPrototypeStartsWith(request, 'node:') || | ||
NativeModule.canBeRequiredByUsers(request)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bifurcation will have implications for mocking, but I can appreciate the benefit too. I'd still prefer a more convergent path here eg, to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Existing mocking tools (such as Jest) provide their own |
||
return request; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,42 @@ | ||||
'use strict'; | ||||
|
||||
require('../common'); | ||||
const assert = require('assert'); | ||||
const fs = require('fs'); | ||||
|
||||
const errUnknownBuiltinModuleRE = /^No such built-in module: /u; | ||||
|
||||
// For direct use of require expressions inside of CJS modules, | ||||
// all kinds of specifiers should work without issue. | ||||
{ | ||||
assert.strictEqual(require('fs'), fs); | ||||
assert.strictEqual(require('node:fs'), fs); | ||||
|
||||
assert.throws( | ||||
() => require('node:unknown'), | ||||
{ | ||||
code: 'ERR_UNKNOWN_BUILTIN_MODULE', | ||||
message: errUnknownBuiltinModuleRE, | ||||
}, | ||||
); | ||||
|
||||
assert.throws( | ||||
() => require('node:internal/test/binding'), | ||||
{ | ||||
code: 'ERR_UNKNOWN_BUILTIN_MODULE', | ||||
message: errUnknownBuiltinModuleRE, | ||||
}, | ||||
); | ||||
} | ||||
|
||||
// `node:`-prefixed `require(...)` calls bypass the require cache: | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this differs from other builtins, I love it, but want to be sure it is intentional. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this actually seems like an issue - it means someone who replaces a builtin intentionally can’t replace one of these (altho presumably mutating a builtin would be visible in both). That could cause an issue where someone wants to use a package to instrument or lock down There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. policies should intercept before any of the resolution helpers even get called node/lib/internal/modules/cjs/helpers.js Line 52 in 36cc0ee
module.require and get here without some kind of opt-in to the behavior (like dependencies:true ).
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the very least this needs documentation and probably a strong indication people using policies + instrumenting fs now need to update the policy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point was that policies aren’t currently required to ensure this for CJS - a package (not the app itself) can do it. Forcing policies to be the mechanism means packages are no longer capable of abstracting this for users. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally understood. Given that policies are the superior mechanism for app developers to lock things down, what's the benefit of adding special, inconsistent, cache-bypassing behavior for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ljharb i have no strong opinion on if we should diverge, but cache first behavior of CJS isn't shared by ESM and isn't robust against things like core destructuring the original impls. My like is just that it isn't a confusing situation like with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is demonstrated by: const fs = require('fs');
const fakeModule = {};
require.cache.fs = { exports: fakeModule };
assert.strictEqual((await import('fs')).default, fs); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indeed, but that doesn’t mean it’s a good idea to make CJS inconsistent with itself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think CJS is able to be claimed as consistent with itself since in general the natives don't normally populate the cache. I think it was just ad-hoc written together and the cache behavior is evolutionary not intentional or well understood (even by me). |
||||
{ | ||||
const fakeModule = {}; | ||||
|
||||
require.cache.fs = { exports: fakeModule }; | ||||
|
||||
assert.strictEqual(require('fs'), fakeModule); | ||||
assert.strictEqual(require('node:fs'), fs); | ||||
|
||||
delete require.cache.fs; | ||||
} |
Uh oh!
There was an error while loading. Please reload this page.