Skip to content

Commit e83917e

Browse files
committed
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`.
1 parent aa54e26 commit e83917e

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

doc/api/process.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,45 @@ 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)` accept 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 built-in modules returned by `process.getBuiltinModule(id)` are always the
1957+
original modules - that is, it's not affected by [`require.cache`][].
1958+
19201959
## `process.getegid()`
19211960
19221961
<!-- YAML
@@ -4020,6 +4059,7 @@ cases:
40204059
[`console.error()`]: console.md#consoleerrordata-args
40214060
[`console.log()`]: console.md#consolelogdata-args
40224061
[`domain`]: domain.md
4062+
[`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename
40234063
[`net.Server`]: net.md#class-netserver
40244064
[`net.Socket`]: net.md#class-netsocket
40254065
[`os.constants.dlopen`]: os.md#dlopen-constants
@@ -4036,9 +4076,11 @@ cases:
40364076
[`queueMicrotask()`]: globals.md#queuemicrotaskcallback
40374077
[`readable.read()`]: stream.md#readablereadsize
40384078
[`require()`]: globals.md#require
4079+
[`require.cache`]: modules.md#requirecache
40394080
[`require.main`]: modules.md#accessing-the-main-module
40404081
[`subprocess.kill()`]: child_process.md#subprocesskillsignal
40414082
[`v8.setFlagsFromString()`]: v8.md#v8setflagsfromstringflags
4083+
[built-in modules with mandatory `node:` prefix]: modules.md#built-in-modules-with-mandatory-node-prefix
40424084
[debugger]: debugger.md
40434085
[deprecation code]: deprecations.md
40444086
[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
@@ -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 built-in module. ID may or may not be prefixed by `node:` and
347+
* will be normalized.
348+
* @param {string} id ID of the built-in to be loaded.
349+
* @returns {object|undefined} exports of the built-in. Undefined if the built-in
350+
* does not exist.
351+
*/
352+
function getBuiltinModule(id) {
353+
validateString(id, 'id');
354+
const normalizedId = BuiltinModule.normalizeRequirableId(id);
355+
return normalizedId ? require(normalizedId) : undefined;
356+
}
357+
345358
module.exports = {
346359
addBuiltinLibsToObject,
360+
getBuiltinModule,
347361
getCjsConditions,
348362
initializeCjsConditions,
349363
loadBuiltinModule,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import '../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+
for (const id of builtinModules) {
19+
assert.strictEqual(process.getBuiltinModule(id), require(id));
20+
}
21+
// Check that import(id).default returns the same thing as process.getBuiltinModule(id).
22+
for (const id of builtinModules) {
23+
const imported = await import(`node:${id}`);
24+
assert.strictEqual(process.getBuiltinModule(id), imported.default);
25+
}
26+
27+
// builtinModules does not include 'test' which requires the node: prefix.
28+
const ids = builtinModules.concat(['test']);
29+
// Check that import(id).default returns the same thing as process.getBuiltinModule(id).
30+
for (const id of ids) {
31+
const prefixed = `node:${id}`;
32+
const imported = await import(prefixed);
33+
assert.strictEqual(process.getBuiltinModule(prefixed), imported.default);
34+
}

0 commit comments

Comments
 (0)