Skip to content

Commit 7dafab3

Browse files
cjihrigxtx1130
authored andcommitted
module,repl: support 'node:'-only core modules
This commit makes it possible to add new core modules that can only be require()'ed and imported when the 'node:' scheme is used. The 'test' module is the first such module. These 'node:'-only modules are not included in the list returned by module.builtinModules. PR-URL: nodejs#42325 Refs: nodejs#40954 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent bd1d997 commit 7dafab3

8 files changed

+73
-16
lines changed

lib/internal/bootstrap/loaders.js

+16
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
/* global process, getLinkedBinding, getInternalBinding, primordials */
4545

4646
const {
47+
ArrayFrom,
4748
ArrayPrototypeMap,
4849
ArrayPrototypePush,
4950
ArrayPrototypeSlice,
@@ -119,6 +120,11 @@ const legacyWrapperList = new SafeSet([
119120
'util',
120121
]);
121122

123+
// Modules that can only be imported via the node: scheme.
124+
const schemelessBlockList = new SafeSet([
125+
'test',
126+
]);
127+
122128
// Set up process.binding() and process._linkedBinding().
123129
{
124130
const bindingObj = ObjectCreate(null);
@@ -242,6 +248,16 @@ class NativeModule {
242248
return mod && mod.canBeRequiredByUsers;
243249
}
244250

251+
// Determine if a core module can be loaded without the node: prefix. This
252+
// function does not validate if the module actually exists.
253+
static canBeRequiredWithoutScheme(id) {
254+
return !schemelessBlockList.has(id);
255+
}
256+
257+
static getSchemeOnlyModuleNames() {
258+
return ArrayFrom(schemelessBlockList);
259+
}
260+
245261
// Used by user-land module loaders to compile and load builtins.
246262
compileForPublicLoader() {
247263
if (!this.canBeRequiredByUsers) {

lib/internal/modules/cjs/loader.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ function Module(id = '', parent) {
182182

183183
const builtinModules = [];
184184
for (const { 0: id, 1: mod } of NativeModule.map) {
185-
if (mod.canBeRequiredByUsers) {
185+
if (mod.canBeRequiredByUsers &&
186+
NativeModule.canBeRequiredWithoutScheme(id)) {
186187
ArrayPrototypePush(builtinModules, id);
187188
}
188189
}
@@ -802,7 +803,13 @@ Module._load = function(request, parent, isMain) {
802803
}
803804

804805
const mod = loadNativeModule(filename, request);
805-
if (mod?.canBeRequiredByUsers) return mod.exports;
806+
if (mod?.canBeRequiredByUsers) {
807+
if (!NativeModule.canBeRequiredWithoutScheme(filename)) {
808+
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
809+
}
810+
811+
return mod.exports;
812+
}
806813

807814
// Don't call updateChildren(), Module constructor already does.
808815
const module = cachedModule || new Module(filename, parent);

lib/internal/modules/esm/resolve.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const {
5757
ERR_PACKAGE_PATH_NOT_EXPORTED,
5858
ERR_UNSUPPORTED_DIR_IMPORT,
5959
ERR_NETWORK_IMPORT_DISALLOWED,
60+
ERR_UNKNOWN_BUILTIN_MODULE,
6061
ERR_UNSUPPORTED_ESM_URL_SCHEME,
6162
} = require('internal/errors').codes;
6263
const { Module: CJSModule } = require('internal/modules/cjs/loader');
@@ -860,8 +861,13 @@ function parsePackageName(specifier, base) {
860861
* @returns {resolved: URL, format? : string}
861862
*/
862863
function packageResolve(specifier, base, conditions) {
863-
if (NativeModule.canBeRequiredByUsers(specifier))
864+
if (NativeModule.canBeRequiredByUsers(specifier)) {
865+
if (!NativeModule.canBeRequiredWithoutScheme(specifier)) {
866+
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
867+
}
868+
864869
return new URL('node:' + specifier);
870+
}
865871

866872
const { packageName, packageSubpath, isScoped } =
867873
parsePackageName(specifier, base);

lib/repl.js

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const {
100100
globalThis,
101101
} = primordials;
102102

103+
const { NativeModule } = require('internal/bootstrap/loaders');
103104
const {
104105
makeRequireFunction,
105106
addBuiltinLibsToObject
@@ -129,6 +130,10 @@ let _builtinLibs = ArrayPrototypeFilter(
129130
);
130131
const nodeSchemeBuiltinLibs = ArrayPrototypeMap(
131132
_builtinLibs, (lib) => `node:${lib}`);
133+
ArrayPrototypeForEach(
134+
NativeModule.getSchemeOnlyModuleNames(),
135+
(lib) => ArrayPrototypePush(nodeSchemeBuiltinLibs, `node:${lib}`),
136+
);
132137
const domain = require('domain');
133138
let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
134139
debug = fn;

test/parallel/test-code-cache.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const {
1616
} = internalBinding('native_module');
1717

1818
for (const key of canBeRequired) {
19-
require(key);
19+
require(`node:${key}`);
2020
}
2121

2222
// The computation has to be delayed until we have done loading modules

test/parallel/test-repl-tab-complete-import.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,18 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => {
5353
assert.strictEqual(data[1], 'n');
5454
const completions = data[0];
5555
// import(...) completions include `node:` URL modules:
56-
publicModules.forEach((lib, index) =>
57-
assert.strictEqual(completions[index], `node:${lib}`));
58-
assert.strictEqual(completions[publicModules.length], '');
56+
let lastIndex = -1;
57+
58+
publicModules.forEach((lib, index) => {
59+
lastIndex = completions.indexOf(`node:${lib}`);
60+
assert.notStrictEqual(lastIndex, -1);
61+
});
62+
assert.strictEqual(completions[lastIndex + 1], '');
5963
// There is only one Node.js module that starts with n:
60-
assert.strictEqual(completions[publicModules.length + 1], 'net');
61-
assert.strictEqual(completions[publicModules.length + 2], '');
64+
assert.strictEqual(completions[lastIndex + 2], 'net');
65+
assert.strictEqual(completions[lastIndex + 3], '');
6266
// It's possible to pick up non-core modules too
63-
completions.slice(publicModules.length + 3).forEach((completion) => {
67+
completions.slice(lastIndex + 4).forEach((completion) => {
6468
assert.match(completion, /^n/);
6569
});
6670
}));

test/parallel/test-repl-tab-complete.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,18 @@ testMe.complete("require\t( 'n", common.mustCall(function(error, data) {
261261
assert.strictEqual(data.length, 2);
262262
assert.strictEqual(data[1], 'n');
263263
// require(...) completions include `node:`-prefixed modules:
264-
publicModules.forEach((lib, index) =>
265-
assert.strictEqual(data[0][index], `node:${lib}`));
266-
assert.strictEqual(data[0][publicModules.length], '');
264+
let lastIndex = -1;
265+
266+
publicModules.forEach((lib, index) => {
267+
lastIndex = data[0].indexOf(`node:${lib}`);
268+
assert.notStrictEqual(lastIndex, -1);
269+
});
270+
assert.strictEqual(data[0][lastIndex + 1], '');
267271
// There is only one Node.js module that starts with n:
268-
assert.strictEqual(data[0][publicModules.length + 1], 'net');
269-
assert.strictEqual(data[0][publicModules.length + 2], '');
272+
assert.strictEqual(data[0][lastIndex + 2], 'net');
273+
assert.strictEqual(data[0][lastIndex + 3], '');
270274
// It's possible to pick up non-core modules too
271-
data[0].slice(publicModules.length + 3).forEach((completion) => {
275+
data[0].slice(lastIndex + 4).forEach((completion) => {
272276
assert.match(completion, /^n/);
273277
});
274278
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
assert.throws(
6+
() => require('test'),
7+
common.expectsError({ code: 'ERR_UNKNOWN_BUILTIN_MODULE' }),
8+
);
9+
10+
(async () => {
11+
await assert.rejects(
12+
async () => import('test'),
13+
common.expectsError({ code: 'ERR_UNKNOWN_BUILTIN_MODULE' }),
14+
);
15+
})().then(common.mustCall());

0 commit comments

Comments
 (0)