Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

process: add process.getBuiltinModule(id) #52762

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

joyeecheung
Copy link
Member

@joyeecheung joyeecheung commented Apr 30, 2024

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.

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.

Fixes: #52599

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/loaders
  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 30, 2024
doc/api/process.md Outdated Show resolved Hide resolved
doc/api/process.md Outdated Show resolved Hide resolved
lib/internal/modules/helpers.js Outdated Show resolved Hide resolved
Copy link
Contributor

@JakobJingleheimer JakobJingleheimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, quite simple :)

doc/api/process.md Outdated Show resolved Hide resolved
doc/api/process.md Outdated Show resolved Hide resolved
doc/api/process.md Outdated Show resolved Hide resolved
lib/internal/modules/helpers.js Outdated Show resolved Hide resolved
doc/api/process.md Outdated Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@jakebailey
Copy link

This is obviously exactly what I asked for (😄) and gets it "done" the quickest, but I'm curious if there are any other opinions about this in general.

Is it possible that import.now or "weak/optional" imports come to fruition, such that those are more usable and processed by downstream tools? I'd hate to accidentally open the door to "breaking" node polyfills (node-polyfill-webpack-plugin, vite-plugin-node-polyfills, etc) and so on, just because it's not require or import, but some other expression.

@joyeecheung
Copy link
Member Author

joyeecheung commented May 1, 2024

I'd hate to accidentally open the door to "breaking" node polyfills (node-polyfill-webpack-plugin, vite-plugin-node-polyfills

Not quite sure if I am following this but I think process.getBuiltin(id) should be polyfill-able by these kind of bundler plugins - just patch the its polyfilled process object to load its own polyfilled libraries by id? (In general plugins like this already polyfill the process object). For code that uses the polyfill/gets transformed to use the polyfills, if the polyfill doesn't support this yet, using this would not be too different than "using a new API that the polyfill doesn't support yet" which happens all the time.

@jakebailey
Copy link

Yeah, I guess it's no different than detecting calls to require and error/warn on ones which cannot be statically determined. It's just not quite as injectable as require.

Just poking holes in my own idea.

@joyeecheung
Copy link
Member Author

joyeecheung commented May 1, 2024

We can also suggest in the documentation that users should only consider using this for code paths that can only be run in Node.js (the code example is basically already suggesting that but we can make it clearer). If they want the code depending on the built-ins to be polyfill-able in the browser, they should still use require or import.

@nicolo-ribaudo
Copy link
Contributor

nicolo-ribaudo commented May 1, 2024

There is a TC39 proposal to get original built-in functions, through a new getIntrinsic global (https://github.com/tc39/proposal-get-intrinsic). getBuiltin is similar enough that could cause confusion — what do you think about something like process.getBuiltinModule to be more clear about what it does?

@jakebailey
Copy link

Just to prove this out to its full extent (and to procrastinate other work I didn't want to do), I polyfilled this API using --require and was able to get TypeScript's CLI, API, and test suite all working: microsoft/TypeScript#58419

doc/api/process.md Outdated Show resolved Hide resolved
`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`.
@joyeecheung joyeecheung changed the title process: add process.getBuiltin(id) process: add process.getBuiltinModule(id) May 7, 2024
@joyeecheung
Copy link
Member Author

Updated the PR a bit:

@nodejs-github-bot
Copy link
Collaborator

@GeoffreyBooth
Copy link
Member

Changed it to process.getBuiltinModule() as requested in process: add process.getBuiltinModule(id)

Now it mismatches isBuiltin. Personally I don't see the confusion between getIntrinsic and getBuiltin; I think getBuiltin is fine, and there's more potential for confusion with this not corresponding with isBuiltin.

Alternatively we can add isBuiltinModule as an alias for isBuiltin.

doc/api/process.md Outdated Show resolved Hide resolved
@legendecas
Copy link
Member

Now it mismatches isBuiltin. Personally I don't see the confusion between getIntrinsic and getBuiltin; I think getBuiltin is fine, and there's more potential for confusion with this not corresponding with isBuiltin.

Alternatively we can add isBuiltinModule as an alias for isBuiltin.

isBuiltin is scoped in node:module. It is not a function available from the global scope, like global.process.getBuiltinModule.

@GeoffreyBooth
Copy link
Member

isBuiltin is scoped in node:module. It is not a function available from the global scope, like global.process.getBuiltinModule.

Fair enough. I still think we should create an alias on node:module isBuiltinModule, but that can come later.

@jakebailey jakebailey mentioned this pull request May 7, 2024
7 tasks
Copy link
Member

@legendecas legendecas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just one small concern about docs that we should not make an impression that the result of getBuiltinModule can not be modified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide some mechanism to conditionally and synchronously import modules (or just builtins) from ESM