feat!: Add support for "main world" content scripts (#284)

BREAKING CHANGE: `defineContentScript` and `defineBackground` are now exported from `wxt/sandbox` instead of `wxt/client`. If you use auto-imports, no changes are required. If you have disabled auto-imports, you'll need to manually update your import statements.
This commit is contained in:
Aaron
2023-12-13 15:41:39 -06:00
committed by GitHub
parent 0873c24ad8
commit fde488ac82
23 changed files with 113 additions and 44 deletions
@@ -0,0 +1,8 @@
export default defineContentScript({
matches: ['*://*/*'],
world: 'MAIN',
main() {
console.log(`Hello from ${location.hostname}!`);
},
});
+4 -4
View File
@@ -11,13 +11,13 @@ To setup your test environment for auto-imports, see [Testing](/guide/testing).
Some WXT APIs can be used without importing them:
- [`browser`](/api/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
- [`defineContentScript`](/api/wxt/client/functions/defineContentScript) from `wxt/client`
- [`defineBackground`](/api/wxt/client/functions/defineBackground) from `wxt/client`
- [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi) from `wxt/client`
- [`defineContentScript`](/api/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
- [`defineBackground`](/api/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
- [`defineUnlistedScript`](/api/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
- [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi) from `wxt/client`
- [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser) from `wxt/testing`
And more. All `wxt/*` APIs can be used without imports.
And more!
## Project Auto-imports
+2 -2
View File
@@ -22,9 +22,9 @@ describe('Auto Imports', () => {
const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe']
const createContentScriptUi: typeof import('wxt/client')['createContentScriptUi']
const createStorage: typeof import('wxt/storage')['createStorage']
const defineBackground: typeof import('wxt/client')['defineBackground']
const defineBackground: typeof import('wxt/sandbox')['defineBackground']
const defineConfig: typeof import('wxt')['defineConfig']
const defineContentScript: typeof import('wxt/client')['defineContentScript']
const defineContentScript: typeof import('wxt/sandbox')['defineContentScript']
const defineDriver: typeof import('wxt/storage')['defineDriver']
const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript']
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
+6 -3
View File
@@ -23,7 +23,8 @@ const preset: tsup.Options = {
external: [
'vite',
'virtual:user-unlisted-script',
'virtual:user-content-script',
'virtual:user-content-script-isolated-world',
'virtual:user-content-script-main-world',
'virtual:user-background',
],
};
@@ -76,8 +77,10 @@ const config: tsup.Options[] = [
...preset,
entry: {
'virtual/background-entrypoint': 'src/virtual/background-entrypoint.ts',
'virtual/content-script-entrypoint':
'src/virtual/content-script-entrypoint.ts',
'virtual/content-script-isolated-world-entrypoint':
'src/virtual/content-script-isolated-world-entrypoint.ts',
'virtual/content-script-main-world-entrypoint':
'src/virtual/content-script-main-world-entrypoint.ts',
'virtual/mock-browser': 'src/virtual/mock-browser.ts',
'virtual/reload-html': 'src/virtual/reload-html.ts',
'virtual/unlisted-script-entrypoint':
-1
View File
@@ -1,4 +1,3 @@
export * from './content-script-context';
export * from './content-script-ui';
export * from './content-script-iframe';
export * from './define-content-script';
+2 -1
View File
@@ -1,7 +1,8 @@
/**
* Any runtime APIs that use the web extension APIs.
*
* @module wxt/client
*/
export * from './define-background';
export * from './content-scripts';
export {
ContentScriptOverlayAlignment,
+18 -8
View File
@@ -5,6 +5,7 @@ import {
InlineConfig,
InternalConfig,
UserConfig,
VirtualEntrypointType,
WxtBuilder,
WxtBuilderServer,
} from '~/types';
@@ -46,7 +47,8 @@ export async function craeteViteBuilder(
wxtPlugins.devHtmlPrerender(wxtConfig),
wxtPlugins.unimport(wxtConfig),
wxtPlugins.virtualEntrypoint('background', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig),
wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig),
wxtPlugins.devServerGlobals(wxtConfig),
wxtPlugins.tsconfigPaths(wxtConfig),
@@ -65,13 +67,21 @@ export async function craeteViteBuilder(
* Return the basic config for building an entrypoint in [lib mode](https://vitejs.dev/guide/build.html#library-mode).
*/
const getLibModeConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
const isVirtual = [
'background',
'content-script',
'unlisted-script',
].includes(entrypoint.type);
const entry = isVirtual
? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}`
let virtualEntrypointType: VirtualEntrypointType | undefined;
switch (entrypoint.type) {
case 'background':
case 'unlisted-script':
virtualEntrypointType = entrypoint.type;
break;
case 'content-script':
virtualEntrypointType =
entrypoint.options.world === 'MAIN'
? 'content-script-main-world'
: 'content-script-isolated-world';
break;
}
const entry = virtualEntrypointType
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
: entrypoint.inputPath;
const plugins: NonNullable<vite.UserConfig['plugins']> = [
@@ -1,5 +1,5 @@
import { Plugin } from 'vite';
import { Entrypoint, InternalConfig } from '~/types';
import { InternalConfig, VirtualEntrypointType } from '~/types';
import fs from 'fs-extra';
import { resolve } from 'path';
import { normalizePath } from '~/core/utils/paths';
@@ -8,7 +8,7 @@ import { normalizePath } from '~/core/utils/paths';
* Wraps a user's entrypoint with a vitual version with additional logic.
*/
export function virtualEntrypoint(
type: Entrypoint['type'],
type: VirtualEntrypointType,
config: Omit<InternalConfig, 'builder'>,
): Plugin {
const virtualId = `virtual:wxt-${type}?`;
@@ -1,4 +1,4 @@
import { defineBackground } from '~/client';
import { defineBackground } from '~/sandbox';
export default defineBackground({
main() {},
@@ -1,4 +1,4 @@
import { defineContentScript } from '~/client';
import { defineContentScript } from '~/sandbox';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -1,4 +1,4 @@
import { defineBackground } from '~/client';
import { defineBackground } from '~/sandbox';
export const a = {};
+1 -2
View File
@@ -22,8 +22,7 @@ export function removeImportStatements(text: string): string {
export function removeProjectImportStatements(text: string): string {
const noImports = removeImportStatements(text);
return `import { defineContentScript, defineBackground } from 'wxt/client';
import { defineUnlistedScript } from 'wxt/sandbox';
return `import { defineUnlistedScript, defineContentScript, defineBackground } from 'wxt/sandbox';
${noImports}`;
}
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
import { defineBackground } from '~/client/define-background';
import { defineBackground } from '~/sandbox/define-background';
import { BackgroundDefinition } from '~/types';
describe('defineBackground', () => {
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
import { defineContentScript } from '~/client/content-scripts/define-content-script';
import { defineContentScript } from '~/sandbox/define-content-script';
import { ContentScriptDefinition } from '~/types';
describe('defineContentScript', () => {
@@ -1,4 +1,4 @@
import { BackgroundDefinition } from '~/types';
import type { BackgroundDefinition } from '~/types';
export function defineBackground(main: () => void): BackgroundDefinition;
export function defineBackground(
@@ -1,4 +1,4 @@
import { ContentScriptDefinition } from '~/types';
import type { ContentScriptDefinition } from '~/types';
export function defineContentScript(
definition: ContentScriptDefinition,
+1 -1
View File
@@ -1,4 +1,4 @@
import { UnlistedScriptDefinition } from '~/types';
import type { UnlistedScriptDefinition } from '~/types';
export function defineUnlistedScript(
main: () => void,
+4
View File
@@ -1,4 +1,8 @@
/**
* Any runtime APIs that don't use the web extension APIs.
*
* @module wxt/sandbox
*/
export * from './define-unlisted-script';
export * from './define-background';
export * from './define-content-script';
+30 -10
View File
@@ -439,7 +439,36 @@ export type EntrypointGroup = Entrypoint | Entrypoint[];
export type OnContentScriptStopped = (cb: () => void) => void;
export interface ContentScriptDefinition extends ExcludableEntrypoint {
export type ContentScriptDefinition =
| ContentScriptIsolatedWorldDefinition
| ContentScriptMainWorldDefinition;
export interface ContentScriptIsolatedWorldDefinition
extends ContentScriptBaseDefinition {
/**
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
* @default "ISOLATED"
*/
world?: 'ISOLATED';
/**
* Main function executed when the content script is loaded.
*/
main(ctx: ContentScriptContext): void | Promise<void>;
}
export interface ContentScriptMainWorldDefinition
extends ContentScriptBaseDefinition {
/**
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
*/
world: 'MAIN';
/**
* Main function executed when the content script is loaded.
*/
main(): void | Promise<void>;
}
export interface ContentScriptBaseDefinition extends ExcludableEntrypoint {
matches: PerBrowserOption<Manifest.ContentScript['matches']>;
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
@@ -478,11 +507,6 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint {
* @default false
*/
matchOriginAsFallback?: PerBrowserOption<boolean>;
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default "ISOLATED"
*/
world?: PerBrowserOption<'ISOLATED' | 'MAIN'>;
/**
* Customize how imported/generated styles are injected with the content script. Regardless of the
* mode selected, CSS will always be built and included in the output directory.
@@ -497,10 +521,6 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint {
* @default "manifest"
*/
cssInjectionMode?: PerBrowserOption<'manifest' | 'manual' | 'ui'>;
/**
* Main function executed when the content script is loaded.
*/
main(ctx: ContentScriptContext): void | Promise<void>;
}
export interface BackgroundDefinition extends ExcludableEntrypoint {
+6
View File
@@ -67,3 +67,9 @@ export interface ExtensionRunner {
openBrowser(config: InternalConfig): Promise<void>;
closeBrowser(): Promise<void>;
}
export type VirtualEntrypointType =
| 'content-script-main-world'
| 'content-script-isolated-world'
| 'background'
| 'unlisted-script';
@@ -1,4 +1,4 @@
import definition from 'virtual:user-content-script';
import definition from 'virtual:user-content-script-isolated-world';
import { logger } from '~/client/utils/logger';
import { ContentScriptContext } from '~/client/content-scripts/content-script-context';
@@ -0,0 +1,14 @@
import definition from 'virtual:user-content-script-main-world';
import { logger } from '~/client/utils/logger';
(async () => {
try {
const { main } = definition;
await main();
} catch (err) {
logger.error(
`The content script "${__ENTRYPOINT__}" crashed on startup!`,
err,
);
}
})();
+7 -2
View File
@@ -10,8 +10,13 @@ declare module 'virtual:user-background' {
export default definition;
}
declare module 'virtual:user-content-script' {
const definition: import('~/types').ContentScriptDefinition;
declare module 'virtual:user-content-script-isolated-world' {
const definition: import('~/types').ContentScriptIsolatedWorldDefinition;
export default definition;
}
declare module 'virtual:user-content-script-main-world' {
const definition: import('~/types').ContentScriptMainWorldDefinition;
export default definition;
}