Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9f10c62ad | |||
| 35d3c03e4b | |||
| fdd38a1580 | |||
| 3018801f06 | |||
| 8e96bfef06 | |||
| 31071bd11e | |||
| f5619f6be1 | |||
| adad1b5a2c | |||
| a6c4e19a5d | |||
| af6f74c344 | |||
| ce45cb8a89 | |||
| 14d4aa202d | |||
| 4c35798dba | |||
| 656a9b365f | |||
| 67fa3db921 | |||
| f2d3061e97 | |||
| 760c34e416 | |||
| 48398b315c | |||
| 45d0d9d7f1 | |||
| b0f4ac8221 |
@@ -71,6 +71,8 @@ const chromeExtensionIds = [
|
||||
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
|
||||
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
|
||||
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -8,6 +8,16 @@ outline: deep
|
||||
|
||||
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
|
||||
:::danger
|
||||
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT relied on to open the browser with an extension installed. So this feature will not work for Chrome.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. Install [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (which still supports the `--load-extension` flag) and [point the `chrome` binary to it](#set-browser-binaries), or
|
||||
2. [Disable this feature](#disable-opening-browser) and manually load your extension
|
||||
|
||||
:::
|
||||
|
||||
## Config Files
|
||||
|
||||
You can configure browser startup in 3 places:
|
||||
|
||||
@@ -211,12 +211,12 @@ When defining your background entrypoint, keep in mind that WXT will import this
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
browser.action.onClick.addListener(() => { // [!code --]
|
||||
browser.action.onClicked.addListener(() => { // [!code --]
|
||||
// ... // [!code --]
|
||||
}); // [!code --]
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.action.onClick.addListener(() => { // [!code ++]
|
||||
browser.action.onClicked.addListener(() => { // [!code ++]
|
||||
// ... // [!code ++]
|
||||
}); // [!code ++]
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ import { injectScript, createShadowRootUi } from '#imports';
|
||||
But Vitest sees this:
|
||||
|
||||
```ts
|
||||
import { injectScript } from 'wxt/browser';
|
||||
import { injectScript } from 'wxt/utils/inject-script';
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.0.315",
|
||||
"version": "0.0.317",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -23,7 +23,7 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.0.315",
|
||||
"@types/chrome": "0.0.317",
|
||||
"fs-extra": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
|
||||
@@ -8958,7 +8958,7 @@ export namespace Browser {
|
||||
default_popup?: string | undefined;
|
||||
}
|
||||
|
||||
// Source: https://developer.Browser.com/docs/extensions/mv3/declare_permissions/
|
||||
/** Source: https://developer.Browser.com/docs/extensions/reference/permissions-list */
|
||||
export type ManifestPermissions =
|
||||
| "accessibilityFeatures.modify"
|
||||
| "accessibilityFeatures.read"
|
||||
@@ -9015,11 +9015,11 @@ export namespace Browser {
|
||||
| "privacy"
|
||||
| "processes"
|
||||
| "proxy"
|
||||
| "readingList"
|
||||
| "scripting"
|
||||
| "search"
|
||||
| "sessions"
|
||||
| "sidePanel"
|
||||
| "signedInDevices"
|
||||
| "storage"
|
||||
| "system.cpu"
|
||||
| "system.display"
|
||||
@@ -9042,6 +9042,23 @@ export namespace Browser {
|
||||
| "webRequestBlocking"
|
||||
| "webRequestAuthProvider";
|
||||
|
||||
/** Source : https://developer.Browser.com/docs/extensions/reference/api/permissions */
|
||||
export type ManifestOptionalPermissions = Exclude<
|
||||
ManifestPermissions,
|
||||
| "debugger"
|
||||
| "declarativeNetRequest"
|
||||
| "devtools"
|
||||
| "experimental"
|
||||
| "fontSettings"
|
||||
| "geolocation"
|
||||
| "proxy"
|
||||
| "tts"
|
||||
| "ttsEngine"
|
||||
| "unlimitedStorage"
|
||||
| "wallpaper"
|
||||
| "webAuthenticationProxy"
|
||||
>;
|
||||
|
||||
export interface SearchProvider {
|
||||
name?: string | undefined;
|
||||
keyword?: string | undefined;
|
||||
@@ -9261,8 +9278,8 @@ export namespace Browser {
|
||||
}
|
||||
| undefined;
|
||||
content_security_policy?: string | undefined;
|
||||
optional_permissions?: string[] | undefined;
|
||||
permissions?: string[] | undefined;
|
||||
optional_permissions?: ManifestOptionalPermissions[] | string[] | undefined;
|
||||
permissions?: ManifestPermissions[] | string[] | undefined;
|
||||
web_accessible_resources?: string[] | undefined;
|
||||
}
|
||||
|
||||
@@ -9297,7 +9314,7 @@ export namespace Browser {
|
||||
sandbox?: string;
|
||||
};
|
||||
host_permissions?: string[] | undefined;
|
||||
optional_permissions?: ManifestPermissions[] | undefined;
|
||||
optional_permissions?: ManifestOptionalPermissions[] | undefined;
|
||||
optional_host_permissions?: string[] | undefined;
|
||||
permissions?: ManifestPermissions[] | undefined;
|
||||
web_accessible_resources?: Array<{ resources: string[]; matches: string[] }> | undefined;
|
||||
@@ -14845,6 +14862,18 @@ export namespace Browser {
|
||||
*/
|
||||
export type ExecutionWorld = "MAIN" | "USER_SCRIPT";
|
||||
|
||||
/** @since Chrome 135 */
|
||||
export interface InjectionResult {
|
||||
/** The document associated with the injection. */
|
||||
documentId: string;
|
||||
/** The error, if any. `error` and `result` are mutually exclusive. */
|
||||
error?: string;
|
||||
/** The frame associated with the injection. */
|
||||
frameId: number;
|
||||
/** The result of the script execution. */
|
||||
result: any;
|
||||
}
|
||||
|
||||
export interface WorldProperties {
|
||||
/** Specifies the world csp. The default is the `ISOLATED` world csp. */
|
||||
csp?: string;
|
||||
@@ -14861,6 +14890,18 @@ export namespace Browser {
|
||||
ids?: string[];
|
||||
}
|
||||
|
||||
/** @since Chrome 135 */
|
||||
export interface InjectionTarget {
|
||||
/** Whether the script should inject into all frames within the tab. Defaults to false. This must not be true if `frameIds` is specified. */
|
||||
allFrames?: boolean;
|
||||
/** The IDs of specific documentIds to inject into. This must not be set if `frameIds` is set. */
|
||||
documentIds?: string[];
|
||||
/** The IDs of specific frames to inject into. */
|
||||
frameIds?: number[];
|
||||
/** The ID of the tab into which to inject. */
|
||||
tabId: number;
|
||||
}
|
||||
|
||||
export interface RegisteredUserScript {
|
||||
/** If true, it will inject into all frames, even if the frame is not the top-most frame in the tab. Each frame is checked independently for URL requirements; it will not inject into child frames if the URL requirements are not met. Defaults to false, meaning that only the top frame is matched. */
|
||||
allFrames?: boolean;
|
||||
@@ -14887,6 +14928,20 @@ export namespace Browser {
|
||||
worldId?: string;
|
||||
}
|
||||
|
||||
/** @since Chrome 135 */
|
||||
export interface UserScriptInjection {
|
||||
/** Whether the injection should be triggered in the target as soon as possible. Note that this is not a guarantee that injection will occur prior to page load, as the page may have already loaded by the time the script reaches the target. */
|
||||
injectImmediately?: boolean;
|
||||
/** The list of ScriptSource objects defining sources of scripts to be injected into the target. */
|
||||
js: ScriptSource[];
|
||||
/** Details specifying the target into which to inject the script. */
|
||||
target: InjectionTarget;
|
||||
/** The JavaScript "world" to run the script in. The default is `USER_SCRIPT`. */
|
||||
world?: ExecutionWorld;
|
||||
/** Specifies the user script world ID to execute in. If omitted, the script will execute in the default user script world. Only valid if `world` is omitted or is `USER_SCRIPT`. Values with leading underscores (`_`) are reserved. */
|
||||
worldId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for a script source.
|
||||
*/
|
||||
@@ -14939,6 +14994,13 @@ export namespace Browser {
|
||||
export function getWorldConfigurations(): Promise<WorldProperties[]>;
|
||||
export function getWorldConfigurations(callback: (worlds: WorldProperties[]) => void): void;
|
||||
|
||||
/**
|
||||
* Injects a script into a target context. By default, the script will be run at `document_idle`, or immediately if the page has already loaded. If the `injectImmediately` property is set, the script will inject without waiting, even if the page has not finished loading. If the script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value.
|
||||
* @since Chrome 135
|
||||
*/
|
||||
export function execute(injection: UserScriptInjection): Promise<InjectionResult[]>;
|
||||
export function execute(injection: UserScriptInjection, callback: (result: InjectionResult[]) => void): void;
|
||||
|
||||
/**
|
||||
* Registers one or more user scripts for this extension.
|
||||
*
|
||||
|
||||
@@ -1281,6 +1281,19 @@ describe('Storage Utils', () => {
|
||||
it('should define a nullable value when options are not passed', () => {
|
||||
const item = storage.defineItem<number>(`local:test`);
|
||||
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
|
||||
|
||||
const item2 = storage.defineItem<number>(`local:test`, {});
|
||||
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
|
||||
|
||||
const item3 = storage.defineItem<number>(`local:test`, {
|
||||
fallback: undefined,
|
||||
});
|
||||
expectTypeOf(item3).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
|
||||
|
||||
const item4 = storage.defineItem<number>(`local:test`, {
|
||||
defaultValue: undefined,
|
||||
});
|
||||
expectTypeOf(item4).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
|
||||
});
|
||||
|
||||
it('should define a non-null value when options are passed with a nullish default value', () => {
|
||||
@@ -1288,6 +1301,11 @@ describe('Storage Utils', () => {
|
||||
defaultValue: 123,
|
||||
});
|
||||
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number, {}>>();
|
||||
|
||||
const item2 = storage.defineItem(`local:test`, {
|
||||
fallback: 123,
|
||||
});
|
||||
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number, {}>>();
|
||||
});
|
||||
|
||||
it('should define a nullable value when options are passed with null default value', () => {
|
||||
|
||||
@@ -750,8 +750,16 @@ export interface WxtStorage {
|
||||
): WxtStorageItem<TValue | null, TMetadata>;
|
||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||
key: StorageItemKey,
|
||||
options: WxtStorageItemOptions<TValue>,
|
||||
options: WxtStorageItemOptions<TValue> & { fallback: TValue },
|
||||
): WxtStorageItem<TValue, TMetadata>;
|
||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||
key: StorageItemKey,
|
||||
options: WxtStorageItemOptions<TValue> & { defaultValue: TValue },
|
||||
): WxtStorageItem<TValue, TMetadata>;
|
||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||
key: StorageItemKey,
|
||||
options: WxtStorageItemOptions<TValue>,
|
||||
): WxtStorageItem<TValue | null, TMetadata>;
|
||||
}
|
||||
|
||||
interface WxtStorageDriver {
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.3...wxt-v0.20.4)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Ignore popup/index.ts instead of erroring ([#1520](https://github.com/wxt-dev/wxt/pull/1520))
|
||||
- Ignore elements with a `vite-ignore` or `wxt-ignore` attribute ([#1603](https://github.com/wxt-dev/wxt/pull/1603))
|
||||
- Add `{{packageVersion}}` as template variable ([#1604](https://github.com/wxt-dev/wxt/pull/1604))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Adding missing `""` to `PublicPath` and `browser.runtime.getUrl` ([#1597](https://github.com/wxt-dev/wxt/pull/1597))
|
||||
- Fix CORS error in Firefox ([#1607](https://github.com/wxt-dev/wxt/pull/1607))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **Content Script UI:** Add additional details about when `onRemove` is called ([656a9b3](https://github.com/wxt-dev/wxt/commit/656a9b3))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Yunsup Sim <pedogunu@gmail.com>
|
||||
- ТΞNSΛI <tensai@gmx.net>
|
||||
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.2...wxt-v0.20.3)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Automatically place document-level CSS outside shadow root ([#1594](https://github.com/wxt-dev/wxt/pull/1594))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fix double hashing of inline script keys ([b0f4ac8](https://github.com/wxt-dev/wxt/commit/b0f4ac8))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.1...wxt-v0.20.2)
|
||||
|
||||
@@ -48,6 +48,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
declare module "wxt/browser" {
|
||||
export type PublicPath =
|
||||
| ""
|
||||
| "/"
|
||||
| "/options.html"
|
||||
| "/popup.html"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.20.2",
|
||||
"version": "0.20.4",
|
||||
"description": "⚡ Next-gen Web Extension Framework",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -167,6 +167,10 @@
|
||||
"types": "./dist/utils/match-patterns.d.ts",
|
||||
"default": "./dist/utils/match-patterns.mjs"
|
||||
},
|
||||
"./utils/split-shadow-root-css": {
|
||||
"types": "./dist/utils/split-shadow-root-css.d.ts",
|
||||
"default": "./dist/utils/split-shadow-root-css.mjs"
|
||||
},
|
||||
"./utils/storage": {
|
||||
"types": "./dist/utils/storage.d.ts",
|
||||
"default": "./dist/utils/storage.mjs"
|
||||
|
||||
@@ -85,13 +85,13 @@ export function devHtmlPrerender(
|
||||
inlineScripts.forEach((script) => {
|
||||
// Save the text content for later
|
||||
const textContent = script.textContent ?? '';
|
||||
const textHash = hash(textContent);
|
||||
inlineScriptContents[textHash] = textContent;
|
||||
const key = hash(textContent);
|
||||
inlineScriptContents[key] = textContent;
|
||||
|
||||
// Replace unsafe inline script
|
||||
const virtualScript = document.createElement('script');
|
||||
virtualScript.type = 'module';
|
||||
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${textHash}`;
|
||||
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${key}`;
|
||||
script.replaceWith(virtualScript);
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ export function devHtmlPrerender(
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'wxt:virtualize-react-refresh',
|
||||
name: 'wxt:virtualize-inline-scripts',
|
||||
apply: 'serve',
|
||||
resolveId(id) {
|
||||
// Resolve inline scripts
|
||||
@@ -127,9 +127,9 @@ export function devHtmlPrerender(
|
||||
load(id) {
|
||||
// Resolve virtualized inline scripts
|
||||
if (id.startsWith(resolvedVirtualInlineScript)) {
|
||||
// id="virtual:wxt-inline-script?<hash>"
|
||||
const newHash = hash(id.substring(id.indexOf('?')));
|
||||
return inlineScriptContents[newHash];
|
||||
// id="virtual:wxt-inline-script?<key>"
|
||||
const key = id.substring(id.indexOf('?') + 1);
|
||||
return inlineScriptContents[key];
|
||||
}
|
||||
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
@@ -150,6 +150,13 @@ export function pointToDevServer(
|
||||
attr: string,
|
||||
) {
|
||||
document.querySelectorAll(querySelector).forEach((element) => {
|
||||
if (
|
||||
element.hasAttribute('vite-ignore') ||
|
||||
element.hasAttribute('wxt-ignore')
|
||||
) {
|
||||
element.removeAttribute('wxt-ignore');
|
||||
return;
|
||||
}
|
||||
const src = element.getAttribute(attr);
|
||||
if (!src || isUrl(src)) return;
|
||||
|
||||
|
||||
@@ -73,16 +73,18 @@ async function getPathsDeclarationEntry(
|
||||
isHtmlEntrypoint(entry) ? '.html' : '.js',
|
||||
),
|
||||
)
|
||||
.concat([''])
|
||||
.concat(await getPublicFiles());
|
||||
|
||||
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
|
||||
|
||||
const unions = paths
|
||||
.map(normalizePath)
|
||||
.sort()
|
||||
.map((path) => ` | "/${path}"`)
|
||||
.join('\n');
|
||||
const unions = [
|
||||
` | ""`,
|
||||
` | "/"`,
|
||||
...paths
|
||||
.map(normalizePath)
|
||||
.sort()
|
||||
.map((path) => ` | "/${path}"`),
|
||||
].join('\n');
|
||||
|
||||
const template = `// Generated by wxt
|
||||
import "wxt/browser";
|
||||
|
||||
@@ -1503,7 +1503,7 @@ describe('Manifest Utils', () => {
|
||||
expect(actual).toMatchObject({
|
||||
content_security_policy:
|
||||
"script-src 'self' http://localhost:3000; object-src 'self';",
|
||||
permissions: ['http://localhost:3000/*', 'tabs'],
|
||||
permissions: ['http://localhost/*', 'tabs'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1535,7 +1535,7 @@ describe('Manifest Utils', () => {
|
||||
sandbox:
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:3000; sandbox allow-scripts allow-forms allow-popups allow-modals; child-src 'self';",
|
||||
},
|
||||
host_permissions: ['http://localhost:3000/*'],
|
||||
host_permissions: ['http://localhost/*'],
|
||||
permissions: ['tabs', 'scripting'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -668,27 +668,46 @@ describe('findEntrypoints', () => {
|
||||
expect(entrypoints[0]).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not allow multiple entrypoints with the same name', async () => {
|
||||
it('should ignore other index files in the same directory when index.html exists', async () => {
|
||||
globMock.mockResolvedValueOnce([
|
||||
'content/index.ts',
|
||||
'popup/index.html',
|
||||
'popup/index.ts',
|
||||
'popup/index.css',
|
||||
]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(2);
|
||||
expect(entrypoints[0]).toMatchObject({
|
||||
type: 'content-script',
|
||||
name: 'content',
|
||||
});
|
||||
expect(entrypoints[1]).toMatchObject({
|
||||
type: 'popup',
|
||||
name: 'popup',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow a file entrypoint and directory entrypoint to have the same name', async () => {
|
||||
globMock.mockResolvedValueOnce([
|
||||
'options/index.html',
|
||||
'options/index.jsx',
|
||||
'popup.html',
|
||||
'popup/index.html',
|
||||
'popup/index.ts',
|
||||
'ui.html',
|
||||
'other.ts',
|
||||
'other/index.ts',
|
||||
]);
|
||||
|
||||
await expect(() => findEntrypoints()).rejects.toThrowError(
|
||||
[
|
||||
'Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.',
|
||||
'',
|
||||
'- options',
|
||||
` - ${unnormalizePath('src/entrypoints/options/index.html')}`,
|
||||
` - ${unnormalizePath('src/entrypoints/options/index.jsx')}`,
|
||||
'- other',
|
||||
` - ${unnormalizePath('src/entrypoints/other.ts')}`,
|
||||
` - ${unnormalizePath('src/entrypoints/other/index.ts')}`,
|
||||
'- popup',
|
||||
` - ${unnormalizePath('src/entrypoints/popup.html')}`,
|
||||
` - ${unnormalizePath('src/entrypoints/popup/index.html')}`,
|
||||
` - ${unnormalizePath('src/entrypoints/popup/index.ts')}`,
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -53,20 +53,34 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
relativePaths.sort();
|
||||
|
||||
const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
|
||||
const entrypointInfos: EntrypointInfo[] = relativePaths.reduce<
|
||||
EntrypointInfo[]
|
||||
>((results, relativePath) => {
|
||||
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
|
||||
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
|
||||
const matchingGlob = pathGlobs.find((glob) =>
|
||||
minimatch(relativePath, glob),
|
||||
);
|
||||
if (matchingGlob) {
|
||||
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
|
||||
results.push({ name, inputPath, type });
|
||||
}
|
||||
return results;
|
||||
}, []);
|
||||
const entrypointInfos: EntrypointInfo[] = relativePaths
|
||||
.reduce<EntrypointInfo[]>((results, relativePath) => {
|
||||
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
|
||||
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
|
||||
const matchingGlob = pathGlobs.find((glob) =>
|
||||
minimatch(relativePath, glob),
|
||||
);
|
||||
if (matchingGlob) {
|
||||
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
|
||||
results.push({ name, inputPath, type });
|
||||
}
|
||||
return results;
|
||||
}, [])
|
||||
.filter(({ name, inputPath }, _, entrypointInfos) => {
|
||||
// Remove <name>/index.* if <name>/index.html exists
|
||||
|
||||
if (inputPath.endsWith('.html')) return true;
|
||||
const isIndexFile = /index\..+$/.test(inputPath);
|
||||
if (!isIndexFile) return true;
|
||||
|
||||
const hasIndexHtml = entrypointInfos.some(
|
||||
(entry) =>
|
||||
entry.name === name && entry.inputPath.endsWith('index.html'),
|
||||
);
|
||||
if (hasIndexHtml) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
|
||||
|
||||
|
||||
@@ -459,7 +459,13 @@ function discoverIcons(
|
||||
}
|
||||
|
||||
function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
|
||||
const permission = `${wxt.server?.origin ?? ''}/*`;
|
||||
let permissonUrl = wxt.server?.origin;
|
||||
if (permissonUrl) {
|
||||
const permissionUrlInstance = new URL(permissonUrl);
|
||||
permissionUrlInstance.port = '';
|
||||
permissonUrl = permissionUrlInstance.toString();
|
||||
}
|
||||
const permission = `${permissonUrl}*`;
|
||||
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
|
||||
@@ -26,11 +26,10 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
wxt.logger.info('Zipping extension...');
|
||||
const zipFiles: string[] = [];
|
||||
|
||||
const packageJson = await getPackageJson();
|
||||
const projectName =
|
||||
wxt.config.zip.name ??
|
||||
safeFilename(
|
||||
(await getPackageJson())?.name || path.basename(process.cwd()),
|
||||
);
|
||||
safeFilename(packageJson?.name || path.basename(process.cwd()));
|
||||
const applyTemplate = (template: string): string =>
|
||||
template
|
||||
.replaceAll('{{name}}', projectName)
|
||||
@@ -39,6 +38,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
'{{version}}',
|
||||
output.manifest.version_name ?? output.manifest.version,
|
||||
)
|
||||
.replaceAll('{{packageVersion}}', packageJson?.version)
|
||||
.replaceAll('{{mode}}', wxt.config.mode)
|
||||
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
|
||||
|
||||
|
||||
@@ -147,6 +147,7 @@ export interface InlineConfig {
|
||||
*
|
||||
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
|
||||
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
|
||||
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
|
||||
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
|
||||
* - <span v-pre>`{{mode}}`</span> - The current mode
|
||||
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
|
||||
@@ -171,6 +172,7 @@ export interface InlineConfig {
|
||||
*
|
||||
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
|
||||
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
|
||||
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
|
||||
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
|
||||
* - <span v-pre>`{{mode}}`</span> - The current mode
|
||||
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ContentScriptUi, ContentScriptUiOptions } from './types';
|
||||
import { createIsolatedElement } from '@webext-core/isolated-element';
|
||||
import { applyPosition, createMountFunctions, mountUi } from './shared';
|
||||
import { logger } from '../internal/logger';
|
||||
import { splitShadowRootCss } from '../split-shadow-root-css';
|
||||
|
||||
/**
|
||||
* Create a content script UI inside a [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).
|
||||
@@ -17,6 +18,7 @@ export async function createShadowRootUi<TMounted>(
|
||||
ctx: ContentScriptContext,
|
||||
options: ShadowRootContentScriptUiOptions<TMounted>,
|
||||
): Promise<ShadowRootContentScriptUi<TMounted>> {
|
||||
const instanceId = crypto.randomUUID();
|
||||
const css: string[] = [];
|
||||
|
||||
if (!options.inheritStyles) {
|
||||
@@ -31,6 +33,9 @@ export async function createShadowRootUi<TMounted>(
|
||||
css.push(entryCss.replaceAll(':root', ':host'));
|
||||
}
|
||||
|
||||
// Some rules must be applied outside the shadow root, so split the CSS apart
|
||||
const { shadowCss, documentCss } = splitShadowRootCss(css.join('\n').trim());
|
||||
|
||||
const {
|
||||
isolatedElement: uiContainer,
|
||||
parentElement: shadowHost,
|
||||
@@ -38,7 +43,7 @@ export async function createShadowRootUi<TMounted>(
|
||||
} = await createIsolatedElement({
|
||||
name: options.name,
|
||||
css: {
|
||||
textContent: css.join('\n').trim(),
|
||||
textContent: shadowCss,
|
||||
},
|
||||
mode: options.mode ?? 'open',
|
||||
isolateEvents: options.isolateEvents,
|
||||
@@ -51,6 +56,20 @@ export async function createShadowRootUi<TMounted>(
|
||||
// Add shadow root element to DOM
|
||||
mountUi(shadowHost, options);
|
||||
applyPosition(shadowHost, shadow.querySelector('html'), options);
|
||||
|
||||
// Add document CSS
|
||||
if (
|
||||
documentCss &&
|
||||
!document.querySelector(
|
||||
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
|
||||
)
|
||||
) {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = documentCss;
|
||||
style.setAttribute('wxt-shadow-root-document-styles', instanceId);
|
||||
(document.head ?? document.body).append(style);
|
||||
}
|
||||
|
||||
// Mount UI inside shadow root
|
||||
mounted = options.onMount(uiContainer, shadow, shadowHost);
|
||||
};
|
||||
@@ -58,11 +77,20 @@ export async function createShadowRootUi<TMounted>(
|
||||
const remove = () => {
|
||||
// Cleanup mounted state
|
||||
options.onRemove?.(mounted);
|
||||
|
||||
// Detach shadow root from DOM
|
||||
shadowHost.remove();
|
||||
|
||||
// Remove document CSS
|
||||
const documentStyle = document.querySelector(
|
||||
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
|
||||
);
|
||||
documentStyle?.remove();
|
||||
|
||||
// Remove children from uiContainer
|
||||
while (uiContainer.lastChild)
|
||||
uiContainer.removeChild(uiContainer.lastChild);
|
||||
|
||||
// Clear mounted value
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,9 @@ export type ContentScriptUiOptions<TMounted> = ContentScriptPositioningOptions &
|
||||
/**
|
||||
* Callback called before the UI is removed from the webpage. Use to cleanup your UI, like
|
||||
* unmounting your Vue or React apps.
|
||||
*
|
||||
* Note that this callback is called only when `ui.remove` is called - that means it is
|
||||
* not called automatically when the anchor is removed, unless you use `autoMount`.
|
||||
*/
|
||||
onRemove?: (mounted: TMounted | undefined) => void;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Given a CSS string that will be loaded into a shadow root, split it into two parts:
|
||||
* - `documentCss`: CSS that needs to be applied to the document (like `@property`)
|
||||
* - `shadowCss`: CSS that needs to be applied to the shadow root
|
||||
* @param css
|
||||
*/
|
||||
export function splitShadowRootCss(css: string): {
|
||||
documentCss: string;
|
||||
shadowCss: string;
|
||||
} {
|
||||
let shadowCss = css;
|
||||
let documentCss = '';
|
||||
|
||||
const rulesRegex = /(\s*@property[\s\S]*?{[\s\S]*?})/gm;
|
||||
let match;
|
||||
while ((match = rulesRegex.exec(css)) !== null) {
|
||||
documentCss += match[1];
|
||||
shadowCss = shadowCss.replace(match[1], '');
|
||||
}
|
||||
|
||||
return {
|
||||
documentCss: documentCss.trim(),
|
||||
shadowCss: shadowCss.trim(),
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"src/utils/define-unlisted-script.ts",
|
||||
"src/utils/define-wxt-plugin.ts",
|
||||
"src/utils/match-patterns.ts",
|
||||
"src/utils/split-shadow-root-css.ts",
|
||||
"src/utils/storage.ts",
|
||||
"src/testing/index.ts",
|
||||
"src/modules.ts"
|
||||
|
||||
@@ -464,8 +464,8 @@ importers:
|
||||
version: 1.2.15
|
||||
devDependencies:
|
||||
'@types/chrome':
|
||||
specifier: 0.0.315
|
||||
version: 0.0.315
|
||||
specifier: 0.0.317
|
||||
version: 0.0.317
|
||||
fs-extra:
|
||||
specifier: 'catalog:'
|
||||
version: 11.3.0
|
||||
@@ -2188,8 +2188,8 @@ packages:
|
||||
'@types/chrome@0.0.313':
|
||||
resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==}
|
||||
|
||||
'@types/chrome@0.0.315':
|
||||
resolution: {integrity: sha512-Oy1dYWkr6BCmgwBtOngLByCHstQ3whltZg7/7lubgIZEYvKobDneqplgc6LKERNRBwckFviV4UU5AZZNUFrJ4A==}
|
||||
'@types/chrome@0.0.317':
|
||||
resolution: {integrity: sha512-ibKycbXX8ZZToFshjgWg98BTvFUSvQht8m53Xc+87ye3Z6ZoHJubLjoiDsil8rtW+noWE+Z0+7y0nwLxArU+CQ==}
|
||||
|
||||
'@types/conventional-commits-parser@5.0.1':
|
||||
resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
|
||||
@@ -6441,7 +6441,7 @@ snapshots:
|
||||
'@types/filesystem': 0.0.36
|
||||
'@types/har-format': 1.2.15
|
||||
|
||||
'@types/chrome@0.0.315':
|
||||
'@types/chrome@0.0.317':
|
||||
dependencies:
|
||||
'@types/filesystem': 0.0.36
|
||||
'@types/har-format': 1.2.15
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 559 B |
|
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 916 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |