Skip to content

Commit 88cf0d0

Browse files
cjihrigguangwong
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/node#42325 Backport-PR-URL: nodejs/node#43904 Refs: nodejs/node#40954 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 14d5f90 commit 88cf0d0

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,
@@ -120,6 +121,11 @@ const legacyWrapperList = new SafeSet([
120121
'util',
121122
]);
122123

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

252+
// Determine if a core module can be loaded without the node: prefix. This
253+
// function does not validate if the module actually exists.
254+
static canBeRequiredWithoutScheme(id) {
255+
return !schemelessBlockList.has(id);
256+
}
257+
258+
static getSchemeOnlyModuleNames() {
259+
return ArrayFrom(schemelessBlockList);
260+
}
261+
246262
// Used by user-land module loaders to compile and load builtins.
247263
compileForPublicLoader() {
248264
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
}
@@ -805,7 +806,13 @@ Module._load = function(request, parent, isMain) {
805806
}
806807

807808
const mod = loadNativeModule(filename, request);
808-
if (mod?.canBeRequiredByUsers) return mod.exports;
809+
if (mod?.canBeRequiredByUsers) {
810+
if (!NativeModule.canBeRequiredWithoutScheme(filename)) {
811+
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
812+
}
813+
814+
return mod.exports;
815+
}
809816

810817
// Don't call updateChildren(), Module constructor already does.
811818
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');
@@ -887,8 +888,13 @@ function parsePackageName(specifier, base) {
887888
* @returns {URL}
888889
*/
889890
function packageResolve(specifier, base, conditions) {
890-
if (NativeModule.canBeRequiredByUsers(specifier))
891+
if (NativeModule.canBeRequiredByUsers(specifier)) {
892+
if (!NativeModule.canBeRequiredWithoutScheme(specifier)) {
893+
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
894+
}
895+
891896
return new URL('node:' + specifier);
897+
}
892898

893899
const { packageName, packageSubpath, isScoped } =
894900
parsePackageName(specifier, base);

lib/repl.js

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

104+
const { NativeModule } = require('internal/bootstrap/loaders');
104105
const {
105106
makeRequireFunction,
106107
addBuiltinLibsToObject
@@ -130,6 +131,10 @@ let _builtinLibs = ArrayPrototypeFilter(
130131
);
131132
const nodeSchemeBuiltinLibs = ArrayPrototypeMap(
132133
_builtinLibs, (lib) => `node:${lib}`);
134+
ArrayPrototypeForEach(
135+
NativeModule.getSchemeOnlyModuleNames(),
136+
(lib) => ArrayPrototypePush(nodeSchemeBuiltinLibs, `node:${lib}`),
137+
);
133138
const domain = require('domain');
134139
let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
135140
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
@@ -293,14 +293,18 @@ testMe.complete("require\t( 'n", common.mustCall(function(error, data) {
293293
assert.strictEqual(data.length, 2);
294294
assert.strictEqual(data[1], 'n');
295295
// require(...) completions include `node:`-prefixed modules:
296-
publicModules.forEach((lib, index) =>
297-
assert.strictEqual(data[0][index], `node:${lib}`));
298-
assert.strictEqual(data[0][publicModules.length], '');
296+
let lastIndex = -1;
297+
298+
publicModules.forEach((lib, index) => {
299+
lastIndex = data[0].indexOf(`node:${lib}`);
300+
assert.notStrictEqual(lastIndex, -1);
301+
});
302+
assert.strictEqual(data[0][lastIndex + 1], '');
299303
// There is only one Node.js module that starts with n:
300-
assert.strictEqual(data[0][publicModules.length + 1], 'net');
301-
assert.strictEqual(data[0][publicModules.length + 2], '');
304+
assert.strictEqual(data[0][lastIndex + 2], 'net');
305+
assert.strictEqual(data[0][lastIndex + 3], '');
302306
// It's possible to pick up non-core modules too
303-
data[0].slice(publicModules.length + 3).forEach((completion) => {
307+
data[0].slice(lastIndex + 4).forEach((completion) => {
304308
assert.match(completion, /^n/);
305309
});
306310
}));
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)