Skip to content

Commit

Permalink
process: add process.getBuiltin(id)
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
joyeecheung committed Apr 30, 2024
1 parent d20515a commit 719213c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
39 changes: 39 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,44 @@ console.log('After:', getActiveResourcesInfo());
// After: [ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]
```
## `process.getBuiltin(id)`
<!-- YAML
added: REPLACEME
-->
* `id` {string} ID of the built-in module being requested. Must not contain the `node:` prefix.
* Returns: {Object}
`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.
## `process.getegid()`
<!-- YAML
Expand Down Expand Up @@ -4010,6 +4048,7 @@ cases:
[`ChildProcess.disconnect()`]: child_process.md#subprocessdisconnect
[`ChildProcess.send()`]: child_process.md#subprocesssendmessage-sendhandle-options-callback
[`ChildProcess`]: child_process.md#class-childprocess
[`ERR_UNKNOWN_BUILTIN_MODULE`]: errors.md#err_unknown_builtin_module
[`Error`]: errors.md#class-error
[`EventEmitter`]: events.md#class-eventemitter
[`NODE_OPTIONS`]: cli.md#node_optionsoptions
Expand Down
5 changes: 5 additions & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync);
setMaybeCacheGeneratedSourceMap(maybeCacheGeneratedSourceMap);
}

{
const { getBuiltin } = require('internal/modules/helpers');
process.getBuiltin = getBuiltin;
}

function setupProcessObject() {
const EventEmitter = require('events');
const origProcProto = ObjectGetPrototypeOf(process);
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/modules/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,22 @@ let _hasStartedUserCJSExecution = false;
// there is little value checking whether any user JS code is run anyway.
let _hasStartedUserESMExecution = false;

/**
* Load a public builtin.
* @param {string} id id of the builtin to be loaded. ID must be without the node: prefix.
* @returns {object} exports of the builtin.
*/
function getBuiltin(id) {
validateString(id, 'id');
if (!BuiltinModule.canBeRequiredByUsers(id)) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
}
return require(id);
}

module.exports = {
addBuiltinLibsToObject,
getBuiltin,
getCjsConditions,
initializeCjsConditions,
loadBuiltinModule,
Expand Down
32 changes: 32 additions & 0 deletions test/parallel/test-process-get-builtin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import '../common/index.mjs';
import assert from 'node:assert';
import { builtinModules } from 'node:module';

const { getBuiltin } = globalThis.process;
for (const invalid of [1, undefined, null, false, [], {}, () => {}, Symbol('test')]) {
assert.throws(() => getBuiltin(invalid), { code: 'ERR_INVALID_ARG_TYPE' });
}

for (const invalid of [
'invalid', 'node:test', 'node:fs', 'internal/bootstrap/realm',
'internal/deps/undici/undici', 'internal/util',
]) {
assert.throws(() => getBuiltin(invalid), { code: 'ERR_UNKNOWN_BUILTIN_MODULE' });
}

// Check that createRequire()(id) returns the same thing as getBuiltin(id).
const require = getBuiltin('module').createRequire(import.meta.url);
for (const id of builtinModules) {
assert.strictEqual(getBuiltin(id), require(id));
}
// builtinModules does not include 'test' which requires the node: prefix.
const ids = builtinModules.concat(['test']);
for (const id of builtinModules) {
assert.strictEqual(getBuiltin(id), require(`node:${id}`));
}

// Check that import(id).default returns the same thing as getBuiltin(id).
for (const id of ids) {
const imported = await import(`node:${id}`);
assert.strictEqual(getBuiltin(id), imported.default);
}

0 comments on commit 719213c

Please sign in to comment.