vite icon indicating copy to clipboard operation
vite copied to clipboard

feat: v6 - Environment API

Open patak-dev opened this issue 3 months ago • 35 comments

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.

image

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

patak-dev avatar Apr 19 '24 12:04 patak-dev

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

stackblitz[bot] avatar Apr 19 '24 12:04 stackblitz[bot]

/ecosystem-ci run

patak-dev avatar Apr 19 '24 13:04 patak-dev

📝 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

vite-ecosystem-ci[bot] avatar Apr 19 '24 13:04 vite-ecosystem-ci[bot]

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.

patak-dev avatar Apr 26 '24 10:04 patak-dev

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) provides environment.mode: "dev" | "build", I use it to split plugins for dev/build as well.
  • since environment already has environment.config, hot, moduleGraph etc..., I didn't need to access specific environment via server.environments.(env) via configureServer(server)

hi-ogawa avatar Apr 30 '24 00:04 hi-ogawa

It feels really nice to use, so only positive feedback here to confirm if I'm using it right:

  • since create(environment) provides environment.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 has environment.config, hot, moduleGraph etc..., I didn't need to access specific environment via server.environments.(env) via configureServer(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?

patak-dev avatar Apr 30 '24 08:04 patak-dev

  • since environment already has environment.config, hot, moduleGraph etc..., I didn't need to access specific environment via server.environments.(env) via configureServer(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?

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( .... ));
      }
      ...
    }
  }
}

hi-ogawa avatar Apr 30 '24 08:04 hi-ogawa

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

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!

patak-dev avatar Apr 30 '24 08:04 patak-dev

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:
    function frameworkPlugin() {
      return [
        sharedPlugin(), // can have enforce: 'pre'
        () => isolatedPlugin() // it will stay separated from the prev plugin
      ]
    }
    
    This wasn't that bad at the end, 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', ... }),
  ]

patak-dev avatar May 01 '24 20:05 patak-dev

/ecosystem-ci run

patak-dev avatar May 02 '24 09:05 patak-dev

📝 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

vite-ecosystem-ci[bot] avatar May 02 '24 09:05 vite-ecosystem-ci[bot]