feat(experimental): Replace viteRuntime option with entrypointImporter option, and implement vite-node importer (#761)
This commit is contained in:
@@ -19,7 +19,7 @@ export default defineConfig({
|
||||
open: true,
|
||||
},
|
||||
experimental: {
|
||||
viteRuntime: true,
|
||||
entrypointImporter: 'vite-node',
|
||||
},
|
||||
runner: {
|
||||
startUrls: ['https://duckduckgo.com'],
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
@@ -204,21 +208,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function resolveConfig(
|
||||
alias,
|
||||
experimental: defu(mergedConfig.experimental, {
|
||||
includeBrowserPolyfill: true,
|
||||
viteRuntime: false,
|
||||
entrypointImporter: 'jiti' as const,
|
||||
}),
|
||||
dev: {
|
||||
server: devServerConfig,
|
||||
|
||||
@@ -297,7 +297,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
alias: {},
|
||||
experimental: {
|
||||
includeBrowserPolyfill: true,
|
||||
viteRuntime: false,
|
||||
entrypointImporter: 'jiti',
|
||||
},
|
||||
dev: {
|
||||
reloadCommand: 'Alt+R',
|
||||
|
||||
@@ -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.
|
||||
@@ -1174,7 +1177,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 */
|
||||
|
||||
Generated
+6
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user