Skip to content

Commit 719213c

Browse files
committed
process: add process.getBuiltin(id)
`process.getBuiltin(id)` provides a way to load the 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 builtin when it is run in Node.js, without having to deal with the resolution error that can be thrown by `import 'node:id'` 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 && globalThis.process.getBuiltin) { // Run in Node.js, use the Node.js fs module. const fs = globalThis.process.getBuiltin('fs'); // If `require()` is needed to load user-modules, use createRequire() const m = globalThis.process.getBuiltin('module'); const require = m.createRequire(import.meta.url); const foo = require('foo'); } ``` If `id` specifies a built-in available in the current Node.js process, `process.getBuiltin()` method returns the corresponding built-in module, which is the same as the object returned by [`require(id)`][`require()`]. If `id` does not correspond to any built-in module, an [`ERR_UNKNOWN_BUILTIN_MODULE`][] would be thrown. Unlike [`require(id)`][`require()`], `process.getBuiltin(id)` does not need or accept IDs with the `node:` prefix.
1 parent d20515a commit 719213c

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

doc/api/process.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,44 @@ console.log('After:', getActiveResourcesInfo());
19171917
// After: [ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
19181918
```
19191919
1920+
## `process.getBuiltin(id)`
1921+
1922+
<!-- YAML
1923+
added: REPLACEME
1924+
-->
1925+
1926+
* `id` {string} ID of the built-in module being requested. Must not contain the `node:` prefix.
1927+
* Returns: {Object}
1928+
1929+
`process.getBuiltin(id)` provides a way to load the 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 builtin
1932+
when it is run in Node.js, without having to deal with the resolution
1933+
error that can be thrown by `import 'node:id'` in a non-Node.js
1934+
environment or having to use dynamic `import()` which either turns the
1935+
module into an asynchronous module, or turns a synchronous API into an
1936+
asynchronous one.
1937+
1938+
```mjs
1939+
if (globalThis.process && globalThis.process.getBuiltin) {
1940+
// Run in Node.js, use the Node.js fs module.
1941+
const fs = globalThis.process.getBuiltin('fs');
1942+
// If `require()` is needed to load user-modules, use createRequire()
1943+
const m = globalThis.process.getBuiltin('module');
1944+
const require = m.createRequire(import.meta.url);
1945+
const foo = require('foo');
1946+
}
1947+
```
1948+
1949+
If `id` specifies a built-in available in the current Node.js process,
1950+
`process.getBuiltin()` method returns the corresponding built-in
1951+
module, which is the same as the object returned by
1952+
[`require(id)`][`require()`]. If `id` does not correspond to any
1953+
built-in module, an [`ERR_UNKNOWN_BUILTIN_MODULE`][] would be thrown.
1954+
1955+
Unlike [`require(id)`][`require()`], `process.getBuiltin(id)` does not
1956+
need or accept IDs with the `node:` prefix.
1957+
19201958
## `process.getegid()`
19211959
19221960
<!-- YAML
@@ -4010,6 +4048,7 @@ cases:
40104048
[`ChildProcess.disconnect()`]: child_process.md#subprocessdisconnect
40114049
[`ChildProcess.send()`]: child_process.md#subprocesssendmessage-sendhandle-options-callback
40124050
[`ChildProcess`]: child_process.md#class-childprocess
4051+
[`ERR_UNKNOWN_BUILTIN_MODULE`]: errors.md#err_unknown_builtin_module
40134052
[`Error`]: errors.md#class-error
40144053
[`EventEmitter`]: events.md#class-eventemitter
40154054
[`NODE_OPTIONS`]: cli.md#node_optionsoptions

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 { getBuiltin } = require('internal/modules/helpers');
357+
process.getBuiltin = getBuiltin;
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
@@ -342,8 +342,22 @@ let _hasStartedUserCJSExecution = false;
342342
// there is little value checking whether any user JS code is run anyway.
343343
let _hasStartedUserESMExecution = false;
344344

345+
/**
346+
* Load a public builtin.
347+
* @param {string} id id of the builtin to be loaded. ID must be without the node: prefix.
348+
* @returns {object} exports of the builtin.
349+
*/
350+
function getBuiltin(id) {
351+
validateString(id, 'id');
352+
if (!BuiltinModule.canBeRequiredByUsers(id)) {
353+
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
354+
}
355+
return require(id);
356+
}
357+
345358
module.exports = {
346359
addBuiltinLibsToObject,
360+
getBuiltin,
347361
getCjsConditions,
348362
initializeCjsConditions,
349363
loadBuiltinModule,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import '../common/index.mjs';
2+
import assert from 'node:assert';
3+
import { builtinModules } from 'node:module';
4+
5+
const { getBuiltin } = globalThis.process;
6+
for (const invalid of [1, undefined, null, false, [], {}, () => {}, Symbol('test')]) {
7+
assert.throws(() => getBuiltin(invalid), { code: 'ERR_INVALID_ARG_TYPE' });
8+
}
9+
10+
for (const invalid of [
11+
'invalid', 'node:test', 'node:fs', 'internal/bootstrap/realm',
12+
'internal/deps/undici/undici', 'internal/util',
13+
]) {
14+
assert.throws(() => getBuiltin(invalid), { code: 'ERR_UNKNOWN_BUILTIN_MODULE' });
15+
}
16+
17+
// Check that createRequire()(id) returns the same thing as getBuiltin(id).
18+
const require = getBuiltin('module').createRequire(import.meta.url);
19+
for (const id of builtinModules) {
20+
assert.strictEqual(getBuiltin(id), require(id));
21+
}
22+
// builtinModules does not include 'test' which requires the node: prefix.
23+
const ids = builtinModules.concat(['test']);
24+
for (const id of builtinModules) {
25+
assert.strictEqual(getBuiltin(id), require(`node:${id}`));
26+
}
27+
28+
// Check that import(id).default returns the same thing as getBuiltin(id).
29+
for (const id of ids) {
30+
const imported = await import(`node:${id}`);
31+
assert.strictEqual(getBuiltin(id), imported.default);
32+
}

0 commit comments

Comments
 (0)