Compare commits

..

22 Commits

Author SHA1 Message Date
GitHub Actions e5723aab03 chore(release): wxt v0.19.17
vhs / vhs (push) Waiting to run
2024-11-28 14:42:49 +00:00
Jaguar Zhou f51f5dd19f doc: remove dead links generated by typedoc (#1222) 2024-11-28 08:33:11 -06:00
Aaron aae5f0789d fix: Make content script matches optional (#1220) 2024-11-28 00:00:18 -06:00
1natsu 586fe3950c fix: Correctly remove child elements with integrated UI remove (#1219) 2024-11-27 22:39:25 -06:00
Aaron 241c907c65 fix: Automatically convert MV3 content_security_policy to MV2 (#1168)
Co-authored-by: windmillcode0 <michaelodumosu57@gmail.com>
Co-authored-by: windmillcode0 <shieldmousetower734@gmail.com>
2024-11-27 10:14:32 -06:00
Aaron aab4244d84 fix: Update vite dependency range to support v6 (#1215) 2024-11-26 14:26:11 -06:00
Aaron 767a3a987c chore(vue-template): Fix incompatible typescript and vue-tsc versions (#1208) 2024-11-25 15:25:42 -06:00
Aaron a2924d872a fix: Revert "Validate entrypoints after hooks have modified them (54baf21)"
This reverts commit 54baf21490.
2024-11-25 15:08:55 -06:00
Aaron 54baf21490 fix: Validate entrypoints after hooks have modified them 2024-11-25 09:29:46 -06:00
Nishu 5a3790a7d3 fix: ESLint config being generated when ESLint is not installed. (#1198)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-11-22 08:54:05 -06:00
Aaron 236e099b81 docs: Update windows config for persisting browser profile 2024-11-22 08:53:16 -06:00
GitHub Actions 9b54bc4704 chore(release): @wxt-dev/storage v1.0.1
vhs / vhs (push) Waiting to run
2024-11-22 14:41:52 +00:00
Aaron ffb99af719 fix: use browser for mv2 storage (#1200) 2024-11-22 08:36:29 -06:00
Jaguar Zhou 90bb145d91 chore(vue-template): remove references to Vetur and TypeScript Vue Plugin (Volar) (#1197) 2024-11-22 08:21:06 -06:00
Mira Dobrovolskaya 190c87d120 docs: fix typo in entrypoints options example (#1194) 2024-11-21 11:15:10 -06:00
Tran Ngoc Tan 4c511d5728 docs: Add "YouTube Hider" to homepage (#1193) 2024-11-21 03:07:45 +01:00
Florian Metz b8ecbc5a54 docs: fix analyze typo in type (#1187) 2024-11-20 08:46:42 -06:00
Nithur 8c3d756187 docs: Add "Wandpen" to homepage (#1191)
Co-authored-by: Florian Metz <me@timeraa.dev>
2024-11-20 10:18:15 +01:00
Patato cfdc6b3e6b chore: Update Svelte template to Svelte 5 (#1184)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-11-17 14:58:09 -06:00
Aaron 226e6c9fbe docs: Add FAQ around configuring browser startup 2024-11-16 11:33:58 -06:00
Aaron db41f27072 feat: New server:created, server:started, and server:closed hooks (#1179) 2024-11-16 09:29:32 -06:00
Aaron db025cd3a2 fix: Re-initialize WXT modules correctly during development (#1176) 2024-11-16 08:56:08 -06:00
31 changed files with 452 additions and 175 deletions
@@ -56,6 +56,8 @@ const chromeExtensionIds = [
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
@@ -62,7 +62,8 @@ import { resolve } from 'node:path';
export default defineRunnerConfig({
// On Windows, the path must be absolute
chromiumArgs: [`--user-data-dir="${resolve('.wxt/chrome-data')}"`],
chromiumProfile: resolve('.wxt/chrome-data'),
keepProfileChanges: true,
});
```
+1 -4
View File
@@ -304,10 +304,7 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewportYou are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to web_accessible_resources."
content="width=device-width, initial-scale=1.0"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options Title</title>
<meta name="manifest.open_in_tab" content="true|false" />
<meta name="manifest.chrome_style" content="true|false" />
+8 -5
View File
@@ -12,15 +12,18 @@ When using `browser.scripting.executeScript`, you can execute content scripts or
// entrypoints/background.ts
const res = await browser.scripting.executeScript({
target: { tabId },
files: ['injected.js'],
files: ['content-scripts/example.js'],
});
console.log(res); // "Hello John!"
```
```ts
// entrypoints/injected.js
export default defineUnlistedScript(() => {
console.log('Script was injected!');
return 'Hello John!';
// entrypoints/example.content.ts
export default defineContentScript({
registration: 'runtime',
main(ctx) {
console.log('Script was executed!');
return 'Hello John!';
},
});
```
+8
View File
@@ -18,6 +18,14 @@ To list the content scripts registered during development, open the service work
await chrome.scripting.getRegisteredContentScripts();
```
## How do I disable opening the browser automatically during development?
See https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser
## How do I stay logged into a website during development?
See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
## My component library doesn't work in content scripts!
Component libraries place their CSS in the document's `<head>` by default. When using `createShadowRoot`, your UI is isolated from the document's styles because it's inside a ShadowRoot.
+9 -2
View File
@@ -2,11 +2,18 @@
"$schema": "https://typedoc.org/schema.json",
"entryPointStrategy": "packages",
"entryPoints": ["../packages/wxt"],
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
"plugin": [
"typedoc-plugin-markdown",
"typedoc-vitepress-theme",
"typedoc-plugin-frontmatter"
],
"out": "./api/reference",
"githubPages": false,
"excludePrivate": true,
"excludeProtected": true,
"excludeInternal": true,
"readme": "none"
"readme": "none",
"frontmatterGlobals": {
"editLink": false
}
}
+1
View File
@@ -32,6 +32,7 @@
"simple-git-hooks": "^2.11.1",
"tsx": "4.15.7",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.1.0",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.6.3",
+21 -1
View File
@@ -1,5 +1,25 @@
# Changelog
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.0.0...storage-v1.0.1)
### 🩹 Fixes
- Use `browser` for mv2 storage ([#1200](https://github.com/wxt-dev/wxt/pull/1200))
### 📖 Documentation
- Cleanup changelog ([f5b7f7e](https://github.com/wxt-dev/wxt/commit/f5b7f7e))
### 🏡 Chore
- Init changelog for storage package ([6fc227b](https://github.com/wxt-dev/wxt/commit/6fc227b))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v1.0.0
Extracted `wxt/storage` into it's own package, `@wxt-dev/storage`!
@@ -11,4 +31,4 @@ It's still shipped inside WXT and accessible via `wxt/storage`, but now:
[Read the docs](https://wxt.dev/storage.html) for more details.
> This is apart of the v1.0 initiative for WXT.
> This is apart of the v1.0 initiative for WXT.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/storage",
"description": "Web extension storage API provided by WXT, supports all browsers.",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"repository": {
"type": "git",
+12 -5
View File
@@ -8,6 +8,13 @@
import { dequal } from 'dequal/lite';
import { Mutex } from 'async-mutex';
const browser: typeof chrome =
// @ts-expect-error
globalThis.browser?.runtime?.id == null
? globalThis.chrome
: // @ts-expect-error
globalThis.browser;
export const storage = createStorage();
function createStorage(): WxtStorage {
@@ -197,7 +204,7 @@ function createStorage(): WxtStorage {
const resultsMap: Record<string, any> = {};
await Promise.all(
Object.entries(areaToDriverMetaKeysMap).map(async ([area, keys]) => {
const areaRes = await chrome.storage[area as StorageArea].get(
const areaRes = await browser.storage[area as StorageArea].get(
keys.map((key) => key.driverMetaKey),
);
keys.forEach((key) => {
@@ -474,7 +481,7 @@ function createStorage(): WxtStorage {
function createDriver(storageArea: StorageArea): WxtStorageDriver {
const getStorageArea = () => {
if (chrome.runtime == null) {
if (browser.runtime == null) {
throw Error(
[
"'wxt/storage' must be loaded in a web extension environment",
@@ -483,15 +490,15 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
].join('\n'),
);
}
if (chrome.storage == null) {
if (browser.storage == null) {
throw Error(
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
);
}
const area = chrome.storage[storageArea];
const area = browser.storage[storageArea];
if (area == null)
throw Error(`"chrome.storage.${storageArea}" is undefined`);
throw Error(`"browser.storage.${storageArea}" is undefined`);
return area;
};
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
+1 -1
View File
@@ -25,7 +25,7 @@ export default defineConfig({
},
example: {
a: 'a',
// @ts-expect-error: c is not defined, this should error out
// @ts-expect-error: c is not defined, this should be a type error, but it should show up in the module
c: 'c',
},
unocss: {
+30
View File
@@ -1,5 +1,35 @@
# Changelog
## v0.19.17
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.16...wxt-v0.19.17)
### 🚀 Enhancements
- New `server:created`, `server:started`, and `server:closed` hooks ([#1179](https://github.com/wxt-dev/wxt/pull/1179))
### 🩹 Fixes
- Re-initialize WXT modules correctly during development ([#1176](https://github.com/wxt-dev/wxt/pull/1176))
- ESLint config being generated when ESLint is not installed. ([#1198](https://github.com/wxt-dev/wxt/pull/1198))
- Validate entrypoints after hooks have modified them ([54baf21](https://github.com/wxt-dev/wxt/commit/54baf21))
- Revert "Validate entrypoints after hooks have modified them (54baf21)" ([a2924d8](https://github.com/wxt-dev/wxt/commit/a2924d8))
- Update `vite` dependency range to support v6 ([#1215](https://github.com/wxt-dev/wxt/pull/1215))
- Automatically convert MV3 `content_security_policy` to MV2 ([#1168](https://github.com/wxt-dev/wxt/pull/1168))
- Correctly remove child elements with integrated UI remove ([#1219](https://github.com/wxt-dev/wxt/pull/1219))
- Make content script `matches` optional ([#1220](https://github.com/wxt-dev/wxt/pull/1220))
### 📖 Documentation
- Fix analyze typo in type ([#1187](https://github.com/wxt-dev/wxt/pull/1187))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
- 1natsu ([@1natsu172](http://github.com/1natsu172))
- Nishu <nnmurmu@enacton.com>
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v0.19.16
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.15...wxt-v0.19.16)
@@ -152,6 +152,40 @@ describe('Auto Imports', () => {
).toMatchSnapshot();
});
it('"enabled: false" should NOT output an ESlint config file', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare({
imports: {
eslintrc: {
enabled: false,
},
},
});
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
false,
);
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
false,
);
});
it('should NOT output an ESlint config file by default', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare();
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
false,
);
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
false,
);
});
it('should allow customizing the output', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
+19
View File
@@ -21,6 +21,9 @@ const hooks: WxtHooks = {
'zip:sources:start': vi.fn(),
'zip:sources:done': vi.fn(),
'zip:done': vi.fn(),
'server:created': vi.fn(),
'server:started': vi.fn(),
'server:closed': vi.fn(),
};
function expectHooksToBeCalled(
@@ -67,6 +70,9 @@ describe('Hooks', () => {
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
'server:created': false,
'server:started': false,
'server:closed': false,
});
});
@@ -95,6 +101,9 @@ describe('Hooks', () => {
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
'server:created': false,
'server:started': false,
'server:closed': false,
});
});
@@ -123,6 +132,9 @@ describe('Hooks', () => {
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': true,
'server:created': false,
'server:started': false,
'server:closed': false,
});
});
@@ -151,6 +163,9 @@ describe('Hooks', () => {
'zip:sources:start': true,
'zip:sources:done': true,
'zip:done': true,
'server:created': false,
'server:started': false,
'server:closed': false,
});
});
@@ -164,6 +179,7 @@ describe('Hooks', () => {
disabled: true,
},
});
expect(hooks['server:closed']).not.toBeCalled();
await server.stop();
expectHooksToBeCalled({
@@ -185,6 +201,9 @@ describe('Hooks', () => {
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
'server:created': 1,
'server:started': 1,
'server:closed': 1,
});
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ import { normalizePath } from '../../src/core/utils/paths';
describe('Module Helpers', () => {
describe('options', () => {
it('should recieve the options defined in wxt.config.ts based on the configKey field', async () => {
it('should receive the options defined in wxt.config.ts based on the configKey field', async () => {
const options = { key: '123' };
const reportOptions = vi.fn();
vi.stubGlobal('reportOptions', reportOptions);
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.19.16",
"version": "0.19.17",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -100,7 +100,7 @@
"fast-glob": "^3.3.2",
"filesize": "^10.1.6",
"fs-extra": "^11.2.0",
"get-port": "^7.1.0",
"get-port-please": "^3.1.2",
"giget": "^1.2.3",
"hookable": "^5.5.3",
"is-wsl": "^3.1.0",
@@ -122,7 +122,7 @@
"publish-browser-extension": "^2.2.2",
"scule": "^1.3.0",
"unimport": "^3.13.1",
"vite": "^5.4.11",
"vite": "^5.0.0 || ^6.0.0",
"vite-node": "^2.1.4",
"web-ext-run": "^0.2.1",
"webextension-polyfill": "^0.12.0"
@@ -34,6 +34,7 @@ export function createIntegratedUi<TMounted>(
};
const remove = () => {
options.onRemove?.(mounted);
wrapper.replaceChildren();
wrapper.remove();
mounted = undefined;
};
+4 -2
View File
@@ -28,7 +28,7 @@ import { installSourcemapsSupport } from 'vite-node/source-map';
export async function createViteBuilder(
wxtConfig: ResolvedConfig,
hooks: Hookable<WxtHooks>,
server?: WxtDevServer,
getWxtDevServer?: () => WxtDevServer | undefined,
): Promise<WxtBuilder> {
const vite = await import('vite');
@@ -65,6 +65,8 @@ export async function createViteBuilder(
ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`],
};
const server = getWxtDevServer?.();
config.plugins ??= [];
config.plugins.push(
wxtPlugins.download(wxtConfig),
@@ -193,7 +195,7 @@ export async function createViteBuilder(
};
/**
* Return the basic config for building a sinlge CSS entrypoint in [multi-page mode](https://vitejs.dev/guide/build.html#multi-page-app).
* Return the basic config for building a single CSS entrypoint in [multi-page mode](https://vitejs.dev/guide/build.html#multi-page-app).
*/
const getCssConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
return {
+94 -70
View File
@@ -21,7 +21,7 @@ import { createExtensionRunner } from './runners';
import { Mutex } from 'async-mutex';
import pc from 'picocolors';
import { relative } from 'node:path';
import { registerWxt, wxt } from './wxt';
import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt';
import { unnormalizePath } from './utils/paths';
import {
getContentScriptJs,
@@ -40,64 +40,108 @@ import {
export async function createServer(
inlineConfig?: InlineConfig,
): Promise<WxtDevServer> {
await registerWxt('serve', inlineConfig, async (config) => {
const { port, hostname } = config.dev.server!;
const serverInfo: ServerInfo = {
await registerWxt('serve', inlineConfig);
wxt.server = await createServerInternal();
await wxt.hooks.callHook('server:created', wxt, wxt.server);
return wxt.server;
}
async function createServerInternal(): Promise<WxtDevServer> {
const getServerInfo = (): ServerInfo => {
const { port, hostname } = wxt.config.dev.server!;
return {
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),
wxt.builder.createServer(getServerInfo()),
]);
// Used to track if modules need to be re-initialized
let wasStopped = false;
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
get hostname() {
return getServerInfo().hostname;
},
get port() {
return getServerInfo().port;
},
get origin() {
return getServerInfo().origin;
},
get watcher() {
return builderServer.watcher;
},
get ws() {
return builderServer.ws;
},
currentOutput: undefined,
async start() {
if (wasStopped) {
await wxt.reloadConfig();
runner = await createExtensionRunner();
builderServer = await wxt.builder.createServer(getServerInfo());
await initWxtModules();
}
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${server.origin}`);
await wxt.hooks.callHook('server:started', wxt, server);
await buildAndOpenBrowser();
// 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', () => {
if (server.currentOutput == null) return;
reloadContentScripts(server.currentOutput.steps, server);
});
// Listen for file changes and reload different parts of the extension accordingly
const reloadOnChange = createFileReloader(server);
server.watcher.on('all', reloadOnChange);
},
async stop() {
wasStopped = true;
await runner.closeBrowser();
await builderServer.close();
await wxt.hooks.callHook('server:closed', wxt, server);
deinitWxtModules();
server.currentOutput = undefined;
},
async restart() {
await server.stop();
await server.start();
},
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 runner.closeBrowser();
await wxt.reloadConfig();
runner = await createExtensionRunner();
await runner.openBrowser();
},
};
const buildAndOpenBrowser = async () => {
// Build after starting the dev server so it can be used to transform HTML files
server.currentOutput = await internalBuild();
@@ -114,26 +158,6 @@ export async function createServer(
await runner.openBrowser();
};
/**
* Stops the previous runner, grabs the latest config, and recreates the runner.
*/
const closeAndRecreateRunner = async () => {
await runner.closeBrowser();
await wxt.reloadConfig();
runner = await createExtensionRunner();
};
// 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', () => {
if (server.currentOutput == null) return;
reloadContentScripts(server.currentOutput.steps, server);
});
// Listen for file changes and reload different parts of the extension accordingly
const reloadOnChange = createFileReloader(server);
server.watcher.on('all', reloadOnChange);
return server;
}
+11 -4
View File
@@ -27,6 +27,7 @@ import { builtinModules } from '../builtin-modules';
import { getEslintVersion } from './utils/eslint';
import { safeStringToNumber } from './utils/number';
import { loadEnv } from './utils/env';
import { getPort } from 'get-port-please';
/**
* Given an inline config, discover the config file if necessary, merge the results, resolve any
@@ -137,14 +138,19 @@ export async function resolveConfig(
let devServerConfig: ResolvedConfig['dev']['server'];
if (command === 'serve') {
const hostname = mergedConfig.dev?.server?.hostname ?? 'localhost';
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) });
port = await getPort({
port: 3000,
portRange: [3001, 3010],
// Passing host required for Mac, unsure of Windows/Linux
host: hostname,
});
}
devServerConfig = {
port,
hostname: mergedConfig.dev?.server?.hostname ?? 'localhost',
hostname,
watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800,
};
}
@@ -386,10 +392,11 @@ async function getUnimportEslintOptions(
case 'auto':
const version = await getEslintVersion();
let major = parseInt(version[0]);
if (isNaN(major)) eslintEnabled = false;
if (major <= 8) eslintEnabled = 8;
else if (major >= 9) eslintEnabled = 9;
// NaN
else eslintEnabled = 8;
else eslintEnabled = false;
break;
case true:
eslintEnabled = 8;
@@ -1581,6 +1581,41 @@ describe('Manifest Utils', () => {
permissions: ['tabs', 'scripting'],
});
});
it('should convert MV3 CSP object to MV2 CSP string with localhost for MV2', async () => {
const entrypoints: Entrypoint[] = [];
const buildOutput = fakeBuildOutput();
const inputCsp =
"script-src 'self' 'wasm-unsafe-eval'; object-src 'self';";
const expectedCsp =
"script-src 'self' 'wasm-unsafe-eval' http://localhost:3000; object-src 'self';";
// Setup WXT for Firefox and serve command
setFakeWxt({
config: {
browser: 'firefox',
command: 'serve',
manifestVersion: 2,
manifest: {
content_security_policy: {
extension_pages: inputCsp,
},
},
},
server: fakeWxtDevServer({
port: 3000,
hostname: 'localhost',
origin: 'http://localhost:3000',
}),
});
const { manifest: actual } = await generateManifest(
entrypoints,
buildOutput,
);
expect(actual.content_security_policy).toEqual(expectedCsp);
});
});
});
@@ -71,6 +71,33 @@ describe('Detect Dev Changes', () => {
});
});
describe('modules/*', () => {
it("should return 'full-restart' when one of the changed files is in the WXT modules folder", () => {
const modulesDir = '/root/modules';
setFakeWxt({
config: {
modulesDir,
},
});
const changes = [
'/root/src/public/image.svg',
`${modulesDir}/example.ts`,
];
const currentOutput: BuildOutput = {
manifest: fakeManifest(),
publicAssets: [],
steps: [],
};
const expected: DevModeChange = {
type: 'full-restart',
};
const actual = detectDevChanges(changes, currentOutput);
expect(actual).toEqual(expected);
});
});
describe('web-ext.config.ts', () => {
it("should return 'browser-restart' when one of the changed files is the config file", () => {
const runnerFile = '/root/web-ext.config.ts';
@@ -26,7 +26,11 @@ import { wxt } from '../../wxt';
* - Background script is changed
* - Manifest is different
* - Restart browser
* - Config file changed (wxt.config.ts, .env, web-ext.config.ts, etc)
* - web-ext.config.ts (runner config changes)
* - Full dev server restart
* - wxt.config.ts (main config file)
* - modules/* (any file related to WXT modules)
* - .env (environment variable changed could effect build)
*/
export function detectDevChanges(
changedFiles: string[],
@@ -38,6 +42,11 @@ export function detectDevChanges(
);
if (isConfigChange) return { type: 'full-restart' };
const isWxtModuleChange = some(changedFiles, (file) =>
file.startsWith(wxt.config.modulesDir),
);
if (isWxtModuleChange) return { type: 'full-restart' };
const isRunnerChange = some(
changedFiles,
(file) => file === wxt.config.runnerConfig.configFile,
@@ -51,7 +51,7 @@ export function mapWxtOptionsToContentScript(
css: string[] | undefined,
): Manifest.ContentScript {
return {
matches: options.matches,
matches: options.matches ?? [],
all_frames: options.allFrames,
match_about_blank: options.matchAboutBlank,
exclude_globs: options.excludeGlobs,
+38 -26
View File
@@ -117,11 +117,12 @@ export async function generateManifest(
if (wxt.config.manifestVersion === 2) {
convertWebAccessibleResourcesToMv2(manifest);
convertActionToMv2(manifest);
convertCspToMv2(manifest);
moveHostPermissionsToPermissions(manifest);
}
if (wxt.config.manifestVersion === 3) {
validateMv3WebAccessbileResources(manifest);
validateMv3WebAccessibleResources(manifest);
}
stripKeys(manifest);
@@ -143,7 +144,7 @@ export async function generateManifest(
}
/**
* Removes suffixes from the version, like X.Y.Z-alpha1 (which brosers don't allow), so it's a
* Removes suffixes from the version, like X.Y.Z-alpha1 (which browsers don't allow), so it's a
* simple version number, like X or X.Y or X.Y.Z, which browsers allow.
*/
function simplifyVersion(versionName: string): string {
@@ -362,7 +363,7 @@ function addEntrypoints(
// at runtime
if (wxt.config.command === 'serve' && wxt.config.manifestVersion === 3) {
contentScripts.forEach((script) => {
script.options.matches.forEach((matchPattern) => {
script.options.matches?.forEach((matchPattern) => {
addHostPermission(manifest, matchPattern);
});
});
@@ -405,7 +406,7 @@ function addEntrypoints(
);
}
runtimeContentScripts.forEach((script) => {
script.options.matches.forEach((matchPattern) => {
script.options.matches?.forEach((matchPattern) => {
addHostPermission(manifest, matchPattern);
});
});
@@ -467,34 +468,28 @@ function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
}
const extensionPagesCsp = new ContentSecurityPolicy(
manifest.manifest_version === 3
? // @ts-expect-error: extension_pages is not typed
(manifest.content_security_policy?.extension_pages ??
"script-src 'self' 'wasm-unsafe-eval'; object-src 'self';") // default extension_pages CSP for MV3
: (manifest.content_security_policy ??
"script-src 'self'; object-src 'self';"), // default CSP for MV2
// @ts-expect-error: extension_pages exists, we convert MV2 CSPs to this earlier in the process
manifest.content_security_policy?.extension_pages ??
(manifest.manifest_version === 3
? DEFAULT_MV3_EXTENSION_PAGES_CSP
: DEFAULT_MV2_CSP),
);
const sandboxCsp = new ContentSecurityPolicy(
// @ts-expect-error: sandbox is not typed
manifest.content_security_policy?.sandbox ??
"sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';", // default sandbox CSP for MV3
manifest.content_security_policy?.sandbox ?? DEFAULT_MV3_SANDBOX_CSP,
);
if (wxt.server) {
if (wxt.config.command === 'serve') {
extensionPagesCsp.add('script-src', allowedCsp);
sandboxCsp.add('script-src', allowedCsp);
}
if (manifest.manifest_version === 3) {
manifest.content_security_policy ??= {};
// @ts-expect-error: extension_pages is not typed
manifest.content_security_policy.extension_pages =
extensionPagesCsp.toString();
// @ts-expect-error: sandbox is not typed
manifest.content_security_policy.sandbox = sandboxCsp.toString();
} else {
manifest.content_security_policy = extensionPagesCsp.toString();
}
manifest.content_security_policy ??= {};
// @ts-expect-error: extension_pages is not typed
manifest.content_security_policy.extension_pages =
extensionPagesCsp.toString();
// @ts-expect-error: sandbox is not typed
manifest.content_security_policy.sandbox = sandboxCsp.toString();
}
function addDevModePermissions(manifest: Manifest.WebExtensionManifest) {
@@ -552,7 +547,7 @@ export function getContentScriptCssWebAccessibleResources(
resources.push({
resources: [cssFile],
matches: script.options.matches.map((matchPattern) =>
matches: script.options.matches?.map((matchPattern) =>
stripPathFromMatchPattern(matchPattern),
),
});
@@ -613,7 +608,7 @@ export function stripPathFromMatchPattern(pattern: string) {
/**
* Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are
* generated in this file, and may be defined by the user in their manifest. In both cases, when
* targetting MV2, automatically convert their definitions down to the basic MV2 array.
* targeting MV2, automatically convert their definitions down to the basic MV2 array.
*/
export function convertWebAccessibleResourcesToMv2(
manifest: Manifest.WebExtensionManifest,
@@ -652,10 +647,21 @@ function convertActionToMv2(manifest: Manifest.WebExtensionManifest): void {
manifest.browser_action = manifest.action;
}
function convertCspToMv2(manifest: Manifest.WebExtensionManifest): void {
if (
typeof manifest.content_security_policy === 'string' ||
manifest.content_security_policy?.extension_pages == null
)
return;
manifest.content_security_policy =
manifest.content_security_policy.extension_pages;
}
/**
* Make sure all resources are in MV3 format. If not, add a wanring
*/
export function validateMv3WebAccessbileResources(
export function validateMv3WebAccessibleResources(
manifest: Manifest.WebExtensionManifest,
): void {
if (manifest.web_accessible_resources == null) return;
@@ -718,3 +724,9 @@ const mv3OnlyKeys = [
'side_panel',
];
const firefoxMv3OnlyKeys = ['host_permissions'];
const DEFAULT_MV3_EXTENSION_PAGES_CSP =
"script-src 'self' 'wasm-unsafe-eval'; object-src 'self';";
const DEFAULT_MV3_SANDBOX_CSP =
"sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';";
const DEFAULT_MV2_CSP = "script-src 'self'; object-src 'self';";
+30 -26
View File
@@ -1,12 +1,4 @@
import {
InlineConfig,
ResolvedConfig,
Wxt,
WxtCommand,
WxtDevServer,
WxtHooks,
WxtModule,
} from '../types';
import { InlineConfig, Wxt, WxtCommand, WxtHooks, WxtModule } from '../types';
import { resolveConfig } from './resolve-config';
import { createHooks } from 'hookable';
import { createWxtPackageManager } from './package-managers';
@@ -26,7 +18,6 @@ export let wxt: Wxt;
export async function registerWxt(
command: WxtCommand,
inlineConfig: InlineConfig = {},
getServer?: (config: ResolvedConfig) => Promise<WxtDevServer>,
): Promise<void> {
// Default NODE_ENV environment variable before other packages, like vite, do it
// See https://github.com/wxt-dev/wxt/issues/873#issuecomment-2254555523
@@ -34,8 +25,7 @@ export async function registerWxt(
const hooks = createHooks<WxtHooks>();
const config = await resolveConfig(inlineConfig, command);
const server = await getServer?.(config);
const builder = await createViteBuilder(config, hooks, server);
const builder = await createViteBuilder(config, hooks, () => wxt.server);
const pm = await createWxtPackageManager(config.root);
wxt = {
@@ -51,27 +41,25 @@ export async function registerWxt(
},
pm,
builder,
server,
server: undefined,
};
// Initialize modules
const initModule = async (module: WxtModule<any>) => {
if (module.hooks) wxt.hooks.addHooks(module.hooks);
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);
await initWxtModules();
}
export async function initWxtModules() {
// Call setup function and add hooks
for (const mod of builtinModules) await initWxtModule(mod);
for (const mod of wxt.config.userModules) await initWxtModule(mod);
// Initialize hooks
wxt.hooks.addHooks(config.hooks);
wxt.hooks.addHooks(wxt.config.hooks);
// Print order for debugging
if (wxt.config.debug) {
const order = [
...builtinModules.map((module) => module.name),
...config.userModules.map((module) =>
...wxt.config.userModules.map((module) =>
relative(wxt.config.root, module.id),
),
'wxt.config.ts > hooks',
@@ -86,6 +74,22 @@ export async function registerWxt(
await wxt.hooks.callHook('config:resolved', wxt);
}
async function initWxtModule(module: WxtModule<any>): Promise<void> {
if (module.hooks) wxt.hooks.addHooks(module.hooks);
await module.setup?.(
wxt,
// @ts-expect-error: Untyped configKey field
module.configKey ? wxt.config[module.configKey] : undefined,
);
}
/**
* Unloads WXT modules.
*/
export function deinitWxtModules(): void {
wxt.hooks.removeAllHooks();
}
/**
* @internal ONLY USE FOR TESTING.
*
+23 -9
View File
@@ -271,7 +271,7 @@ export interface InlineConfig {
analysis?: {
/**
* Explicitly include bundle analysis when running `wxt build`. This can be overridden by the
* command line `--analysis` option.
* command line `--analyze` option.
*
* @default false
*/
@@ -588,7 +588,7 @@ export interface BackgroundEntrypointOptions extends BaseEntrypointOptions {
export interface BaseContentScriptEntrypointOptions
extends BaseEntrypointOptions {
matches: PerBrowserOption<Manifest.ContentScript['matches']>;
matches?: PerBrowserOption<Manifest.ContentScript['matches']>;
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default "documentIdle"
@@ -1100,7 +1100,8 @@ export type HookResult = Promise<void> | void;
export interface WxtHooks {
/**
* Called only one time after WXT initialization, when the WXT instance is ready to work.
* Called after WXT modules are initialized, when the WXT instance is ready to
* be used. `wxt.server` isn't available yet, use `server:created` to get it.
* @param wxt The configured WXT object
*/
ready: (wxt: Wxt) => HookResult;
@@ -1196,39 +1197,52 @@ export interface WxtHooks {
* @param wxt The configured WXT object
*/
'zip:start': (wxt: Wxt) => HookResult;
/**
* Called before zipping the extension files.
* @param wxt The configured WXT object
*/
'zip:extension:start': (wxt: Wxt) => HookResult;
/**
* Called after zipping the extension files.
* @param wxt The configured WXT object
* @param zipPath The path to the created extension zip file
*/
'zip:extension:done': (wxt: Wxt, zipPath: string) => HookResult;
/**
* Called before zipping the source files (for Firefox).
* @param wxt The configured WXT object
*/
'zip:sources:start': (wxt: Wxt) => HookResult;
/**
* Called after zipping the source files (for Firefox).
* @param wxt The configured WXT object
* @param zipPath The path to the created sources zip file
*/
'zip:sources:done': (wxt: Wxt, zipPath: string) => HookResult;
/**
* Called after the entire zip process is complete.
* @param wxt The configured WXT object
* @param zipFiles An array of paths to all created zip files
*/
'zip:done': (wxt: Wxt, zipFiles: string[]) => HookResult;
/**
* Called when the dev server is created (and `wxt.server` is assigned). Server has not been started yet.
* @param wxt The configured WXT object
* @param server Same as `wxt.server`, the object WXT uses to control the dev server.
*/
'server:created': (wxt: Wxt, server: WxtDevServer) => HookResult;
/**
* Called when the dev server is started.
* @param wxt The configured WXT object
* @param server Same as `wxt.server`, the object WXT uses to control the dev server.
*/
'server:started': (wxt: Wxt, server: WxtDevServer) => HookResult;
/**
* Called when the dev server is stopped.
* @param wxt The configured WXT object
* @param server Same as `wxt.server`, the object WXT uses to control the dev server.
*/
'server:closed': (wxt: Wxt, server: WxtDevServer) => HookResult;
}
export interface Wxt {
@@ -1243,7 +1257,7 @@ export interface Wxt {
*/
logger: Logger;
/**
* Reload config file and update the `config` field with the result.
* Reload config file and update `wxt.config` with the result.
*/
reloadConfig: () => Promise<void>;
/**
+20 -8
View File
@@ -58,6 +58,9 @@ importers:
typedoc:
specifier: ^0.25.4
version: 0.25.4(typescript@5.6.3)
typedoc-plugin-frontmatter:
specifier: ^1.1.0
version: 1.1.0(typedoc-plugin-markdown@4.0.0-next.23(typedoc@0.25.4(typescript@5.6.3)))
typedoc-plugin-markdown:
specifier: 4.0.0-next.23
version: 4.0.0-next.23(typedoc@0.25.4(typescript@5.6.3))
@@ -384,9 +387,9 @@ importers:
fs-extra:
specifier: ^11.2.0
version: 11.2.0
get-port:
specifier: ^7.1.0
version: 7.1.0
get-port-please:
specifier: ^3.1.2
version: 3.1.2
giget:
specifier: ^1.2.3
version: 1.2.3
@@ -451,7 +454,7 @@ importers:
specifier: ^3.13.1
version: 3.13.1(rollup@4.24.0)(webpack-sources@3.2.3)
vite:
specifier: ^5.4.11
specifier: ^5.0.0 || ^6.0.0
version: 5.4.11(@types/node@20.17.6)(sass@1.80.7)
vite-node:
specifier: ^2.1.4
@@ -3006,9 +3009,8 @@ packages:
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
get-port@7.1.0:
resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==}
engines: {node: '>=16'}
get-port-please@3.1.2:
resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==}
get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
@@ -4683,6 +4685,11 @@ packages:
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typedoc-plugin-frontmatter@1.1.0:
resolution: {integrity: sha512-4PW4V2xDY2hw+fEWmg7g6FBCIWZdiEE+tzjJ5K4JhurvJ0t0Vp0IE/0nuHGGIZVtV5WxPIed+GpiH1uZrpDquQ==}
peerDependencies:
typedoc-plugin-markdown: '>=4.3.0'
typedoc-plugin-markdown@4.0.0-next.23:
resolution: {integrity: sha512-bKD0LnrQxUTbuDRiJfCWYLlzyERuskWldJ5eWKKuNY38xBOyBhSN8P+vD5KxlJoAifTOm76MK58sNrl4wgOoWg==}
peerDependencies:
@@ -7586,7 +7593,7 @@ snapshots:
get-func-name@2.0.2: {}
get-port@7.1.0: {}
get-port-please@3.1.2: {}
get-stream@5.2.0:
dependencies:
@@ -9295,6 +9302,11 @@ snapshots:
typedarray@0.0.6: {}
typedoc-plugin-frontmatter@1.1.0(typedoc-plugin-markdown@4.0.0-next.23(typedoc@0.25.4(typescript@5.6.3))):
dependencies:
typedoc-plugin-markdown: 4.0.0-next.23(typedoc@0.25.4(typescript@5.6.3))
yaml: 2.5.1
typedoc-plugin-markdown@4.0.0-next.23(typedoc@0.25.4(typescript@5.6.3)):
dependencies:
typedoc: 0.25.4(typescript@5.6.3)
@@ -1,7 +1,8 @@
import './app.css';
import { mount } from 'svelte';
import App from './App.svelte';
import './app.css';
const app = new App({
const app = mount(App, {
target: document.getElementById('app')!,
});
+1 -1
View File
@@ -4,4 +4,4 @@ This template should help get you started developing with Vue 3 in WXT.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar).
+1 -1
View File
@@ -20,7 +20,7 @@
"devDependencies": {
"@types/chrome": "^0.0.280",
"@wxt-dev/module-vue": "^1.0.1",
"typescript": "^5.6.3",
"typescript": "5.6.3",
"vue-tsc": "^2.1.10",
"wxt": "^0.19.13"
}