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

feat: add the builtins environment resolve #18584

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

Conversation

dario-piotrowicz
Copy link
Contributor

@dario-piotrowicz dario-piotrowicz commented Nov 5, 2024

Description

This is just a quick idea I had and thought I'd open a PR to see what people think

The "issue" I'm trying to address is that the isBuiltin methods seems to be hardcoded for node:

export function isBuiltin(id: string): boolean {
if (process.versions.deno && id.startsWith(NPM_BUILTIN_NAMESPACE)) return true
if (process.versions.bun && id.startsWith(BUN_BUILTIN_NAMESPACE)) return true
return isNodeBuiltin(id)
}

This doesn't fully seems right to me given the new environment API, since different environments can have different builtins.

For example Cloudflare's workerd runtime has the following builtins:

  • cloudflare:email
  • cloudflare:sockets
  • cloudflare:workers

It can also include the node builtsins based on the user's configuration.

So I think that it would be nice if environment authors could declare what builtin modules their environment has.

I'm not fully sure if it is a good idea, what scares me most is that end users could tweak this value, so this might not be the right abstraction/API 😕


One final note, without this option it is pretty easy to workaround the hardcoded node isBuiltin method, in dev environments could implement their fetchModule to externalize all their builtins (example) and for build those could be marked as external (example). There are also a few other places where these might need to be set like in optimizeDeps.exclude (example). So this option I'm proposing is probably something not absolutely necessary but just something to avoiding having to specify such modules all over the place (but there might also be other benefits in letting isBuiltin know how to distinguish environment specific builtins 🤷 ).


Note

If this were to be properly considered I do need to add tests before merging it

@patak-dev
Copy link
Member

I personally think this is a good idea.

@dario-piotrowicz dario-piotrowicz changed the title feat: add the new environment config option isBuiltin feat: add the new environment resolve option isBuiltin Nov 6, 2024
@@ -115,7 +114,7 @@ export function esbuildDepPlugin(
namespace: 'optional-peer-dep',
}
}
if (environment.config.consumer === 'server' && isBuiltin(resolved)) {
if (environment.config.resolve.isBuiltin(resolved)) {
return
}
Copy link
Member

Choose a reason for hiding this comment

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

I guess this does not work. cloudflare:* will be processed by esbuild and IIRC esbuild does not externalize them automatically and then it throws Could not resolve "cloudflare:*" error. I guess it needs to be return { path: resolved, external: true }.
But I wonder if we should set external: true for anything that was externalized by rollup plugins or resolve.external instead of checking isBuiltin here.

Comment on lines 459 to 449
this.environment.config.resolve.isBuiltin(id)
) {
if (
options.noExternal === true &&
// if both noExternal and external are true, noExternal will take the higher priority and bundle it.
// only if the id is explicitly listed in external, we will externalize it and skip this error.
(options.external === true || !options.external.includes(id))
Copy link
Member

@sapphi-red sapphi-red Nov 6, 2024

Choose a reason for hiding this comment

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

resolve.isBuiltin and resolve.external and resolve.noExternal interacts here. I tried to write down the use case for each settings:

external noExternal isBuiltin use case
[] [] isNodeBuiltin for applications that runs on Node
[] [...] isNodeBuiltin for applications that runs on Node and wants to bundle some deps
[...] [...] isNodeBuiltin for applications that runs on Node and wants to bundle some deps
[] true isNodeBuiltin for applications that runs on Node and wants to bundle all deps
[...] / true true isNodeBuiltin for applications that runs on non-Node compat runtime and wants to bundle all deps
[] [] customFunction ? (I guess this won't work. Dependencies will be externalized but if the runtime is not Node compatible then many deps won't work)
[] [...] customFunction ? (same with above)
[...] [...] customFunction ? (same with above)
[] true customFunction for applications that runs on non-Node compat runtime and wants to bundle all deps (What is the advantage of it compared to external: [...], noExternal: true, isBuiltin: isNodeBuiltin?)
[...] / true true customFunction for applications that runs on non-Node compat runtime and wants to bundle some deps (What is the advantage of it compared to external: [...], noExternal: true, isBuiltin: isNodeBuiltin?)

Do we have use cases for the ones with ?? I wonder if the option should be part of resolve.external (something like external: { isBuiltin: [], dependencies: [] }). or maybe making resolve.external work better could work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by I guess this won't work. Dependencies will be externalized but if the runtime is not Node compatible then many deps won't work?

If I have a runtime that does not support node builtin modules (or only a subset of them) like for example workerd without the nodejs compat flag turned on, it seems ok that "many deps won't work" since they would also not work in the final deployed environment, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

or maybe making resolve.external work better could work?

Yes... maybe 🤔 but improving resolve.external won't help the fact that the current isBuiltin function is hardcoded for (more or less) node.

What I mean is, let's say that user code is importing node:path in a workerd worker without setting the nodejs compat flag, currently there is no way to tell vite that node:path is not a builtin module of the workerd environment, is there?

Copy link
Member

Choose a reason for hiding this comment

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

What do you mean by I guess this won't work. Dependencies will be externalized but if the runtime is not Node compatible then many deps won't work?

If I have a runtime that does not support node builtin modules (or only a subset of them) like for example workerd without the nodejs compat flag turned on, it seems ok that "many deps won't work" since they would also not work in the final deployed environment, no?

I'm not sure why I wrote that. Probably I mixed up something. Please ignore it 😅

What I mean is, let's say that user code is importing node:path in a workerd worker without setting the nodejs compat flag, currently there is no way to tell vite that node:path is not a builtin module of the workerd environment, is there?

If noExternal is set to true and external does not include node builtins, it should work like that (#15656).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If noExternal is set to true and external does not include node builtins

Yeah I double checked that with our plugin, it does work as you said 👍

@bluwy
Copy link
Member

bluwy commented Nov 6, 2024

I think I'm ok with this if it helps simplify managing builtins. I was thinking resolve.external could help here, but maybe it's better to reserve that for modules/packages, and it's not quite the perfect fit to use that for optimizeDeps.exclude too I think.

If we do land this, I'd however prefer the option to be an array of strings or regexes instead of a function.

@dario-piotrowicz
Copy link
Contributor Author

dario-piotrowicz commented Nov 6, 2024

If we do land this, I'd however prefer the option to be an array of strings or regexes instead of a function.

I've updated it 🙂

Having this as an array of strings/regexes does feel much more natural 😄 (example diff)

@dario-piotrowicz dario-piotrowicz changed the title feat: add the new environment resolve option isBuiltin feat: add the builtins environment resolve Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants