import type { Plugin, ViteDevServer } from 'vite' import type { AutomockPluginOptions } from './automockPlugin' import type { HoistMocksPluginOptions } from './hoistMocksPlugin' import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { dirname, resolve } from 'pathe' import { automockPlugin } from './automockPlugin' import { dynamicImportPlugin } from './dynamicImportPlugin' import { hoistMocksPlugin } from './hoistMocksPlugin' import { interceptorPlugin } from './interceptorPlugin' import { ServerMockResolver } from './resolver' interface MockerPluginOptions extends AutomockPluginOptions { hoistMocks?: HoistMocksPluginOptions } // this is an implementation for public usage // vitest doesn't use this plugin directly export function mockerPlugin(options: MockerPluginOptions = {}): Plugin[] { let server: ViteDevServer const registerPath = resolve(fileURLToPath(new URL('./register.js', import.meta.url))) const distDir = `${dirname(registerPath)}/` return [ { name: 'vitest:mocker:msw', enforce: 'pre', async resolveId(id, importer, options) { if (id !== 'msw/http' || !importer?.startsWith(distDir)) { return } try { return await this.resolve(id, importer, { ...options, skipSelf: true }) } catch { // msw v2 exports `http` from `msw/core/http` return this.resolve('msw/core/http', importer, { ...options, skipSelf: true }) } }, }, { name: 'vitest:mocker:ws-rpc', config(_, { command }) { if (command !== 'serve') { return } return { server: { // don't pre-transform request because they might be mocked at runtime preTransformRequests: false, }, optimizeDeps: { exclude: ['@vitest/mocker/register', '@vitest/mocker/browser'], }, } }, configureServer(server_) { server = server_ const mockResolver = new ServerMockResolver(server) server.ws.on( 'vitest:mocks:resolveId', async ({ id, importer }: { id: string; importer: string }) => { const resolved = await mockResolver.resolveId(id, importer) server.ws.send('vitest:mocks:resolvedId:result', resolved) }, ) server.ws.on( 'vitest:mocks:resolveMock', async ({ id, importer, options }: { id: string; importer: string; options: any }) => { const resolved = await mockResolver.resolveMock(id, importer, options) server.ws.send('vitest:mocks:resolveMock:result', resolved) }, ) server.ws.on('vitest:mocks:invalidate', async ({ ids }: { ids: string[] }) => { mockResolver.invalidate(ids) server.ws.send('vitest:mocks:invalidate:result') }) }, async load(id) { if (id !== registerPath) { return } if (!server) { // mocker doesn't work during build return 'export {}' } const content = await readFile(registerPath, 'utf-8') const result = content .replace( /__VITEST_GLOBAL_THIS_ACCESSOR__/g, options.globalThisAccessor ?? '"__vitest_mocker__"', ) .replace('__VITEST_MOCKER_ROOT__', JSON.stringify(server.config.root)) return result }, }, hoistMocksPlugin(options.hoistMocks), interceptorPlugin(options), automockPlugin(options), dynamicImportPlugin(options), ] }