diff --git a/docs/guide/essentials/config/auto-imports.md b/docs/guide/essentials/config/auto-imports.md index 9c2a0fdeb..a8cd47458 100644 --- a/docs/guide/essentials/config/auto-imports.md +++ b/docs/guide/essentials/config/auto-imports.md @@ -22,6 +22,41 @@ All named and default exports from files in these directories are available ever To see the complete list of auto-imported APIs, run [`wxt prepare`](/api/cli/wxt-prepare) and look at your project's `.wxt/types/imports-module.d.ts` file. +## Extending Default Directories + +Add additional directories with `imports.dirs`. They're combined with the default directories listed above. + +```ts +export default defineConfig({ + imports: { + dirs: ['lib'], + }, +}); +``` + +## Overriding or Disabling Default Directories + +Set `imports.scan` to `false` to stop WXT from scanning its default directories (`components`, `composables`, `hooks`, and `utils`). Preset/library based imports (like `browser`, `storage`, etc.) are unaffected, so this can be used to keep auto-imports enabled while opting out of directory scanning. + +```ts +export default defineConfig({ + imports: { + scan: false, + }, +}); +``` + +Combine `scan: false` with `dirs` to replace the default directories with your own instead of extending them: + +```ts +export default defineConfig({ + imports: { + scan: false, + dirs: ['lib'], + }, +}); +``` + ## TypeScript For TypeScript and your editor to recognize auto-imported variables, you need to run the [`wxt prepare` command](/api/cli/wxt-prepare). diff --git a/packages/wxt/e2e/tests/auto-imports.test.ts b/packages/wxt/e2e/tests/auto-imports.test.ts index 1c3c5b11c..bfc6d5ec6 100644 --- a/packages/wxt/e2e/tests/auto-imports.test.ts +++ b/packages/wxt/e2e/tests/auto-imports.test.ts @@ -133,6 +133,56 @@ describe('Auto Imports', () => { }); }); + describe('imports: { scan: false }', () => { + it('should not auto-import from the default directories, but should keep preset/library imports', async () => { + const project = new TestProject(); + project.setConfigFileConfig({ + imports: { scan: false }, + }); + project.addFile('entrypoints/popup.html', ``); + project.addFile( + 'utils/time.ts', + `export function startOfDay(date: Date): Date { + throw Error("TODO") + }`, + ); + + await project.prepare(); + + const declaration = await project.serializeFile( + '.wxt/types/imports.d.ts', + ); + expect(declaration).not.toContain('startOfDay'); + expect(declaration).toContain('browser'); + }); + + it('should only auto-import from directories explicitly listed in dirs', async () => { + const project = new TestProject(); + project.setConfigFileConfig({ + imports: { scan: false, dirs: ['helpers'] }, + }); + project.addFile('entrypoints/popup.html', ``); + project.addFile( + 'utils/time.ts', + `export function startOfDay(date: Date): Date { + throw Error("TODO") + }`, + ); + project.addFile( + 'helpers/index.ts', + `export function helperFn(): void {}`, + ); + + await project.prepare(); + + const declaration = await project.serializeFile( + '.wxt/types/imports.d.ts', + ); + expect(declaration).not.toContain('startOfDay'); + expect(declaration).toContain('helperFn'); + }); + }); + describe('imports: false', () => { it('should not generate a imports.d.ts file', async () => { const project = new TestProject(); diff --git a/packages/wxt/src/internal/__tests__/resolve-config.test.ts b/packages/wxt/src/internal/__tests__/resolve-config.test.ts index d91d0d22e..0b9e90f11 100644 --- a/packages/wxt/src/internal/__tests__/resolve-config.test.ts +++ b/packages/wxt/src/internal/__tests__/resolve-config.test.ts @@ -37,4 +37,58 @@ describe('resolveConfig', () => { ); }); }); + + describe('imports.dirs', () => { + const logger = mock(); + + it('should default to the built-in directories', async () => { + const config = await resolveConfig({ logger }, 'build'); + + expect(config.imports.dirs).toEqual([ + 'components', + 'composables', + 'hooks', + 'utils', + ]); + }); + + it('should extend the built-in directories when dirs is set', async () => { + const config = await resolveConfig( + { logger, imports: { dirs: ['some-directory'] } }, + 'build', + ); + + expect(config.imports.dirs).toEqual([ + 'some-directory', + 'components', + 'composables', + 'hooks', + 'utils', + ]); + }); + + it('should clear the built-in directories when scan is false', async () => { + const config = await resolveConfig( + { logger, imports: { scan: false } }, + 'build', + ); + + expect(config.imports.dirs).toEqual([]); + }); + + it('should replace the built-in directories when scan is false and dirs is set', async () => { + const config = await resolveConfig( + { logger, imports: { scan: false, dirs: ['some-directory'] } }, + 'build', + ); + + expect(config.imports.dirs).toEqual(['some-directory']); + }); + + it('should stay empty when imports is disabled', async () => { + const config = await resolveConfig({ logger, imports: false }, 'build'); + + expect(config.imports.dirs).toEqual([]); + }); + }); }); diff --git a/packages/wxt/src/internal/resolve-config.ts b/packages/wxt/src/internal/resolve-config.ts index a1d8d4981..410686d79 100644 --- a/packages/wxt/src/internal/resolve-config.ts +++ b/packages/wxt/src/internal/resolve-config.ts @@ -485,7 +485,10 @@ async function getUnimportOptions( cwd: srcDir, }, eslintrc, - dirs: disabled ? [] : ['components', 'composables', 'hooks', 'utils'], + dirs: + disabled || (config.imports !== false && config.imports?.scan === false) + ? [] + : ['components', 'composables', 'hooks', 'utils'], disabled, }; diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts index b4e389f87..4fcddb12f 100644 --- a/packages/wxt/src/types.ts +++ b/packages/wxt/src/types.ts @@ -1774,6 +1774,27 @@ export type WxtUnimportOptions = Partial & { * [https://wxt.dev/guide/essentials/config/auto-imports.html#eslint](https://wxt.dev/guide/essentials/config/auto-imports.html#eslint) */ eslintrc?: Eslintrc; + /** + * Set to `false` to stop WXT from scanning its default auto-import + * directories (`components`, `composables`, `hooks`, and `utils`, relative to + * `srcDir`). Preset/library based imports (from `imports` and `presets`) are + * unaffected. + * + * Combine with `dirs` to replace the default directories with your own, + * instead of extending them: + * + * ```ts + * export default defineConfig({ + * imports: { + * scan: false, + * dirs: ['some-directory'], + * }, + * }); + * ``` + * + * @default true + */ + scan?: boolean; }; export type WxtResolvedUnimportOptions = Partial & {