Skip to content

Commit 67333a5

Browse files
joyeecheungmarco-ippolito
authored andcommitted
process: add process.getBuiltinModule(id)
`process.getBuiltinModule(id)` provides a way to load built-in modules in a globally available function. ES Modules that need to support other environments can use it to conditionally load a Node.js built-in when it is run in Node.js, without having to deal with the resolution error that can be thrown by `import` in a non-Node.js environment or having to use dynamic `import()` which either turns the module into an asynchronous module, or turns a synchronous API into an asynchronous one. ```mjs if (globalThis.process.getBuiltinModule) { // Run in Node.js, use the Node.js fs module. const fs = globalThis.process.getBuiltinModule('fs'); // If `require()` is needed to load user-modules, use // createRequire() const module = globalThis.process.getBuiltinModule('module'); const require = module.createRequire(import.meta.url); const foo = require('foo'); } ``` If `id` specifies a built-in module available in the current Node.js process, `process.getBuiltinModule(id)` method returns the corresponding built-in module. If `id` does not correspond to any built-in module, `undefined` is returned. `process.getBuiltinModule(id)` accept built-in module IDs that are recognized by `module.isBuiltin(id)`. Some built-in modules must be loaded with the `node:` prefix. The built-in modules returned by `process.getBuiltinModule(id)` are always the original modules - that is, it's not affected by `require.cache`. PR-URL: #52762 Fixes: #52599 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Zijian Liu <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Mohammed Keyvanzadeh <[email protected]>
1 parent a8601ef commit 67333a5

File tree

5 files changed

+119
-0
lines changed

5 files changed

+119
-0
lines changed

doc/api/process.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,46 @@ console.log('After:', getActiveResourcesInfo());
19171917
// After: [ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
19181918
```
19191919
1920+
## `process.getBuiltinModule(id)`
1921+
1922+
<!-- YAML
1923+
added: REPLACEME
1924+
-->
1925+
1926+
* `id` {string} ID of the built-in module being requested.
1927+
* Returns: {Object|undefined}
1928+
1929+
`process.getBuiltinModule(id)` provides a way to load built-in modules
1930+
in a globally available function. ES Modules that need to support
1931+
other environments can use it to conditionally load a Node.js built-in
1932+
when it is run in Node.js, without having to deal with the resolution
1933+
error that can be thrown by `import` in a non-Node.js environment or
1934+
having to use dynamic `import()` which either turns the module into
1935+
an asynchronous module, or turns a synchronous API into an asynchronous one.
1936+
1937+
```mjs
1938+
if (globalThis.process?.getBuiltinModule) {
1939+
// Run in Node.js, use the Node.js fs module.
1940+
const fs = globalThis.process.getBuiltinModule('fs');
1941+
// If `require()` is needed to load user-modules, use createRequire()
1942+
const module = globalThis.process.getBuiltinModule('module');
1943+
const require = module.createRequire(import.meta.url);
1944+
const foo = require('foo');
1945+
}
1946+
```
1947+
1948+
If `id` specifies a built-in module available in the current Node.js process,
1949+
`process.getBuiltinModule(id)` method returns the corresponding built-in
1950+
module. If `id` does not correspond to any built-in module, `undefined`
1951+
is returned.
1952+
1953+
`process.getBuiltinModule(id)` accepts built-in module IDs that are recognized
1954+
by [`module.isBuiltin(id)`][]. Some built-in modules must be loaded with the
1955+
`node:` prefix, see [built-in modules with mandatory `node:` prefix][].
1956+
The references returned by `process.getBuiltinModule(id)` always point to
1957+
the built-in module corresponding to `id` even if users modify
1958+
[`require.cache`][] so that `require(id)` returns something else.
1959+
19201960
## `process.getegid()`
19211961
19221962
<!-- YAML
@@ -4016,6 +4056,7 @@ cases:
40164056
[`console.error()`]: console.md#consoleerrordata-args
40174057
[`console.log()`]: console.md#consolelogdata-args
40184058
[`domain`]: domain.md
4059+
[`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename
40194060
[`net.Server`]: net.md#class-netserver
40204061
[`net.Socket`]: net.md#class-netsocket
40214062
[`os.constants.dlopen`]: os.md#dlopen-constants
@@ -4032,9 +4073,11 @@ cases:
40324073
[`queueMicrotask()`]: globals.md#queuemicrotaskcallback
40334074
[`readable.read()`]: stream.md#readablereadsize
40344075
[`require()`]: globals.md#require
4076+
[`require.cache`]: modules.md#requirecache
40354077
[`require.main`]: modules.md#accessing-the-main-module
40364078
[`subprocess.kill()`]: child_process.md#subprocesskillsignal
40374079
[`v8.setFlagsFromString()`]: v8.md#v8setflagsfromstringflags
4080+
[built-in modules with mandatory `node:` prefix]: modules.md#built-in-modules-with-mandatory-node-prefix
40384081
[debugger]: debugger.md
40394082
[deprecation code]: deprecations.md
40404083
[note on process I/O]: #a-note-on-process-io

lib/internal/bootstrap/node.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,11 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync);
352352
setMaybeCacheGeneratedSourceMap(maybeCacheGeneratedSourceMap);
353353
}
354354

355+
{
356+
const { getBuiltinModule } = require('internal/modules/helpers');
357+
process.getBuiltinModule = getBuiltinModule;
358+
}
359+
355360
function setupProcessObject() {
356361
const EventEmitter = require('events');
357362
const origProcProto = ObjectGetPrototypeOf(process);

lib/internal/modules/helpers.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,22 @@ let _hasStartedUserCJSExecution = false;
332332
// there is little value checking whether any user JS code is run anyway.
333333
let _hasStartedUserESMExecution = false;
334334

335+
/**
336+
* Load a public built-in module. ID may or may not be prefixed by `node:` and
337+
* will be normalized.
338+
* @param {string} id ID of the built-in to be loaded.
339+
* @returns {object|undefined} exports of the built-in. Undefined if the built-in
340+
* does not exist.
341+
*/
342+
function getBuiltinModule(id) {
343+
validateString(id, 'id');
344+
const normalizedId = BuiltinModule.normalizeRequirableId(id);
345+
return normalizedId ? require(normalizedId) : undefined;
346+
}
347+
335348
module.exports = {
336349
addBuiltinLibsToObject,
350+
getBuiltinModule,
337351
getCjsConditions,
338352
initializeCjsConditions,
339353
loadBuiltinModule,

test/common/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const {
1818
getCallSite,
1919
getTTYfd,
2020
hasCrypto,
21+
hasIntl,
2122
hasIPv6,
2223
hasMultiLocalhost,
2324
isAIX,
@@ -72,6 +73,7 @@ export {
7273
getPort,
7374
getTTYfd,
7475
hasCrypto,
76+
hasIntl,
7577
hasIPv6,
7678
hasMultiLocalhost,
7779
isAIX,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { isMainThread, hasCrypto, hasIntl } from '../common/index.mjs';
2+
import assert from 'node:assert';
3+
import { builtinModules } from 'node:module';
4+
5+
for (const invalid of [1, undefined, null, false, [], {}, () => {}, Symbol('test')]) {
6+
assert.throws(() => process.getBuiltinModule(invalid), { code: 'ERR_INVALID_ARG_TYPE' });
7+
}
8+
9+
for (const invalid of [
10+
'invalid', 'test', 'sea', 'test/reporter', 'internal/bootstrap/realm',
11+
'internal/deps/undici/undici', 'internal/util',
12+
]) {
13+
assert.strictEqual(process.getBuiltinModule(invalid), undefined);
14+
}
15+
16+
// Check that createRequire()(id) returns the same thing as process.getBuiltinModule(id).
17+
const require = process.getBuiltinModule('module').createRequire(import.meta.url);
18+
const publicBuiltins = new Set(builtinModules);
19+
20+
// Remove built-ins not available in the current setup.
21+
if (!isMainThread) {
22+
publicBuiltins.delete('trace_events');
23+
}
24+
if (!hasCrypto) {
25+
publicBuiltins.delete('crypto');
26+
publicBuiltins.delete('tls');
27+
publicBuiltins.delete('_tls_common');
28+
publicBuiltins.delete('_tls_wrap');
29+
publicBuiltins.delete('http2');
30+
publicBuiltins.delete('https');
31+
publicBuiltins.delete('inspector');
32+
publicBuiltins.delete('inspector/promises');
33+
}
34+
if (!hasIntl) {
35+
publicBuiltins.delete('inspector');
36+
publicBuiltins.delete('trace_events');
37+
}
38+
39+
for (const id of publicBuiltins) {
40+
assert.strictEqual(process.getBuiltinModule(id), require(id));
41+
}
42+
// Check that import(id).default returns the same thing as process.getBuiltinModule(id).
43+
for (const id of publicBuiltins) {
44+
const imported = await import(`node:${id}`);
45+
assert.strictEqual(process.getBuiltinModule(id), imported.default);
46+
}
47+
48+
// publicBuiltins does not include 'test' which requires the node: prefix.
49+
const ids = publicBuiltins.add('test');
50+
// Check that import(id).default returns the same thing as process.getBuiltinModule(id).
51+
for (const id of ids) {
52+
const prefixed = `node:${id}`;
53+
const imported = await import(prefixed);
54+
assert.strictEqual(process.getBuiltinModule(prefixed), imported.default);
55+
}

0 commit comments

Comments
 (0)