vite
vite copied to clipboard
feat: v6 - Environment API
Description
[!NOTE] Check out the Environment API Docs
We're starting a new PR for the Environment API work in progress proposal for v6 to have a clean slate for reviews and discussions now that the implementation and docs are more stable.
This PR merges the work done in:
- #16129
- #16089
Refer to the discussions in these PRs for context. If you have general feedback about the APIs, let's use this discussion so we can have proper threads:
- https://github.com/vitejs/vite/discussions/16358
If you have comments about the implementation or would like to collaborate a feature, fix, test, or docs, please comment in this PR as an usual review or send a PR against the v6/environment-api
branch.
Ecosystem Compatibility
These projects are failing in CI because of known reasons.
Framework | Reason | PR |
---|---|---|
VitePress | using internal server._importGlobMap , which is moved |
https://github.com/vuejs/vitepress/pull/3706 |
Run & review this pull request in StackBlitz Codeflow.
/ecosystem-ci run
📝 Ran ecosystem CI on 777967d
: Open
suite | result | latest scheduled |
---|---|---|
analogjs | :x: failure | :white_check_mark: success |
astro | :x: failure | :white_check_mark: success |
histoire | :x: failure | :white_check_mark: success |
marko | :x: failure | :white_check_mark: success |
nuxt | :x: failure | :white_check_mark: success |
qwik | :x: failure | :white_check_mark: success |
rakkas | :x: failure | :white_check_mark: success |
remix | :x: failure | :white_check_mark: success |
sveltekit | :x: failure | :white_check_mark: success |
unocss | :x: failure | :white_check_mark: success |
vike | :x: failure | :white_check_mark: success |
vite-plugin-react-swc | :x: failure | :white_check_mark: success |
vite-plugin-svelte | :x: failure | :white_check_mark: success |
vite-plugin-vue | :x: failure | :white_check_mark: success |
vite-setup-catalogue | :x: failure | :white_check_mark: success |
vitepress | :x: failure | :white_check_mark: success |
:white_check_mark: ladle, laravel, previewjs, quasar, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-pages, vitest
After https://github.com/vitejs/vite/pull/16471/commits/2866d4f2f6cbd184c9d94bc0cdda3c79e790b1aa, we have opt-in for build plugins to be shared, matching the way things work during dev.
Individual plugins can set a sharedDuringBuild: true
to opt-in, or all plugins can be forced to be shared using builder: { sharedPlugins: true }
(this will probably break most plugins right now). There is also a builder: { sharedConfigBuild: true }
to directly share the whole config. This will break even more plugins, even ours internally because right now we are all using config.build
instead of environment.options.build
.
We can deprecate config.build
for ResolvedBuildOptions
because these are the defaults and shouldn't be used. And later on directly remove config.build
after resolving forcing everyone to use environment.options
(this is the same we already were discussing to with config.resolve
). I think this may be one of the trickiest parts in getting the ecosystem to move. We could always leave an opt-in into backward compatibility when we reach this point.
One interesting problem about shared plugins is that once they are used, the only command that makes sense is vite build --app
. Building a single environment could fail because the author of the plugin has the expectation of the plugin processing both client and ssr code for example. This is equivalent to what happens now in SvelteKit and other frameworks where building only ssr doesn't work because you need to build the client first, get the SSR manifest and use it to build the ssr (and they do all this in a single vite build
).
So maybe we should not have vite build --environment={name}
at all, and have some config opt-in to vite build
being vite build --app
instead of a CLI option.
Does it ever makes sense to build only a part of an app with Vite? If it is about performance, I think that may need to be cached in a diff way.
After 2866d4f, we have opt-in for build plugins to be shared, matching the way things work during dev. Individual plugins can set a
sharedDuringBuild: true
to opt-in, ...
I experimented with sharedDuringBuild
for unocss on multi environment use cases (namely react server component) https://github.com/hi-ogawa/vite-environment-examples/pull/57/. It's still a minimal proof-of-concept (I picked up some code from original unocss plugin and tailwind v4 plugin), but my impression is that create(environment)
and sharedDuringBuild: true
nicely solved the concerns I had with using unocss for RSC. Awesome work!
It feels really nice to use, so only positive feedback here to confirm if I'm using it right:
- since
create(environment)
providesenvironment.mode: "dev" | "build"
, I use it to split plugins for dev/build as well. - since
environment
already hasenvironment.config, hot, moduleGraph
etc..., I didn't need to access specific environment viaserver.environments.(env)
viaconfigureServer(server)
It feels really nice to use, so only positive feedback here to confirm if I'm using it right:
- since
create(environment)
providesenvironment.mode: "dev" | "build"
, I use it to split plugins for dev/build as well.
Yes, I think it makes sense to do it there. apply
let's you do the same for a shared plugin so I think it still makes sense to also keep it.
- since
environment
already hasenvironment.config, hot, moduleGraph
etc..., I didn't need to access specific environment viaserver.environments.(env)
viaconfigureServer(server)
You shouldn't be doing it either for shared plugins. You'll directly use this.environment
in the hooks. We removed all configureServer(server)
to access these in Vite's core. Or maybe you have other needs? Could you expand into why this.environment
wasn't enough?
- since
environment
already hasenvironment.config, hot, moduleGraph
etc..., I didn't need to access specific environment viaserver.environments.(env)
viaconfigureServer(server)
You shouldn't be doing it either for shared plugins. You'll directly use
this.environment
in the hooks. We removed allconfigureServer(server)
to access these in Vite's core. Or maybe you have other needs? Could you expand into whythis.environment
wasn't enough?
Indeed, calling this.environment.hot
during transform
where the tokens are extracted can work normally, but it was a reminiscent of the original unocss plugin as it provides asynchronous onInvalidate
listener, so I was setting up environment.hot
callback there, something like:
export function vitePluginSharedUnocss(): Plugin {
const ctx = getUnocssContext();
return {
...
create(environment) {
if (environment.mode === "dev") {
ctx.onInvalidate(() => environment.hot.send( .... ));
}
...
}
}
}
Indeed, calling
this.environment.hot
duringtransform
where the tokens are extracted can work normally, but it was a reminiscent of the original unocss plugin as it provides asynchronousonInvalidate
listener, so I was setting upenvironment.hot
callback there (...)
Ah, this makes sense. Then yes, the new create
hook is a good place to setup this listener. We should check if the listener is really needed now though. Interesting use case, thanks!
We discussed with @sapphi-red and he proposed we move away from the create
hook for bounded plugins, and use a factory (both Anthony and me preferred this form too initially). I actually started using a factory but switched to the create
hook for the following reasons that I now think are not real blockers:
- I felt that a function may not be enough in the future, imagine if we want to add more things like enforce or apply that needs to happen before the plugin gets evaluated. I think this may be a no-problem though, as we can first evaluate and then check the flags.
- I thought that it wasn't easy to support enforce and that it would be needed. For example:
This wasn't that bad at the end,function frameworkPlugin() { return [ sharedPlugin(), // can have enforce: 'pre' () => isolatedPlugin() // it will stay separated from the prev plugin ] }
enforce
works in bounded plugins now -
apply
is evaluated before environments are created, so we need to make the last environment param optional or have a new hook. I think it isn't needed though, because we can pass environment as the argument when constructing the isolated plugin and that can be used for filtering - I thought that it will be easier to have backward compat with the create hook. Imagine if someone is reading plugins and now PluginOption also can be a function. We could have code broken at runtime or at types level. Maybe it is a price we should pay though.
- I was also afraid that frameworks could not sniff the plugins to remove these isolated factories at config time anymore. But now that I think about it, we can use the same trick we have for middlewares and properly name the constructor functions so they could still remove them if they want. Or we only use shared plugins internally.
After [email protected], instead of
function plugin() {
return {
name: 'shared-plugin',
create(environment) {
return {
name: 'bounded-plugin',
transform() { ... }
}
}
}
}
we switched to
function plugin() {
return (environment) => {
return {
name: 'bounded-plugin',
transform() { ... }
}
}
}
@hi-ogawa for the case you were having both a shared plugin hook and create in the same plugin, you can get the same by returning an array:
[
{ name: 'shared-plugin', ... },
(environment) => ({ name: 'bounded-plugin', ... }),
]
/ecosystem-ci run
📝 Ran ecosystem CI on cf9a79c
: Open
suite | result | latest scheduled |
---|---|---|
analogjs | :x: failure | :white_check_mark: success |
astro | :x: failure | :white_check_mark: success |
histoire | :x: failure | :white_check_mark: success |
marko | :x: failure | :white_check_mark: success |
nuxt | :x: failure | :white_check_mark: success |
previewjs | :x: failure | :white_check_mark: success |
quasar | :x: failure | :white_check_mark: success |
qwik | :x: failure | :white_check_mark: success |
remix | :x: failure | :white_check_mark: success |
sveltekit | :x: failure | :white_check_mark: success |
unocss | :x: failure | :white_check_mark: success |
vike | :x: failure | :white_check_mark: success |
vite-plugin-react-swc | :x: failure | :white_check_mark: success |
vite-plugin-svelte | :x: failure | :white_check_mark: success |
vite-plugin-vue | :x: failure | :white_check_mark: success |
vite-setup-catalogue | :x: failure | :white_check_mark: success |
vitepress | :x: failure | :white_check_mark: success |
vitest | :x: failure | :x: failure |
:white_check_mark: ladle, laravel, rakkas, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-pages