Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb143d2bbc | |||
| f3e7758521 | |||
| 23ef101954 | |||
| c2f5efbde4 | |||
| 4ce178c592 | |||
| 7aa30afa7f | |||
| 90aaed202c | |||
| c92ed13d66 | |||
| 0e4953fbfc | |||
| 799d687061 | |||
| 866eb0694c |
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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!');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 />
|
||||
|
||||
@@ -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) • [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 🎉
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# 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)
|
||||
@@ -12,8 +33,6 @@
|
||||
|
||||
- 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))
|
||||
@@ -27,7 +46,7 @@
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
- Nishu <nnmurmu@enacton.com>
|
||||
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
|
||||
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||
|
||||
## v0.19.16
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.19.17",
|
||||
"version": "0.19.18",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 ??= {};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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[];
|
||||
/**
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user