Compare commits

...

9 Commits

37 changed files with 545 additions and 324 deletions
+26 -26
View File
@@ -4,30 +4,30 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
## Overview
| Features | WXT | Plasmo | CRXJS |
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
| Create Extension ZIPs | ✅ | ✅ | |
| Create Firefox Sources ZIP | ✅ | ❌ | |
| First-class TypeScript support | ✅ | ✅ | |
| Entrypoint discovery | File based | File based | |
| Inline entrypoint config | ✅ | ✅ | Manifest based |
| Auto-imports | ✅ | ❌ | |
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | |
| Automated publishing | ✅ | ✅ | |
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | |
| Features | WXT | Plasmo | CRXJS |
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :-------------------------------------: |
| Supports all browsers | ✅ | ✅ | ✅ As of `v2.0.0-beta.23` |
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
| Create Extension ZIPs | ✅ | ✅ | |
| Create Firefox Sources ZIP | ✅ | ❌ | |
| First-class TypeScript support | ✅ | ✅ | |
| Entrypoint discovery | File based | File based | |
| Inline entrypoint config | ✅ | ✅ | Manifest based |
| Auto-imports | ✅ | ❌ | |
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | |
| Automated publishing | ✅ | ✅ | |
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | |
| <strong style="opacity: 50%">Dev Mode</strong> | | |
| `.env` Files | ✅ | ✅ | |
| Opens browser and install extension | ✅ | ❌ | |
| HMR for UIs | ✅ | 🟡 React only | |
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | |
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | |
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
| Storage | ✅ | ✅ | |
| Messaging | ❌ | ✅ | |
| Content Script UI | ✅ | ✅ | |
| `.env` Files | ✅ | ✅ | |
| Opens browser and install extension | ✅ | ❌ | |
| HMR for UIs | ✅ | 🟡 React only | |
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | |
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | |
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
| Storage | ✅ | ✅ | |
| Messaging | ❌ | ✅ | |
| Content Script UI | ✅ | ✅ | |
+1 -1
View File
@@ -19,7 +19,7 @@ export default defineConfig({
open: true,
},
experimental: {
viteRuntime: true,
entrypointImporter: 'vite-node',
},
runner: {
startUrls: ['https://duckduckgo.com'],
+26
View File
@@ -1,5 +1,31 @@
# Changelog
## v0.18.10
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.9...wxt-v0.18.10)
### 🚀 Enhancements
- Add `prepare:types` hook to extend `.wxt/` directory generation ([#767](https://github.com/wxt-dev/wxt/pull/767))
- **modules:** Allow adding generated public files ([#769](https://github.com/wxt-dev/wxt/pull/769))
### 🩹 Fixes
- Await `prepare:types` hook ([b29d49c](https://github.com/wxt-dev/wxt/commit/b29d49c))
### 🏡 Chore
- Refactor package manager test fixtures ([39f6c29](https://github.com/wxt-dev/wxt/commit/39f6c29))
- Consolidate `unimport` code into a built-in module ([#771](https://github.com/wxt-dev/wxt/pull/771))
## v0.18.9
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.8...wxt-v0.18.9)
### 🚀 Enhancements
- **experimental:** Replace `viteRuntime` option with `entrypointImporter` option, and implement `vite-node` importer ([#761](https://github.com/wxt-dev/wxt/pull/761))
## v0.18.8
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.7...wxt-v0.18.8)
+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" />
"
`);
});
+5
View File
@@ -4,6 +4,7 @@ import { WxtHooks } from '~/types';
const hooks: WxtHooks = {
ready: vi.fn(),
'prepare:types': vi.fn(),
'build:before': vi.fn(),
'build:done': vi.fn(),
'build:manifestGenerated': vi.fn(),
@@ -41,6 +42,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'build:before': false,
'build:done': false,
'build:publicAssets': false,
@@ -60,6 +62,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -79,6 +82,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -104,6 +108,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -1,9 +1,9 @@
import { describe, it, expect } from 'vitest';
import { TestProject } from '../utils';
describe.each([true, false])(
describe.each(['jiti', 'vite-runtime', 'vite-node'] as const)(
'Manifest Content (Vite runtime? %s)',
(viteRuntime) => {
(entrypointImporter) => {
it.each([
{ browser: undefined, outDir: 'chrome-mv3', expected: undefined },
{ browser: 'chrome', outDir: 'chrome-mv3', expected: undefined },
@@ -24,7 +24,7 @@ describe.each([true, false])(
main: () => {},
})`,
);
await project.build({ browser, experimental: { viteRuntime } });
await project.build({ browser, experimental: { entrypointImporter } });
const safariManifest = await project.getOutputManifest(
`.output/${outDir}/manifest.json`,
+10 -1
View File
@@ -101,7 +101,13 @@ describe('Module Helpers', () => {
export default defineWxtModule((wxt) => {
addPublicAssets(wxt, "${normalizePath(dir)}")
})
wxt.hooks.hook("build:publicAssets", (_, assets) => {
assets.push({
relativeDest: "example/generated.txt",
contents: "",
});
});
});
`,
);
@@ -114,6 +120,9 @@ describe('Module Helpers', () => {
await expect(
project.fileExists('.output/chrome-mv3/module.txt'),
).resolves.toBe(true);
await expect(
project.fileExists('.output/chrome-mv3/example/generated.txt'),
).resolves.toBe(true);
});
it("should not overwrite the user's public files", async () => {
@@ -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" />
"
`);
});
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.18.8",
"version": "0.18.10",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -138,6 +138,7 @@
"publish-browser-extension": "^2.1.3",
"unimport": "^3.7.2",
"vite": "^5.3.0",
"vite-node": "^1.6.0",
"web-ext-run": "^0.2.0",
"webextension-polyfill": "^0.10.0"
},
@@ -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',
};
}
@@ -1,22 +0,0 @@
import { describe, it, expect } from 'vitest';
import { createViteBuilder } from '../index';
import { fakeResolvedConfig } from '~/core/utils/testing/fake-objects';
import { createHooks } from 'hookable';
describe('Vite Builder', () => {
describe('importEntrypoint', () => {
it('should import entrypoints, removing runtime values (like the main function)', async () => {
const {
default: { main: _, ...expected },
} = await import('./fixtures/module');
const builder = await createViteBuilder(
fakeResolvedConfig({ root: __dirname }),
createHooks(),
);
const actual = await builder.importEntrypoint<{ default: any }>(
'./fixtures/module.ts',
);
expect(actual).toEqual(expected);
});
});
});
+63 -16
View File
@@ -20,6 +20,10 @@ import {
import { Hookable } from 'hookable';
import { toArray } from '~/core/utils/arrays';
import { safeVarName } from '~/core/utils/strings';
import { importEntrypointFile } from '~/core/utils/building';
import { ViteNodeServer } from 'vite-node/server';
import { ViteNodeRunner } from 'vite-node/client';
import { installSourcemapsSupport } from 'vite-node/source-map';
export async function createViteBuilder(
wxtConfig: ResolvedConfig,
@@ -57,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),
@@ -204,21 +207,65 @@ export async function createViteBuilder(
return {
name: 'Vite',
version: vite.version,
async importEntrypoint(url) {
const baseConfig = await getBaseConfig();
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.webextensionPolyfillMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, url),
],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
await server.listen();
const runtime = await vite.createViteRuntime(server, { hmr: false });
const module = await runtime.executeUrl(url);
await server.close();
return module.default;
async importEntrypoint(path) {
switch (wxtConfig.experimental.entrypointImporter) {
default:
case 'jiti': {
return await importEntrypointFile(path);
}
case 'vite-runtime': {
const baseConfig = await getBaseConfig();
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.webextensionPolyfillMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
await server.listen();
const runtime = await vite.createViteRuntime(server, { hmr: false });
const module = await runtime.executeUrl(path);
await server.close();
return module.default;
}
case 'vite-node': {
const baseConfig = await getBaseConfig();
// Disable dep optimization, as recommended by vite-node's README
baseConfig.optimizeDeps ??= {};
baseConfig.optimizeDeps.noDiscovery = true;
baseConfig.optimizeDeps.include = [];
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.webextensionPolyfillMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
await server.pluginContainer.buildStart({});
const node = new ViteNodeServer(server);
installSourcemapsSupport({
getSourceMap: (source) => node.getSourceMap(source),
});
const runner = new ViteNodeRunner({
root: server.config.root,
base: server.config.base,
// when having the server and runner in a different context,
// you will need to handle the communication between them
// and pass to this function
fetchModule(id) {
return node.fetchModule(id);
},
resolveId(id, importer) {
return node.resolveId(id, importer);
},
});
const res = await runner.executeFile(path);
await server.close();
return res.default;
}
}
},
async build(group) {
let entryConfig;
@@ -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 }),
};
},
};
}
@@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest';
import path from 'node:path';
import { bun } from '../bun';
const cwd = path.resolve(__dirname, 'fixtures/bun-project');
describe.skipIf(() => process.platform === 'win32')(
'Bun Package Management Utils',
() => {
describe('listDependencies', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-bun-project');
it('should list direct dependencies', async () => {
const actual = await bun.listDependencies({ cwd });
expect(actual).toEqual([
@@ -4,15 +4,14 @@ import { npm } from '../npm';
import { execaCommand } from 'execa';
import { exists } from 'fs-extra';
const cwd = path.resolve(__dirname, 'fixtures/npm-project');
describe('NPM Package Management Utils', () => {
beforeAll(async () => {
// NPM needs the modules installed for 'npm ls' to work
await execaCommand('npm i', { cwd });
}, 60e3);
describe('listDependencies', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
beforeAll(async () => {
// NPM needs the modules installed for 'npm ls' to work
await execaCommand('npm i', { cwd });
}, 60e3);
it('should list direct dependencies', async () => {
const actual = await npm.listDependencies({ cwd });
expect(actual).toEqual([
@@ -32,6 +31,8 @@ describe('NPM Package Management Utils', () => {
});
describe('downloadDependency', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
it('should download the dependency as a tarball', async () => {
const downloadDir = path.resolve(cwd, 'dist');
const id = 'mime-db@1.52.0';
@@ -3,17 +3,16 @@ import path from 'node:path';
import { pnpm } from '../pnpm';
import { execaCommand } from 'execa';
const cwd = path.resolve(__dirname, 'fixtures/pnpm-project');
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
describe('PNPM Package Management Utils', () => {
beforeAll(async () => {
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
await execaCommand('pnpm i --ignore-workspace', { cwd });
});
describe('listDependencies', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
beforeAll(async () => {
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
await execaCommand('pnpm i --ignore-workspace', { cwd });
});
it('should list direct dependencies', async () => {
const actual = await pnpm.listDependencies({ cwd });
expect(actual).toEqual([
@@ -2,10 +2,10 @@ import { describe, expect, it } from 'vitest';
import path from 'node:path';
import { yarn } from '../yarn';
const cwd = path.resolve(__dirname, 'fixtures/yarn-project');
describe('Yarn Package Management Utils', () => {
describe('listDependencies', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-project');
it('should list direct dependencies', async () => {
const actual = await yarn.listDependencies({ cwd });
expect(actual).toEqual([
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import {
BackgroundEntrypoint,
BackgroundEntrypointOptions,
@@ -12,16 +12,13 @@ import {
import { resolve } from 'path';
import { findEntrypoints } from '../find-entrypoints';
import fs from 'fs-extra';
import { importEntrypointFile } from '../import-entrypoint';
import glob from 'fast-glob';
import {
fakeResolvedConfig,
setFakeWxt,
} from '~/core/utils/testing/fake-objects';
import { unnormalizePath } from '~/core/utils/paths';
vi.mock('../import-entrypoint');
const importEntrypointFileMock = vi.mocked(importEntrypointFile);
import { wxt } from '~/core/wxt';
vi.mock('fast-glob');
const globMock = vi.mocked(glob);
@@ -39,9 +36,11 @@ describe('findEntrypoints', () => {
outDir: resolve('.output'),
command: 'build',
});
let importEntrypointMock: Mock;
beforeEach(() => {
setFakeWxt({ config });
importEntrypointMock = vi.mocked(wxt.builder.importEntrypoint);
});
it.each<[string, string, PopupEntrypoint]>([
@@ -211,13 +210,13 @@ describe('findEntrypoints', () => {
matches: ['<all_urls>'],
};
globMock.mockResolvedValueOnce([path]);
importEntrypointFileMock.mockResolvedValue(options);
importEntrypointMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints();
expect(entrypoints).toHaveLength(1);
expect(entrypoints[0]).toEqual({ ...expected, options });
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
},
);
@@ -249,13 +248,13 @@ describe('findEntrypoints', () => {
type: 'module',
};
globMock.mockResolvedValueOnce([path]);
importEntrypointFileMock.mockResolvedValue(options);
importEntrypointMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints();
expect(entrypoints).toHaveLength(1);
expect(entrypoints[0]).toEqual({ ...expected, options });
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
},
);
@@ -338,12 +337,13 @@ describe('findEntrypoints', () => {
config: {
manifestVersion: 2,
},
builder: wxt.builder,
});
const options: BackgroundEntrypointOptions = {
type: 'module',
};
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue(options);
importEntrypointMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints();
@@ -355,12 +355,13 @@ describe('findEntrypoints', () => {
config: {
manifestVersion: 3,
},
builder: wxt.builder,
});
const options: BackgroundEntrypointOptions = {
type: 'module',
};
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue(options);
importEntrypointMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints();
@@ -373,6 +374,7 @@ describe('findEntrypoints', () => {
...config,
command: 'serve',
},
builder: wxt.builder,
});
globMock.mockResolvedValueOnce(['popup.html']);
@@ -410,13 +412,13 @@ describe('findEntrypoints', () => {
};
const options: BaseEntrypointOptions = {};
globMock.mockResolvedValueOnce([path]);
importEntrypointFileMock.mockResolvedValue(options);
importEntrypointMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints();
expect(entrypoints).toHaveLength(1);
expect(entrypoints[0]).toEqual({ ...expected, options });
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
},
);
@@ -701,7 +703,7 @@ describe('findEntrypoints', () => {
describe('include option', () => {
it("should filter out the background when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue({
importEntrypointMock.mockResolvedValue({
include: ['not' + config.browser],
});
@@ -712,7 +714,7 @@ describe('findEntrypoints', () => {
it("should filter out content scripts when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['example.content.ts']);
importEntrypointFileMock.mockResolvedValue({
importEntrypointMock.mockResolvedValue({
include: ['not' + config.browser],
});
@@ -776,7 +778,7 @@ describe('findEntrypoints', () => {
describe('exclude option', () => {
it('should filter out the background when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue({
importEntrypointMock.mockResolvedValue({
exclude: [config.browser],
});
@@ -787,7 +789,7 @@ describe('findEntrypoints', () => {
it('should filter out content scripts when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['example.content.ts']);
importEntrypointFileMock.mockResolvedValue({
importEntrypointMock.mockResolvedValue({
exclude: [config.browser],
});
@@ -850,7 +852,6 @@ describe('findEntrypoints', () => {
'ui.content/index.ts',
'injected.content/index.ts',
]);
importEntrypointFileMock.mockResolvedValue({});
const filterEntrypoints = ['popup', 'ui'];
setFakeWxt({
config: {
@@ -860,8 +861,11 @@ describe('findEntrypoints', () => {
command: 'build',
filterEntrypoints: new Set(filterEntrypoints),
},
builder: wxt.builder,
});
importEntrypointMock.mockResolvedValue({});
const entrypoints = await findEntrypoints();
const names = entrypoints.map((item) => item.name);
expect(names).toHaveLength(2);
@@ -45,14 +45,18 @@ async function copyPublicDirectory(): Promise<BuildOutput['publicAssets']> {
if (files.length === 0) return [];
const publicAssets: BuildOutput['publicAssets'] = [];
for (const { absoluteSrc, relativeDest } of files) {
const absoluteDest = resolve(wxt.config.outDir, relativeDest);
for (const file of files) {
const absoluteDest = resolve(wxt.config.outDir, file.relativeDest);
await fs.ensureDir(dirname(absoluteDest));
await fs.copyFile(absoluteSrc, absoluteDest);
if ('absoluteSrc' in file) {
await fs.copyFile(file.absoluteSrc, absoluteDest);
} else {
await fs.writeFile(absoluteDest, file.contents, 'utf8');
}
publicAssets.push({
type: 'asset',
fileName: relativeDest,
fileName: file.relativeDest,
});
}
@@ -28,7 +28,6 @@ import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '~/core/utils/constants';
import { CSS_EXTENSIONS_PATTERN } from '~/core/utils/paths';
import pc from 'picocolors';
import { wxt } from '../../wxt';
import { importEntrypointFile } from './import-entrypoint';
/**
* Return entrypoints and their configuration by looking through the project's files.
@@ -290,7 +289,7 @@ async function getUnlistedScriptEntrypoint({
skipped,
}: EntrypointInfo): Promise<GenericEntrypoint> {
const defaultExport =
await importEntrypoint<UnlistedScriptDefinition>(inputPath);
await wxt.builder.importEntrypoint<UnlistedScriptDefinition>(inputPath);
if (defaultExport == null) {
throw Error(
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`,
@@ -315,7 +314,7 @@ async function getBackgroundEntrypoint({
let options: Omit<BackgroundDefinition, 'main'> = {};
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
const defaultExport =
await importEntrypoint<BackgroundDefinition>(inputPath);
await wxt.builder.importEntrypoint<BackgroundDefinition>(inputPath);
if (defaultExport == null) {
throw Error(
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`,
@@ -344,8 +343,15 @@ async function getContentScriptEntrypoint({
name,
skipped,
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
const { main: _, ...options } =
await importEntrypoint<ContentScriptDefinition>(inputPath);
const defaultExport =
await wxt.builder.importEntrypoint<ContentScriptDefinition>(inputPath);
if (defaultExport == null) {
throw Error(
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
);
}
const { main: _, ...options } = defaultExport;
if (options == null) {
throw Error(
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
@@ -488,9 +494,3 @@ const PATH_GLOB_TO_TYPE_MAP: Record<string, Entrypoint['type']> = {
};
const CONTENT_SCRIPT_OUT_DIR = 'content-scripts';
function importEntrypoint<T>(path: string) {
return wxt.config.experimental.viteRuntime
? wxt.builder.importEntrypoint<T>(path)
: importEntrypointFile<T>(path);
}
@@ -1,11 +1,6 @@
import { Unimport, createUnimport } from 'unimport';
import {
EslintGlobalsPropValue,
Entrypoint,
WxtResolvedUnimportOptions,
} from '~/types';
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '~/types';
import fs from 'fs-extra';
import { relative, resolve } from 'path';
import { dirname, relative, resolve } from 'node:path';
import {
getEntrypointBundlePath,
isHtmlEntrypoint,
@@ -25,61 +20,57 @@ export async function generateTypesDir(
): Promise<void> {
await fs.ensureDir(wxt.config.typesDir);
const references: string[] = [];
const entries: WxtDirEntry[] = [
// Hard-coded entries
{ module: 'wxt/vite-builder-env' },
];
if (wxt.config.imports !== false) {
const unimport = createUnimport(wxt.config.imports);
references.push(await writeImportsDeclarationFile(unimport));
if (wxt.config.imports.eslintrc.enabled) {
await writeImportsEslintFile(unimport, wxt.config.imports);
}
}
// Add references to modules installed from NPM to the TS project so their
// 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.userModules.forEach((module) => {
if (module.type === 'node_module' && module.configKey != null)
entries.push({ module: module.id });
});
references.push(await writePathsDeclarationFile(entrypoints));
references.push(await writeI18nDeclarationFile());
references.push(await writeGlobalsDeclarationFile());
// browser.runtime.getURL
entries.push(await getPathsDeclarationEntry(entrypoints));
const mainReference = await writeMainDeclarationFile(references);
await writeTsConfigFile(mainReference);
}
// browser.i18n.getMessage
entries.push(await getI18nDeclarationEntry());
async function writeImportsDeclarationFile(unimport: Unimport) {
const filePath = resolve(wxt.config.typesDir, 'imports.d.ts');
// import.meta.env.*
entries.push(await getGlobalsDeclarationEntry());
// Load project imports into unimport memory so they are output via generateTypeDeclarations
await unimport.scanImportsFromDir(undefined, { cwd: wxt.config.srcDir });
// tsconfig.json
entries.push(await getTsConfigEntry());
await writeFileIfDifferent(
filePath,
['// Generated by wxt', await unimport.generateTypeDeclarations()].join(
'\n',
) + '\n',
// Let modules add more entries
await wxt.hooks.callHook('prepare:types', wxt, entries);
// Add main declaration file, not editable
entries.push(getMainDeclarationEntry(entries));
// Write all the files
const absoluteFileEntries = (
entries.filter((entry) => 'path' in entry) as WxtDirFileEntry[]
).map<WxtDirFileEntry>((entry) => ({
...entry,
path: resolve(wxt.config.wxtDir, entry.path),
}));
await Promise.all(
absoluteFileEntries.map(async (file) => {
await fs.ensureDir(dirname(file.path));
await writeFileIfDifferent(file.path, file.text);
}),
);
return filePath;
}
async function writeImportsEslintFile(
unimport: Unimport,
options: WxtResolvedUnimportOptions,
) {
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;
});
await fs.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
}
async function writePathsDeclarationFile(
async function getPathsDeclarationEntry(
entrypoints: Entrypoint[],
): Promise<string> {
const filePath = resolve(wxt.config.typesDir, 'paths.d.ts');
): Promise<WxtDirFileEntry> {
const unions = entrypoints
.map((entry) =>
getEntrypointBundlePath(
@@ -108,16 +99,14 @@ declare module "wxt/browser" {
}
`;
await writeFileIfDifferent(
filePath,
template.replace('{{ union }}', unions || ' | never'),
);
return filePath;
return {
path: 'types/paths.d.ts',
text: template.replace('{{ union }}', unions || ' | never'),
tsReference: true,
};
}
async function writeI18nDeclarationFile(): Promise<string> {
const filePath = resolve(wxt.config.typesDir, 'i18n.d.ts');
async function getI18nDeclarationEntry(): Promise<WxtDirFileEntry> {
const defaultLocale = wxt.config.manifest.default_locale;
const template = `// Generated by wxt
import "wxt/browser";
@@ -165,20 +154,19 @@ declare module "wxt/browser" {
options?: GetMessageOptions,
): string;`;
});
await writeFileIfDifferent(
filePath,
template.replace('{{ overrides }}', overrides.join('\n')),
);
return filePath;
return {
path: 'types/i18n.d.ts',
text: template.replace('{{ overrides }}', overrides.join('\n')),
tsReference: true,
};
}
async function writeGlobalsDeclarationFile(): Promise<string> {
const filePath = resolve(wxt.config.typesDir, 'globals.d.ts');
async function getGlobalsDeclarationEntry(): Promise<WxtDirFileEntry> {
const globals = [...getGlobals(wxt.config), ...getEntrypointGlobals('')];
await writeFileIfDifferent(
filePath,
[
return {
path: 'types/globals.d.ts',
text: [
'// Generated by wxt',
'export {}',
'interface ImportMetaEnv {',
@@ -187,39 +175,30 @@ async function writeGlobalsDeclarationFile(): Promise<string> {
'interface ImportMeta {',
' readonly env: ImportMetaEnv',
'}',
].join('\n') + '\n',
);
return filePath;
'',
].join('\n'),
tsReference: true,
};
}
async function writeMainDeclarationFile(references: string[]): Promise<string> {
const dir = wxt.config.wxtDir;
const filePath = resolve(dir, 'wxt.d.ts');
await writeFileIfDifferent(
filePath,
[
'// Generated by wxt',
`/// <reference types="wxt/vite-builder-env" />`,
...references.map(
(ref) =>
`/// <reference types="./${normalizePath(relative(dir, ref))}" />`,
),
// Add references to modules installed from NPM to the TS project so
// their 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
.filter(
(module) => module.type === 'node_module' && module.configKey != null,
)
.map((module) => `/// <reference types="${module.id}" />`),
].join('\n') + '\n',
);
return filePath;
function getMainDeclarationEntry(references: WxtDirEntry[]): WxtDirFileEntry {
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);
lines.push(`/// <reference types="./${normalizePath(relativePath)}" />`);
}
});
return {
path: 'wxt.d.ts',
text: lines.join('\n') + '\n',
};
}
async function writeTsConfigFile(mainReference: string) {
async function getTsConfigEntry(): Promise<WxtDirFileEntry> {
const dir = wxt.config.wxtDir;
const getTsconfigPath = (path: string) => normalizePath(relative(dir, path));
const paths = Object.entries(wxt.config.alias)
@@ -232,9 +211,7 @@ async function writeTsConfigFile(mainReference: string) {
})
.join(',\n');
await writeFileIfDifferent(
resolve(dir, 'tsconfig.json'),
`{
const text = `{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
@@ -251,9 +228,13 @@ ${paths}
},
"include": [
"${getTsconfigPath(wxt.config.root)}/**/*",
"./${getTsconfigPath(mainReference)}"
"./wxt.d.ts"
],
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
}`,
);
}`;
return {
path: 'tsconfig.json',
text,
};
}
@@ -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,
@@ -167,7 +174,7 @@ export async function resolveConfig(
alias,
experimental: defu(mergedConfig.experimental, {
includeBrowserPolyfill: true,
viteRuntime: false,
entrypointImporter: 'jiti' as const,
}),
dev: {
server: devServerConfig,
@@ -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: {
@@ -297,7 +298,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
alias: {},
experimental: {
includeBrowserPolyfill: true,
viteRuntime: false,
entrypointImporter: 'jiti',
},
dev: {
reloadCommand: 'Alt+R',
+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;
});
}
+74 -14
View File
@@ -317,14 +317,17 @@ export interface InlineConfig {
*/
includeBrowserPolyfill?: boolean;
/**
* When set to `true`, use the Vite Runtime API to load entrypoint options instead of the default, `jiti`.
* Method used to import entrypoint files during the build process to extract their options.
*
* Lets you use imported variables and leverage your Vite config to add support for non-standard APIs/syntax.
* - "jiti": Simplest and fastest, but doesn't allow using any imported variables outside the entrypoint's main function
* - "vite-runtime" (unstable): Uses Vite 5.3's new runtime API to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
* - "vite-node" (unstable): Uses `vite-node` to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
*
* @experimental Early access to try out the feature before it becomes the default.
* @default false
* @see {@link https://wxt.dev/guide/go-further/entrypoint-side-effects.html|Entrypoint Side-effect Docs}
*
* @default "jiti"
*/
viteRuntime?: boolean;
entrypointImporter?: 'jiti' | 'vite-runtime' | 'vite-node';
};
/**
* Config effecting dev mode only.
@@ -1031,6 +1034,23 @@ export interface WxtHooks {
* @returns Promise
*/
ready: (wxt: Wxt) => HookResult;
/**
* Called before WXT writes .wxt/tsconfig.json and .wxt/wxt.d.ts, allowing
* addition of custom references and declarations in wxt.d.ts, or directly
* modifying the options in `tsconfig.json`.
*
* @example
* wxt.hooks.hook("prepare:types", (wxt, entries) => {
* // Add a file, ".wxt/types/example.d.ts", that defines a global
* // variable called "example" in the TS project.
* entries.push({
* path: "types/example.d.ts",
* textContent: "declare const a: string;",
* tsReference: true,
* });
* })
*/
'prepare:types': (wxt: Wxt, entries: WxtDirEntry[]) => HookResult;
/**
* Called before the build is started in both dev mode and build mode.
*
@@ -1174,7 +1194,7 @@ export interface ResolvedConfig {
alias: Record<string, string>;
experimental: {
includeBrowserPolyfill: boolean;
viteRuntime: boolean;
entrypointImporter: 'jiti' | 'vite-runtime' | 'vite-node';
};
dev: {
/** Only defined during dev command */
@@ -1185,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(...)`.
@@ -1331,13 +1352,9 @@ export interface WxtModuleWithMetadata<TOptions extends WxtModuleOptions>
id: string;
}
export interface ResolvedPublicFile {
/**
* The absolute path to the file that will be copied to the output directory.
* @example
* "/path/to/any/file.css"
*/
absoluteSrc: string;
export type ResolvedPublicFile = CopiedPublicFile | GeneratedPublicFile;
export interface ResolvedBasePublicFile {
/**
* The relative path in the output directory to copy the file to.
* @example
@@ -1346,4 +1363,47 @@ export interface ResolvedPublicFile {
relativeDest: string;
}
export interface CopiedPublicFile extends ResolvedBasePublicFile {
/**
* The absolute path to the file that will be copied to the output directory.
* @example
* "/path/to/any/file.css"
*/
absoluteSrc: string;
}
export interface GeneratedPublicFile extends ResolvedBasePublicFile {
/**
* Text to write to the file.
*/
contents: string;
}
export type WxtPlugin = () => void;
export type WxtDirEntry = WxtDirTypeReferenceEntry | WxtDirFileEntry;
/**
* Represents type reference to a node module to be added to `.wxt/wxt.d.ts` file
*/
export interface WxtDirTypeReferenceEntry {
module: string;
}
/**
* Represents a file to be written to the project's `.wxt/` directory.
*/
export interface WxtDirFileEntry {
/**
* Path relative to the `.wxt/` directory. So "tsconfig.json" would resolve to ".wxt/tsconfig.json".
*/
path: string;
/**
* The text that will be written to the file.
*/
text: string;
/**
* Set to `true` to add a reference to this file in `.wxt/wxt.d.ts`.
*/
tsReference?: boolean;
}
+6 -3
View File
@@ -291,6 +291,9 @@ importers:
vite:
specifier: ^5.3.0
version: 5.3.0(@types/node@20.14.2)
vite-node:
specifier: ^1.6.0
version: 1.6.0(@types/node@20.14.2)
web-ext-run:
specifier: ^0.2.0
version: 0.2.0
@@ -741,6 +744,7 @@ packages:
/@babel/highlight@7.24.7:
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
engines: {node: '>=6.9.0'}
requiresBuild: true
dependencies:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
@@ -7028,7 +7032,7 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
/vite-node@1.6.0:
/vite-node@1.6.0(@types/node@20.14.2):
resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -7047,7 +7051,6 @@ packages:
- sugarss
- supports-color
- terser
dev: true
/vite-plugin-solid@2.10.2(solid-js@1.8.17)(vite@5.3.0):
resolution: {integrity: sha512-AOEtwMe2baBSXMXdo+BUwECC8IFHcKS6WQV/1NEd+Q7vHPap5fmIhLcAzr+DUJ04/KHx/1UBU0l1/GWP+rMAPQ==}
@@ -7235,7 +7238,7 @@ packages:
tinybench: 2.5.1
tinypool: 0.8.4
vite: 5.3.0(@types/node@20.14.2)
vite-node: 1.6.0
vite-node: 1.6.0(@types/node@20.14.2)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less