chore: Consolidate unimport code into a built-in module (#771)
This commit is contained in:
@@ -47,10 +47,10 @@ describe('Auto Imports', () => {
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
/// <reference types="wxt/vite-builder-env" />
|
||||
/// <reference types="./types/imports.d.ts" />
|
||||
/// <reference types="./types/paths.d.ts" />
|
||||
/// <reference types="./types/i18n.d.ts" />
|
||||
/// <reference types="./types/globals.d.ts" />
|
||||
/// <reference types="./types/imports.d.ts" />
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -233,10 +233,10 @@ describe('TypeScript Project', () => {
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
/// <reference types="wxt/vite-builder-env" />
|
||||
/// <reference types="./types/imports.d.ts" />
|
||||
/// <reference types="./types/paths.d.ts" />
|
||||
/// <reference types="./types/i18n.d.ts" />
|
||||
/// <reference types="./types/globals.d.ts" />
|
||||
/// <reference types="./types/imports.d.ts" />
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { WxtModule } from '~/types';
|
||||
import unimport from './unimport';
|
||||
|
||||
export const builtinModules: WxtModule<any>[] = [unimport];
|
||||
@@ -0,0 +1,122 @@
|
||||
import { addViteConfig, defineWxtModule } from '~/modules';
|
||||
import type {
|
||||
EslintGlobalsPropValue,
|
||||
WxtDirFileEntry,
|
||||
WxtModule,
|
||||
WxtResolvedUnimportOptions,
|
||||
} from '~/types';
|
||||
import { type Unimport, createUnimport } from 'unimport';
|
||||
import { Plugin } from 'vite';
|
||||
import { extname } from 'node:path';
|
||||
|
||||
export default defineWxtModule({
|
||||
name: 'wxt:built-in:unimport',
|
||||
setup(wxt) {
|
||||
const options = wxt.config.imports;
|
||||
if (options === false) return;
|
||||
|
||||
let unimport: Unimport;
|
||||
|
||||
// Add user module imports to config
|
||||
wxt.hooks.hook('ready', () => {
|
||||
const addModuleImports = (module: WxtModule<any>) => {
|
||||
if (!module.imports) return;
|
||||
|
||||
options.imports ??= [];
|
||||
options.imports.push(...module.imports);
|
||||
};
|
||||
|
||||
wxt.config.builtinModules.forEach(addModuleImports);
|
||||
wxt.config.userModules.forEach(addModuleImports);
|
||||
});
|
||||
|
||||
// Create unimport instance after READY so any modifications to the config
|
||||
// inside "ready" are applied.
|
||||
wxt.hooks.afterEach((event) => {
|
||||
if (event.name === 'ready') {
|
||||
unimport = createUnimport(options);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate types
|
||||
wxt.hooks.hook('prepare:types', async (_, entries) => {
|
||||
// Update cache before each rebuild
|
||||
await unimport.init();
|
||||
|
||||
entries.push(await getImportsDeclarationEntry(unimport));
|
||||
|
||||
if (!options.eslintrc.enabled) return;
|
||||
entries.push(await getImportsEslintEntry(unimport, options));
|
||||
});
|
||||
|
||||
// Add vite plugin
|
||||
addViteConfig(wxt, () => ({
|
||||
plugins: [vitePlugin(unimport)],
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export function vitePlugin(unimport: Unimport): Plugin {
|
||||
const ENABLED_EXTENSIONS = new Set([
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
'.vue',
|
||||
'.svelte',
|
||||
]);
|
||||
return {
|
||||
name: 'wxt:unimport',
|
||||
async transform(code, id) {
|
||||
// Don't transform dependencies
|
||||
if (id.includes('node_modules')) return;
|
||||
|
||||
// Don't transform non-js files
|
||||
if (!ENABLED_EXTENSIONS.has(extname(id))) return;
|
||||
|
||||
const injected = await unimport.injectImports(code, id);
|
||||
return {
|
||||
code: injected.code,
|
||||
map: injected.s.generateMap({ hires: 'boundary', source: id }),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getImportsDeclarationEntry(
|
||||
unimport: Unimport,
|
||||
): Promise<WxtDirFileEntry> {
|
||||
// Load project imports into unimport memory so they are output via generateTypeDeclarations
|
||||
await unimport.init();
|
||||
|
||||
return {
|
||||
path: 'types/imports.d.ts',
|
||||
text: [
|
||||
'// Generated by wxt',
|
||||
await unimport.generateTypeDeclarations(),
|
||||
'',
|
||||
].join('\n'),
|
||||
tsReference: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function getImportsEslintEntry(
|
||||
unimport: Unimport,
|
||||
options: WxtResolvedUnimportOptions,
|
||||
): Promise<WxtDirFileEntry> {
|
||||
const globals: Record<string, EslintGlobalsPropValue> = {};
|
||||
const eslintrc = { globals };
|
||||
|
||||
(await unimport.getImports())
|
||||
.map((i) => i.as ?? i.name)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.forEach((name) => {
|
||||
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
|
||||
});
|
||||
|
||||
return {
|
||||
path: options.eslintrc.filePath,
|
||||
text: JSON.stringify(eslintrc, null, 2) + '\n',
|
||||
};
|
||||
}
|
||||
@@ -61,7 +61,6 @@ export async function createViteBuilder(
|
||||
config.plugins.push(
|
||||
wxtPlugins.download(wxtConfig),
|
||||
wxtPlugins.devHtmlPrerender(wxtConfig, server),
|
||||
wxtPlugins.unimport(wxtConfig),
|
||||
wxtPlugins.resolveVirtualModules(wxtConfig),
|
||||
wxtPlugins.devServerGlobals(wxtConfig, server),
|
||||
wxtPlugins.tsconfigPaths(wxtConfig),
|
||||
|
||||
@@ -2,7 +2,6 @@ export * from './devHtmlPrerender';
|
||||
export * from './devServerGlobals';
|
||||
export * from './download';
|
||||
export * from './multipageMove';
|
||||
export * from './unimport';
|
||||
export * from './resolveVirtualModules';
|
||||
export * from './tsconfigPaths';
|
||||
export * from './noopBackground';
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { createUnimport } from 'unimport';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import type * as vite from 'vite';
|
||||
import { extname } from 'path';
|
||||
|
||||
const ENABLED_EXTENSIONS = new Set([
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
'.vue',
|
||||
'.svelte',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Inject any global imports defined by unimport
|
||||
*/
|
||||
export function unimport(config: ResolvedConfig): vite.PluginOption {
|
||||
const options = config.imports;
|
||||
if (options === false) return [];
|
||||
|
||||
const unimport = createUnimport(options);
|
||||
|
||||
return {
|
||||
name: 'wxt:unimport',
|
||||
async config() {
|
||||
await unimport.scanImportsFromDir(undefined, { cwd: config.srcDir });
|
||||
},
|
||||
async transform(code, id) {
|
||||
// Don't transform dependencies
|
||||
if (id.includes('node_modules')) return;
|
||||
|
||||
// Don't transform non-js files
|
||||
if (!ENABLED_EXTENSIONS.has(extname(id))) return;
|
||||
|
||||
const injected = await unimport.injectImports(code, id);
|
||||
return {
|
||||
code: injected.code,
|
||||
map: injected.s.generateMap({ hires: 'boundary', source: id }),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
import { Unimport, createUnimport } from 'unimport';
|
||||
import {
|
||||
EslintGlobalsPropValue,
|
||||
Entrypoint,
|
||||
WxtResolvedUnimportOptions,
|
||||
WxtDirEntry,
|
||||
WxtDirFileEntry,
|
||||
} from '~/types';
|
||||
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { dirname, relative, resolve } from 'node:path';
|
||||
import {
|
||||
@@ -36,20 +29,11 @@ export async function generateTypesDir(
|
||||
// type augmentation can update InlineConfig correctly. Local modules defined
|
||||
// in <root>/modules are already apart of the project, so we don't need to
|
||||
// add them.
|
||||
wxt.config.modules.forEach((module) => {
|
||||
wxt.config.userModules.forEach((module) => {
|
||||
if (module.type === 'node_module' && module.configKey != null)
|
||||
entries.push({ module: module.id });
|
||||
});
|
||||
|
||||
// Auto-imports
|
||||
if (wxt.config.imports !== false) {
|
||||
const unimport = createUnimport(wxt.config.imports);
|
||||
entries.push(await getImportsDeclarationEntry(unimport));
|
||||
if (wxt.config.imports.eslintrc.enabled) {
|
||||
entries.push(await getImportsEslintEntry(unimport, wxt.config.imports));
|
||||
}
|
||||
}
|
||||
|
||||
// browser.runtime.getURL
|
||||
entries.push(await getPathsDeclarationEntry(entrypoints));
|
||||
|
||||
@@ -84,44 +68,6 @@ export async function generateTypesDir(
|
||||
);
|
||||
}
|
||||
|
||||
async function getImportsDeclarationEntry(
|
||||
unimport: Unimport,
|
||||
): Promise<WxtDirFileEntry> {
|
||||
// Load project imports into unimport memory so they are output via generateTypeDeclarations
|
||||
await unimport.scanImportsFromDir(undefined, { cwd: wxt.config.srcDir });
|
||||
|
||||
return {
|
||||
path: 'types/imports.d.ts',
|
||||
text: [
|
||||
'// Generated by wxt',
|
||||
await unimport.generateTypeDeclarations(),
|
||||
'',
|
||||
].join('\n'),
|
||||
tsReference: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function getImportsEslintEntry(
|
||||
unimport: Unimport,
|
||||
options: WxtResolvedUnimportOptions,
|
||||
): Promise<WxtDirFileEntry> {
|
||||
const globals: Record<string, EslintGlobalsPropValue> = {};
|
||||
const eslintrc = { globals };
|
||||
|
||||
(await unimport.getImports())
|
||||
.map((i) => i.as ?? i.name)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.forEach((name) => {
|
||||
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
|
||||
});
|
||||
|
||||
return {
|
||||
path: options.eslintrc.filePath,
|
||||
text: JSON.stringify(eslintrc, null, 2) + '\n',
|
||||
};
|
||||
}
|
||||
|
||||
async function getPathsDeclarationEntry(
|
||||
entrypoints: Entrypoint[],
|
||||
): Promise<WxtDirFileEntry> {
|
||||
@@ -236,20 +182,19 @@ async function getGlobalsDeclarationEntry(): Promise<WxtDirFileEntry> {
|
||||
}
|
||||
|
||||
function getMainDeclarationEntry(references: WxtDirEntry[]): WxtDirFileEntry {
|
||||
const text = [
|
||||
'// Generated by wxt',
|
||||
...references.map((ref) => {
|
||||
if ('module' in ref) return `/// <reference types="${ref.module}" />`;
|
||||
if (!ref.tsReference) return;
|
||||
|
||||
const lines = ['// Generated by wxt'];
|
||||
references.forEach((ref) => {
|
||||
if ('module' in ref) {
|
||||
return lines.push(`/// <reference types="${ref.module}" />`);
|
||||
} else if (ref.tsReference) {
|
||||
const absolutePath = resolve(wxt.config.wxtDir, ref.path);
|
||||
const relativePath = relative(wxt.config.wxtDir, absolutePath);
|
||||
return `/// <reference types="./${normalizePath(relativePath)}" />`;
|
||||
}),
|
||||
].join('\n');
|
||||
lines.push(`/// <reference types="./${normalizePath(relativePath)}" />`);
|
||||
}
|
||||
});
|
||||
return {
|
||||
path: 'wxt.d.ts',
|
||||
text,
|
||||
text: lines.join('\n') + '\n',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { isModuleInstalled } from '../package';
|
||||
import fs from 'fs-extra';
|
||||
import { normalizePath } from '../paths';
|
||||
import glob from 'fast-glob';
|
||||
import { builtinModules } from '~/builtin-modules';
|
||||
|
||||
/**
|
||||
* Given an inline config, discover the config file if necessary, merge the results, resolve any
|
||||
@@ -127,15 +128,21 @@ export async function resolveConfig(
|
||||
};
|
||||
}
|
||||
|
||||
const modules = await resolveWxtModules(modulesDir, mergedConfig.modules);
|
||||
const moduleOptions = modules.reduce<Record<string, any>>((map, module) => {
|
||||
if (module.configKey) {
|
||||
map[module.configKey] =
|
||||
// @ts-expect-error
|
||||
mergedConfig[module.configKey];
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
const userModules = await resolveWxtUserModules(
|
||||
modulesDir,
|
||||
mergedConfig.modules,
|
||||
);
|
||||
const moduleOptions = userModules.reduce<Record<string, any>>(
|
||||
(map, module) => {
|
||||
if (module.configKey) {
|
||||
map[module.configKey] =
|
||||
// @ts-expect-error
|
||||
mergedConfig[module.configKey];
|
||||
}
|
||||
return map;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
browser,
|
||||
@@ -146,7 +153,7 @@ export async function resolveConfig(
|
||||
filterEntrypoints,
|
||||
env,
|
||||
fsCache: createFsCache(wxtDir),
|
||||
imports: await getUnimportOptions(wxtDir, logger, mergedConfig),
|
||||
imports: await getUnimportOptions(wxtDir, srcDir, logger, mergedConfig),
|
||||
logger,
|
||||
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
|
||||
manifestVersion,
|
||||
@@ -175,7 +182,8 @@ export async function resolveConfig(
|
||||
},
|
||||
hooks: mergedConfig.hooks ?? {},
|
||||
vite: mergedConfig.vite ?? (() => ({})),
|
||||
modules,
|
||||
builtinModules,
|
||||
userModules,
|
||||
plugins: [],
|
||||
...moduleOptions,
|
||||
};
|
||||
@@ -285,20 +293,21 @@ function resolveAnalysisConfig(
|
||||
|
||||
async function getUnimportOptions(
|
||||
wxtDir: string,
|
||||
srcDir: string,
|
||||
logger: Logger,
|
||||
config: InlineConfig,
|
||||
): Promise<WxtResolvedUnimportOptions | false> {
|
||||
if (config.imports === false) return false;
|
||||
|
||||
const enabledConfig = config.imports?.eslintrc?.enabled;
|
||||
let enabled: boolean;
|
||||
switch (enabledConfig) {
|
||||
const rawEslintEnabled = config.imports?.eslintrc?.enabled;
|
||||
let eslintEnabled: boolean;
|
||||
switch (rawEslintEnabled) {
|
||||
case undefined:
|
||||
case 'auto':
|
||||
enabled = await isModuleInstalled('eslint');
|
||||
eslintEnabled = await isModuleInstalled('eslint');
|
||||
break;
|
||||
default:
|
||||
enabled = enabledConfig;
|
||||
eslintEnabled = rawEslintEnabled;
|
||||
}
|
||||
|
||||
const defaultOptions: WxtResolvedUnimportOptions = {
|
||||
@@ -315,8 +324,11 @@ async function getUnimportOptions(
|
||||
],
|
||||
warn: logger.warn,
|
||||
dirs: ['components', 'composables', 'hooks', 'utils'],
|
||||
dirsScanOptions: {
|
||||
cwd: srcDir,
|
||||
},
|
||||
eslintrc: {
|
||||
enabled,
|
||||
enabled: eslintEnabled,
|
||||
filePath: path.resolve(wxtDir, 'eslintrc-auto-import.json'),
|
||||
globalsPropValue: true,
|
||||
},
|
||||
@@ -378,7 +390,7 @@ export async function mergeBuilderConfig(
|
||||
throw Error('Builder not found. Make sure vite is installed.');
|
||||
}
|
||||
|
||||
export async function resolveWxtModules(
|
||||
export async function resolveWxtUserModules(
|
||||
modulesDir: string,
|
||||
modules: string[] = [],
|
||||
): Promise<WxtModuleWithMetadata<any>[]> {
|
||||
|
||||
@@ -239,7 +239,8 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
command,
|
||||
entrypointsDir: fakeDir(),
|
||||
modulesDir: fakeDir(),
|
||||
modules: [],
|
||||
builtinModules: [],
|
||||
userModules: [],
|
||||
env: { browser, command, manifestVersion, mode },
|
||||
fsCache: mock<FsCache>(),
|
||||
imports: {
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
WxtCommand,
|
||||
WxtDevServer,
|
||||
WxtHooks,
|
||||
WxtModule,
|
||||
} from '~/types';
|
||||
import { resolveConfig } from './utils/building';
|
||||
import { createHooks } from 'hookable';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
import { builtinModules } from '~/builtin-modules';
|
||||
|
||||
/**
|
||||
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
|
||||
@@ -46,18 +48,16 @@ export async function registerWxt(
|
||||
};
|
||||
|
||||
// Initialize modules
|
||||
for (const module of config.modules) {
|
||||
const initModule = async (module: WxtModule<any>) => {
|
||||
if (module.hooks) wxt.hooks.addHooks(module.hooks);
|
||||
if (wxt.config.imports !== false && module.imports) {
|
||||
wxt.config.imports.imports ??= [];
|
||||
wxt.config.imports.imports.push(...module.imports);
|
||||
}
|
||||
await module.setup?.(
|
||||
wxt,
|
||||
// @ts-expect-error: Untyped configKey field
|
||||
module.configKey ? config[module.configKey] : undefined,
|
||||
);
|
||||
}
|
||||
};
|
||||
for (const builtinModule of builtinModules) await initModule(builtinModule);
|
||||
for (const userModule of config.userModules) await initModule(userModule);
|
||||
|
||||
// Initialize hooks
|
||||
wxt.hooks.addHooks(config.hooks);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type * as vite from 'vite';
|
||||
import {
|
||||
unimport,
|
||||
download,
|
||||
tsconfigPaths,
|
||||
globals,
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
} from '../core/builders/vite/plugins';
|
||||
import { resolveConfig } from '~/core/utils/building';
|
||||
import { InlineConfig } from '../types';
|
||||
import { vitePlugin as unimportPlugin } from '~/builtin-modules/unimport';
|
||||
import { createUnimport } from 'unimport';
|
||||
|
||||
/**
|
||||
* Vite plugin that configures Vitest with everything required to test a WXT extension, based on the `<root>/wxt.config.ts`
|
||||
@@ -25,11 +26,18 @@ import { InlineConfig } from '../types';
|
||||
* @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file.
|
||||
*/
|
||||
export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption {
|
||||
return resolveConfig(inlineConfig ?? {}, 'serve').then((config) => [
|
||||
webextensionPolyfillMock(config),
|
||||
unimport(config),
|
||||
globals(config),
|
||||
download(config),
|
||||
tsconfigPaths(config),
|
||||
]);
|
||||
return resolveConfig(inlineConfig ?? {}, 'serve').then(async (config) => {
|
||||
const plugins = [
|
||||
webextensionPolyfillMock(config),
|
||||
globals(config),
|
||||
download(config),
|
||||
tsconfigPaths(config),
|
||||
];
|
||||
if (config.imports !== false) {
|
||||
const unimport = createUnimport(config.imports);
|
||||
await unimport.init();
|
||||
plugins.push(unimportPlugin(unimport));
|
||||
}
|
||||
return plugins;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1205,7 +1205,8 @@ export interface ResolvedConfig {
|
||||
reloadCommand: string | false;
|
||||
};
|
||||
hooks: NestedHooks<WxtHooks>;
|
||||
modules: WxtModuleWithMetadata<any>[];
|
||||
builtinModules: WxtModule<any>[];
|
||||
userModules: WxtModuleWithMetadata<any>[];
|
||||
/**
|
||||
* An array of string to import plugins from. These paths should be
|
||||
* resolvable by vite, and they should `export default defineWxtPlugin(...)`.
|
||||
|
||||
Reference in New Issue
Block a user