Compare commits

...

22 Commits

Author SHA1 Message Date
GitHub Actions cb143d2bbc chore(release): wxt v0.19.18
vhs / vhs (push) Waiting to run
2024-12-03 23:00:59 +00:00
Aaron f3e7758521 types: Add overloads to ContentScriptContext#addEventListener for different targets (#1245) 2024-12-03 16:55:33 -06:00
Aaron 23ef101954 chore: Refactor findEntrypoints to return all entrypoints with skipped set properly (#1244) 2024-12-03 12:36:11 -06:00
spookyuser c2f5efbde4 docs: Update injectScript docs to match example from examples repo (#1240)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-12-03 12:23:33 -06:00
Takuya HARA 4ce178c592 docs: Changed "Chrome Extension" to "web extension" (#1229)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-12-03 11:07:46 -06:00
dependabot[bot] 7aa30afa7f chore(deps): bump codecov/codecov-action from 4 to 5 (#1237)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 16:13:47 -06:00
GitHub Actions 90aaed202c chore(release): @wxt-dev/module-svelte v2.0.1
vhs / vhs (push) Waiting to run
2024-12-01 15:33:52 +00:00
Aaron c92ed13d66 fix: Modify dev vite.resolve.conditions to support Vite 6 + Svelte 5 (#1230) 2024-12-01 09:28:30 -06:00
Jaguar Zhou 0e4953fbfc fix: correct dev.reloadCommand restriction to consider only commands with suggested keys (#1226) 2024-11-29 12:07:09 -06:00
roechsli 799d687061 docs: Add "QA Compass" to homepage (#1223) 2024-11-28 10:34:20 -06:00
Aaron 866eb0694c Cleanup changelog 2024-11-28 08:48:40 -06:00
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
32 changed files with 487 additions and 160 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
- name: pnpm test:coverage
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
windows-tests:
@@ -58,6 +58,7 @@ const chromeExtensionIds = [
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+1 -1
View File
@@ -18,7 +18,7 @@ import { version as storageVersion } from '../../packages/storage/package.json';
const title = 'Next-gen Web Extension Framework';
const titleSuffix = ' WXT';
const description =
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
"WXT provides the best developer experience, making it quick, easy, and fun to develop web extensions. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
const ogTitle = `${title}${titleSuffix}`;
const ogUrl = 'https://wxt.dev';
const ogImage = 'https://wxt.dev/social-preview.png';
@@ -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,
});
```
+10 -5
View File
@@ -548,16 +548,21 @@ To use `injectScript`, we need two entrypoints, one content script and one unlis
```ts
// entrypoints/example-main-world.ts
export default defineUnlistedScript(() => {
console.log('Hello from the main world!');
console.log('Hello from the main world');
});
```
```ts
// entrypoints/example.content.ts
export default defineContentScript(async () => {
await injectScript('/example-main-world.js', {
keepInDom: true,
});
export default defineContentScript({
matches: ['*://*/*'],
async main() {
console.log('Injecting script...');
await injectScript('/example-main-world.js', {
keepInDom: true,
});
console.log('Done!');
},
});
```
+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!';
},
});
```
@@ -75,3 +75,5 @@ Here are some examples:
</body>
</html>
```
Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build.
+1 -1
View File
@@ -9,7 +9,7 @@ Check out the [comparison](/guide/resources/compare) to see how WXT compares to
## Prerequisites
These docs assume you have a basic knowledge of how Chrome extensions are structured and how you access the extension APIs.
These docs assume you have a basic knowledge of how web extensions are structured and how you access the extension APIs.
:::warning New to extension development?
If you have never written an extension before, follow Chrome's [Hello World tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to first **_create an extension without WXT_**, then come back here.
+3 -3
View File
@@ -6,7 +6,7 @@ title: Next-gen Web Extension Framework
hero:
name: WXT
text: Next-gen Web Extension Framework
tagline: An open source tool that makes Chrome Extension development faster than ever before.
tagline: An open source tool that makes web extension development faster than ever before.
image:
src: /hero-logo.svg
alt: WXT
@@ -75,7 +75,7 @@ features:
## Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First
WXT simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
WXT simplifies the web extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
<div style="margin: auto; width: 100%; max-width: 900px; text-align: center">
<video src="https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94" controls></video>
@@ -87,7 +87,7 @@ WXT simplifies the chrome extension development process by providing tools for z
## Who's Using WXT?
Battle tested and ready for production. Explore chrome extensions made with WXT.
Battle tested and ready for production. Explore web extensions made with WXT.
<ClientOnly>
<UsingWxtSection />
+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",
+17 -1
View File
@@ -1,5 +1,21 @@
# Changelog
## v2.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.0...module-svelte-v2.0.1)
### 🩹 Fixes
- Modify dev `vite.resolve.conditions` to support Vite 6 + Svelte 5 ([#1230](https://github.com/wxt-dev/wxt/pull/1230))
### 🏡 Chore
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v2.0.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v1.0.1...module-svelte-v2.0.0)
@@ -51,4 +67,4 @@ To upgrade to svelte 5, just install `svelte@5`.
## v1.0.0
Initial release 🎉
Initial release 🎉
+1 -1
View File
@@ -17,7 +17,7 @@
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "2.0.0",
"version": "2.0.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
+4 -1
View File
@@ -12,7 +12,7 @@ export default defineWxtModule<SvelteModuleOptions>({
setup(wxt, options) {
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
addViteConfig(wxt, ({ mode }) => ({
plugins: [
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
@@ -21,6 +21,9 @@ export default defineWxtModule<SvelteModuleOptions>({
...vite,
}),
],
resolve: {
conditions: mode === 'development' ? ['browser'] : [],
},
}));
addImportPreset(wxt, 'svelte');
+49
View File
@@ -1,5 +1,54 @@
# Changelog
## v0.19.18
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.17...wxt-v0.19.18)
### 🩹 Fixes
- Correct `dev.reloadCommand` restriction to consider only commands with suggested keys ([#1226](https://github.com/wxt-dev/wxt/pull/1226))
### 🌊 Types
- Add overloads to `ContentScriptContext#addEventListener` for different targets ([#1245](https://github.com/wxt-dev/wxt/pull/1245))
### 🏡 Chore
- Refactor `findEntrypoints` to return all entrypoints with `skipped` set properly ([#1244](https://github.com/wxt-dev/wxt/pull/1244))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
- Jaguar Zhou ([@aiktb](http://github.com/aiktb))
## 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))
- 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 ([@nishu-murmu](https://github.com/nishu-murmu))
- 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>`);
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.19.16",
"version": "0.19.18",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -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"
@@ -3,7 +3,15 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ContentScriptContext } from '..';
import { fakeBrowser } from '@webext-core/fake-browser';
import { sleep } from '../../../core/utils/time';
/**
* When dispatching events on document/window/etc, they are fired on the next
* tick of the event loop. So waiting a timeout of 0 will ensure they've been
* fired.
*/
function waitForEventsToFire() {
return new Promise((res) => setTimeout(res));
}
describe('Content Script Context', () => {
beforeEach(() => {
@@ -31,11 +39,11 @@ describe('Content Script Context', () => {
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
await waitForEventsToFire();
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext(name);
await sleep(0);
await waitForEventsToFire();
expect(onInvalidated).toBeCalled();
expect(ctx.isValid).toBe(false);
@@ -47,13 +55,35 @@ describe('Content Script Context', () => {
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
await waitForEventsToFire();
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext('test2');
await sleep(0);
await waitForEventsToFire();
expect(onInvalidated).not.toBeCalled();
expect(ctx.isValid).toBe(true);
});
describe('addEventListener', () => {
const context = new ContentScriptContext('test');
it('should infer types correctly for the window target', () => {
context.addEventListener(window, 'DOMContentLoaded', (_) => {});
context.addEventListener(window, 'orientationchange', (_) => {});
context.addEventListener(window, 'wxt:locationchange', (_) => {});
// @ts-expect-error
context.addEventListener(window, 'visibilitychange', (_) => {});
});
it('should infer types correctly for the document target', () => {
context.addEventListener(document, 'visibilitychange', (_) => {});
context.addEventListener(document, 'readystatechange', (_) => {});
});
it('should infer types correctly for HTML element targets', () => {
const button = document.createElement('button');
context.addEventListener(button, 'click', (_) => {});
context.addEventListener(button, 'mouseover', (_) => {});
});
});
});
@@ -175,19 +175,32 @@ export class ContentScriptContext implements AbortController {
* ctx.addEventListener(document, "visibilitychange", () => {
* // ...
* });
* ctx.addEventListener(document, "wxt:locationchange", () => {
* ctx.addEventListener(window, "wxt:locationchange", () => {
* // ...
* });
*/
addEventListener<
TTarget extends EventTarget,
TType extends keyof WxtContentScriptEventMap,
>(
target: TTarget,
addEventListener<TType extends keyof WxtWindowEventMap>(
target: Window,
type: TType,
handler: (event: WxtContentScriptEventMap[TType]) => void,
handler: (event: WxtWindowEventMap[TType]) => void,
options?: AddEventListenerOptions,
) {
): void;
addEventListener<TType extends keyof DocumentEventMap>(
target: Document,
type: keyof DocumentEventMap,
handler: (event: DocumentEventMap[TType]) => void,
options?: AddEventListenerOptions,
): void;
addEventListener<TTarget extends EventTarget>(
target: TTarget,
...params: Parameters<TTarget['addEventListener']>
): void;
addEventListener(
target: EventTarget,
type: string,
handler: (event: Event) => void,
options?: AddEventListenerOptions,
): void {
if (type === 'wxt:locationchange') {
// Start the location watcher when adding the event for the first time
if (this.isValid) this.locationWatcher.run();
@@ -195,7 +208,6 @@ export class ContentScriptContext implements AbortController {
target.addEventListener?.(
type.startsWith('wxt:') ? getUniqueEventName(type) : type,
// @ts-expect-error: Event don't match, but that's OK, EventTarget doesn't allow custom types in the callback
handler,
{
...options,
@@ -247,6 +259,6 @@ export class ContentScriptContext implements AbortController {
}
}
interface WxtContentScriptEventMap extends WindowEventMap {
export interface WxtWindowEventMap extends WindowEventMap {
'wxt:locationchange': WxtLocationChangeEvent;
}
@@ -34,6 +34,7 @@ export function createIntegratedUi<TMounted>(
};
const remove = () => {
options.onRemove?.(mounted);
wrapper.replaceChildren();
wrapper.remove();
mounted = undefined;
};
+2 -1
View File
@@ -392,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);
});
});
});
@@ -701,7 +701,7 @@ describe('findEntrypoints', () => {
});
describe('include option', () => {
it("should filter out the background when include doesn't contain the target browser", async () => {
it("should mark the background as skipped when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointMock.mockResolvedValue({
include: ['not' + config.browser],
@@ -709,10 +709,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'background',
skipped: true,
}),
]);
});
it("should filter out content scripts when include doesn't contain the target browser", async () => {
it("should mark content scripts as skipped when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['example.content.ts']);
importEntrypointMock.mockResolvedValue({
include: ['not' + config.browser],
@@ -720,10 +725,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'example',
skipped: true,
}),
]);
});
it("should filter out the popup when include doesn't contain the target browser", async () => {
it("should mark the popup as skipped when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['popup.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -737,10 +747,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'popup',
skipped: true,
}),
]);
});
it("should filter out the options page when include doesn't contain the target browser", async () => {
it("should mark the options page as skipped when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['options.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -754,10 +769,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'options',
skipped: true,
}),
]);
});
it("should filter out an unlisted page when include doesn't contain the target browser", async () => {
it("should mark unlisted pages as skipped when include doesn't contain the target browser", async () => {
globMock.mockResolvedValueOnce(['unlisted.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -771,12 +791,17 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'unlisted',
skipped: true,
}),
]);
});
});
describe('exclude option', () => {
it('should filter out the background when exclude contains the target browser', async () => {
it('should mark the background as skipped when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointMock.mockResolvedValue({
exclude: [config.browser],
@@ -784,10 +809,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'background',
skipped: true,
}),
]);
});
it('should filter out content scripts when exclude contains the target browser', async () => {
it('should mark content scripts as skipped when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['example.content.ts']);
importEntrypointMock.mockResolvedValue({
exclude: [config.browser],
@@ -795,10 +825,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'example',
skipped: true,
}),
]);
});
it('should filter out the popup when exclude contains the target browser', async () => {
it('should mark the popup as skipped when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['popup.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -810,10 +845,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'popup',
skipped: true,
}),
]);
});
it('should filter out the options page when exclude contains the target browser', async () => {
it('should mark the options page as skipped when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['options.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -825,10 +865,15 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'options',
skipped: true,
}),
]);
});
it('should filter out an unlisted page when exclude contains the target browser', async () => {
it('should mark unlisted pages as skipped when exclude contains the target browser', async () => {
globMock.mockResolvedValueOnce(['unlisted.html']);
readFileMock.mockResolvedValueOnce(
`<html>
@@ -840,12 +885,17 @@ describe('findEntrypoints', () => {
const entrypoints = await findEntrypoints();
expect(entrypoints).toEqual([]);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'unlisted',
skipped: true,
}),
]);
});
});
describe('filterEntrypoints option', () => {
it('should control entrypoints accessible', async () => {
it('should override include/exclude of individual entrypoint options', async () => {
globMock.mockResolvedValue([
'options/index.html',
'popup/index.html',
@@ -867,9 +917,25 @@ describe('findEntrypoints', () => {
importEntrypointMock.mockResolvedValue({});
const entrypoints = await findEntrypoints();
const names = entrypoints.map((item) => item.name);
expect(names).toHaveLength(2);
expect(names).toEqual(filterEntrypoints);
expect(entrypoints).toEqual([
expect.objectContaining({
name: 'injected',
skipped: true,
}),
expect.objectContaining({
name: 'options',
skipped: true,
}),
expect.objectContaining({
name: 'popup',
skipped: false,
}),
expect.objectContaining({
name: 'ui',
skipped: false,
}),
]);
});
});
});
@@ -152,12 +152,16 @@ describe('groupEntrypoints', () => {
options: {
type: 'module',
},
skipped: false,
});
const popup = fakePopupEntrypoint({
skipped: false,
});
const popup = fakePopupEntrypoint();
const sandbox = fakeGenericEntrypoint({
inputPath: '/entrypoints/sandbox.html',
name: 'sandbox',
type: 'sandbox',
skipped: false,
});
const actual = groupEntrypoints([background, popup, sandbox]);
@@ -165,6 +169,22 @@ describe('groupEntrypoints', () => {
expect(actual).toEqual([[background, popup], [sandbox]]);
});
it('should exclude skipped entrypoints from the groups to build', () => {
const background = fakeBackgroundEntrypoint({
options: {
type: 'module',
},
skipped: false,
});
const popup = fakePopupEntrypoint({
skipped: true,
});
const actual = groupEntrypoints([background, popup]);
expect(actual).toEqual([[background]]);
});
it.todo(
'should group ESM compatible sandbox scripts with sandbox pages',
() => {
@@ -65,14 +65,7 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
);
if (matchingGlob) {
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
results.push({
name,
inputPath,
type,
skipped:
wxt.config.filterEntrypoints != null &&
!wxt.config.filterEntrypoints.has(name),
});
results.push({ name, inputPath, type });
}
return results;
}, []);
@@ -84,7 +77,7 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
// Import entrypoints to get their config
let hasBackground = false;
const env = createExtensionEnvironment();
const entrypoints: Entrypoint[] = await env.run(() =>
const entrypointsWithoutSkipped: Entrypoint[] = await env.run(() =>
Promise.all(
entrypointInfos.map(async (info): Promise<Entrypoint> => {
const { type } = info;
@@ -130,61 +123,44 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
);
if (wxt.config.command === 'serve' && !hasBackground) {
entrypoints.push(
entrypointsWithoutSkipped.push(
await getBackgroundEntrypoint({
inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
name: 'background',
type: 'background',
skipped: false,
}),
);
}
// Mark entrypoints as skipped or not
const entrypoints = entrypointsWithoutSkipped.map((entry) => ({
...entry,
skipped: isEntrypointSkipped(entry),
}));
wxt.logger.debug('All entrypoints:', entrypoints);
const skippedEntrypointNames = entrypointInfos
const skippedEntrypointNames = entrypoints
.filter((item) => item.skipped)
.map((item) => item.name);
if (skippedEntrypointNames.length) {
wxt.logger.warn(
`Filter excluded the following entrypoints:\n${skippedEntrypointNames
.map((item) => `${pc.dim('-')} ${pc.cyan(item)}`)
.join('\n')}`,
[
'The following entrypoints have been skipped:',
...skippedEntrypointNames.map(
(item) => `${pc.dim('-')} ${pc.cyan(item)}`,
),
].join('\n'),
);
}
const targetEntrypoints = entrypoints.filter((entry) => {
const { include, exclude } = entry.options;
if (include?.length && exclude?.length) {
wxt.logger.warn(
`The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`,
);
return false;
}
if (exclude?.length && !include?.length) {
return !exclude.includes(wxt.config.browser);
}
if (include?.length && !exclude?.length) {
return include.includes(wxt.config.browser);
}
if (skippedEntrypointNames.includes(entry.name)) {
return false;
}
await wxt.hooks.callHook('entrypoints:resolved', wxt, entrypoints);
return true;
});
wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
await wxt.hooks.callHook('entrypoints:resolved', wxt, targetEntrypoints);
return targetEntrypoints;
return entrypoints;
}
interface EntrypointInfo {
name: string;
inputPath: string;
type: Entrypoint['type'];
/**
* @default false
*/
skipped: boolean;
}
function preventDuplicateEntrypointNames(files: EntrypointInfo[]) {
@@ -252,7 +228,6 @@ async function getPopupEntrypoint(
options: resolvePerBrowserOptions(options, wxt.config.browser),
inputPath: info.inputPath,
outputDir: wxt.config.outDir,
skipped: info.skipped,
};
}
@@ -275,7 +250,6 @@ async function getOptionsEntrypoint(
options: resolvePerBrowserOptions(options, wxt.config.browser),
inputPath: info.inputPath,
outputDir: wxt.config.outDir,
skipped: info.skipped,
};
}
@@ -293,14 +267,12 @@ async function getUnlistedPageEntrypoint(
inputPath: info.inputPath,
outputDir: wxt.config.outDir,
options,
skipped: info.skipped,
};
}
async function getUnlistedScriptEntrypoint({
inputPath,
name,
skipped,
}: EntrypointInfo): Promise<GenericEntrypoint> {
const defaultExport =
await wxt.builder.importEntrypoint<UnlistedScriptDefinition>(inputPath);
@@ -316,14 +288,12 @@ async function getUnlistedScriptEntrypoint({
inputPath,
outputDir: wxt.config.outDir,
options: resolvePerBrowserOptions(options, wxt.config.browser),
skipped,
};
}
async function getBackgroundEntrypoint({
inputPath,
name,
skipped,
}: EntrypointInfo): Promise<BackgroundEntrypoint> {
let options: Omit<BackgroundDefinition, 'main'> = {};
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
@@ -348,14 +318,12 @@ async function getBackgroundEntrypoint({
inputPath,
outputDir: wxt.config.outDir,
options: resolvePerBrowserOptions(options, wxt.config.browser),
skipped,
};
}
async function getContentScriptEntrypoint({
inputPath,
name,
skipped,
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
const defaultExport =
await wxt.builder.importEntrypoint<ContentScriptDefinition>(inputPath);
@@ -377,7 +345,6 @@ async function getContentScriptEntrypoint({
inputPath,
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
options: resolvePerBrowserOptions(options, wxt.config.browser),
skipped,
};
}
@@ -409,7 +376,6 @@ async function getSidepanelEntrypoint(
options: resolvePerBrowserOptions(options, wxt.config.browser),
inputPath: info.inputPath,
outputDir: wxt.config.outDir,
skipped: info.skipped,
};
}
@@ -457,6 +423,29 @@ async function getHtmlEntrypointOptions<T extends BaseEntrypointOptions>(
return options;
}
function isEntrypointSkipped(entry: Omit<Entrypoint, 'skipped'>): boolean {
if (wxt.config.filterEntrypoints != null) {
return !wxt.config.filterEntrypoints.has(entry.name);
}
const { include, exclude } = entry.options;
if (include?.length && exclude?.length) {
wxt.logger.warn(
`The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint skipped.`,
);
return true;
}
if (exclude?.length && !include?.length) {
return exclude.includes(wxt.config.browser);
}
if (include?.length && !exclude?.length) {
return !include.includes(wxt.config.browser);
}
return false;
}
const PATH_GLOB_TO_TYPE_MAP: Record<string, Entrypoint['type']> = {
'sandbox.html': 'sandbox',
'sandbox/index.html': 'sandbox',
@@ -1,8 +1,8 @@
import { Entrypoint, EntrypointGroup } from '../../../types';
/**
* Entrypoints can be build in groups. HTML pages can all be built together in a single step, while
* content scripts must be build individually.
* Entrypoints are built in groups. HTML pages can all be built together in a single step,
* content scripts must be build individually, etc.
*
* This function returns the entrypoints put into these types of groups.
*/
@@ -11,6 +11,8 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] {
const groups: EntrypointGroup[] = [];
for (const entry of entrypoints) {
if (entry.skipped) continue;
let group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
if (entry.type === 'background' && entry.options.type === 'module') {
group = 'esm';
@@ -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,
+47 -28
View File
@@ -82,9 +82,16 @@ export async function generateManifest(
// Add reload command in dev mode
if (wxt.config.command === 'serve' && wxt.config.dev.reloadCommand) {
if (manifest.commands && Object.keys(manifest.commands).length >= 4) {
if (
manifest.commands &&
// If the following limit is exceeded, Chrome will fail to load the extension.
// Error: "Too many commands specified for 'commands': The maximum is 4."
Object.values(manifest.commands).filter(
(command) => command.suggested_key,
).length >= 4
) {
warnings.push([
"Extension already has 4 registered commands, WXT's reload command is disabled",
"Extension already has 4 registered commands with suggested keys, WXT's reload command is disabled",
]);
} else {
manifest.commands ??= {};
@@ -117,11 +124,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 +151,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 +370,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 +413,7 @@ function addEntrypoints(
);
}
runtimeContentScripts.forEach((script) => {
script.options.matches.forEach((matchPattern) => {
script.options.matches?.forEach((matchPattern) => {
addHostPermission(manifest, matchPattern);
});
});
@@ -467,34 +475,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 +554,7 @@ export function getContentScriptCssWebAccessibleResources(
resources.push({
resources: [cssFile],
matches: script.options.matches.map((matchPattern) =>
matches: script.options.matches?.map((matchPattern) =>
stripPathFromMatchPattern(matchPattern),
),
});
@@ -613,7 +615,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 +654,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 +731,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';";
@@ -4,7 +4,7 @@
import { resolve } from 'path';
import { faker } from '@faker-js/faker';
import merge from 'lodash.merge';
import { Commands, type Manifest } from 'wxt/browser';
import type { Manifest } from 'wxt/browser';
import {
FsCache,
ResolvedConfig,
@@ -355,13 +355,18 @@ export const fakeBuildStepOutput = fakeObjectCreator<BuildStepOutput>(() => ({
entrypoints: fakeArray(fakeEntrypoint),
}));
export const fakeManifestCommand = fakeObjectCreator<Commands.Command>(() => ({
description: faker.string.sample(),
shortcut: `${faker.helpers.arrayElement(['ctrl', 'alt'])}+${faker.number.int({
min: 0,
max: 9,
})}`,
}));
export const fakeManifestCommand =
fakeObjectCreator<Manifest.WebExtensionManifestCommandsType>(() => ({
description: faker.string.sample(),
suggested_key: {
default: `${faker.helpers.arrayElement(['ctrl', 'alt'])}+${faker.number.int(
{
min: 0,
max: 9,
},
)}`,
},
}));
export const fakeDevServer = fakeObjectCreator<WxtDevServer>(() => ({
hostname: 'localhost',
+10 -2
View File
@@ -45,6 +45,7 @@ export interface InlineConfig {
* A list of entrypoint names (`"popup"`, `"options"`, etc.) to build. Will speed up the build if
* your extension has lots of entrypoints, and you don't need to build all of them to develop a
* feature.
* If specified, this completely overrides the `include`/`exclude` option provided per-entrypoint.
*/
filterEntrypoints?: string[];
/**
@@ -588,7 +589,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"
@@ -728,7 +729,14 @@ export interface BaseEntrypoint {
* subdirectory of it.
*/
outputDir: string;
skipped: boolean;
/**
* When true, the entrypoint will not be built by WXT. Normally this is set
* based on the `filterEntrypoints` config or the entrypoint's
* `include`/`exclude` options defined inside the file.
*
* See https://wxt.dev/guide/essentials/target-different-browsers.html#filtering-entrypoints
*/
skipped?: boolean;
}
export interface GenericEntrypoint extends BaseEntrypoint {
+18 -1
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))
@@ -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
@@ -3041,10 +3044,12 @@ packages:
glob@6.0.4:
resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==}
deprecated: Glob versions prior to v9 are no longer supported
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
deprecated: Glob versions prior to v9 are no longer supported
global-dirs@3.0.1:
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
@@ -3174,6 +3179,7 @@ packages:
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -4283,6 +4289,7 @@ packages:
rimraf@2.4.5:
resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup-plugin-dts@6.1.1:
@@ -4678,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:
@@ -9290,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 -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"
}