Compare commits

..

8 Commits

Author SHA1 Message Date
GitHub Actions 28c7e7d9ed chore(release): v0.17.10 2024-03-29 05:52:30 +00:00
Zizheng Tai 5a3b42a4b0 feat: Add dev.server.port config (#577)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-03-29 00:49:36 -05:00
Aaron c9c5d721ec chore: Refactor order of config resolution (#578) 2024-03-29 00:21:09 -05:00
Aaron 1556b9ad73 chore: Use @aklinker1/check to simplify checks setup (#550) 2024-03-25 15:15:40 -05:00
GitHub Actions ca15b1f321 chore(release): v0.17.9 2024-03-25 06:13:23 +00:00
Aaron 448436cb08 chore: Merge user config using defu (#568) 2024-03-25 01:09:52 -05:00
Aaron c9ddc3d0f2 fix: Don't override wxt.config.ts options when CLI flags are not passed (#567) 2024-03-25 00:24:56 -05:00
Guillaume 6b72f883b6 feat: Add {{mode}} Template Variable (#566)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-03-25 00:01:59 -05:00
21 changed files with 353 additions and 238 deletions
+2 -14
View File
@@ -7,24 +7,12 @@ on:
- main
jobs:
formatting:
checks:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm format:check
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm lint
type-check:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm compile
- run: pnpm check
validate-demo:
runs-on: ubuntu-22.04
steps:
+37
View File
@@ -1,5 +1,42 @@
# Changelog
## v0.17.10
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.9...v0.17.10)
### 🚀 Enhancements
- Add `dev.server.port` config ([#577](https://github.com/wxt-dev/wxt/pull/577))
### 🏡 Chore
- Use `@aklinker1/check` to simplify checks setup ([#550](https://github.com/wxt-dev/wxt/pull/550))
- Refactor order of config resolution ([#578](https://github.com/wxt-dev/wxt/pull/578))
### ❤️ Contributors
- Zizheng Tai ([@zizhengtai](http://github.com/zizhengtai))
## v0.17.9
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.8...v0.17.9)
### 🚀 Enhancements
- Add `{{mode}}` Template Variable ([#566](https://github.com/wxt-dev/wxt/pull/566))
### 🩹 Fixes
- Don't override `wxt.config.ts` options when CLI flags are not passed ([#567](https://github.com/wxt-dev/wxt/pull/567))
### 🏡 Chore
- Merge user config using `defu` ([#568](https://github.com/wxt-dev/wxt/pull/568))
### ❤️ Contributors
- Guillaume ([@GuiEpi](http://github.com/GuiEpi))
## v0.17.8
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.7...v0.17.8)
+3
View File
@@ -18,4 +18,7 @@ export default defineConfig({
zip: {
downloadPackages: ['sass'],
},
analysis: {
open: true,
},
});
+25
View File
@@ -71,4 +71,29 @@ describe('Zipping', () => {
}"
`);
});
it('should correctly apply template variables for zip file names based on provided config', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const artifactZip = '.output/test-1.0.0-firefox-development.zip';
const sourcesZip = '.output/test-1.0.0-development-sources.zip';
await project.zip({
browser: 'firefox',
mode: 'development',
zip: {
artifactTemplate: '{{name}}-{{version}}-{{browser}}-{{mode}}.zip',
sourcesTemplate: '{{name}}-{{version}}-{{mode}}-sources.zip',
},
});
expect(await project.fileExists(artifactZip)).toBe(true);
expect(await project.fileExists(sourcesZip)).toBe(true);
});
});
+5 -9
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.17.8",
"version": "0.17.10",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -87,14 +87,9 @@
"scripts": {
"wxt": "tsx src/cli/index.ts",
"build": "tsx scripts/build.ts",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "run-p -c -s lint:*",
"lint:eslint": "echo 'ESLint: TODO'",
"lint:package": "publint",
"compile": "run-s -c compile:*",
"compile:wxt": "tsc --noEmit",
"compile:virtual": "tsc --noEmit -p src/virtual",
"check": "run-s -c check:*",
"check:default": "check",
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
"test": "vitest",
"test:coverage": "vitest run --coverage.enabled \"--coverage.include=src/**\" \"--coverage.exclude=src/core/utils/testing/**\" \"--coverage.exclude=**/*.d.ts\"",
"prepare": "simple-git-hooks",
@@ -146,6 +141,7 @@
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
"@aklinker1/check": "^1.1.1",
"@faker-js/faker": "^8.3.1",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
+20 -3
View File
@@ -123,6 +123,9 @@ importers:
specifier: ^0.10.0
version: 0.10.0
devDependencies:
'@aklinker1/check':
specifier: ^1.1.1
version: 1.1.1(typescript@5.3.3)
'@faker-js/faker':
specifier: ^8.3.1
version: 8.3.1
@@ -238,6 +241,18 @@ importers:
packages:
/@aklinker1/check@1.1.1(typescript@5.3.3):
resolution: {integrity: sha512-P6+vKw9Qm4y3newTcKt4kfxr4cCZgjaoDpCddpqutTZm6bKTgpmtUQNUzWbmpT/V41+86wNAko/jh58Klmn/5A==}
hasBin: true
peerDependencies:
typescript: ^5.0.0
dependencies:
'@antfu/utils': 0.7.7
ci-info: 4.0.0
citty: 0.1.6
typescript: 5.3.3
dev: true
/@aklinker1/rollup-plugin-visualizer@5.12.0:
resolution: {integrity: sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==}
engines: {node: '>=14'}
@@ -402,6 +417,10 @@ packages:
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@antfu/utils@0.7.7:
resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==}
dev: true
/@babel/code-frame@7.22.5:
resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==}
engines: {node: '>=6.9.0'}
@@ -1910,13 +1929,12 @@ packages:
/ci-info@4.0.0:
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
engines: {node: '>=8'}
dev: false
dev: true
/citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
dependencies:
consola: 3.2.3
dev: false
/cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
@@ -2054,7 +2072,6 @@ packages:
/consola@3.2.3:
resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
engines: {node: ^14.18.0 || >=16.10.0}
dev: false
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+14
View File
@@ -116,6 +116,20 @@ describe('CLI', () => {
});
});
it('should respect passing --port', async () => {
const expectedPort = 3100;
mockArgv('--port', String(expectedPort));
await importCli();
expect(createServerMock).toBeCalledWith({
dev: {
server: {
port: expectedPort,
},
},
});
});
it('should respect passing --debug', async () => {
mockArgv('--debug');
await importCli();
+9
View File
@@ -16,6 +16,7 @@ cli
.option('-c, --config <file>', 'use specified config file')
.option('-m, --mode <mode>', 'set env mode')
.option('-b, --browser <browser>', 'specify a browser')
.option('-p, --port <port>', 'specify a port for the dev server')
.option(
'-e, --filter-entrypoint <entrypoint>',
'only build specific entrypoints',
@@ -35,6 +36,14 @@ cli
configFile: flags.config,
debug: flags.debug,
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
dev:
flags.port == null
? undefined
: {
server: {
port: parseInt(flags.port),
},
},
});
await server.start();
return { isOngoing: true };
+6 -15
View File
@@ -2,12 +2,11 @@ import type * as vite from 'vite';
import {
BuildStepOutput,
Entrypoint,
InlineConfig,
ResolvedConfig,
UserConfig,
VirtualEntrypointType,
WxtBuilder,
WxtBuilderServer,
WxtDevServer,
} from '~/types';
import * as wxtPlugins from './plugins';
import {
@@ -16,9 +15,8 @@ import {
} from '~/core/utils/entrypoints';
export async function createViteBuilder(
inlineConfig: InlineConfig,
userConfig: UserConfig,
wxtConfig: Omit<ResolvedConfig, 'builder'>,
wxtConfig: ResolvedConfig,
server?: WxtDevServer,
): Promise<WxtBuilder> {
const vite = await import('vite');
@@ -26,14 +24,7 @@ export async function createViteBuilder(
* Returns the base vite config shared by all builds based on the inline and user config.
*/
const getBaseConfig = async () => {
const resolvedInlineConfig =
(await inlineConfig.vite?.(wxtConfig.env)) ?? {};
const resolvedUserConfig = (await userConfig.vite?.(wxtConfig.env)) ?? {};
const config: vite.InlineConfig = vite.mergeConfig(
resolvedUserConfig,
resolvedInlineConfig,
);
const config: vite.InlineConfig = await wxtConfig.vite(wxtConfig.env);
config.root = wxtConfig.root;
config.configFile = false;
@@ -55,13 +46,13 @@ export async function createViteBuilder(
config.plugins ??= [];
config.plugins.push(
wxtPlugins.download(wxtConfig),
wxtPlugins.devHtmlPrerender(wxtConfig),
wxtPlugins.devHtmlPrerender(wxtConfig, server),
wxtPlugins.unimport(wxtConfig),
wxtPlugins.virtualEntrypoint('background', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig),
wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig),
wxtPlugins.devServerGlobals(wxtConfig),
wxtPlugins.devServerGlobals(wxtConfig, server),
wxtPlugins.tsconfigPaths(wxtConfig),
wxtPlugins.noopBackground(),
wxtPlugins.globals(wxtConfig),
@@ -13,6 +13,7 @@ let reactRefreshPreamble = '';
*/
export function devHtmlPrerender(
config: Omit<ResolvedConfig, 'builder'>,
server: WxtDevServer | undefined,
): vite.PluginOption {
const htmlReloadId = '@wxt/reload-html';
const resolvedHtmlReloadId = resolve(
@@ -38,7 +39,6 @@ export function devHtmlPrerender(
// Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
// before the paths are replaced with their bundled path
transform(code, id) {
const server = config.server;
if (
config.command !== 'serve' ||
server == null ||
@@ -68,7 +68,6 @@ export function devHtmlPrerender(
// Pass the HTML through the dev server to add dev-mode specific code
async transformIndexHtml(html, ctx) {
const server = config.server;
if (config.command !== 'serve' || server == null) return;
const originalUrl = `${server.origin}${ctx.path}`;
@@ -1,22 +1,23 @@
import { Plugin } from 'vite';
import { ResolvedConfig } from '~/types';
import { ResolvedConfig, WxtDevServer } from '~/types';
/**
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
*/
export function devServerGlobals(
config: Omit<ResolvedConfig, 'builder'>,
server: WxtDevServer | undefined,
): Plugin {
return {
name: 'wxt:dev-server-globals',
config() {
if (config.server == null || config.command == 'build') return;
if (server == null || config.command == 'build') return;
return {
define: {
__DEV_SERVER_PROTOCOL__: JSON.stringify('ws:'),
__DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(config.server.port),
__DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(server.port),
},
};
},
+57 -62
View File
@@ -40,14 +40,63 @@ import { mapWxtOptionsToRegisteredContentScript } from './utils/content-scripts'
export async function createServer(
inlineConfig?: InlineConfig,
): Promise<WxtDevServer> {
const port = await getPort();
const hostname = 'localhost';
const origin = `http://${hostname}:${port}`;
const serverInfo: ServerInfo = {
port,
hostname,
origin,
};
await registerWxt('serve', inlineConfig, async (config) => {
const { port, hostname } = config.dev.server!;
const serverInfo: ServerInfo = {
port,
hostname,
origin: `http://${hostname}:${port}`,
};
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
...serverInfo,
get watcher() {
return builderServer.watcher;
},
get ws() {
return builderServer.ws;
},
currentOutput: undefined,
async start() {
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
await buildAndOpenBrowser();
},
async stop() {
await runner.closeBrowser();
await builderServer.close();
},
async restart() {
await closeAndRecreateRunner();
await buildAndOpenBrowser();
},
transformHtml(url, html, originalUrl) {
return builderServer.transformHtml(url, html, originalUrl);
},
reloadContentScript(payload) {
server.ws.send('wxt:reload-content-script', payload);
},
reloadPage(path) {
server.ws.send('wxt:reload-page', path);
},
reloadExtension() {
server.ws.send('wxt:reload-extension');
},
async restartBrowser() {
await closeAndRecreateRunner();
await runner.openBrowser();
},
};
return server;
});
const server = wxt.server!;
let [runner, builderServer] = await Promise.all([
createExtensionRunner(),
wxt.builder.createServer(server),
]);
const buildAndOpenBrowser = async () => {
// Build after starting the dev server so it can be used to transform HTML files
@@ -74,55 +123,6 @@ export async function createServer(
runner = await createExtensionRunner();
};
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
...serverInfo,
get watcher() {
return builderServer.watcher;
},
get ws() {
return builderServer.ws;
},
currentOutput: undefined,
async start() {
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
await buildAndOpenBrowser();
},
async stop() {
await runner.closeBrowser();
await builderServer.close();
},
async restart() {
await closeAndRecreateRunner();
await buildAndOpenBrowser();
},
transformHtml(url, html, originalUrl) {
return builderServer.transformHtml(url, html, originalUrl);
},
reloadContentScript(payload) {
server.ws.send('wxt:reload-content-script', payload);
},
reloadPage(path) {
server.ws.send('wxt:reload-page', path);
},
reloadExtension() {
server.ws.send('wxt:reload-extension');
},
async restartBrowser() {
await closeAndRecreateRunner();
await runner.openBrowser();
},
};
await registerWxt('serve', inlineConfig, server);
let [runner, builderServer] = await Promise.all([
createExtensionRunner(),
wxt.config.builder.createServer(server),
]);
// Register content scripts for the first time after the background starts up since they're not
// listed in the manifest
server.ws.on('wxt:background-initialized', () => {
@@ -137,11 +137,6 @@ export async function createServer(
return server;
}
async function getPort(): Promise<number> {
const { default: getPort, portNumbers } = await import('get-port');
return await getPort({ port: portNumbers(3000, 3010) });
}
/**
* Returns a function responsible for reloading different parts of the extension when a file
* changes.
+1 -1
View File
@@ -18,7 +18,7 @@ export async function buildEntrypoints(
spinner.text =
pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
try {
steps.push(await wxt.config.builder.build(group));
steps.push(await wxt.builder.build(group));
} catch (err) {
spinner.stop().clear();
wxt.logger.error(err);
+1 -1
View File
@@ -37,7 +37,7 @@ export async function internalBuild(): Promise<BuildOutput> {
const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
wxt.logger.info(
`${verb} ${pc.cyan(target)} for ${pc.cyan(wxt.config.mode)} with ${pc.green(
`${wxt.config.builder.name} ${wxt.config.builder.version}`,
`${wxt.builder.name} ${wxt.builder.version}`,
)}`,
);
const startTime = Date.now();
+98 -101
View File
@@ -7,14 +7,13 @@ import {
UserManifestFn,
UserManifest,
ExtensionRunnerConfig,
WxtDevServer,
WxtResolvedUnimportOptions,
Logger,
WxtCommand,
} from '~/types';
import path from 'node:path';
import { createFsCache } from '~/core/utils/cache';
import consola, { LogLevels } from 'consola';
import { createViteBuilder } from '~/core/builders/vite';
import defu from 'defu';
import { NullablyRequired } from '../types';
import { isModuleInstalled } from '../package';
@@ -30,8 +29,7 @@ import { normalizePath } from '../paths';
*/
export async function resolveConfig(
inlineConfig: InlineConfig,
command: 'build' | 'serve',
server?: WxtDevServer,
command: WxtCommand,
): Promise<ResolvedConfig> {
// Load user config
@@ -53,7 +51,7 @@ export async function resolveConfig(
// Merge it into the inline config
const mergedConfig = mergeInlineConfig(inlineConfig, userConfig);
const mergedConfig = await mergeInlineConfig(inlineConfig, userConfig);
// Apply defaults to make internal config.
@@ -65,8 +63,7 @@ export async function resolveConfig(
const manifestVersion =
mergedConfig.manifestVersion ??
(browser === 'firefox' || browser === 'safari' ? 2 : 3);
const mode =
mergedConfig.mode ?? (command === 'build' ? 'production' : 'development');
const mode = mergedConfig.mode ?? COMMAND_MODES[command];
const env: ConfigEnv = { browser, command, manifestVersion, mode };
const root = path.resolve(
@@ -113,14 +110,20 @@ export async function resolveConfig(
}).map(([key, value]) => [key, path.resolve(root, value)]),
);
const analysisOutputFile = path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
);
const analysisOutputDir = path.dirname(analysisOutputFile);
const analysisOutputName = path.parse(analysisOutputFile).name;
let devServerConfig: ResolvedConfig['dev']['server'];
if (command === 'serve') {
let port = mergedConfig.dev?.server?.port;
if (port == null || !isFinite(port)) {
const { default: getPort, portNumbers } = await import('get-port');
port = await getPort({ port: portNumbers(3000, 3010) });
}
devServerConfig = {
port,
hostname: 'localhost',
};
}
const finalConfig: Omit<ResolvedConfig, 'builder'> = {
return {
browser,
command,
debug,
@@ -142,42 +145,20 @@ export async function resolveConfig(
srcDir,
typesDir,
wxtDir,
zip: resolveInternalZipConfig(root, mergedConfig),
transformManifest(manifest) {
userConfig.transformManifest?.(manifest);
inlineConfig.transformManifest?.(manifest);
},
analysis: {
enabled: mergedConfig.analysis?.enabled ?? false,
open: mergedConfig.analysis?.open ?? false,
template: mergedConfig.analysis?.template ?? 'treemap',
outputFile: analysisOutputFile,
outputDir: analysisOutputDir,
outputName: analysisOutputName,
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
},
zip: resolveZipConfig(root, mergedConfig),
transformManifest: mergedConfig.transformManifest,
analysis: resolveAnalysisConfig(root, mergedConfig),
userConfigMetadata: userConfigMetadata ?? {},
alias,
experimental: {
includeBrowserPolyfill:
mergedConfig.experimental?.includeBrowserPolyfill ?? true,
},
server,
experimental: defu(mergedConfig.experimental, {
includeBrowserPolyfill: true,
}),
dev: {
server: devServerConfig,
reloadCommand,
},
hooks: mergedConfig.hooks ?? {},
};
const builder = await createViteBuilder(
inlineConfig,
userConfig,
finalConfig,
);
return {
...finalConfig,
builder,
vite: mergedConfig.vite ?? (() => ({})),
};
}
@@ -193,77 +174,45 @@ async function resolveManifestConfig(
/**
* Merge the inline config and user config. Inline config is given priority. Defaults are not applied here.
*/
function mergeInlineConfig(
async function mergeInlineConfig(
inlineConfig: InlineConfig,
userConfig: UserConfig,
): NullablyRequired<InlineConfig> {
let imports: InlineConfig['imports'];
if (inlineConfig.imports === false || userConfig.imports === false) {
imports = false;
} else if (userConfig.imports == null && inlineConfig.imports == null) {
imports = undefined;
} else {
imports = defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
}
): Promise<InlineConfig> {
// Merge imports option
const imports: InlineConfig['imports'] =
inlineConfig.imports === false || userConfig.imports === false
? false
: userConfig.imports == null && inlineConfig.imports == null
? undefined
: defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
// Merge manifest option
const manifest: UserManifestFn = async (env) => {
const user = await resolveManifestConfig(env, userConfig.manifest);
const inline = await resolveManifestConfig(env, inlineConfig.manifest);
return defu(inline, user);
};
const runner: InlineConfig['runner'] = defu(
inlineConfig.runner ?? {},
userConfig.runner ?? {},
);
const zip: InlineConfig['zip'] = defu(
inlineConfig.zip ?? {},
userConfig.zip ?? {},
);
const hooks: InlineConfig['hooks'] = defu(
inlineConfig.hooks ?? {},
userConfig.hooks ?? {},
);
// Merge transformManifest option
const transformManifest: InlineConfig['transformManifest'] = (manifest) => {
userConfig.transformManifest?.(manifest);
inlineConfig.transformManifest?.(manifest);
};
// Builders
const builderConfig = await mergeBuilderConfig(inlineConfig, userConfig);
return {
root: inlineConfig.root ?? userConfig.root,
browser: inlineConfig.browser ?? userConfig.browser,
manifestVersion: inlineConfig.manifestVersion ?? userConfig.manifestVersion,
configFile: inlineConfig.configFile,
debug: inlineConfig.debug ?? userConfig.debug,
entrypointsDir: inlineConfig.entrypointsDir ?? userConfig.entrypointsDir,
filterEntrypoints:
inlineConfig.filterEntrypoints ?? userConfig.filterEntrypoints,
...defu(inlineConfig, userConfig),
// Custom merge values
transformManifest,
imports,
logger: inlineConfig.logger ?? userConfig.logger,
manifest,
mode: inlineConfig.mode ?? userConfig.mode,
publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
runner,
srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
outDir: inlineConfig.outDir ?? userConfig.outDir,
zip,
analysis: {
...userConfig.analysis,
...inlineConfig.analysis,
},
alias: {
...userConfig.alias,
...inlineConfig.alias,
},
experimental: {
...userConfig.experimental,
...inlineConfig.experimental,
},
vite: undefined,
transformManifest: undefined,
dev: {
...userConfig.dev,
...inlineConfig.dev,
},
hooks,
...builderConfig,
};
}
function resolveInternalZipConfig(
function resolveZipConfig(
root: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['zip']> {
@@ -292,6 +241,28 @@ function resolveInternalZipConfig(
};
}
function resolveAnalysisConfig(
root: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['analysis']> {
const analysisOutputFile = path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
);
const analysisOutputDir = path.dirname(analysisOutputFile);
const analysisOutputName = path.parse(analysisOutputFile).name;
return {
enabled: mergedConfig.analysis?.enabled ?? false,
open: mergedConfig.analysis?.open ?? false,
template: mergedConfig.analysis?.template ?? 'treemap',
outputFile: analysisOutputFile,
outputDir: analysisOutputDir,
outputName: analysisOutputName,
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
};
}
async function getUnimportOptions(
wxtDir: string,
logger: Logger,
@@ -360,3 +331,29 @@ function logMissingDir(logger: Logger, name: string, expected: string) {
)}`,
);
}
/**
* Map of `ConfigEnv` commands to their default modes.
*/
const COMMAND_MODES: Record<WxtCommand, string> = {
build: 'production',
serve: 'development',
};
export async function mergeBuilderConfig(
inlineConfig: InlineConfig,
userConfig: UserConfig,
): Promise<Pick<InlineConfig, 'vite'>> {
const vite = await import('vite').catch(() => void 0);
if (vite) {
return {
vite: async (env) => {
const resolvedInlineConfig = (await inlineConfig.vite?.(env)) ?? {};
const resolvedUserConfig = (await userConfig.vite?.(env)) ?? {};
return vite.mergeConfig(resolvedUserConfig, resolvedInlineConfig);
},
};
}
throw Error('Builder not found. Make sure vite is installed.');
}
+4 -4
View File
@@ -111,7 +111,7 @@ export async function generateManifest(
if (wxt.config.command === 'serve') addDevModePermissions(manifest);
// TODO: Remove in v1
wxt.config.transformManifest(manifest);
wxt.config.transformManifest?.(manifest);
await wxt.hooks.callHook('build:manifestGenerated', wxt, manifest);
if (wxt.config.manifestVersion === 2) {
@@ -455,8 +455,8 @@ function discoverIcons(
}
function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
const permission = `http://${wxt.config.server?.hostname ?? ''}/*`;
const allowedCsp = wxt.config.server?.origin ?? 'http://localhost:*';
const permission = `http://${wxt.server?.hostname ?? ''}/*`;
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
if (manifest.manifest_version === 3) {
addHostPermission(manifest, permission);
@@ -473,7 +473,7 @@ function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
"script-src 'self'; object-src 'self';", // default CSP for MV2
);
if (wxt.config.server) csp.add('script-src', allowedCsp);
if (wxt.server) csp.add('script-src', allowedCsp);
if (manifest.manifest_version === 3) {
manifest.content_security_policy ??= {};
+3 -2
View File
@@ -270,7 +270,6 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
srcDir: fakeDir(),
typesDir: fakeDir(),
wxtDir: fakeDir(),
server: mock<WxtDevServer>(),
analysis: {
enabled: false,
open: false,
@@ -296,11 +295,11 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
experimental: {
includeBrowserPolyfill: true,
},
builder: mock(),
dev: {
reloadCommand: 'Alt+R',
},
hooks: {},
vite: () => ({}),
};
});
@@ -310,6 +309,8 @@ export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
logger: mock(),
reloadConfig: vi.fn(),
pm: mock(),
server: faker.helpers.arrayElement([undefined, mock<WxtDevServer>()]),
builder: mock(),
}));
export function setFakeWxt(overrides?: DeepPartial<Wxt>) {
+17 -5
View File
@@ -1,7 +1,15 @@
import { InlineConfig, Wxt, WxtDevServer, WxtHooks } from '~/types';
import {
InlineConfig,
ResolvedConfig,
Wxt,
WxtCommand,
WxtDevServer,
WxtHooks,
} from '~/types';
import { resolveConfig } from './utils/building';
import { createHooks } from 'hookable';
import { createWxtPackageManager } from './package-managers';
import { createViteBuilder } from './builders/vite';
/**
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
@@ -13,12 +21,14 @@ export let wxt: Wxt;
* Create and register a global instance of the Wxt interface for use throughout the project.
*/
export async function registerWxt(
command: 'build' | 'serve',
command: WxtCommand,
inlineConfig: InlineConfig = {},
server?: WxtDevServer,
getServer?: (config: ResolvedConfig) => Promise<WxtDevServer>,
): Promise<void> {
const config = await resolveConfig(inlineConfig, command, server);
const hooks = createHooks<WxtHooks>();
const config = await resolveConfig(inlineConfig, command);
const server = await getServer?.(config);
const builder = await createViteBuilder(config, server);
const pm = await createWxtPackageManager(config.root);
wxt = {
@@ -28,9 +38,11 @@ export async function registerWxt(
return config.logger;
},
async reloadConfig() {
wxt.config = await resolveConfig(inlineConfig, command, server);
wxt.config = await resolveConfig(inlineConfig, command);
},
pm,
builder,
server,
};
// Initialize hooks
+1
View File
@@ -38,6 +38,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
'{{version}}',
output.manifest.version_name ?? output.manifest.version,
)
.replaceAll('{{mode}}', wxt.config.mode)
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
await fs.ensureDir(wxt.config.outBaseDir);
+1 -1
View File
@@ -4,7 +4,7 @@ declare const __DEV_SERVER_PORT__: string;
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
interface ImportMetaEnv {
readonly COMMAND: 'build' | 'serve';
readonly COMMAND: WxtCommand;
readonly MANIFEST_VERSION: 2 | 3;
readonly ENTRYPOINT: string;
}
+43 -14
View File
@@ -117,10 +117,11 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}}` - The version_name or version from the manifest
* - `{{browser}}` - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
*
* @default "{{name}}-{{version}}-{{browser}}.zip"
*/
@@ -130,10 +131,11 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}}` - The version_name or version from the manifest
* - `{{browser}}` - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
*
* @default "{{name}}-{{version}}-sources.zip"
*/
@@ -307,6 +309,12 @@ export interface InlineConfig {
* Config effecting dev mode only.
*/
dev?: {
server?: {
/**
* Port to run the dev server on. Defaults to the first open port from 3000 to 3010.
*/
port?: number;
};
/**
* Controls whether a custom keyboard shortcut command, `Alt+R`, is added during dev mode to
* quickly reload the extension.
@@ -342,6 +350,11 @@ export interface InlineConfig {
vite?: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
}
// TODO: Move into @wxt/vite-builder
export interface ResolvedConfig {
vite: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
}
// TODO: Move into @wxt/vite-builder
export type WxtViteConfig = Omit<
vite.UserConfig,
@@ -772,7 +785,7 @@ export interface ConfigEnv {
/**
* The command used to run WXT. `"serve"` during development and `"build"` for any other command.
*/
command: 'build' | 'serve';
command: WxtCommand;
/**
* Browser passed in from the CLI via the `-b` or `--browser` flag. Defaults to `"chrome"` when not passed.
*/
@@ -785,6 +798,8 @@ export interface ConfigEnv {
manifestVersion: 2 | 3;
}
export type WxtCommand = 'build' | 'serve';
/**
* Configure how the browser starts up.
*/
@@ -998,6 +1013,14 @@ export interface Wxt {
* Package manager utilities.
*/
pm: WxtPackageManager;
/**
* If the dev server was started, it will be availble.
*/
server?: WxtDevServer;
/**
* The module in charge of executing all the build steps.
*/
builder: WxtBuilder;
}
export interface ResolvedConfig {
@@ -1016,7 +1039,7 @@ export interface ResolvedConfig {
*/
wxtModuleDir: string;
mode: string;
command: 'build' | 'serve';
command: WxtCommand;
browser: TargetBrowser;
manifestVersion: TargetManifestVersion;
env: ConfigEnv;
@@ -1024,7 +1047,6 @@ export interface ResolvedConfig {
imports: false | WxtResolvedUnimportOptions;
manifest: UserManifest;
fsCache: FsCache;
server?: WxtDevServer;
runnerConfig: C12ResolvedConfig<ExtensionRunnerConfig>;
zip: {
name?: string;
@@ -1036,7 +1058,10 @@ export interface ResolvedConfig {
downloadedPackagesDir: string;
downloadPackages: string[];
};
transformManifest: (manifest: Manifest.WebExtensionManifest) => void;
/**
* @deprecated Use `build:manifestGenerated` hook instead.
*/
transformManifest?: (manifest: Manifest.WebExtensionManifest) => void;
analysis: {
enabled: boolean;
open: boolean;
@@ -1057,11 +1082,15 @@ export interface ResolvedConfig {
experimental: {
includeBrowserPolyfill: boolean;
};
builder: WxtBuilder;
dev: {
/** Only defined during dev command */
server?: {
port: number;
hostname: string;
};
reloadCommand: string | false;
};
hooks: Partial<WxtHooks>;
hooks: NestedHooks<WxtHooks>;
}
export interface FsCache {