Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28c7e7d9ed | |||
| 5a3b42a4b0 | |||
| c9c5d721ec | |||
| 1556b9ad73 | |||
| ca15b1f321 | |||
| 448436cb08 | |||
| c9ddc3d0f2 | |||
| 6b72f883b6 |
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,4 +18,7 @@ export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: ['sass'],
|
||||
},
|
||||
analysis: {
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Generated
+20
-3
@@ -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==}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
@@ -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 ??= {};
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user