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 6 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.

@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 20, 2024
@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@RedYetiDev RedYetiDev left a comment

Choose a reason for hiding this comment

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

I'm not a core collaborator, so this is only a suggestion: Shouldn't this become a no-op with --experimental-permission?

@joyeecheung
Copy link
Member Author

joyeecheung commented May 20, 2024

I'm not a core collaborator, so this is only a suggestion: Shouldn't this become a no-op with --experimental-permission?

I don't think it's relevant for the permission model as that guards things at the native layer when resources are actually accessed. The removed policy feature did guard access to builtins at the module loader level, but that's..removed (in general guarding things at the module loader level isn't super useful anyway because how widely monkey-patching of the module loader is used). But cc @nodejs/security-wg to be sure.

@joyeecheung joyeecheung added request-ci Add this label to start a Jenkins CI on a PR. labels May 20, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 20, 2024
@nodejs-github-bot
Copy link
Collaborator

@RedYetiDev
Copy link
Member

I'm not a core collaborator, so this is only a suggestion: Shouldn't this become a no-op with --experimental-permission?

I don't think it's relevant for the permission model as that guards things at the native layer when resources are actually accessed. The removed policy feature did guard access to builtins at the module loader level, but that's..removed (in general guarding things at the module loader level isn't super useful anyway because how widely monkey-patching of the module loader is used). But cc @nodejs/security-wg to be sure.

Thanks, I was thinking this was like process.binding, my bad, sorry

doc/api/process.md Outdated Show resolved Hide resolved
doc/api/process.md Outdated Show resolved Hide resolved
Co-authored-by: Michaël Zasso <targos@protonmail.com>
@joyeecheung joyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label May 21, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 21, 2024
@nodejs-github-bot
Copy link
Collaborator

jasnell added a commit to cloudflare/workerd that referenced this pull request May 21, 2024
The `nodejs_compat_v2` flag supersedes the 'nodejs_compat' flag.
When enabled...

1. Node.js built-ins are available for import/require
2. Unlike the original `nodejs_compat` flag, Node.js imports do not
   require the 'node:' specifier prefix. Internally, the implementation
   will detect a Node.js raw specifier and convert it into the appropriate
   prefixed specifier, e.g. 'fs' becomes 'node:fs'
3. The `Buffer` and `process` global objects are exposed on the global
4. A user worker bundle can still provide it's own implementations of
   all `node:` modules which will take precendence over the built-ins
5. The new `process.getBuiltinModule(...)` API is implemented.
   See nodejs/node#52762

A worker can replace the implementation of `node:process` if they
choose, which may mean that `getBuiltinModule(...)` is not available.
@nodejs-github-bot
Copy link
Collaborator

@joyeecheung joyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label May 22, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 22, 2024
@nodejs-github-bot
Copy link
Collaborator

jasnell added a commit to cloudflare/workerd that referenced this pull request May 22, 2024
The `nodejs_compat_v2` flag supersedes the 'nodejs_compat' flag.
When enabled...

1. Node.js built-ins are available for import/require
2. Unlike the original `nodejs_compat` flag, Node.js imports do not
   require the 'node:' specifier prefix. Internally, the implementation
   will detect a Node.js raw specifier and convert it into the appropriate
   prefixed specifier, e.g. 'fs' becomes 'node:fs'
3. The `Buffer` and `process` global objects are exposed on the global
4. A user worker bundle can still provide it's own implementations of
   all `node:` modules which will take precendence over the built-ins
5. The new `process.getBuiltinModule(...)` API is implemented.
   See nodejs/node#52762

A worker can replace the implementation of `node:process` if they
choose, which may mean that `getBuiltinModule(...)` is not available.
jasnell added a commit to cloudflare/workerd that referenced this pull request May 22, 2024
The `nodejs_compat_v2` flag supersedes the 'nodejs_compat' flag.
When enabled...

1. Node.js built-ins are available for import/require
2. Unlike the original `nodejs_compat` flag, Node.js imports do not
   require the 'node:' specifier prefix. Internally, the implementation
   will detect a Node.js raw specifier and convert it into the appropriate
   prefixed specifier, e.g. 'fs' becomes 'node:fs'
3. The `Buffer` and `process` global objects are exposed on the global
4. A user worker bundle can still provide it's own implementations of
   all `node:` modules which will take precendence over the built-ins
5. The new `process.getBuiltinModule(...)` API is implemented.
   See nodejs/node#52762

A worker can replace the implementation of `node:process` if they
choose, which may mean that `getBuiltinModule(...)` is not available.
jasnell added a commit to cloudflare/workerd that referenced this pull request May 22, 2024
The `nodejs_compat_v2` flag supersedes the 'nodejs_compat' flag.
When enabled...

1. Node.js built-ins are available for import/require
2. Unlike the original `nodejs_compat` flag, Node.js imports do not
   require the 'node:' specifier prefix. Internally, the implementation
   will detect a Node.js raw specifier and convert it into the appropriate
   prefixed specifier, e.g. 'fs' becomes 'node:fs'
3. The `Buffer` and `process` global objects are exposed on the global
4. A user worker bundle can still provide it's own implementations of
   all `node:` modules which will take precendence over the built-ins
5. The new `process.getBuiltinModule(...)` API is implemented.
   See nodejs/node#52762

A worker can replace the implementation of `node:process` if they
choose, which may mean that `getBuiltinModule(...)` is not available.
@nodejs-github-bot
Copy link
Collaborator

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