diff --git a/packages/wxt/e2e/tests/dev.test.ts b/packages/wxt/e2e/tests/dev.test.ts index 7597a6b88..a319dd585 100644 --- a/packages/wxt/e2e/tests/dev.test.ts +++ b/packages/wxt/e2e/tests/dev.test.ts @@ -1,3 +1,4 @@ +import type { InlineConfig } from 'vite'; import { describe, expect, it } from 'vitest'; import { TestProject, occupyPort } from '../utils'; @@ -84,4 +85,56 @@ describe('Dev Mode', () => { await freePort(); } }); + it('should pass watchOptions to the Vite dev server watcher', async () => { + let watcherOptions: NonNullable['watch']; + + const project = new TestProject(); + project.addFile( + 'entrypoints/background.ts', + 'export default defineBackground(() => {})', + ); + + const server = await project.startServer({ + runner: { disabled: true }, + vite: () => ({ + server: { + watch: { + awaitWriteFinish: true, + ignored: ['vite-ignore/**'], + }, + }, + }), + watchOptions: { + usePolling: true, + interval: 1000, + ignored: ['custom-ignore/**'], + }, + hooks: { + 'vite:devServer:extendConfig': (config) => { + watcherOptions = config.server?.watch; + }, + }, + }); + + try { + expect(watcherOptions).toMatchObject({ + awaitWriteFinish: true, + usePolling: true, + interval: 1000, + }); + + expect(watcherOptions).toEqual( + expect.objectContaining({ + ignored: expect.arrayContaining([ + expect.stringContaining('.output'), + expect.stringContaining('.wxt'), + 'vite-ignore/**', + 'custom-ignore/**', + ]), + }), + ); + } finally { + await server.stop(); + } + }); }); diff --git a/packages/wxt/src/core/builders/vite/index.ts b/packages/wxt/src/core/builders/vite/index.ts index 43da7ebec..a08280fb6 100644 --- a/packages/wxt/src/core/builders/vite/index.ts +++ b/packages/wxt/src/core/builders/vite/index.ts @@ -28,6 +28,7 @@ import { VirtualModuleId, } from '../../utils/virtual-modules'; import * as wxtPlugins from './plugins'; +import { resolveWatchOptions } from '../../utils/watch-options'; export async function createViteBuilder( wxtConfig: ResolvedConfig, @@ -66,9 +67,8 @@ export async function createViteBuilder( } config.server ??= {}; - config.server.watch = { - ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`], - }; + + config.server.watch = resolveWatchOptions(wxtConfig, config.server.watch); // TODO: Remove once https://github.com/wxt-dev/wxt/pull/1411 is merged config.legacy ??= {}; diff --git a/packages/wxt/src/core/create-server.ts b/packages/wxt/src/core/create-server.ts index da748345f..95d5e0a2e 100644 --- a/packages/wxt/src/core/create-server.ts +++ b/packages/wxt/src/core/create-server.ts @@ -10,6 +10,7 @@ import { createFileReloader, reloadContentScripts, } from './utils/create-file-reloader'; +import { resolveWatchOptions } from './utils/watch-options'; /** * Creates a dev server and pre-builds all the files that need to exist before @@ -152,7 +153,10 @@ async function createServerInternal(): Promise { logBabelSyntaxError(err); wxt.logger.info('Waiting for syntax error to be fixed...'); await new Promise((resolve) => { - const watcher = chokidar.watch(err.id, { ignoreInitial: true }); + const watcher = chokidar.watch(err.id, { + ...resolveWatchOptions(wxt.config), + ignoreInitial: true, + }); watcher.on('all', () => { watcher.close(); wxt.logger.info('Syntax error resolved, rebuilding...'); diff --git a/packages/wxt/src/core/resolve-config.ts b/packages/wxt/src/core/resolve-config.ts index 9e7903a76..0252825bd 100644 --- a/packages/wxt/src/core/resolve-config.ts +++ b/packages/wxt/src/core/resolve-config.ts @@ -244,6 +244,7 @@ export async function resolveConfig( builtinModules, userModules, plugins: [], + watchOptions: mergedConfig.watchOptions, ...moduleOptions, }; } diff --git a/packages/wxt/src/core/utils/watch-options.ts b/packages/wxt/src/core/utils/watch-options.ts new file mode 100644 index 000000000..311cc6ae6 --- /dev/null +++ b/packages/wxt/src/core/utils/watch-options.ts @@ -0,0 +1,25 @@ +import type { AnymatchPattern } from 'vite'; +import type { ResolvedConfig, WxtWatchOptions } from '../../types'; + +export function resolveWatchOptions( + config: ResolvedConfig, + viteWatchOptions?: WxtWatchOptions | null, +): WxtWatchOptions { + return { + ...viteWatchOptions, + ...config.watchOptions, + ignored: [ + `${config.outBaseDir}/**`, + `${config.wxtDir}/**`, + ...normalizeIgnored(viteWatchOptions?.ignored), + ...normalizeIgnored(config.watchOptions?.ignored), + ], + }; +} + +function normalizeIgnored( + ignored: WxtWatchOptions['ignored'] | undefined, +): AnymatchPattern[] { + if (ignored == null) return []; + return Array.isArray(ignored) ? ignored : [ignored]; +} diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts index 7998ec28a..139edd45a 100644 --- a/packages/wxt/src/types.ts +++ b/packages/wxt/src/types.ts @@ -429,6 +429,12 @@ export interface InlineConfig { * "wxt-module-analytics"). */ modules?: string[]; + + /** + * Configure file watching behavior during development. These options are + * passed to Vite's dev server watcher. + */ + watchOptions?: WxtWatchOptions; } // TODO: Extract to @wxt/vite-builder and use module augmentation to include the vite field @@ -1571,6 +1577,7 @@ export interface ResolvedConfig { hooks: NestedHooks; builtinModules: WxtModule[]; userModules: WxtModuleWithMetadata[]; + watchOptions?: WxtWatchOptions; /** * An array of string to import plugins from. These paths should be resolvable * by vite, and they should `export default defineWxtPlugin(...)`. @@ -1801,3 +1808,5 @@ export interface WxtDirFileEntry { /** Set to `true` to add a reference to this file in `.wxt/wxt.d.ts`. */ tsReference?: boolean; } + +export type WxtWatchOptions = NonNullable;