From fde488ac829fb20b04133c4d0ff0e9c3842679ea Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 13 Dec 2023 15:41:39 -0600 Subject: [PATCH] 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. --- demo/src/entrypoints/main-world.content.ts | 8 ++++ docs/guide/auto-imports.md | 8 ++-- e2e/tests/auto-imports.test.ts | 4 +- scripts/build.ts | 9 +++-- src/client/content-scripts/index.ts | 1 - src/client/index.ts | 3 +- src/core/builders/vite/index.ts | 26 ++++++++---- .../vite/plugins/virtualEntrypoint.ts | 4 +- .../__tests__/test-entrypoints/background.ts | 2 +- .../__tests__/test-entrypoints/content.ts | 2 +- .../__tests__/test-entrypoints/with-named.ts | 2 +- src/core/utils/strings.ts | 3 +- .../__tests__/define-background.test.ts | 2 +- .../__tests__/define-content-script.test.ts | 2 +- src/{client => sandbox}/define-background.ts | 2 +- .../define-content-script.ts | 2 +- src/sandbox/define-unlisted-script.ts | 2 +- src/sandbox/index.ts | 4 ++ src/types/external.ts | 40 ++++++++++++++----- src/types/internal.ts | 6 +++ ...ntent-script-isolated-world-entrypoint.ts} | 2 +- .../content-script-main-world-entrypoint.ts | 14 +++++++ src/virtual/virtual-module-globals.d.ts | 9 ++++- 23 files changed, 113 insertions(+), 44 deletions(-) create mode 100644 demo/src/entrypoints/main-world.content.ts rename src/{client => sandbox}/__tests__/define-background.test.ts (90%) rename src/{client/content-scripts => sandbox}/__tests__/define-content-script.test.ts (82%) rename src/{client => sandbox}/define-background.ts (86%) rename src/{client/content-scripts => sandbox}/define-content-script.ts (69%) rename src/virtual/{content-script-entrypoint.ts => content-script-isolated-world-entrypoint.ts} (86%) create mode 100644 src/virtual/content-script-main-world-entrypoint.ts diff --git a/demo/src/entrypoints/main-world.content.ts b/demo/src/entrypoints/main-world.content.ts new file mode 100644 index 00000000..0e63a988 --- /dev/null +++ b/demo/src/entrypoints/main-world.content.ts @@ -0,0 +1,8 @@ +export default defineContentScript({ + matches: ['*://*/*'], + world: 'MAIN', + + main() { + console.log(`Hello from ${location.hostname}!`); + }, +}); diff --git a/docs/guide/auto-imports.md b/docs/guide/auto-imports.md index d3b49201..640dd641 100644 --- a/docs/guide/auto-imports.md +++ b/docs/guide/auto-imports.md @@ -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 diff --git a/e2e/tests/auto-imports.test.ts b/e2e/tests/auto-imports.test.ts index b46ad590..f2b42b47 100644 --- a/e2e/tests/auto-imports.test.ts +++ b/e2e/tests/auto-imports.test.ts @@ -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'] diff --git a/scripts/build.ts b/scripts/build.ts index 499b7c4f..df70e7d9 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -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': diff --git a/src/client/content-scripts/index.ts b/src/client/content-scripts/index.ts index e3964f02..6ebca155 100644 --- a/src/client/content-scripts/index.ts +++ b/src/client/content-scripts/index.ts @@ -1,4 +1,3 @@ export * from './content-script-context'; export * from './content-script-ui'; export * from './content-script-iframe'; -export * from './define-content-script'; diff --git a/src/client/index.ts b/src/client/index.ts index c0ce4209..4fdc302a 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -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, diff --git a/src/core/builders/vite/index.ts b/src/core/builders/vite/index.ts index beaa56e3..bc9d8924 100644 --- a/src/core/builders/vite/index.ts +++ b/src/core/builders/vite/index.ts @@ -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 = [ diff --git a/src/core/builders/vite/plugins/virtualEntrypoint.ts b/src/core/builders/vite/plugins/virtualEntrypoint.ts index d6e00132..cc478f25 100644 --- a/src/core/builders/vite/plugins/virtualEntrypoint.ts +++ b/src/core/builders/vite/plugins/virtualEntrypoint.ts @@ -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, ): Plugin { const virtualId = `virtual:wxt-${type}?`; diff --git a/src/core/utils/__tests__/test-entrypoints/background.ts b/src/core/utils/__tests__/test-entrypoints/background.ts index 0cdd11c9..2ea54a2d 100644 --- a/src/core/utils/__tests__/test-entrypoints/background.ts +++ b/src/core/utils/__tests__/test-entrypoints/background.ts @@ -1,4 +1,4 @@ -import { defineBackground } from '~/client'; +import { defineBackground } from '~/sandbox'; export default defineBackground({ main() {}, diff --git a/src/core/utils/__tests__/test-entrypoints/content.ts b/src/core/utils/__tests__/test-entrypoints/content.ts index ddca3ba1..332c618b 100644 --- a/src/core/utils/__tests__/test-entrypoints/content.ts +++ b/src/core/utils/__tests__/test-entrypoints/content.ts @@ -1,4 +1,4 @@ -import { defineContentScript } from '~/client'; +import { defineContentScript } from '~/sandbox'; export default defineContentScript({ matches: [''], diff --git a/src/core/utils/__tests__/test-entrypoints/with-named.ts b/src/core/utils/__tests__/test-entrypoints/with-named.ts index 0b401242..a344ce1b 100644 --- a/src/core/utils/__tests__/test-entrypoints/with-named.ts +++ b/src/core/utils/__tests__/test-entrypoints/with-named.ts @@ -1,4 +1,4 @@ -import { defineBackground } from '~/client'; +import { defineBackground } from '~/sandbox'; export const a = {}; diff --git a/src/core/utils/strings.ts b/src/core/utils/strings.ts index 58f06bf4..287b1619 100644 --- a/src/core/utils/strings.ts +++ b/src/core/utils/strings.ts @@ -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}`; } diff --git a/src/client/__tests__/define-background.test.ts b/src/sandbox/__tests__/define-background.test.ts similarity index 90% rename from src/client/__tests__/define-background.test.ts rename to src/sandbox/__tests__/define-background.test.ts index 0ce67832..6c671c9f 100644 --- a/src/client/__tests__/define-background.test.ts +++ b/src/sandbox/__tests__/define-background.test.ts @@ -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', () => { diff --git a/src/client/content-scripts/__tests__/define-content-script.test.ts b/src/sandbox/__tests__/define-content-script.test.ts similarity index 82% rename from src/client/content-scripts/__tests__/define-content-script.test.ts rename to src/sandbox/__tests__/define-content-script.test.ts index d3488256..a431f034 100644 --- a/src/client/content-scripts/__tests__/define-content-script.test.ts +++ b/src/sandbox/__tests__/define-content-script.test.ts @@ -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', () => { diff --git a/src/client/define-background.ts b/src/sandbox/define-background.ts similarity index 86% rename from src/client/define-background.ts rename to src/sandbox/define-background.ts index 5aced337..9e3854d7 100644 --- a/src/client/define-background.ts +++ b/src/sandbox/define-background.ts @@ -1,4 +1,4 @@ -import { BackgroundDefinition } from '~/types'; +import type { BackgroundDefinition } from '~/types'; export function defineBackground(main: () => void): BackgroundDefinition; export function defineBackground( diff --git a/src/client/content-scripts/define-content-script.ts b/src/sandbox/define-content-script.ts similarity index 69% rename from src/client/content-scripts/define-content-script.ts rename to src/sandbox/define-content-script.ts index 31c46df3..77836d47 100644 --- a/src/client/content-scripts/define-content-script.ts +++ b/src/sandbox/define-content-script.ts @@ -1,4 +1,4 @@ -import { ContentScriptDefinition } from '~/types'; +import type { ContentScriptDefinition } from '~/types'; export function defineContentScript( definition: ContentScriptDefinition, diff --git a/src/sandbox/define-unlisted-script.ts b/src/sandbox/define-unlisted-script.ts index 8aabfe8d..2fbf2522 100644 --- a/src/sandbox/define-unlisted-script.ts +++ b/src/sandbox/define-unlisted-script.ts @@ -1,4 +1,4 @@ -import { UnlistedScriptDefinition } from '~/types'; +import type { UnlistedScriptDefinition } from '~/types'; export function defineUnlistedScript( main: () => void, diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index a47f351c..50725005 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -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'; diff --git a/src/types/external.ts b/src/types/external.ts index bf32baf4..48719ea4 100644 --- a/src/types/external.ts +++ b/src/types/external.ts @@ -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; +} + +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; +} + +export interface ContentScriptBaseDefinition extends ExcludableEntrypoint { matches: PerBrowserOption; /** * See https://developer.chrome.com/docs/extensions/mv3/content_scripts/ @@ -478,11 +507,6 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint { * @default false */ matchOriginAsFallback?: PerBrowserOption; - /** - * 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; } export interface BackgroundDefinition extends ExcludableEntrypoint { diff --git a/src/types/internal.ts b/src/types/internal.ts index d1aa8caa..c4e626d0 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -67,3 +67,9 @@ export interface ExtensionRunner { openBrowser(config: InternalConfig): Promise; closeBrowser(): Promise; } + +export type VirtualEntrypointType = + | 'content-script-main-world' + | 'content-script-isolated-world' + | 'background' + | 'unlisted-script'; diff --git a/src/virtual/content-script-entrypoint.ts b/src/virtual/content-script-isolated-world-entrypoint.ts similarity index 86% rename from src/virtual/content-script-entrypoint.ts rename to src/virtual/content-script-isolated-world-entrypoint.ts index 0458f206..f881ec84 100644 --- a/src/virtual/content-script-entrypoint.ts +++ b/src/virtual/content-script-isolated-world-entrypoint.ts @@ -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'; diff --git a/src/virtual/content-script-main-world-entrypoint.ts b/src/virtual/content-script-main-world-entrypoint.ts new file mode 100644 index 00000000..5ab4d457 --- /dev/null +++ b/src/virtual/content-script-main-world-entrypoint.ts @@ -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, + ); + } +})(); diff --git a/src/virtual/virtual-module-globals.d.ts b/src/virtual/virtual-module-globals.d.ts index 9a150c54..a2523d85 100644 --- a/src/virtual/virtual-module-globals.d.ts +++ b/src/virtual/virtual-module-globals.d.ts @@ -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; }