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: support log related functions in dev #18922

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/vite/rollup.dts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const identifierReplacements: Record<string, Record<string, string>> = {
rollup: {
Plugin$1: 'rollup.Plugin',
PluginContext$1: 'rollup.PluginContext',
MinimalPluginContext$1: 'rollup.MinimalPluginContext',
TransformPluginContext$1: 'rollup.TransformPluginContext',
TransformResult$2: 'rollup.TransformResult',
},
Expand Down
112 changes: 111 additions & 1 deletion packages/vite/src/node/server/__tests__/pluginContainer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { describe, expect, it } from 'vitest'
import { stripVTControlCharacters } from 'node:util'
import { describe, expect, it, vi } from 'vitest'
import type { UserConfig } from '../../config'
import { resolveConfig } from '../../config'
import type { Plugin } from '../../plugin'
import { DevEnvironment } from '../environment'
import { createLogger } from '../../logger'

describe('plugin container', () => {
describe('getModuleInfo', () => {
Expand Down Expand Up @@ -142,6 +144,55 @@ describe('plugin container', () => {
})
})

describe('options', () => {
it('should not throw errors when this.debug is called', async () => {
const plugin: Plugin = {
name: 'p1',
options() {
this.debug('test')
},
}
await getDevEnvironment({
plugins: [plugin],
})
})

const logFunctions = ['info', 'warn'] as const
for (const logFunction of logFunctions) {
it(`should support this.${logFunction}`, async () => {
const logger = createLogger()
const mockedFn = vi
.spyOn(logger, logFunction)
.mockImplementation(() => {})
const plugin: Plugin = {
name: 'p1',
options() {
this[logFunction]('test')
},
}
await getDevEnvironment({
plugins: [plugin],
customLogger: logger,
})
expect(mockedFn).toHaveBeenCalledOnce()
})
}

it('should support this.error', async () => {
const plugin: Plugin = {
name: 'p1',
options() {
this.error('test')
},
}
await expect(() =>
getDevEnvironment({
plugins: [plugin],
}),
).rejects.toThrowError('test')
})
})

describe('load', () => {
it('can resolve a secondary module', async () => {
const entryUrl = '/x.js'
Expand Down Expand Up @@ -212,6 +263,65 @@ describe('plugin container', () => {
)
expect(result.code).equals('3')
})

it('should not throw errors when this.debug is called', async () => {
const plugin: Plugin = {
name: 'p1',
load() {
this.debug({ message: 'test', pos: 12 })
},
}
const environment = await getDevEnvironment({
plugins: [plugin],
})
await environment.pluginContainer.load('foo')
})

const logFunctions = ['info', 'warn'] as const
for (const logFunction of logFunctions) {
it(`should support this.${logFunction}`, async () => {
const logger = createLogger()
const mockedFn = vi
.spyOn(logger, logFunction)
.mockImplementation(() => {})
const plugin: Plugin = {
name: 'p1',
load() {
this[logFunction]({ message: 'test', pos: 12 })
},
}
const environment = await getDevEnvironment({
plugins: [plugin],
customLogger: logger,
})
await environment.pluginContainer.load('foo')
expect(mockedFn).toHaveBeenCalledOnce()
expect(stripVTControlCharacters(mockedFn.mock.calls[0][0])).toBe(
`${logFunction === 'warn' ? 'warning' : logFunction}: test\n` +
' Plugin: p1',
)
})
}

it('should support this.error', async () => {
const plugin: Plugin = {
name: 'p1',
load() {
this.error({ message: 'test', pos: 12 })
},
}
const environment = await getDevEnvironment({
plugins: [plugin],
})
await expect(() => environment.pluginContainer.load('foo')).rejects
.toThrowErrorMatchingInlineSnapshot(`
{
"message": "test",
"plugin": "p1",
"pos": 12,
}
`)
})
})

describe('resolveId', () => {
Expand Down
117 changes: 77 additions & 40 deletions packages/vite/src/node/server/pluginContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,18 @@ import type {
FunctionPluginHooks,
InputOptions,
LoadResult,
MinimalPluginContext,
ModuleInfo,
ModuleOptions,
NormalizedInputOptions,
OutputOptions,
ParallelPluginHooks,
PartialNull,
PartialResolvedId,
PluginContextMeta,
ResolvedId,
RollupError,
RollupLog,
MinimalPluginContext as RollupMinimalPluginContext,
PluginContext as RollupPluginContext,
TransformPluginContext as RollupTransformPluginContext,
SourceDescription,
Expand Down Expand Up @@ -88,8 +89,6 @@ import type {
EnvironmentModuleNode,
} from './moduleGraph'

const noop = () => {}

// same default value of "moduleInfo.meta" as in Rollup
const EMPTY_OBJECT = Object.freeze({})

Expand All @@ -105,6 +104,9 @@ const debugPluginResolve = createDebugger('vite:plugin-resolve', {
const debugPluginTransform = createDebugger('vite:plugin-transform', {
onlyWhenFocused: 'vite:plugin',
})
const debugPluginContainerContext = createDebugger(
'vite:plugin-container-context',
)

export const ERR_CLOSED_SERVER = 'ERR_CLOSED_SERVER'

Expand Down Expand Up @@ -181,18 +183,10 @@ class EnvironmentPluginContainer {
public plugins: Plugin[],
public watcher?: FSWatcher,
) {
this.minimalContext = {
meta: {
rollupVersion,
watchMode: true,
},
debug: noop,
info: noop,
warn: noop,
// @ts-expect-error noop
error: noop,
this.minimalContext = new MinimalPluginContext(
{ rollupVersion, watchMode: true },
environment,
}
)
const utils = createPluginHookUtils(plugins)
this.getSortedPlugins = utils.getSortedPlugins
this.getSortedPluginHooks = utils.getSortedPluginHooks
Expand Down Expand Up @@ -544,22 +538,63 @@ class EnvironmentPluginContainer {
}
}

class PluginContext implements Omit<RollupPluginContext, 'cache'> {
class MinimalPluginContext implements RollupMinimalPluginContext {
constructor(
public meta: PluginContextMeta,
public environment: Environment,
) {}

debug(rawLog: string | RollupLog | (() => string | RollupLog)): void {
const log = this._normalizeRawLog(rawLog)
const msg = buildErrorMessage(log, [`debug: ${log.message}`], false)
debugPluginContainerContext?.(msg)
}

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 })
}

warn(rawLog: string | RollupLog | (() => string | RollupLog)): void {
const log = this._normalizeRawLog(rawLog)
const msg = buildErrorMessage(
log,
[colors.yellow(`warning: ${log.message}`)],
false,
)
this.environment.logger.warn(msg, { clear: true, timestamp: true })
}

error(e: string | RollupError): never {
const err = (typeof e === 'string' ? new Error(e) : e) as RollupError
throw err
}

private _normalizeRawLog(
rawLog: string | RollupLog | (() => string | RollupLog),
): RollupLog {
const logValue = typeof rawLog === 'function' ? rawLog() : rawLog
return typeof logValue === 'string' ? new Error(logValue) : logValue
}
}

class PluginContext
extends MinimalPluginContext
implements Omit<RollupPluginContext, 'cache'>
{
ssr = false
_scan = false
_activeId: string | null = null
_activeCode: string | null = null
_resolveSkips?: Set<Plugin>
_resolveSkipCalls?: readonly SkipInformation[]
meta: RollupPluginContext['meta']
environment: Environment

constructor(
public _plugin: Plugin,
public _container: EnvironmentPluginContainer,
) {
this.environment = this._container.environment
this.meta = this._container.minimalContext.meta
super(_container.minimalContext.meta, _container.environment)
}

parse(code: string, opts: any) {
Expand Down Expand Up @@ -668,39 +703,41 @@ class PluginContext implements Omit<RollupPluginContext, 'cache'> {
return ''
}

warn(
e: string | RollupLog | (() => string | RollupLog),
override debug(log: string | RollupLog | (() => string | RollupLog)): void {
const err = this._formatLog(typeof log === 'function' ? log() : log)
super.debug(err)
}

override info(log: string | RollupLog | (() => string | RollupLog)): void {
const err = this._formatLog(typeof log === 'function' ? log() : log)
super.info(err)
}

override warn(
log: string | RollupLog | (() => string | RollupLog),
position?: number | { column: number; line: number },
): void {
const err = this._formatError(typeof e === 'function' ? e() : e, position)
const msg = buildErrorMessage(
err,
[colors.yellow(`warning: ${err.message}`)],
false,
const err = this._formatLog(
typeof log === 'function' ? log() : log,
position,
)
this.environment.logger.warn(msg, {
clear: true,
timestamp: true,
})
super.warn(err)
}

error(
override error(
e: string | RollupError,
position?: number | { column: number; line: number },
): never {
// error thrown here is caught by the transform middleware and passed on
// the the error middleware.
throw this._formatError(e, position)
throw this._formatLog(e, position)
}

debug = noop
info = noop

private _formatError(
e: string | RollupError,
position: number | { column: number; line: number } | undefined,
): RollupError {
const err = (typeof e === 'string' ? new Error(e) : e) as RollupError
private _formatLog<E extends RollupLog>(
e: string | E,
position?: number | { column: number; line: number } | undefined,
): E {
const err = (typeof e === 'string' ? new Error(e) : e) as E
if (err.pluginCode) {
return err // The plugin likely called `this.error`
}
Expand Down
Loading