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 21 commits into
base: main
Choose a base branch
from

Conversation

dario-piotrowicz
Copy link
Contributor

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

Description

The issue this PR is addressing that the current 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 builtins 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.


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 🤷 ).

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

packages/vite/src/node/plugins/resolve.ts Outdated Show resolved Hide resolved
@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 dario-piotrowicz force-pushed the dario/env-isbuiltin branch 2 times, most recently from 572b283 to 4b53f67 Compare November 6, 2024 22:56
@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
packages/vite/src/node/plugins/resolve.ts Outdated Show resolved Hide resolved
Comment on lines 472 to 475
} else if (
currentEnvironmentOptions.consumer === 'client' &&
isNodeLikeBuiltin(id)
) {
Copy link
Member

@sapphi-red sapphi-red Dec 3, 2024

Choose a reason for hiding this comment

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

Suggested change
} else if (
currentEnvironmentOptions.consumer === 'client' &&
isNodeLikeBuiltin(id)
) {
} else if (
currentEnvironmentOptions.consumer === 'server' &&
isNodeLikeBuiltin(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))
) {
let message = `Cannot bundle node built-in module "${id}"`
if (importer) {
message += ` imported from "${path.relative(
process.cwd(),
importer,
)}"`
}
message += `. Consider disabling environments.${this.environment.name}.noExternal or remove the built-in dependency.`
this.error(message)
}
} if (
currentEnvironmentOptions.consumer === 'client' &&
isNodeLikeBuiltin(id)
) {

How about moving the "if options.enableBuiltinNoExternalCheck block" here from above and removing options.enableBuiltinNoExternalCheck && and setting builtin: [] by default for ssr.target: 'webworker'?

By setting builtin: [], I mean updating this line to (consumer === 'server' && !isSsrTargetWebworkerEnvironment.
https://github.com/vitejs/vite/pull/18584/files#diff-11e17761d4ecfee8f8fde15c6d79b7bc0260176396a30dfd8e6f6bbaf5af4745R888

This way if an environment sets builtin and have some node builtin modules not included in builtin, Vite will show a more friendly error (Cannot bundle built-in module) when noExternal is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've made the changes in regards to enableBuiltinNoExternalCheck that you suggested, it sounds sensible to me 🙂👍

@dario-piotrowicz
Copy link
Contributor Author

I've been trying to think of potential tests to add here but it is a bit tricky 🤔 (since builtins are related to specific environments/runtimes, such as workerd, and I don't think that using a whole environment/runtime just for testing this would be worth it 😕)

if anyone would have any idea that'd be great 🙏 😄

@sapphi-red
Copy link
Member

I've been trying to think of potential tests to add here but it is a bit tricky 🤔 (since builtins are related to specific environments/runtimes, such as workerd, and I don't think that using a whole environment/runtime just for testing this would be worth it 😕)

if anyone would have any idea that'd be great 🙏 😄

I think there's a following option:

  • call server.environments.ssr.pluginContainer.resolveId and check the result
    example:
    const resolved = await server.environments.ssr.pluginContainer.resolveId(
    '@vitejs/test-dep-conditions/with-module',
    )
  • call build and check the output whether it includes import as-is
    example:
    test('ssr custom', async () => {
    const builder = await createBuilder({
    root: resolve(__dirname, 'fixtures/dynamic-import'),
    logLevel: 'warn',
    environments: {
    custom: {
    build: {
    ssr: true,
    rollupOptions: {
    input: {
    index: '/entry',
    },
    },
    },
    },
    },
    })
    const result = await builder.build(builder.environments.custom)
    expect((result as RollupOutput).output[0].code).not.toContain('preload')
    })

But actually we already have miniflare installed at playground/ssr-webworker/vite.config.js. So adding an e2e test there would work too 🙂

@sapphi-red sapphi-red added p3-significant High priority enhancement (priority) feat: environment API Vite Environment API labels Dec 4, 2024
@dario-piotrowicz
Copy link
Contributor Author

@sapphi-red thanks a lot for the options for testing this! 🫶

I will try to play with those 🙏

Regarding the miniflare one, right now I would try to avoid using that as I feel that a more generic non-cloudflare specific test would be better, moreover I would not really want to introduce a "miniflare environment" that could confuse people looking at it and thinking that that's the proper way to run use cloudflare/miniflare with the environment API

Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

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

Great! Thank you 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat: environment API Vite Environment API p3-significant High priority enhancement (priority)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants