Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const pages = import.meta.glob('./pages/*.tsx')

export default <div />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default <main />
43 changes: 43 additions & 0 deletions packages/vite/src/node/__tests__/scan.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,49 @@ test('scan jsx-runtime', async (ctx) => {
expect(mod1).toBe(mod2)
})

test('scan import.meta.glob respects rolldown transform jsx options', async (ctx) => {
const server = await createServer({
configFile: false,
logLevel: 'error',
root: path.join(import.meta.dirname, 'fixtures', 'scan-jsx-runtime'),
oxc: {
jsx: {
runtime: 'automatic',
importSource: 'react',
},
},
optimizeDeps: {
force: true,
noDiscovery: false,
entries: ['./entry-glob-custom-oxc.tsx'],
rolldownOptions: {
transform: {
jsx: {
runtime: 'automatic',
importSource: 'vue',
},
},
},
},
})
ctx.onTestFinished(() => server.close())

const { cancel, result } = scanImports(
devToScanEnvironment(server.environments.client),
)
ctx.onTestFinished(cancel)

const scanResult = await result

expect(scanResult).toMatchObject({
deps: {
'vue/jsx-dev-runtime': expect.any(String),
},
missing: {},
})
expect(scanResult.deps).not.toHaveProperty('react/jsx-runtime')
})

test('scan import.meta.glob package imports patterns', async (ctx) => {
const server = await createServer({
configFile: false,
Expand Down
19 changes: 16 additions & 3 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fsp from 'node:fs/promises'
import path from 'node:path'
import { performance } from 'node:perf_hooks'
import { scan } from 'rolldown/experimental'
import type { TransformOptions as OxcTransformOptions } from 'rolldown/utils'
import { transformSync } from 'rolldown/utils'
import type { PartialResolvedId, Plugin } from 'rolldown'
import colors from 'picocolors'
Expand Down Expand Up @@ -252,9 +253,6 @@ async function prepareRolldownScanner(
const { plugins: pluginsFromConfig = [], ...rolldownOptions } =
environment.config.optimizeDeps.rolldownOptions ?? {}

const plugins = await asyncFlatten(arraify(pluginsFromConfig))
plugins.push(...rolldownScanPlugin(environment, deps, missing, entries))

const transformOptions = deepClone(rolldownOptions.transform) ?? {}
if (transformOptions.jsx === undefined) {
transformOptions.jsx = {}
Expand All @@ -267,6 +265,19 @@ async function prepareRolldownScanner(
if (typeof transformOptions.jsx === 'object') {
transformOptions.jsx.development ??= !environment.config.isProduction
}
const transformSyncJsxOptions: OxcTransformOptions['jsx'] =
transformOptions.jsx === false ? undefined : transformOptions.jsx

const plugins = await asyncFlatten(arraify(pluginsFromConfig))
plugins.push(
...rolldownScanPlugin(
environment,
deps,
missing,
entries,
transformSyncJsxOptions,
),
)

async function build() {
await scan({
Expand Down Expand Up @@ -343,6 +354,7 @@ function rolldownScanPlugin(
depImports: Record<string, string>,
missing: Record<string, string>,
entries: string[],
jsxOptions: OxcTransformOptions['jsx'],
): Plugin[] {
const seen = new Map<string, string | undefined>()
async function resolveId(
Expand Down Expand Up @@ -397,6 +409,7 @@ function rolldownScanPlugin(
// transpile because `transformGlobImport` only expects js
if (loader !== 'js') {
const result = transformSync(id, contents, {
...(jsxOptions !== undefined ? { jsx: jsxOptions } : {}),
lang: loader,
tsconfig: false,
})
Expand Down
82 changes: 80 additions & 2 deletions packages/vite/src/shared/__tests__/forwardConsole.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { describe, expect, test } from 'vitest'
import { formatConsoleArgs } from '../forwardConsole'
import { setTimeout } from 'node:timers/promises'
import { describe, expect, test, vi } from 'vitest'
import {
formatConsoleArgs,
setupForwardConsoleHandler,
} from '../forwardConsole'
import {
type NormalizedModuleRunnerTransport,
SendBeforeConnectError,
} from '../moduleRunnerTransport'

describe('formatConsoleArgs', () => {
test('formats placeholders', () => {
Expand Down Expand Up @@ -59,3 +67,73 @@ describe('formatConsoleArgs', () => {
)
})
})

describe('setupForwardConsoleHandler', () => {
function createMockConsole() {
return {
error: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
log: vi.fn(),
debug: vi.fn(),
} as unknown as Console
}

function createMockTransport(
send: (...args: any[]) => Promise<void>,
): NormalizedModuleRunnerTransport {
return {
connect: () => Promise.resolve(),
disconnect: () => Promise.resolve(),
send,
invoke: () => Promise.resolve({ result: undefined } as any),
}
}

test('ignore SendBeforeConnectError from transport.send', async () => {
const transport = createMockTransport(() =>
Promise.reject(new SendBeforeConnectError('not connected yet')),
)
const console = createMockConsole()

setupForwardConsoleHandler(
transport,
{
enabled: true,
unhandledErrors: false,
logLevels: ['log'],
},
console,
)

console.log('hi')
await setTimeout(50)

expect(console.error).not.toHaveBeenCalled()
})

test('log errors from transport.send', async () => {
const transport = createMockTransport(() =>
Promise.reject(new Error('other error')),
)
const console = createMockConsole()

setupForwardConsoleHandler(
transport,
{
enabled: true,
unhandledErrors: false,
logLevels: ['log'],
},
console,
)

console.log('hi')
await setTimeout(50)

expect(console.error).toHaveBeenCalledWith(
'Failed to send error to Vite server:',
new Error('other error'),
)
})
})
64 changes: 46 additions & 18 deletions packages/vite/src/shared/forwardConsole.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { ForwardConsolePayload } from '#types/customEvent'
import type { NormalizedModuleRunnerTransport } from './moduleRunnerTransport'
import {
type NormalizedModuleRunnerTransport,
SendBeforeConnectError,
} from './moduleRunnerTransport'

export type ForwardConsoleLogLevel =
| 'error'
Expand All @@ -23,13 +26,14 @@ export interface ResolvedForwardConsoleOptions {
export function setupForwardConsoleHandler(
transport: NormalizedModuleRunnerTransport,
options: ResolvedForwardConsoleOptions,
console: Console = globalThis.console,
): void {
if (!options.enabled) {
return
}

function sendError(type: 'error' | 'unhandled-rejection', error: any) {
transport.send({
async function sendError(type: 'error' | 'unhandled-rejection', error: any) {
await transport.send({
type: 'custom',
event: 'vite:forward-console',
data: {
Expand All @@ -43,20 +47,32 @@ export function setupForwardConsoleHandler(
})
}

function sendLog(level: ForwardConsoleLogLevel, args: unknown[]) {
transport.send({
type: 'custom',
event: 'vite:forward-console',
data: {
type: 'log',
async function sendLog(level: ForwardConsoleLogLevel, args: unknown[]) {
try {
await transport.send({
type: 'custom',
event: 'vite:forward-console',
data: {
level,
message: formatConsoleArgs(args),
},
} satisfies ForwardConsolePayload,
})
type: 'log',
data: {
level,
message: formatConsoleArgs(args),
},
} satisfies ForwardConsolePayload,
})
} catch (err) {
try {
await sendError('unhandled-rejection', err)
} catch (err) {
if (!(err instanceof SendBeforeConnectError)) {
originalConsoleError('Failed to send error to Vite server:', err)
}
}
}
}

const originalConsoleError = console.error

for (const level of options.logLevels) {
const original = (console as any)[level]
if (typeof original !== 'function') {
Expand All @@ -69,18 +85,30 @@ export function setupForwardConsoleHandler(
}

if (options.unhandledErrors && typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
window.addEventListener('error', async (event) => {
// `ErrorEvent` doesn't necessarily have `ErrorEvent.error`.
// Use `ErrorEvent.message` as fallback e.g. for ResizeObserver error.
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent/error
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors
const error =
event.error ?? (event.message ? new Error(event.message) : event)
sendError('error', error)
try {
await sendError('error', error)
} catch (err) {
if (!(err instanceof SendBeforeConnectError)) {
originalConsoleError('Failed to send error to Vite server:', err)
}
}
})

window.addEventListener('unhandledrejection', (event) => {
sendError('unhandled-rejection', event.reason)
window.addEventListener('unhandledrejection', async (event) => {
try {
await sendError('unhandled-rejection', event.reason)
} catch (err) {
if (!(err instanceof SendBeforeConnectError)) {
originalConsoleError('Failed to send error to Vite server:', err)
}
}
})
}
}
Expand Down
11 changes: 9 additions & 2 deletions packages/vite/src/shared/moduleRunnerTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export const normalizeModuleRunnerTransport = (
if (connectingPromise) {
await connectingPromise
} else {
throw new Error('send was called before connect')
throw new SendBeforeConnectError('send was called before connect')
}
}
await invokeableTransport.send(data)
Expand All @@ -247,14 +247,21 @@ export const normalizeModuleRunnerTransport = (
if (connectingPromise) {
await connectingPromise
} else {
throw new Error('invoke was called before connect')
throw new SendBeforeConnectError('invoke was called before connect')
}
}
return invokeableTransport.invoke(name, data)
},
}
}

export class SendBeforeConnectError extends Error {
constructor(message: string) {
super(message)
this.name = 'SendBeforeConnectError'
}
}

export const createWebSocketModuleRunnerTransport = (options: {
// eslint-disable-next-line n/no-unsupported-features/node-builtins
createConnection: () => WebSocket
Expand Down
Loading