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: extended applyToEnvironment and perEnvironmentPlugin #18544

Merged
merged 12 commits into from
Nov 13, 2024
4 changes: 2 additions & 2 deletions docs/changes/shared-plugins-during-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ function PerEnvironmentCountTransformedModulesPlugin() {
}
```

To simplify this pattern, internally in Vite, we use a `usePerEnvironmentState` helper:
To simplify this pattern, internally in Vite, we use a `perEnvironmentState` helper:
patak-dev marked this conversation as resolved.
Show resolved Hide resolved

```js
function PerEnvironmentCountTransformedModulesPlugin() {
const state = usePerEnvironmentState<{ count: number }>(() => ({ count: 0 }))
const state = perEnvironmentState<{ count: number }>(() => ({ count: 0 }))
return {
name: 'count-transformed-modules',
perEnvironmentStartEndDuringDev: true,
Expand Down
32 changes: 31 additions & 1 deletion docs/guide/api-environment-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ const UnoCssPlugin = () => {
// use global hooks normally
},
applyToEnvironment(environment) {
// return true if this plugin should be active in this environment
// return true if this plugin should be active in this environment, or return
// a new plugin to replace it.
// if the function isn't provided, the plugin is active in all environments
},
resolveId(id, importer) {
Expand All @@ -151,6 +152,35 @@ const UnoCssPlugin = () => {
}
```

If a plugin isn't environment aware and has state that isn't keyed on the current environment (as it is common in Rollup build plugins like `rollup-plugin-visualizer`), the `applyToEnvironment` hook allows to easily make it per-environment.
bluwy marked this conversation as resolved.
Show resolved Hide resolved

```js
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
plugins: [
{
name: 'per-environment-visualizer',
applyToEnvironment() {
return visualizer()
},
},
],
})
```

Vite exports a `perEnvironmentPlugin` helper to simplify these cases where no other hooks are required:

```js
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
plugins: [
perEnvironmentPlugin('per-environment-visualizer', () => visualizer()),
],
})
```

## Environment in build hooks

In the same way as during dev, plugin hooks also receive the environment instance during build, replacing the `ssr` boolean.
Expand Down
45 changes: 18 additions & 27 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
normalizePath,
partialEncodeURIPath,
} from './utils'
import { resolveEnvironmentPlugins } from './plugin'
import { perEnvironmentPlugin, resolveEnvironmentPlugins } from './plugin'
import { manifestPlugin } from './plugins/manifest'
import type { Logger } from './logger'
import { dataURIPlugin } from './plugins/dataUri'
Expand Down Expand Up @@ -466,34 +466,26 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
pre: Plugin[]
post: Plugin[]
}> {
const { commonjsOptions } = config.build
const usePluginCommonjs =
!Array.isArray(commonjsOptions.include) ||
commonjsOptions.include.length !== 0
return {
pre: [
completeSystemWrapPlugin(),
/**
* environment.config.build.commonjsOptions isn't currently supported
* when builder.sharedConfigBuild or builder.sharedPlugins enabled.
* To do it, we could inject one commonjs plugin per environment with
* an applyToEnvironment hook.
*/
...(usePluginCommonjs ? [commonjsPlugin(commonjsOptions)] : []),
perEnvironmentPlugin('commonjs', (environment) => {
const { commonjsOptions } = environment.config.build
const usePluginCommonjs =
!Array.isArray(commonjsOptions.include) ||
commonjsOptions.include.length !== 0
return usePluginCommonjs ? commonjsPlugin(commonjsOptions) : false
}),
dataURIPlugin(),
/**
* environment.config.build.rollupOptions.plugins isn't supported
* when builder.sharedConfigBuild or builder.sharedPlugins is enabled.
* To do it, we should add all these plugins to the global pipeline, each with
* an applyToEnvironment hook. It is similar to letting the user add per
* environment plugins giving them a environment.config.plugins option that
* we decided against.
* For backward compatibility, we are still injecting the rollup plugins
* defined in the default root build options.
*/
...((
await asyncFlatten(arraify(config.build.rollupOptions.plugins))
).filter(Boolean) as Plugin[]),
perEnvironmentPlugin(
'vite:rollup-options-plugins',
bluwy marked this conversation as resolved.
Show resolved Hide resolved
async (environment) =>
(
await asyncFlatten(
arraify(environment.config.build.rollupOptions.plugins),
)
).filter(Boolean) as Plugin[],
),
...(config.isWorker ? [webWorkerPostPlugin()] : []),
],
post: [
Expand Down Expand Up @@ -1457,13 +1449,12 @@ export class BuildEnvironment extends BaseEnvironment {
super(name, config, options)
}

// TODO: This could be sync, discuss if applyToEnvironment should support async
async init(): Promise<void> {
if (this._initiated) {
return
}
this._initiated = true
this._plugins = resolveEnvironmentPlugins(this)
this._plugins = await resolveEnvironmentPlugins(this)
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/vite/src/node/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ export type Environment =
* Creates a function that hides the complexities of a WeakMap with an initial value
* to implement object metadata. Used by plugins to implement cross hooks per
* environment metadata
*
* @experimental
*/
export function usePerEnvironmentState<State>(
export function perEnvironmentState<State>(
initial: (environment: Environment) => State,
): (context: PluginContext) => State {
const stateMap = new WeakMap<Environment, State>()
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export {
resolveConfig,
sortUserPlugins,
} from './config'
export { perEnvironmentPlugin } from './plugin'
export { perEnvironmentState } from './environment'
export { createServer } from './server'
export { preview } from './preview'
export { build, createBuilder } from './build'
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ScanEnvironment extends BaseEnvironment {
return
}
this._initiated = true
this._plugins = resolveEnvironmentPlugins(this)
this._plugins = await resolveEnvironmentPlugins(this)
this._pluginContainer = await createEnvironmentPluginContainer(
this,
this.plugins,
Expand Down
49 changes: 41 additions & 8 deletions packages/vite/src/node/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { HmrContext, HotUpdateOptions } from './server/hmr'
import type { DevEnvironment } from './server/environment'
import type { Environment } from './environment'
import type { PreviewServerHook } from './preview'
import { arraify, asyncFlatten } from './utils'

/**
* Vite plugins extends the Rollup plugin interface with a few extra
Expand Down Expand Up @@ -204,7 +205,9 @@ export interface Plugin<A = any> extends RollupPlugin<A> {
* Define environments where this plugin should be active
* By default, the plugin is active in all environments
*/
applyToEnvironment?: (environment: Environment) => boolean
applyToEnvironment?: (
environment: Environment,
) => boolean | Promise<boolean> | PluginOption
/**
* Modify vite config before it's resolved. The hook can either mutate the
* passed-in config directly, or return a partial config object that will be
Expand Down Expand Up @@ -324,11 +327,41 @@ type FalsyPlugin = false | null | undefined

export type PluginOption = Thenable<Plugin | FalsyPlugin | PluginOption[]>

export function resolveEnvironmentPlugins(environment: Environment): Plugin[] {
return environment
.getTopLevelConfig()
.plugins.filter(
(plugin) =>
!plugin.applyToEnvironment || plugin.applyToEnvironment(environment),
)
export async function resolveEnvironmentPlugins(
environment: Environment,
): Promise<Plugin[]> {
const environmentPlugins: Plugin[] = []
for (const plugin of environment.getTopLevelConfig().plugins) {
if (plugin.applyToEnvironment) {
const applied = await plugin.applyToEnvironment(environment)
if (!applied) {
continue
}
if (applied !== true) {
environmentPlugins.push(
...((await asyncFlatten(arraify(applied))).filter(
Boolean,
) as Plugin[]),
)
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
continue
}
}
environmentPlugins.push(plugin)
}
return environmentPlugins
}

/**
* @experimental
*/
export function perEnvironmentPlugin(
name: string,
applyToEnvironment: (
environment: Environment,
) => boolean | Promise<boolean> | PluginOption,
): Plugin {
return {
name,
applyToEnvironment,
}
}
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/clientInjections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { CLIENT_ENTRY, ENV_ENTRY } from '../constants'
import { isObject, normalizePath, resolveHostname } from '../utils'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'
import { replaceDefine, serializeDefine } from './define'

// ids in transform are normalized to unix style
Expand All @@ -17,7 +17,7 @@ const normalizedEnvEntry = normalizePath(ENV_ENTRY)
export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
let injectConfigValues: (code: string) => string

const getDefineReplacer = usePerEnvironmentState((environment) => {
const getDefineReplacer = perEnvironmentState((environment) => {
const userDefine: Record<string, any> = {}
for (const key in environment.config.define) {
// import.meta.env.* is handled in `importAnalysis` plugin
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/dynamicImportVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
urlRE,
} from '../utils'
import type { Environment } from '../environment'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'
import { hasViteIgnoreRE } from './importAnalysis'
import { workerOrSharedWorkerRE } from './worker'

Expand Down Expand Up @@ -171,7 +171,7 @@ export function dynamicImportVarsPlugin(config: ResolvedConfig): Plugin {
extensions: [],
})

const getFilter = usePerEnvironmentState((environment: Environment) => {
const getFilter = perEnvironmentState((environment: Environment) => {
const { include, exclude } =
environment.config.build.dynamicImportVarsOptions
return createFilter(include, exclude)
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { toOutputFilePathInHtml } from '../build'
import { resolveEnvPrefix } from '../env'
import type { Logger } from '../logger'
import { cleanUrl } from '../../shared/utils'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'
import { getNodeAssetAttributes } from '../assetSource'
import {
assetUrlRE,
Expand Down Expand Up @@ -330,7 +330,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
preHooks.push(htmlEnvHook(config))
postHooks.push(injectNonceAttributeTagHook(config))
postHooks.push(postImportMapHook())
const processedHtml = usePerEnvironmentState(() => new Map<string, string>())
const processedHtml = perEnvironmentState(() => new Map<string, string>())

const isExcludedUrl = (url: string) =>
url[0] === '#' || isExternalUrl(url) || isDataUrl(url)
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
} from 'rollup'
import type { Plugin } from '../plugin'
import { normalizePath, sortObjectKeys } from '../utils'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'
import { cssEntriesMap } from './asset'

const endsWithJSRE = /\.[cm]?js$/
Expand All @@ -27,7 +27,7 @@ export interface ManifestChunk {
}

export function manifestPlugin(): Plugin {
const getState = usePerEnvironmentState(() => {
const getState = perEnvironmentState(() => {
return {
manifest: {} as Manifest,
outputCount: 0,
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/plugins/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { OutputBundle } from 'rollup'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import type { Environment } from '../environment'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'
import { isDefined, isInNodeModules, normalizePath } from '../utils'
import { LogLevels } from '../logger'
import { withTrailingSlash } from '../../shared/utils'
Expand Down Expand Up @@ -40,7 +40,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
const tty = process.stdout.isTTY && !process.env.CI
const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info

const modulesReporter = usePerEnvironmentState((environment: Environment) => {
const modulesReporter = perEnvironmentState((environment: Environment) => {
let hasTransformed = false
let transformedCount = 0

Expand Down Expand Up @@ -83,7 +83,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
}
})

const chunksReporter = usePerEnvironmentState((environment: Environment) => {
const chunksReporter = perEnvironmentState((environment: Environment) => {
let hasRenderedChunk = false
let hasCompressChunk = false
let chunkCount = 0
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class DevEnvironment extends BaseEnvironment {
return
}
this._initiated = true
this._plugins = resolveEnvironmentPlugins(this)
this._plugins = await resolveEnvironmentPlugins(this)
this._pluginContainer = await createEnvironmentPluginContainer(
this,
this._plugins,
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/ssr/ssrManifestPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import {
numberToPos,
sortObjectKeys,
} from '../utils'
import { usePerEnvironmentState } from '../environment'
import { perEnvironmentState } from '../environment'

export function ssrManifestPlugin(): Plugin {
// module id => preload assets mapping
const getSsrManifest = usePerEnvironmentState(() => {
const getSsrManifest = perEnvironmentState(() => {
return {} as Record<string, string[]>
})

Expand Down
Loading