Skip to content
Closed
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
33 changes: 33 additions & 0 deletions docs/guide/essentials/config/auto-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,39 @@ 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.

## Project Directories

Use `imports.dirs` to add project directories while keeping WXT's default project directories:

```ts
export default defineConfig({
imports: {
dirs: ['stores'],
},
});
```

Use `imports.defaultDirs` to change WXT's default project directories. Set it to `false` to keep WXT API auto-imports and any explicit `dirs` entries, but stop scanning `components`, `composables`, `hooks`, and `utils`:

```ts
export default defineConfig({
imports: {
defaultDirs: false,
dirs: ['shared'],
},
});
```

You can also replace WXT's default project directories with another list:

```ts
export default defineConfig({
imports: {
defaultDirs: ['shared'],
},
});
```

## TypeScript

For TypeScript and your editor to recognize auto-imported variables, you need to run the [`wxt prepare` command](/api/cli/wxt-prepare).
Expand Down
102 changes: 102 additions & 0 deletions packages/wxt/e2e/tests/auto-imports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,108 @@ describe('Auto Imports', () => {
"
`);
});

it('extends default project dirs with imports.dirs', async () => {
const project = new TestProject();
project.setConfigFileConfig({
imports: {
dirs: ['shared'],
},
});
project.addFile('entrypoints/popup.html', `<html></html>`);
project.addFile(
'utils/time.ts',
`export function startOfDay(date: Date): Date {
return date
}`,
);
project.addFile(
'shared/session.ts',
`export function getSession() {
return "session"
}`,
);

await project.prepare();

const importsModule = await project.serializeFile(
'.wxt/types/imports-module.d.ts',
);
expect(importsModule).toContain(
"export { startOfDay } from '../utils/time';",
);
expect(importsModule).toContain(
"export { getSession } from '../shared/session';",
);
});

it('allows disabling default project dirs without disabling auto-imports', async () => {
const project = new TestProject();
project.setConfigFileConfig({
imports: {
defaultDirs: false,
dirs: ['shared'],
},
});
project.addFile('entrypoints/popup.html', `<html></html>`);
project.addFile(
'utils/time.ts',
`export function startOfDay(date: Date): Date {
return date
}`,
);
project.addFile(
'shared/session.ts',
`export function getSession() {
return "session"
}`,
);

await project.prepare();

const importsModule = await project.serializeFile(
'.wxt/types/imports-module.d.ts',
);
expect(importsModule).toContain(
"export { browser, Browser } from 'wxt/browser';",
);
expect(importsModule).toContain(
"export { getSession } from '../shared/session';",
);
expect(importsModule).not.toContain('startOfDay');
});

it('allows replacing default project dirs', async () => {
const project = new TestProject();
project.setConfigFileConfig({
imports: {
defaultDirs: ['shared'],
},
});
project.addFile('entrypoints/popup.html', `<html></html>`);
project.addFile(
'utils/time.ts',
`export function startOfDay(date: Date): Date {
return date
}`,
);
project.addFile(
'shared/session.ts',
`export function getSession() {
return "session"
}`,
);

await project.prepare();

const importsModule = await project.serializeFile(
'.wxt/types/imports-module.d.ts',
);
expect(importsModule).toContain(
"export { getSession } from '../shared/session';",
);
expect(importsModule).not.toContain('startOfDay');
});
});

describe('imports: false', () => {
Expand Down
53 changes: 45 additions & 8 deletions packages/wxt/src/core/resolve-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ import { loadEnv } from './utils/env';
import { getPort } from 'get-port-please';
import { fileURLToPath, pathToFileURL } from 'node:url';

const DEFAULT_AUTO_IMPORT_DIRS = [
'components',
'composables',
'hooks',
'utils',
];

/**
* Given an inline config, discover the config file if necessary, merge the
* results, resolve any relative paths, and apply any defaults.
Expand Down Expand Up @@ -266,12 +273,7 @@ async function mergeInlineConfig(
userConfig: UserConfig,
): Promise<InlineConfig> {
// Merge imports option
const imports: InlineConfig['imports'] =
inlineConfig.imports === false || userConfig.imports === false
? false
: userConfig.imports == null && inlineConfig.imports == null
? undefined
: defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
const imports = mergeImportOptions(inlineConfig.imports, userConfig.imports);

// Merge manifest option
const manifest: UserManifestFn = async (env) => {
Expand All @@ -298,6 +300,26 @@ async function mergeInlineConfig(
};
}

function mergeImportOptions(
inlineImports: InlineConfig['imports'],
userImports: UserConfig['imports'],
): InlineConfig['imports'] {
if (inlineImports === false || userImports === false) return false;
if (userImports == null && inlineImports == null) return undefined;

const merged = defu(inlineImports ?? {}, userImports ?? {});
const defaultDirs =
inlineImports != null && 'defaultDirs' in inlineImports
? inlineImports.defaultDirs
: userImports != null && 'defaultDirs' in userImports
? userImports.defaultDirs
: undefined;

if (defaultDirs !== undefined) merged.defaultDirs = defaultDirs;

return merged;
}

function resolveZipConfig(
root: string,
browser: string,
Expand Down Expand Up @@ -365,6 +387,9 @@ async function getUnimportOptions(
): Promise<WxtResolvedUnimportOptions> {
const disabled = config.imports === false;
const eslintrc = await getUnimportEslintOptions(wxtDir, config.imports);
const userImportOptions =
config.imports === false || config.imports == null ? {} : config.imports;
const { defaultDirs, ...unimportOptions } = userImportOptions;
// mlly sometimes picks up things as exports that aren't. That's what this array contains.
const invalidExports = ['options'];

Expand Down Expand Up @@ -493,16 +518,28 @@ async function getUnimportOptions(
cwd: srcDir,
},
eslintrc,
dirs: disabled ? [] : ['components', 'composables', 'hooks', 'utils'],
dirs: disabled ? [] : resolveDefaultAutoImportDirs(defaultDirs),
disabled,
};

return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
config.imports ?? {},
unimportOptions,
defaultOptions,
);
}

function resolveDefaultAutoImportDirs(
defaultDirs: Exclude<
InlineConfig['imports'],
false | undefined
>['defaultDirs'],
): NonNullable<WxtResolvedUnimportOptions['dirs']> {
if (defaultDirs === false) return [];
if (Array.isArray(defaultDirs)) return defaultDirs;

return [...DEFAULT_AUTO_IMPORT_DIRS];
}

async function getUnimportEslintOptions(
wxtDir: string,
options: InlineConfig['imports'],
Expand Down
11 changes: 11 additions & 0 deletions packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,17 @@ export interface ResolvedEslintrc {
}

export type WxtUnimportOptions = Partial<UnimportOptions> & {
/**
* Configure WXT's default project directories.
*
* Defaults to `true`, which scans `components`, `composables`, `hooks`, and
* `utils`. Set to `false` to keep WXT API auto-imports and explicit `dirs`
* entries without scanning those project directories. Provide an array to
* replace the default project directory list.
*
* @default true
*/
defaultDirs?: boolean | NonNullable<UnimportOptions['dirs']>;
/**
* When using ESLint, auto-imported variables are linted as "undeclared
* globals". This option lets you configure a base eslintrc that, when
Expand Down