Compare commits

...

11 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
21 changed files with 308 additions and 123 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';
+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!');
},
});
```
@@ -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 />
+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');
+22 -3
View File
@@ -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 -1
View File
@@ -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';
+9 -2
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 ??= {};
@@ -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',
+9 -1
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[];
/**
@@ -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 {