diff --git a/docs/guide/api-environment-plugins.md b/docs/guide/api-environment-plugins.md index 7a888cc213a03b..8ebe062667b072 100644 --- a/docs/guide/api-environment-plugins.md +++ b/docs/guide/api-environment-plugins.md @@ -146,7 +146,7 @@ The hook can choose to: ## Per-environment State in Plugins -Given that the same plugin instance is used for different environments, the plugin state needs to be keyed with `this.environment`. This is the same pattern the ecosystem has already been using to keep state about modules using the `ssr` boolean as key to avoid mixing client and ssr modules state. A `Map` can be used to keep the state for each environment separately. Note that for backward compatibility, `buildStart` and `buildEnd` are only called for the client environment without the `perEnvironmentStartEndDuringDev: true` flag. +Given that the same plugin instance is used for different environments, the plugin state needs to be keyed with `this.environment`. This is the same pattern the ecosystem has already been using to keep state about modules using the `ssr` boolean as key to avoid mixing client and ssr modules state. A `Map` can be used to keep the state for each environment separately. Note that for backward compatibility, `buildStart` and `buildEnd` are only called for the client environment without the `perEnvironmentStartEndDuringDev: true` flag. Same for `watchChange` and the `perEnvironmentWatchChangeDuringDev: true` flag. ```js function PerEnvironmentCountTransformedModulesPlugin() { diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 08940187b54456..3ba44e3968da2b 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -176,6 +176,14 @@ export interface Plugin extends RollupPlugin { * @experimental */ perEnvironmentStartEndDuringDev?: boolean + /** + * Opt-in this plugin into per-environment watchChange during dev. + * For backward-compatibility, the watchChange hook is called only once during + * dev, for the client environment. Plugins can opt-in to be called + * per-environment, aligning with the watchChange hook behavior. + * @experimental + */ + perEnvironmentWatchChangeDuringDev?: boolean /** * Enforce plugin invocation tier similar to webpack loaders. Hooks ordering * is still subject to the `order` property in the hook object. diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index d0c2d0b29aaa67..27d694dea04541 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -174,12 +174,19 @@ export interface ServerOptions extends CommonServerOptions { | false | ((sourcePath: string, sourcemapPath: string) => boolean) /** - * Backward compatibility. The buildStart and buildEnd hooks were called only once for all - * environments. This option enables per-environment buildStart and buildEnd hooks. + * Backward compatibility. The buildStart and buildEnd hooks were called only once for + * the client environment. This option enables per-environment buildStart and buildEnd hooks. * @default false * @experimental */ perEnvironmentStartEndDuringDev?: boolean + /** + * Backward compatibility. The watchChange hook was called only once for the client environment. + * This option enables per-environment watchChange hooks. + * @default false + * @experimental + */ + perEnvironmentWatchChangeDuringDev?: boolean /** * Run HMR tasks, by default the HMR propagation is done in parallel for all environments * @experimental @@ -802,9 +809,13 @@ export async function _createServer( file = normalizePath(file) reloadOnTsconfigChange(server, file) - await pluginContainer.watchChange(file, { - event: isUnlink ? 'delete' : 'create', - }) + await Promise.all( + Object.values(server.environments).map((environment) => + environment.pluginContainer.watchChange(file, { + event: isUnlink ? 'delete' : 'create', + }), + ), + ) if (publicDir && publicFiles) { if (file.startsWith(publicDir)) { @@ -836,7 +847,11 @@ export async function _createServer( file = normalizePath(file) reloadOnTsconfigChange(server, file) - await pluginContainer.watchChange(file, { event: 'update' }) + await Promise.all( + Object.values(server.environments).map((environment) => + environment.pluginContainer.watchChange(file, { event: 'update' }), + ), + ) // invalidate module graph cache on file change for (const environment of Object.values(server.environments)) { environment.moduleGraph.onFileChange(file) @@ -1104,6 +1119,7 @@ const _serverConfigDefaults = Object.freeze({ preTransformRequests: true, // sourcemapIgnoreList perEnvironmentStartEndDuringDev: false, + perEnvironmentWatchChangeDuringDev: false, // hotUpdateEnvironments } satisfies ServerOptions) export const serverConfigDefaults: Readonly> = diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index a2d96cfdf9d6c8..05f24cc4c71bc5 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -607,10 +607,15 @@ class EnvironmentPluginContainer { id: string, change: { event: 'create' | 'update' | 'delete' }, ): Promise { + const config = this.environment.getTopLevelConfig() await this.hookParallel( 'watchChange', (plugin) => this._getPluginContext(plugin), () => [id, change], + (plugin) => + this.environment.name === 'client' || + config.server.perEnvironmentWatchChangeDuringDev || + plugin.perEnvironmentWatchChangeDuringDev, ) } @@ -1211,7 +1216,8 @@ class PluginContainer { } // For backward compatibility, buildStart and watchChange are called only for the client environment - // buildStart is called per environment for a plugin with the perEnvironmentStartEndDuring dev flag + // buildStart is called per environment for a plugin with the perEnvironmentStartEndDuringDev flag + // watchChange is called per environment for a plugin with the perEnvironmentWatchChangeDuringDev flag async buildStart(_options?: InputOptions): Promise { return (