chore: Consolidate unimport code into a built-in module (#771)

This commit is contained in:
Aaron
2024-06-25 14:59:28 -05:00
committed by GitHub
parent b29d49cbf1
commit eb86413f35
13 changed files with 195 additions and 147 deletions
+1 -1
View File
@@ -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: {
+6 -6
View File
@@ -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);
+16 -8
View File
@@ -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;
});
}
+2 -1
View File
@@ -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(...)`.