diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts new file mode 100644 index 00000000000000..16205f78783c5f --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts @@ -0,0 +1,279 @@ +import path from 'node:path' +import { describe, expect, onTestFinished, test } from 'vitest' +import { build } from '../../build' +import type { Plugin } from '../../plugin' +import { resolveConfig } from '../../config' +import { createServer } from '../../server' +import { preview } from '../../preview' +import { promiseWithResolvers } from '../../../shared/utils' + +const resolveConfigWithPlugin = ( + plugin: Plugin, + command: 'serve' | 'build' = 'serve', +) => { + return resolveConfig( + { configFile: false, plugins: [plugin], logLevel: 'error' }, + command, + ) +} + +const createServerWithPlugin = async (plugin: Plugin) => { + const server = await createServer({ + configFile: false, + root: import.meta.dirname, + plugins: [plugin], + logLevel: 'error', + server: { + middlewareMode: true, + }, + }) + onTestFinished(() => server.close()) + return server +} + +const createPreviewServerWithPlugin = async (plugin: Plugin) => { + const server = await preview({ + configFile: false, + root: import.meta.dirname, + plugins: [ + { + name: 'mock-preview', + configurePreviewServer({ httpServer }) { + // NOTE: make httpServer.listen no-op to avoid starting a server + httpServer.listen = (...args: unknown[]) => { + const listener = args.at(-1) as () => void + listener() + return httpServer as any + } + }, + }, + plugin, + ], + logLevel: 'error', + }) + onTestFinished(() => server.close()) + return server +} + +const buildWithPlugin = async (plugin: Plugin) => { + await build({ + root: path.resolve(import.meta.dirname, '../packages/build-project'), + logLevel: 'error', + build: { + write: false, + }, + plugins: [ + { + name: 'resolve-entry.js', + resolveId(id) { + if (id === 'entry.js') { + return '\0' + id + } + }, + load(id) { + if (id === '\0entry.js') { + return 'export default {}' + } + }, + }, + plugin, + ], + }) +} + +describe('supports plugin context', () => { + test('config hook', async () => { + expect.assertions(3) + + await resolveConfigWithPlugin({ + name: 'test', + config() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + // @ts-expect-error watchMode should not exist in types + expect(this.meta.watchMode).toBeUndefined() + }, + }) + }) + + test('configEnvironment hook', async () => { + expect.assertions(3) + + await resolveConfigWithPlugin({ + name: 'test', + configEnvironment(name) { + if (name !== 'client') return + + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + // @ts-expect-error watchMode should not exist in types + expect(this.meta.watchMode).toBeUndefined() + }, + }) + }) + + test('configResolved hook', async () => { + expect.assertions(3) + + await resolveConfigWithPlugin({ + name: 'test', + configResolved() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(true) + }, + }) + }) + + test('configureServer hook', async () => { + expect.assertions(3) + + await createServerWithPlugin({ + name: 'test', + configureServer() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(true) + }, + }) + }) + + test('configurePreviewServer hook', async () => { + expect.assertions(3) + + await createPreviewServerWithPlugin({ + name: 'test', + configurePreviewServer() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(false) + }, + }) + }) + + test('transformIndexHtml hook in dev', async () => { + expect.assertions(3) + + const server = await createServerWithPlugin({ + name: 'test', + transformIndexHtml() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(true) + }, + }) + await server.transformIndexHtml('/index.html', '') + }) + + test('transformIndexHtml hook in build', async () => { + expect.assertions(3) + + await buildWithPlugin({ + name: 'test', + transformIndexHtml() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(false) + }, + }) + }) + + test('handleHotUpdate hook', async () => { + expect.assertions(3) + + const { promise, resolve } = promiseWithResolvers() + const server = await createServerWithPlugin({ + name: 'test', + handleHotUpdate() { + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(true) + resolve() + }, + }) + server.watcher.emit( + 'change', + path.resolve(import.meta.dirname, 'index.html'), + ) + + await promise + }) + + test('hotUpdate hook', async () => { + expect.assertions(3) + + const { promise, resolve } = promiseWithResolvers() + const server = await createServerWithPlugin({ + name: 'test', + hotUpdate() { + if (this.environment.name !== 'client') return + + expect(this).toMatchObject({ + debug: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + error: expect.any(Function), + meta: expect.any(Object), + environment: expect.any(Object), + }) + expect(this.meta.rollupVersion).toBeTypeOf('string') + expect(this.meta.watchMode).toBe(true) + resolve() + }, + }) + server.watcher.emit( + 'change', + path.resolve(import.meta.dirname, 'index.html'), + ) + + await promise + }) +}) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index eaa8d313e8ce97..75862f50d169b8 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -55,6 +55,7 @@ import { mergeWithDefaults, normalizePath, partialEncodeURIPath, + rollupVersion, } from './utils' import { perEnvironmentPlugin, resolveEnvironmentPlugins } from './plugin' import { manifestPlugin } from './plugins/manifest' @@ -77,8 +78,9 @@ import { BaseEnvironment, getDefaultResolvedEnvironmentOptions, } from './baseEnvironment' -import type { Plugin } from './plugin' +import type { MinimalPluginContextWithoutEnvironment, Plugin } from './plugin' import type { RollupPluginHooks } from './typeUtils' +import { BasicMinimalPluginContext } from './server/pluginContainer' export interface BuildEnvironmentOptions { /** @@ -1573,6 +1575,14 @@ export async function createBuilder( environments, config, async buildApp() { + const pluginContext = new BasicMinimalPluginContext( + { + rollupVersion, + watchMode: false, + }, + config.logger, + ) + // order 'pre' and 'normal' hooks are run first, then config.builder.buildApp, then 'post' hooks let configBuilderBuildAppCalled = false for (const p of config.getSortedPlugins('buildApp')) { @@ -1586,7 +1596,7 @@ export async function createBuilder( await configBuilder.buildApp(builder) } const handler = getHookHandler(hook) - await handler(builder) + await handler.call(pluginContext, builder) } if (!configBuilderBuildAppCalled) { await configBuilder.buildApp(builder) @@ -1670,4 +1680,7 @@ export async function createBuilder( return builder } -export type BuildAppHook = (this: void, builder: ViteBuilder) => Promise +export type BuildAppHook = ( + this: MinimalPluginContextWithoutEnvironment, + builder: ViteBuilder, +) => Promise diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index dcd1b4299b1674..5c8e494e700400 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -8,7 +8,7 @@ import { createRequire } from 'node:module' import crypto from 'node:crypto' import colors from 'picocolors' import type { Alias, AliasOptions } from 'dep-types/alias' -import type { RollupOptions } from 'rollup' +import type { PluginContextMeta, RollupOptions } from 'rollup' import picomatch from 'picomatch' import { build } from 'esbuild' import type { AnymatchFn } from '../types/anymatch' @@ -76,6 +76,7 @@ import { nodeLikeBuiltins, normalizeAlias, normalizePath, + rollupVersion, } from './utils' import { createPluginHookUtils, @@ -103,6 +104,7 @@ import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' import { runnerImport } from './ssr/runnerImport' import { getAdditionalAllowedHosts } from './server/middlewares/hostCheck' +import { BasicMinimalPluginContext } from './server/pluginContainer' const debug = createDebugger('vite:config', { depth: 10 }) const promisifiedRealpath = promisify(fs.realpath) @@ -1232,6 +1234,7 @@ export async function resolveConfig( await runConfigEnvironmentHook( config.environments, userPlugins, + logger, configEnv, config.ssr?.target === 'webworker', ) @@ -1373,6 +1376,16 @@ export async function resolveConfig( const BASE_URL = resolvedBase + const resolvedConfigContext = new BasicMinimalPluginContext( + { + rollupVersion, + watchMode: + (command === 'serve' && !isPreview) || + (command === 'build' && !!resolvedBuildOptions.watch), + } satisfies PluginContextMeta, + logger, + ) + let resolved: ResolvedConfig let createUserWorkerPlugins = config.worker?.plugins @@ -1431,7 +1444,7 @@ export async function resolveConfig( await Promise.all( createPluginHookUtils(resolvedWorkerPlugins) .getSortedPluginHooks('configResolved') - .map((hook) => hook(workerResolved)), + .map((hook) => hook.call(resolvedConfigContext, workerResolved)), ) return { @@ -1592,7 +1605,7 @@ export async function resolveConfig( await Promise.all( resolved .getSortedPluginHooks('configResolved') - .map((hook) => hook(resolved)), + .map((hook) => hook.call(resolvedConfigContext, resolved)), ) optimizeDepsDisabledBackwardCompatibility(resolved, resolved.optimizeDeps) @@ -2091,10 +2104,18 @@ async function runConfigHook( ): Promise { let conf = config + const tempLogger = createLogger(config.logLevel, { + allowClearScreen: config.clearScreen, + customLogger: config.customLogger, + }) + const context = new BasicMinimalPluginContext< + Omit + >({ rollupVersion }, tempLogger) + for (const p of getSortedPluginsByHook('config', plugins)) { const hook = p.config const handler = getHookHandler(hook) - const res = await handler(conf, configEnv) + const res = await handler.call(context, conf, configEnv) if (res && res !== conf) { conf = mergeConfig(conf, res) } @@ -2106,15 +2127,20 @@ async function runConfigHook( async function runConfigEnvironmentHook( environments: Record, plugins: Plugin[], + logger: Logger, configEnv: ConfigEnv, isSsrTargetWebworkerSet: boolean, ): Promise { + const context = new BasicMinimalPluginContext< + Omit + >({ rollupVersion }, logger) + const environmentNames = Object.keys(environments) for (const p of getSortedPluginsByHook('configEnvironment', plugins)) { const hook = p.configEnvironment const handler = getHookHandler(hook) for (const name of environmentNames) { - const res = await handler(name, environments[name], { + const res = await handler.call(context, name, environments[name], { ...configEnv, isSsrTargetWebworker: isSsrTargetWebworkerSet && name === 'ssr', }) diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 8c88befd069e81..6875ca4e4dc31b 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -1,8 +1,10 @@ import type { CustomPluginOptions, LoadResult, + MinimalPluginContext, ObjectHook, PluginContext, + PluginContextMeta, ResolveIdResult, Plugin as RollupPlugin, TransformPluginContext, @@ -61,10 +63,14 @@ export interface PluginContextExtension { environment: Environment } -export interface HotUpdatePluginContext { - environment: DevEnvironment +export interface ConfigPluginContext + extends Omit { + meta: Omit } +export interface MinimalPluginContextWithoutEnvironment + extends Omit {} + // Augment Rollup types to have the PluginContextExtension declare module 'rollup' { export interface MinimalPluginContext extends PluginContextExtension {} @@ -98,7 +104,7 @@ export interface Plugin extends RollupPlugin { */ hotUpdate?: ObjectHook< ( - this: HotUpdatePluginContext, + this: MinimalPluginContext & { environment: DevEnvironment }, options: HotUpdateOptions, ) => | Array @@ -208,7 +214,7 @@ export interface Plugin extends RollupPlugin { */ config?: ObjectHook< ( - this: void, + this: ConfigPluginContext, config: UserConfig, env: ConfigEnv, ) => @@ -229,7 +235,7 @@ export interface Plugin extends RollupPlugin { */ configEnvironment?: ObjectHook< ( - this: void, + this: ConfigPluginContext, name: string, config: EnvironmentOptions, env: ConfigEnv & { @@ -249,7 +255,10 @@ export interface Plugin extends RollupPlugin { * Use this hook to read and store the final resolved vite config. */ configResolved?: ObjectHook< - (this: void, config: ResolvedConfig) => void | Promise + ( + this: MinimalPluginContextWithoutEnvironment, + config: ResolvedConfig, + ) => void | Promise > /** * Configure the vite server. The hook receives the {@link ViteDevServer} @@ -315,7 +324,7 @@ export interface Plugin extends RollupPlugin { */ handleHotUpdate?: ObjectHook< ( - this: void, + this: MinimalPluginContextWithoutEnvironment, ctx: HmrContext, ) => Array | void | Promise | void> > diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index d1754d82e2a778..56e328974cd708 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -12,7 +12,7 @@ import colors from 'picocolors' import type { DefaultTreeAdapterMap, ParserError, Token } from 'parse5' import { stripLiteral } from 'strip-literal' import escapeHtml from 'escape-html' -import type { Plugin } from '../plugin' +import type { MinimalPluginContextWithoutEnvironment, Plugin } from '../plugin' import type { ViteDevServer } from '../server' import { encodeURIPath, @@ -403,7 +403,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { } // pre-transform - html = await applyHtmlTransforms(html, preHooks, { + html = await applyHtmlTransforms(html, preHooks, this, { path: publicPath, filename: id, }) @@ -984,6 +984,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { result = await applyHtmlTransforms( result, [...normalHooks, ...postHooks], + this, { path: '/' + relativeUrlPath, filename: normalizedId, @@ -1112,7 +1113,7 @@ export interface IndexHtmlTransformContext { } export type IndexHtmlTransformHook = ( - this: void, + this: MinimalPluginContextWithoutEnvironment, html: string, ctx: IndexHtmlTransformContext, ) => IndexHtmlTransformResult | void | Promise @@ -1358,10 +1359,11 @@ function headTagInsertCheck( export async function applyHtmlTransforms( html: string, hooks: IndexHtmlTransformHook[], + pluginContext: MinimalPluginContextWithoutEnvironment, ctx: IndexHtmlTransformContext, ): Promise { for (const hook of hooks) { - const res = await hook(html, ctx) + const res = await hook.call(pluginContext, html, ctx) if (!res) { continue } diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index d1ae36406499f3..05c32313e36414 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -28,6 +28,7 @@ import { getServerUrlByHost, resolveHostname, resolveServerUrls, + rollupVersion, setupSIGTERMListener, shouldServeFile, teardownSIGTERMListener, @@ -40,6 +41,8 @@ import type { InlineConfig, ResolvedConfig } from './config' import { DEFAULT_PREVIEW_PORT } from './constants' import type { RequiredExceptFor } from './typeUtils' import { hostValidationMiddleware } from './server/middlewares/hostCheck' +import { BasicMinimalPluginContext } from './server/pluginContainer' +import type { MinimalPluginContextWithoutEnvironment } from './plugin' export interface PreviewOptions extends CommonServerOptions {} @@ -104,7 +107,7 @@ export interface PreviewServer { } export type PreviewServerHook = ( - this: void, + this: MinimalPluginContextWithoutEnvironment, server: PreviewServer, ) => (() => void) | void | Promise<(() => void) | void> @@ -191,9 +194,16 @@ export async function preview( setupSIGTERMListener(closeServerAndExit) // apply server hooks from plugins + const configurePreviewServerContext = new BasicMinimalPluginContext( + { + rollupVersion, + watchMode: false, + }, + config.logger, + ) const postHooks: ((() => void) | void)[] = [] for (const hook of config.getSortedPluginHooks('configurePreviewServer')) { - postHooks.push(await hook(server)) + postHooks.push(await hook.call(configurePreviewServerContext, server)) } // cors diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 3ce3311e60a501..c3120cd129d8b1 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -57,7 +57,7 @@ export class DevEnvironment extends BaseEnvironment { */ _remoteRunnerOptions: DevEnvironmentContext['remoteRunner'] - get pluginContainer(): EnvironmentPluginContainer { + get pluginContainer(): EnvironmentPluginContainer { if (!this._pluginContainer) throw new Error( `${this.name} environment.pluginContainer called before initialized`, @@ -67,7 +67,7 @@ export class DevEnvironment extends BaseEnvironment { /** * @internal */ - _pluginContainer: EnvironmentPluginContainer | undefined + _pluginContainer: EnvironmentPluginContainer | undefined /** * @internal diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 3238a0966c6bb9..9403762e5536ad 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -10,7 +10,12 @@ import type { InvokeSendData, } from '../../shared/invokeMethods' import { CLIENT_DIR } from '../constants' -import { createDebugger, isCSSRequest, normalizePath } from '../utils' +import { + createDebugger, + isCSSRequest, + normalizePath, + rollupVersion, +} from '../utils' import type { InferCustomEventPayload, ViteDevServer } from '..' import { getHookHandler } from '../plugins' import { isExplicitImportRequired } from '../plugins/importAnalysis' @@ -26,6 +31,7 @@ import type { EnvironmentModuleNode } from './moduleGraph' import type { ModuleNode } from './mixedModuleGraph' import type { DevEnvironment } from './environment' import { prepareError } from './middlewares/error' +import { BasicMinimalPluginContext } from './pluginContainer' import type { HttpServer } from '.' import { restartServerWithUrls } from '.' @@ -448,9 +454,16 @@ export async function handleHMRUpdate( modules: [...mixedMods], } + const contextForHandleHotUpdate = new BasicMinimalPluginContext( + { + rollupVersion, + watchMode: true, + }, + config.logger, + ) const clientEnvironment = server.environments.client const ssrEnvironment = server.environments.ssr - const clientContext = { environment: clientEnvironment } + const clientContext = clientEnvironment.pluginContainer.minimalContext const clientHotUpdateOptions = hotMap.get(clientEnvironment)!.options const ssrHotUpdateOptions = hotMap.get(ssrEnvironment)?.options try { @@ -494,9 +507,9 @@ export async function handleHMRUpdate( ) // later on, we'll need: if (runtime === 'client') // Backward compatibility with mixed client and ssr moduleGraph - const filteredModules = await getHookHandler(plugin.handleHotUpdate!)( - mixedHmrContext, - ) + const filteredModules = await getHookHandler( + plugin.handleHotUpdate!, + ).call(contextForHandleHotUpdate, mixedHmrContext) if (filteredModules) { mixedHmrContext.modules = filteredModules clientHotUpdateOptions.modules = @@ -541,12 +554,12 @@ export async function handleHMRUpdate( for (const environment of Object.values(server.environments)) { if (environment.name === 'client') continue const hot = hotMap.get(environment)! - const environmentThis = { environment } + const context = environment.pluginContainer.minimalContext try { for (const plugin of getSortedHotUpdatePlugins(environment)) { if (plugin.hotUpdate) { const filteredModules = await getHookHandler(plugin.hotUpdate).call( - environmentThis, + context, hot.options, ) if (filteredModules) { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index c16d7804961813..b9cf8493c0beef 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -35,6 +35,7 @@ import { normalizePath, resolveHostname, resolveServerUrls, + rollupVersion, setupSIGTERMListener, teardownSIGTERMListener, } from '../utils' @@ -62,8 +63,13 @@ import { import { initPublicFiles } from '../publicDir' import { getEnvFilesForMode } from '../env' import type { RequiredExceptFor } from '../typeUtils' +import type { MinimalPluginContextWithoutEnvironment } from '../plugin' import type { PluginContainer } from './pluginContainer' -import { ERR_CLOSED_SERVER, createPluginContainer } from './pluginContainer' +import { + BasicMinimalPluginContext, + ERR_CLOSED_SERVER, + createPluginContainer, +} from './pluginContainer' import type { WebSocketServer } from './ws' import { createWebSocketServer } from './ws' import { baseMiddleware } from './middlewares/base' @@ -242,7 +248,7 @@ export interface FileSystemServeOptions { } export type ServerHook = ( - this: void, + this: MinimalPluginContextWithoutEnvironment, server: ViteDevServer, ) => (() => void) | void | Promise<(() => void) | void> @@ -854,9 +860,16 @@ export async function _createServer( } // apply server configuration hooks from plugins + const configureServerContext = new BasicMinimalPluginContext( + { + rollupVersion, + watchMode: true, + }, + config.logger, + ) const postHooks: ((() => void) | void)[] = [] for (const hook of config.getSortedPluginHooks('configureServer')) { - postHooks.push(await hook(reflexServer)) + postHooks.push(await hook.call(configureServerContext, reflexServer)) } // Internal middlewares ------------------------------------------------------ diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 2181602687d580..87b73c508d0a48 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -38,12 +38,14 @@ import { joinUrlSegments, normalizePath, processSrcSetSync, + rollupVersion, stripBase, } from '../../utils' import { checkPublicFile } from '../../publicDir' import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' import { cleanUrl, unwrapId, wrapId } from '../../../shared/utils' import { getNodeAssetAttributes } from '../../assetSource' +import { BasicMinimalPluginContext } from '../pluginContainer' interface AssetNode { start: number @@ -79,13 +81,17 @@ export function createDevHtmlTransformFn( injectNonceAttributeTagHook(config), postImportMapHook(), ] + const pluginContext = new BasicMinimalPluginContext( + { rollupVersion, watchMode: true }, + config.logger, + ) return ( server: ViteDevServer, url: string, html: string, originalUrl?: string, ): Promise => { - return applyHtmlTransforms(html, transformHooks, { + return applyHtmlTransforms(html, transformHooks, pluginContext, { path: url, filename: getHtmlFilename(url, server), server, diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index ae8305c5a53d5e..59c4cdfb17776f 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -86,6 +86,7 @@ import { import { cleanUrl, unwrapId } from '../../shared/utils' import type { PluginHookUtils } from '../config' import type { Environment } from '../environment' +import type { Logger } from '../logger' import type { DevEnvironment } from './environment' import { buildErrorMessage } from './middlewares/error' import type { @@ -136,12 +137,14 @@ export interface PluginContainerOptions { * instead of using environment.plugins to allow the creation of different * pipelines working with the same environment (used for createIdResolver). */ -export async function createEnvironmentPluginContainer( - environment: Environment, +export async function createEnvironmentPluginContainer< + Env extends Environment = Environment, +>( + environment: Env, plugins: Plugin[], watcher?: FSWatcher, autoStart = true, -): Promise { +): Promise> { const container = new EnvironmentPluginContainer( environment, plugins, @@ -159,7 +162,7 @@ export type SkipInformation = { called?: boolean } -class EnvironmentPluginContainer { +class EnvironmentPluginContainer { private _pluginContextMap = new Map() private _resolvedRollupOptions?: InputOptions private _processesing = new Set>() @@ -176,7 +179,7 @@ class EnvironmentPluginContainer { moduleGraph: EnvironmentModuleGraph | undefined watchFiles = new Set() - minimalContext: MinimalPluginContext + minimalContext: MinimalPluginContext private _started = false private _buildStartPromise: Promise | undefined @@ -186,7 +189,7 @@ class EnvironmentPluginContainer { * @internal use `createEnvironmentPluginContainer` instead */ constructor( - public environment: Environment, + public environment: Env, public plugins: Plugin[], public watcher?: FSWatcher, autoStart = true, @@ -557,10 +560,10 @@ class EnvironmentPluginContainer { } } -class MinimalPluginContext implements RollupMinimalPluginContext { +export class BasicMinimalPluginContext { constructor( - public meta: PluginContextMeta, - public environment: Environment, + public meta: Meta, + private _logger: Logger, ) {} debug(rawLog: string | RollupLog | (() => string | RollupLog)): void { @@ -572,7 +575,7 @@ class MinimalPluginContext implements RollupMinimalPluginContext { info(rawLog: string | RollupLog | (() => string | RollupLog)): void { const log = this._normalizeRawLog(rawLog) const msg = buildErrorMessage(log, [`info: ${log.message}`], false) - this.environment.logger.info(msg, { clear: true, timestamp: true }) + this._logger.info(msg, { clear: true, timestamp: true }) } warn(rawLog: string | RollupLog | (() => string | RollupLog)): void { @@ -582,7 +585,7 @@ class MinimalPluginContext implements RollupMinimalPluginContext { [colors.yellow(`warning: ${log.message}`)], false, ) - this.environment.logger.warn(msg, { clear: true, timestamp: true }) + this._logger.warn(msg, { clear: true, timestamp: true }) } error(e: string | RollupError): never { @@ -598,6 +601,17 @@ class MinimalPluginContext implements RollupMinimalPluginContext { } } +class MinimalPluginContext + extends BasicMinimalPluginContext + implements RollupMinimalPluginContext +{ + public environment: T + constructor(meta: PluginContextMeta, environment: T) { + super(meta, environment.logger) + this.environment = environment + } +} + class PluginContext extends MinimalPluginContext implements Omit