diff --git a/e2e/tests/auto-imports.test.ts b/e2e/tests/auto-imports.test.ts index f2b42b47..62929fa8 100644 --- a/e2e/tests/auto-imports.test.ts +++ b/e2e/tests/auto-imports.test.ts @@ -17,6 +17,8 @@ describe('Auto Imports', () => { export {} declare global { const ContentScriptContext: typeof import('wxt/client')['ContentScriptContext'] + const InvalidMatchPattern: typeof import('wxt/sandbox')['InvalidMatchPattern'] + const MatchPattern: typeof import('wxt/sandbox')['MatchPattern'] const browser: typeof import('wxt/browser')['browser'] const builtinDrivers: typeof import('wxt/storage')['builtinDrivers'] const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe'] diff --git a/package.json b/package.json index f10ea238..953feea5 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,9 @@ "lint": "run-p -c -s lint:*", "lint:eslint": "echo 'ESLint: TODO'", "lint:package": "publint", - "compile": "tsc --noEmit", + "compile": "run-s -c compile:*", + "compile:wxt": "tsc --noEmit", + "compile:virtual": "tsc --noEmit -p src/virtual", "test": "vitest", "test:coverage": "vitest run --coverage", "prepare": "simple-git-hooks", diff --git a/scripts/build.ts b/scripts/build.ts index df70e7d9..105d395d 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -16,18 +16,17 @@ const startTime = Date.now(); const outDir = 'dist'; await fs.rm(path.join(outDir, '*'), { recursive: true, force: true }); -const preset: tsup.Options = { +const preset = { dts: true, silent: true, sourcemap: false, external: [ - 'vite', 'virtual:user-unlisted-script', 'virtual:user-content-script-isolated-world', 'virtual:user-content-script-main-world', 'virtual:user-background', ], -}; +} satisfies tsup.Options; function spinnerPMap(configs: tsup.Options[]) { let completed = 0; @@ -89,6 +88,7 @@ const config: tsup.Options[] = [ format: ['esm'], splitting: false, dts: false, + external: [...preset.external, 'wxt'], }, // CJS-only { diff --git a/src/browser.ts b/src/browser.ts index 61ee5fab..103b19d6 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -17,3 +17,65 @@ export interface WxtI18n extends I18n.Static { } export const browser: AugmentedBrowser = originalBrowser; + +// re-export all the types from webextension-polyfill +// Because webextension-polyfill uses a weird namespace with "import export", there isn't a good way +// to get these types without re-listing them. +/** @ignore */ +export type { + ActivityLog, + Alarms, + Bookmarks, + Action, + BrowserAction, + BrowserSettings, + BrowsingData, + CaptivePortal, + Clipboard, + Commands, + ContentScripts, + ContextualIdentities, + Cookies, + DeclarativeNetRequest, + Devtools, + Dns, + Downloads, + Events, + Experiments, + Extension, + ExtensionTypes, + Find, + GeckoProfiler, + History, + I18n, + Identity, + Idle, + Management, + Manifest, + ContextMenus, + Menus, + NetworkStatus, + NormandyAddonStudy, + Notifications, + Omnibox, + PageAction, + Permissions, + Pkcs11, + Privacy, + Proxy, + Runtime, + Scripting, + Search, + Sessions, + SidebarAction, + Storage, + Tabs, + Theme, + TopSites, + Types, + Urlbar, + UserScripts, + WebNavigation, + WebRequest, + Windows, +} from 'webextension-polyfill'; diff --git a/src/client/content-scripts/content-script-context.ts b/src/client/content-scripts/content-script-context.ts index 48d925f5..9233113e 100644 --- a/src/client/content-scripts/content-script-context.ts +++ b/src/client/content-scripts/content-script-context.ts @@ -1,6 +1,6 @@ import { ContentScriptDefinition } from '~/types'; import { browser } from '~/browser'; -import { logger } from '~/client/utils/logger'; +import { logger } from '~/sandbox/utils/logger'; import { WxtLocationChangeEvent, getUniqueEventName } from './custom-events'; import { createLocationWatcher } from './location-watcher'; diff --git a/src/client/content-scripts/content-script-iframe.ts b/src/client/content-scripts/content-script-iframe.ts index 8247e13a..2c1ac518 100644 --- a/src/client/content-scripts/content-script-iframe.ts +++ b/src/client/content-scripts/content-script-iframe.ts @@ -1,4 +1,4 @@ -import browser from 'webextension-polyfill'; +import { browser } from '~/browser'; import { ContentScriptAnchoredOptions, ContentScriptPositioningOptions, diff --git a/src/client/content-scripts/content-script-ui.ts b/src/client/content-scripts/content-script-ui.ts index 281a3c87..c8f38e69 100644 --- a/src/client/content-scripts/content-script-ui.ts +++ b/src/client/content-scripts/content-script-ui.ts @@ -1,6 +1,6 @@ import { createIsolatedElement } from '@webext-core/isolated-element'; import { browser } from '~/browser'; -import { logger } from '~/client/utils/logger'; +import { logger } from '~/sandbox/utils/logger'; import { ContentScriptContext } from './content-script-context'; import { ContentScriptAnchoredOptions, diff --git a/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts b/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts index b7023170..cbb1b73f 100644 --- a/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts +++ b/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts @@ -9,7 +9,7 @@ export function webextensionPolyfillInlineDeps(): vite.PluginOption { return { name: 'wxt:testing-inline-deps', config() { - const wxtModules = ['wxt/browser', 'wxt/client']; + const wxtModules = ['wxt/browser']; return { test: { server: { diff --git a/src/core/initialize.ts b/src/core/initialize.ts index b0410b46..6dd77a47 100644 --- a/src/core/initialize.ts +++ b/src/core/initialize.ts @@ -150,14 +150,6 @@ async function cloneProject({ consola.warn('Failed to move _gitignore to .gitignore:', err), ); - // 3. Add .npmrc for pnpm - if (packageManager === 'pnpm') { - await fs.writeFile( - path.join(directory, '.npmrc'), - 'shamefully-hoist=true\n', - ); - } - spinner.succeed(); } catch (err) { spinner.fail(); diff --git a/src/core/utils/building/rebuild.ts b/src/core/utils/building/rebuild.ts index a8d8cfb3..32ab04cb 100644 --- a/src/core/utils/building/rebuild.ts +++ b/src/core/utils/building/rebuild.ts @@ -1,4 +1,4 @@ -import type { Manifest } from 'webextension-polyfill'; +import type { Manifest } from '~/browser'; import { BuildOutput, EntrypointGroup, InternalConfig } from '~/types'; import { findEntrypoints } from './find-entrypoints'; import { generateTypesDir } from './generate-wxt-dir'; diff --git a/src/core/utils/content-scripts.ts b/src/core/utils/content-scripts.ts index 691feb93..a4f016ca 100644 --- a/src/core/utils/content-scripts.ts +++ b/src/core/utils/content-scripts.ts @@ -1,4 +1,4 @@ -import type { Manifest } from 'webextension-polyfill'; +import type { Manifest } from '~/browser'; import { ContentScriptEntrypoint, InternalConfig } from '~/types'; import { resolvePerBrowserOption } from './entrypoints'; diff --git a/src/core/utils/manifest.ts b/src/core/utils/manifest.ts index a4485a77..f43b0890 100644 --- a/src/core/utils/manifest.ts +++ b/src/core/utils/manifest.ts @@ -1,4 +1,4 @@ -import type { Manifest } from 'webextension-polyfill'; +import type { Manifest } from '~/browser'; import { Entrypoint, BackgroundEntrypoint, diff --git a/src/core/utils/testing/fake-objects.ts b/src/core/utils/testing/fake-objects.ts index 949d053a..f846bd8b 100644 --- a/src/core/utils/testing/fake-objects.ts +++ b/src/core/utils/testing/fake-objects.ts @@ -4,7 +4,7 @@ import { resolve } from 'path'; import { faker } from '@faker-js/faker'; import merge from 'lodash.merge'; -import type { Manifest } from 'webextension-polyfill'; +import type { Manifest } from '~/browser'; import { FsCache, InternalConfig, diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 50725005..91b43851 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -6,3 +6,4 @@ export * from './define-unlisted-script'; export * from './define-background'; export * from './define-content-script'; +export * from '@webext-core/match-patterns'; diff --git a/src/client/utils/logger.ts b/src/sandbox/utils/logger.ts similarity index 93% rename from src/client/utils/logger.ts rename to src/sandbox/utils/logger.ts index 66ffcbf6..d2172bd4 100644 --- a/src/client/utils/logger.ts +++ b/src/sandbox/utils/logger.ts @@ -1,3 +1,5 @@ +/// + function print(method: (...args: any[]) => void, ...args: any[]) { if (import.meta.env.MODE === 'production') return; diff --git a/src/storage.ts b/src/storage.ts index 61fe9461..42861467 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -8,7 +8,7 @@ import { defineDriver, Storage, } from 'unstorage'; -import browser, { Storage as BrowserStorage } from 'webextension-polyfill'; +import { browser, Storage as BrowserStorage } from '~/browser'; export interface WebExtensionDriverOptions { storageArea: 'sync' | 'local' | 'managed' | 'session'; diff --git a/src/types/external.ts b/src/types/external.ts index 48719ea4..e535ae7f 100644 --- a/src/types/external.ts +++ b/src/types/external.ts @@ -1,5 +1,5 @@ import type * as vite from 'vite'; -import type { Manifest, Scripting } from 'webextension-polyfill'; +import type { Manifest, Scripting } from '~/browser'; import { UnimportOptions } from 'unimport'; import { LogLevel } from 'consola'; import { ContentScriptContext } from '../client/content-scripts/content-script-context'; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts new file mode 100644 index 00000000..cfa8f0d9 --- /dev/null +++ b/src/types/globals.d.ts @@ -0,0 +1,7 @@ +// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts +declare const __COMMAND__: 'build' | 'serve'; +declare const __DEV_SERVER_PROTOCOL__: string; +declare const __DEV_SERVER_HOSTNAME__: string; +declare const __DEV_SERVER_PORT__: string; +declare const __MANIFEST_VERSION__: 2 | 3; +declare const __ENTRYPOINT__: string; diff --git a/src/types/internal.ts b/src/types/internal.ts index c4e626d0..fc8a631c 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -11,7 +11,7 @@ import { } from './external'; import { UnimportOptions } from 'unimport'; import { ResolvedConfig } from 'c12'; -import type { Manifest } from 'webextension-polyfill'; +import type { Manifest } from '~/browser'; import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; export interface InternalConfig { diff --git a/src/virtual/README.md b/src/virtual/README.md new file mode 100644 index 00000000..b06cebed --- /dev/null +++ b/src/virtual/README.md @@ -0,0 +1,11 @@ +# WXT Virtual Entrypoints + +This folder contains scripts that are either loaded as entrypoints to JS files or included in HTML files, just like a project using WXT might load their own scripts. + +While they are bundled and shipped inside WXT, Vite considers them apart of your project's source code, not WXT's. This means they cannot import 3rd party modules directly, otherwise `pnpm i --shamefully-hoist=false` and Yarn PnP will fail. + +For this reason, the virtual entrypoints get their own TS project to isolate them from the rest of the project. They can only import from `wxt/*` or utils that don't have any imports from node_modules, like the logger. + +When bundling WXT for publishing to NPM, all the `wxt/*` imports are marked as external and resolved when building your application. Other imports are added inline. + +See https://github.com/wxt-dev/wxt/issues/286#issuecomment-1858888390 for more details. diff --git a/src/virtual/background-entrypoint.ts b/src/virtual/background-entrypoint.ts index fac4bfd4..f69897a0 100644 --- a/src/virtual/background-entrypoint.ts +++ b/src/virtual/background-entrypoint.ts @@ -1,9 +1,9 @@ import definition from 'virtual:user-background'; -import { setupWebSocket } from '../client/utils/setup-web-socket'; -import { logger } from '../client/utils/logger'; -import browser from 'webextension-polyfill'; -import { keepServiceWorkerAlive } from '../client/utils/keep-service-worker-alive'; -import { reloadContentScript } from '../client/utils/reload-content-scripts'; +import { setupWebSocket } from './utils/setup-web-socket'; +import { logger } from '../sandbox/utils/logger'; +import { browser } from 'wxt/browser'; +import { keepServiceWorkerAlive } from './utils/keep-service-worker-alive'; +import { reloadContentScript } from './utils/reload-content-scripts'; if (__COMMAND__ === 'serve') { try { diff --git a/src/virtual/content-script-isolated-world-entrypoint.ts b/src/virtual/content-script-isolated-world-entrypoint.ts index f881ec84..429238da 100644 --- a/src/virtual/content-script-isolated-world-entrypoint.ts +++ b/src/virtual/content-script-isolated-world-entrypoint.ts @@ -1,6 +1,6 @@ import definition from 'virtual:user-content-script-isolated-world'; -import { logger } from '~/client/utils/logger'; -import { ContentScriptContext } from '~/client/content-scripts/content-script-context'; +import { logger } from '../sandbox/utils/logger'; +import { ContentScriptContext } from 'wxt/client'; (async () => { try { diff --git a/src/virtual/content-script-main-world-entrypoint.ts b/src/virtual/content-script-main-world-entrypoint.ts index 5ab4d457..43fc689b 100644 --- a/src/virtual/content-script-main-world-entrypoint.ts +++ b/src/virtual/content-script-main-world-entrypoint.ts @@ -1,5 +1,5 @@ import definition from 'virtual:user-content-script-main-world'; -import { logger } from '~/client/utils/logger'; +import { logger } from '../sandbox/utils/logger'; (async () => { try { diff --git a/src/virtual/mock-browser.ts b/src/virtual/mock-browser.ts index f02c0db0..cf37d2f7 100644 --- a/src/virtual/mock-browser.ts +++ b/src/virtual/mock-browser.ts @@ -1,3 +1,3 @@ -import { fakeBrowser as mockBrowser } from '~/testing'; +import { fakeBrowser as mockBrowser } from 'wxt/testing'; export default mockBrowser; diff --git a/src/virtual/reload-html.ts b/src/virtual/reload-html.ts index 0b48da7c..f2a57a17 100644 --- a/src/virtual/reload-html.ts +++ b/src/virtual/reload-html.ts @@ -1,7 +1,5 @@ -/// - -import { logger } from '~/client/utils/logger'; -import { setupWebSocket } from '~/client/utils/setup-web-socket'; +import { logger } from '../sandbox/utils/logger'; +import { setupWebSocket } from './utils/setup-web-socket'; if (__COMMAND__ === 'serve') { try { diff --git a/src/virtual/tsconfig.json b/src/virtual/tsconfig.json new file mode 100644 index 00000000..929d30b6 --- /dev/null +++ b/src/virtual/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["vite/client", "../types/globals.d.ts"] + }, + "include": ["./*"] +} diff --git a/src/virtual/unlisted-script-entrypoint.ts b/src/virtual/unlisted-script-entrypoint.ts index ea17e2e0..f7c1bead 100644 --- a/src/virtual/unlisted-script-entrypoint.ts +++ b/src/virtual/unlisted-script-entrypoint.ts @@ -1,5 +1,5 @@ import definition from 'virtual:user-unlisted-script'; -import { logger } from '~/client/utils/logger'; +import { logger } from '../sandbox/utils/logger'; (async () => { try { diff --git a/src/client/utils/keep-service-worker-alive.ts b/src/virtual/utils/keep-service-worker-alive.ts similarity index 85% rename from src/client/utils/keep-service-worker-alive.ts rename to src/virtual/utils/keep-service-worker-alive.ts index c465b477..6a76d31a 100644 --- a/src/client/utils/keep-service-worker-alive.ts +++ b/src/virtual/utils/keep-service-worker-alive.ts @@ -1,4 +1,4 @@ -import browser from 'webextension-polyfill'; +import { browser } from 'wxt/browser'; /** * https://developer.chrome.com/blog/longer-esw-lifetimes/ diff --git a/src/client/utils/reload-content-scripts.ts b/src/virtual/utils/reload-content-scripts.ts similarity index 74% rename from src/client/utils/reload-content-scripts.ts rename to src/virtual/utils/reload-content-scripts.ts index 73994819..a623c30d 100644 --- a/src/client/utils/reload-content-scripts.ts +++ b/src/virtual/utils/reload-content-scripts.ts @@ -1,8 +1,14 @@ -import browser, { Manifest } from 'webextension-polyfill'; -import { logger } from './logger'; -import { MatchPattern } from '@webext-core/match-patterns'; +import { browser } from 'wxt/browser'; +import { logger } from '../../sandbox/utils/logger'; +import { MatchPattern } from 'wxt/sandbox'; -export function reloadContentScript(contentScript: Manifest.ContentScript) { +interface ContentScript { + matches: string[]; + js?: string[]; + css?: string[]; +} + +export function reloadContentScript(contentScript: ContentScript) { const manifest = browser.runtime.getManifest(); if (manifest.manifest_version == 2) { void reloadContentScriptMv2(contentScript); @@ -11,9 +17,7 @@ export function reloadContentScript(contentScript: Manifest.ContentScript) { } } -export async function reloadContentScriptMv3( - contentScript: Manifest.ContentScript, -) { +export async function reloadContentScriptMv3(contentScript: ContentScript) { const id = `wxt:${contentScript.js![0]}`; logger.log('Reloading content script:', contentScript); const registered = await browser.scripting.getRegisteredContentScripts(); @@ -41,8 +45,6 @@ export async function reloadContentScriptMv3( await Promise.all(matchingTabs.map((tab) => browser.tabs.reload(tab.id))); } -export async function reloadContentScriptMv2( - contentScript: Manifest.ContentScript, -) { +export async function reloadContentScriptMv2(contentScript: ContentScript) { throw Error('TODO: reloadContentScriptMv2'); } diff --git a/src/client/utils/setup-web-socket.ts b/src/virtual/utils/setup-web-socket.ts similarity index 95% rename from src/client/utils/setup-web-socket.ts rename to src/virtual/utils/setup-web-socket.ts index 55c93214..9e1b58e4 100644 --- a/src/client/utils/setup-web-socket.ts +++ b/src/virtual/utils/setup-web-socket.ts @@ -1,4 +1,4 @@ -import { logger } from './logger'; +import { logger } from '../../sandbox/utils/logger'; interface WebSocketMessage { type: string; diff --git a/src/virtual/virtual-module-globals.d.ts b/src/virtual/virtual-module-globals.d.ts index a2523d85..c324452f 100644 --- a/src/virtual/virtual-module-globals.d.ts +++ b/src/virtual/virtual-module-globals.d.ts @@ -1,34 +1,39 @@ // Types required to make the virtual modules happy. -declare module '*?raw' { - const content: any; - export default content; -} - declare module 'virtual:user-background' { - const definition: import('~/types').BackgroundDefinition; + const definition: { main: () => void }; export default definition; } declare module 'virtual:user-content-script-isolated-world' { - const definition: import('~/types').ContentScriptIsolatedWorldDefinition; + const definition: { + main: ( + ctx: import('wxt/client').ContentScriptContext, + ) => void | Promise; + }; export default definition; } declare module 'virtual:user-content-script-main-world' { - const definition: import('~/types').ContentScriptMainWorldDefinition; + const definition: { main: () => void | Promise }; export default definition; } declare module 'virtual:user-unlisted-script' { - const definition: import('~/types').UnlistedScriptDefinition; + const definition: { main: () => void | Promise }; export default definition; } -// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts -declare const __COMMAND__: 'build' | 'serve'; -declare const __DEV_SERVER_PROTOCOL__: string; -declare const __DEV_SERVER_HOSTNAME__: string; -declare const __DEV_SERVER_PORT__: string; -declare const __MANIFEST_VERSION__: 2 | 3; -declare const __ENTRYPOINT__: string; +declare module 'wxt/browser' { + export const browser: import('webextension-polyfill').Browser; +} + +declare module 'wxt/client' { + export class ContentScriptContext { + constructor(name: string, options: any); + } +} + +declare module 'wxt/testing' { + export const fakeBrowser: import('webextension-polyfill').Browser; +} diff --git a/tsconfig.json b/tsconfig.json index de22f577..221324d8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,5 @@ "~/*": ["./src/*"] } }, - "exclude": ["dist", "demo", "e2e/dist", "templates"] + "exclude": ["dist", "demo", "e2e/dist", "templates", "src/virtual"] } diff --git a/vitest.workspace.ts b/vitest.workspace.ts index dafc2686..1a82c8ee 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -20,6 +20,7 @@ const resolve = { alias: { '~': path.resolve('src'), 'webextension-polyfill': path.resolve('src/virtual/mock-browser'), + 'wxt/testing': path.resolve('src/testing'), }, };