├── .codecov.yml ├── .commitlintrc.yml ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.md ├── actions │ └── setup │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── pkg.pr.new.yml │ ├── pr-closed.yml │ ├── pr-title.yml │ ├── publish-docs.yml │ ├── release.yml │ ├── sync-releases.yml │ ├── update-browser-package.yml │ ├── validate.yml │ └── vhs.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.yml ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── .vitepress │ ├── Dockerfile │ ├── components │ │ ├── BlogHome.vue │ │ ├── BlogLayout.vue │ │ ├── BlogPostPreview.vue │ │ ├── EntrypointPatterns.vue │ │ ├── ExampleSearch.vue │ │ ├── ExampleSearchFilterByItem.vue │ │ ├── ExampleSearchResult.vue │ │ ├── Icon.vue │ │ └── UsingWxtSection.vue │ ├── composables │ │ ├── useBlogDate.ts │ │ └── useListExtensionDetails.ts │ ├── config.ts │ ├── loaders │ │ ├── blog.data.ts │ │ └── cli.data.ts │ ├── theme │ │ ├── custom.css │ │ └── index.ts │ └── utils │ │ ├── head.ts │ │ ├── menus.ts │ │ └── types.ts ├── analytics.md ├── api │ └── cli │ │ ├── wxt-build.md │ │ ├── wxt-clean.md │ │ ├── wxt-init.md │ │ ├── wxt-prepare.md │ │ ├── wxt-submit-init.md │ │ ├── wxt-submit.md │ │ ├── wxt-zip.md │ │ └── wxt.md ├── assets │ ├── cli-output.png │ └── init-demo.gif ├── auto-icons.md ├── blog.md ├── blog │ ├── .drafts │ │ └── 2024-10-19-real-world-messaging.md │ └── 2024-12-06-using-imports-module.md ├── examples.md ├── guide │ ├── essentials │ │ ├── assets.md │ │ ├── config │ │ │ ├── auto-imports.md │ │ │ ├── browser-startup.md │ │ │ ├── build-mode.md │ │ │ ├── entrypoint-loaders.md │ │ │ ├── environment-variables.md │ │ │ ├── hooks.md │ │ │ ├── manifest.md │ │ │ ├── runtime.md │ │ │ ├── typescript.md │ │ │ └── vite.md │ │ ├── content-scripts.md │ │ ├── e2e-testing.md │ │ ├── entrypoints.md │ │ ├── es-modules.md │ │ ├── extension-apis.md │ │ ├── frontend-frameworks.md │ │ ├── i18n.md │ │ ├── messaging.md │ │ ├── project-structure.md │ │ ├── publishing.md │ │ ├── remote-code.md │ │ ├── scripting.md │ │ ├── storage.md │ │ ├── target-different-browsers.md │ │ ├── testing-updates.md │ │ ├── unit-testing.md │ │ └── wxt-modules.md │ ├── installation.md │ ├── introduction.md │ └── resources │ │ ├── community.md │ │ ├── compare.md │ │ ├── faq.md │ │ ├── how-wxt-works.md │ │ ├── migrate.md │ │ └── upgrading.md ├── i18n.md ├── index.md ├── public │ ├── _redirects │ ├── content-script-ui-alignment.png │ ├── content-script-ui-append.png │ ├── content-script-ui-position.png │ ├── favicon.ico │ ├── hero-logo.svg │ ├── logo.svg │ ├── robots.txt │ └── social-preview.png ├── storage.md ├── tapes │ └── init-demo.tape ├── typedoc.json └── unocss.md ├── package.json ├── packages ├── analytics │ ├── CHANGELOG.md │ ├── README.md │ ├── app.config.ts │ ├── build.config.ts │ ├── entrypoints │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.ts │ ├── modules │ │ └── analytics │ │ │ ├── background-plugin.ts │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ ├── providers │ │ │ ├── google-analytics-4.ts │ │ │ └── umami.ts │ │ │ └── types.ts │ ├── package.json │ ├── public │ │ └── .keep │ ├── tsconfig.json │ └── wxt.config.ts ├── auto-icons │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── browser │ ├── README.md │ ├── package.json │ ├── scripts │ │ └── generate.ts │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── gen │ │ │ ├── chrome-cast │ │ │ │ └── index.d.ts │ │ │ ├── har-format │ │ │ │ └── index.d.ts │ │ │ └── index.d.ts │ │ ├── index.d.ts │ │ └── index.mjs │ ├── templates │ │ └── package.json │ └── tsconfig.json ├── i18n │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── build.test.ts │ │ │ ├── index.test.ts │ │ │ ├── types.test.ts │ │ │ └── utils.test.ts │ │ ├── build.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── supported-locales.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── module-react │ ├── CHANGELOG.md │ ├── README.md │ ├── build.config.ts │ ├── components │ │ └── App.tsx │ ├── entrypoints │ │ ├── content │ │ │ └── index.tsx │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.tsx │ ├── modules │ │ └── react.ts │ ├── package.json │ ├── public │ │ └── .keep │ └── tsconfig.json ├── module-solid │ ├── CHANGELOG.md │ ├── README.md │ ├── build.config.ts │ ├── components │ │ └── App.tsx │ ├── entrypoints │ │ ├── content │ │ │ └── index.tsx │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.tsx │ ├── modules │ │ └── solid.ts │ ├── package.json │ ├── public │ │ └── .keep │ └── tsconfig.json ├── module-svelte │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── module-vue │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── storage │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── vitest.config.ts │ └── vitest.setup.ts ├── unocss │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── webextension-polyfill │ ├── README.md │ ├── build.config.ts │ ├── entrypoints │ │ ├── content │ │ │ └── index.ts │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.ts │ ├── modules │ │ └── webextension-polyfill │ │ │ ├── browser.ts │ │ │ └── index.ts │ ├── package.json │ ├── public │ │ └── .keep │ └── tsconfig.json ├── wxt-demo │ ├── eslint.config.js │ ├── modules │ │ ├── auto-icons.ts │ │ ├── example.ts │ │ ├── i18n.ts │ │ └── unocss.ts │ ├── package.json │ ├── src │ │ ├── app.config.ts │ │ ├── assets │ │ │ └── icon.png │ │ ├── entrypoints │ │ │ ├── __tests__ │ │ │ │ └── background.test.ts │ │ │ ├── automount.content │ │ │ │ ├── index.ts │ │ │ │ └── style.css │ │ │ ├── background.ts │ │ │ ├── example-2.scss │ │ │ ├── example-tsx.content.tsx │ │ │ ├── example.sandbox │ │ │ │ └── index.html │ │ │ ├── iframe-src │ │ │ │ ├── index.html │ │ │ │ └── main.ts │ │ │ ├── iframe.content.ts │ │ │ ├── injected.content │ │ │ │ └── index.css │ │ │ ├── location-change.content.ts │ │ │ ├── main-world.content.ts │ │ │ ├── options │ │ │ │ ├── index.html │ │ │ │ ├── main.ts │ │ │ │ └── style.css │ │ │ ├── popup.html │ │ │ ├── sandbox.html │ │ │ ├── sidepanel.html │ │ │ ├── ui.content │ │ │ │ ├── index.ts │ │ │ │ ├── manual-style.css │ │ │ │ └── style.css │ │ │ └── unlisted.ts │ │ ├── locales │ │ │ └── en.yml │ │ └── utils │ │ │ └── logger.ts │ ├── tsconfig.json │ ├── vitest.config.ts │ └── wxt.config.ts └── wxt │ ├── .oxlintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ ├── wxt-publish-extension.cjs │ └── wxt.mjs │ ├── build.config.ts │ ├── e2e │ ├── tests │ │ ├── __snapshots__ │ │ │ └── auto-imports.test.ts.snap │ │ ├── analysis.test.ts │ │ ├── auto-imports.test.ts │ │ ├── dev.test.ts │ │ ├── hooks.test.ts │ │ ├── init.test.ts │ │ ├── manifest-content.test.ts │ │ ├── modules.test.ts │ │ ├── npm-packages.test.ts │ │ ├── output-structure.test.ts │ │ ├── react.test.ts │ │ ├── remote-code.test.ts │ │ ├── typescript-project.test.ts │ │ ├── user-config.test.ts │ │ └── zip.test.ts │ └── utils.ts │ ├── package.json │ ├── src │ ├── @types │ │ ├── globals.d.ts │ │ ├── modules.d.ts │ │ └── project-types.d.ts │ ├── __tests__ │ │ └── modules.test.ts │ ├── browser.ts │ ├── builtin-modules │ │ ├── index.ts │ │ └── unimport.ts │ ├── cli │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── cli-utils.ts │ │ ├── commands.ts │ │ └── index.ts │ ├── core │ │ ├── build.ts │ │ ├── builders │ │ │ └── vite │ │ │ │ ├── __tests__ │ │ │ │ └── fixtures │ │ │ │ │ ├── module.ts │ │ │ │ │ └── test.ts │ │ │ │ ├── index.ts │ │ │ │ └── plugins │ │ │ │ ├── __tests__ │ │ │ │ └── devHtmlPrerender.test.ts │ │ │ │ ├── bundleAnalysis.ts │ │ │ │ ├── cssEntrypoints.ts │ │ │ │ ├── defineImportMeta.ts │ │ │ │ ├── devHtmlPrerender.ts │ │ │ │ ├── devServerGlobals.ts │ │ │ │ ├── download.ts │ │ │ │ ├── entrypointGroupGlobals.ts │ │ │ │ ├── extensionApiMock.ts │ │ │ │ ├── globals.ts │ │ │ │ ├── index.ts │ │ │ │ ├── multipageMove.ts │ │ │ │ ├── noopBackground.ts │ │ │ │ ├── removeEntrypointMainFunction.ts │ │ │ │ ├── resolveAppConfig.ts │ │ │ │ ├── resolveVirtualModules.ts │ │ │ │ ├── tsconfigPaths.ts │ │ │ │ └── wxtPluginLoader.ts │ │ ├── clean.ts │ │ ├── create-server.ts │ │ ├── define-config.ts │ │ ├── define-web-ext-config.ts │ │ ├── generate-wxt-dir.ts │ │ ├── index.ts │ │ ├── initialize.ts │ │ ├── keyboard-shortcuts.ts │ │ ├── package-managers │ │ │ ├── __tests__ │ │ │ │ ├── bun.test.ts │ │ │ │ ├── fixtures │ │ │ │ │ ├── simple-bun-project │ │ │ │ │ │ ├── bun.lockb │ │ │ │ │ │ └── package.json │ │ │ │ │ ├── simple-npm-project │ │ │ │ │ │ ├── package-lock.json │ │ │ │ │ │ └── package.json │ │ │ │ │ ├── simple-pnpm-project │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── pnpm-lock.yaml │ │ │ │ │ └── simple-yarn-project │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── yarn.lock │ │ │ │ ├── npm.test.ts │ │ │ │ ├── pnpm.test.ts │ │ │ │ └── yarn.test.ts │ │ │ ├── bun.ts │ │ │ ├── deno.ts │ │ │ ├── index.ts │ │ │ ├── npm.ts │ │ │ ├── pnpm.ts │ │ │ ├── types.ts │ │ │ └── yarn.ts │ │ ├── prepare.ts │ │ ├── resolve-config.ts │ │ ├── runners │ │ │ ├── __tests__ │ │ │ │ └── index.test.ts │ │ │ ├── index.ts │ │ │ ├── manual.ts │ │ │ ├── safari.ts │ │ │ ├── web-ext.ts │ │ │ └── wsl.ts │ │ ├── utils │ │ │ ├── __tests__ │ │ │ │ ├── arrays.test.ts │ │ │ │ ├── content-scripts.test.ts │ │ │ │ ├── content-security-policy.test.ts │ │ │ │ ├── entrypoints.test.ts │ │ │ │ ├── manifest.test.ts │ │ │ │ ├── number.test.ts │ │ │ │ ├── package.test.ts │ │ │ │ ├── paths.test.ts │ │ │ │ ├── strings.test.ts │ │ │ │ ├── transform.test.ts │ │ │ │ ├── validation.test.ts │ │ │ │ └── virtual-modules.test.ts │ │ │ ├── arrays.ts │ │ │ ├── building │ │ │ │ ├── __tests__ │ │ │ │ │ ├── detect-dev-changes.test.ts │ │ │ │ │ ├── find-entrypoints.test.ts │ │ │ │ │ ├── group-entrypoints.test.ts │ │ │ │ │ └── test-entrypoints │ │ │ │ │ │ ├── background.ts │ │ │ │ │ │ ├── content.ts │ │ │ │ │ │ ├── imported-option.ts │ │ │ │ │ │ ├── no-default-export.ts │ │ │ │ │ │ ├── react.tsx │ │ │ │ │ │ ├── unlisted.ts │ │ │ │ │ │ └── with-named.ts │ │ │ │ ├── build-entrypoints.ts │ │ │ │ ├── detect-dev-changes.ts │ │ │ │ ├── find-entrypoints.ts │ │ │ │ ├── group-entrypoints.ts │ │ │ │ ├── index.ts │ │ │ │ ├── internal-build.ts │ │ │ │ └── rebuild.ts │ │ │ ├── cache.ts │ │ │ ├── cli.ts │ │ │ ├── constants.ts │ │ │ ├── content-scripts.ts │ │ │ ├── content-security-policy.ts │ │ │ ├── entrypoints.ts │ │ │ ├── env.ts │ │ │ ├── environments │ │ │ │ ├── browser-environment.ts │ │ │ │ ├── environment.ts │ │ │ │ ├── extension-environment.ts │ │ │ │ └── index.ts │ │ │ ├── eslint.ts │ │ │ ├── fs.ts │ │ │ ├── globals.ts │ │ │ ├── i18n.ts │ │ │ ├── log │ │ │ │ ├── index.ts │ │ │ │ ├── printBuildSummary.ts │ │ │ │ ├── printFileList.ts │ │ │ │ ├── printHeader.ts │ │ │ │ └── printTable.ts │ │ │ ├── manifest.ts │ │ │ ├── network.ts │ │ │ ├── number.ts │ │ │ ├── package.ts │ │ │ ├── paths.ts │ │ │ ├── strings.ts │ │ │ ├── syntax-errors.ts │ │ │ ├── testing │ │ │ │ └── fake-objects.ts │ │ │ ├── time.ts │ │ │ ├── transform.ts │ │ │ ├── types.ts │ │ │ ├── validation.ts │ │ │ ├── virtual-modules.ts │ │ │ └── wsl.ts │ │ ├── wxt.ts │ │ └── zip.ts │ ├── index.ts │ ├── modules.ts │ ├── testing │ │ ├── fake-browser.ts │ │ ├── index.ts │ │ └── wxt-vitest-plugin.ts │ ├── types.ts │ ├── utils │ │ ├── README.md │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── split-shadow-root-css.test.ts.snap │ │ │ ├── content-script-context.test.ts │ │ │ ├── define-background.test.ts │ │ │ ├── define-content-script.test.ts │ │ │ ├── define-unlisted-script.test.ts │ │ │ └── split-shadow-root-css.test.ts │ │ ├── app-config.ts │ │ ├── content-script-context.ts │ │ ├── content-script-ui │ │ │ ├── __tests__ │ │ │ │ └── index.test.ts │ │ │ ├── iframe.ts │ │ │ ├── integrated.ts │ │ │ ├── shadow-root.ts │ │ │ ├── shared.ts │ │ │ └── types.ts │ │ ├── define-app-config.ts │ │ ├── define-background.ts │ │ ├── define-content-script.ts │ │ ├── define-unlisted-script.ts │ │ ├── define-wxt-plugin.ts │ │ ├── inject-script.ts │ │ ├── internal │ │ │ ├── custom-events.ts │ │ │ ├── dev-server-websocket.ts │ │ │ ├── location-watcher.ts │ │ │ └── logger.ts │ │ ├── match-patterns.ts │ │ ├── split-shadow-root-css.ts │ │ └── storage.ts │ ├── version.ts │ ├── virtual │ │ ├── README.md │ │ ├── background-entrypoint.ts │ │ ├── content-script-isolated-world-entrypoint.ts │ │ ├── content-script-main-world-entrypoint.ts │ │ ├── mock-browser.ts │ │ ├── reload-html.ts │ │ ├── tsconfig.json │ │ ├── unlisted-script-entrypoint.ts │ │ ├── utils │ │ │ ├── keep-service-worker-alive.ts │ │ │ └── reload-content-scripts.ts │ │ └── virtual-module-globals.d.ts │ └── vite-builder-env.d.ts │ ├── tsconfig.json │ ├── typedoc.json │ ├── vitest.config.ts │ ├── vitest.globalSetup.ts │ └── vitest.setup.ts ├── patches ├── markdown-it-footnote.md └── markdown-it-footnote.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── benchmarks │ ├── browser-startup.patch │ └── browser-startup.sh ├── bump-package-version.ts ├── create-github-release.ts ├── generate-readmes.sh ├── git.ts └── sync-releases.ts ├── taze.config.ts ├── templates ├── react │ ├── README.md │ ├── _gitignore │ ├── assets │ │ └── react.svg │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.ts │ │ └── popup │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ └── style.css │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts ├── solid │ ├── README.md │ ├── _gitignore │ ├── assets │ │ └── solid.svg │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.ts │ │ └── popup │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ └── style.css │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts ├── svelte │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── _gitignore │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── src │ │ ├── assets │ │ │ └── svelte.svg │ │ ├── entrypoints │ │ │ ├── background.ts │ │ │ ├── content.ts │ │ │ └── popup │ │ │ │ ├── App.svelte │ │ │ │ ├── app.css │ │ │ │ ├── index.html │ │ │ │ └── main.ts │ │ └── lib │ │ │ └── Counter.svelte │ ├── tsconfig.json │ ├── wxt-env.d.ts │ └── wxt.config.ts ├── vanilla │ ├── _gitignore │ ├── assets │ │ └── typescript.svg │ ├── components │ │ └── counter.ts │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.ts │ │ └── popup │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ └── style.css │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts └── vue │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── _gitignore │ ├── assets │ └── vue.svg │ ├── components │ └── HelloWorld.vue │ ├── entrypoints │ ├── background.ts │ ├── content.ts │ └── popup │ │ ├── App.vue │ │ ├── index.html │ │ ├── main.ts │ │ └── style.css │ ├── package.json │ ├── public │ ├── icon │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 96.png │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts ├── tsconfig.base.json └── tsconfig.json /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | -------------------------------------------------------------------------------- /.commitlintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - '@commitlint/config-conventional' 3 | rules: 4 | subject-case: 5 | - 0 6 | - always 7 | - - sentence-case 8 | - start-case 9 | - pascal-case 10 | - upper-case 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`. 2 | 3 | # Normalize EOL for all files that Git considers text files 4 | * text=auto eol=lf 5 | 6 | # Mark lock files as generated to avoid diffing 7 | pnpm-lock.yaml linguist-generated 8 | package-lock.json linguist-generated 9 | bun.lockb linguist-generated 10 | yarn.lock linguist-generated 11 | 12 | # Exclude templates from language statistics 13 | templates/**/* linguist-vendored 14 | 15 | # Other generated files 16 | packages/browser/src/gen/** linguist-generated 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Set default 2 | * @aklinker1 @Timeraa 3 | 4 | # Secure Directories 5 | /.github/ @aklinker1 6 | 7 | # Creator of specific wxt modules 8 | /packages/auto-icons/ @Timeraa 9 | /packages/unocss/ @Timeraa 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files 2 | 3 | github: wxt-dev 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Chat 4 | url: https://discord.gg/ZFsZqGery9 5 | about: Ask questions and discuss with other WXT users in real time. 6 | - name: Questions & Discussions 7 | url: https://github.com/wxt-dev/wxt/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for WXT 4 | title: '' 5 | type: Feature 6 | assignees: '' 7 | --- 8 | 9 | ### Feature Request 10 | 11 | Please describe your feature, be clear and concise. If you have a proposal for required type or API changes, list them here. 12 | 13 | #### Is your feature request related to a bug? 14 | 15 | If so, add a link here. If not, write "N/A" 16 | 17 | ### What are the alternatives? 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ### Additional context 22 | 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Basic Setup 2 | description: Install PNPM, Node, and dependencies 3 | 4 | inputs: 5 | install: 6 | default: 'true' 7 | type: boolean 8 | description: Whether or not to run 'pnpm install' 9 | 10 | installArgs: 11 | default: '' 12 | type: string 13 | description: Additional args to append to "pnpm install" 14 | 15 | runs: 16 | using: composite 17 | 18 | steps: 19 | - name: 🛠️ Setup PNPM 20 | uses: pnpm/action-setup@v4 21 | 22 | - name: 🛠️ Setup NodeJS 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | cache: pnpm 27 | 28 | - name: 📦 Install Dependencies 29 | if: ${{ inputs.install == 'true' }} 30 | shell: bash 31 | run: pnpm install ${{ inputs.installArgs }} 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: 'monthly' 12 | - package-ecosystem: 'github-actions' 13 | directory: '/' 14 | schedule: 15 | interval: 'monthly' 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | 4 | 5 | ### Manual Testing 6 | 7 | 8 | 9 | ### Related Issue 10 | 11 | 12 | 13 | This PR closes # 14 | -------------------------------------------------------------------------------- /.github/workflows/pkg.pr.new.yml: -------------------------------------------------------------------------------- 1 | name: ✨ pkg.pr.new 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | name: Publish Test Packages 16 | runs-on: ubuntu-22.04 17 | if: ${{ github.repository == 'wxt-dev/wxt' }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup 23 | uses: ./.github/actions/setup 24 | 25 | - name: Build All Packages 26 | run: pnpm buildc all 27 | 28 | - name: Publish 29 | run: pnpx pkg-pr-new publish --compact --pnpm './packages/*' 30 | -------------------------------------------------------------------------------- /.github/workflows/pr-closed.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 PR closed 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - closed 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: write 11 | 12 | jobs: 13 | thank-you: 14 | runs-on: ubuntu-latest 15 | if: github.event.pull_request.merged == true 16 | 17 | steps: 18 | - name: Post Thank You Comment 19 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 20 | env: 21 | comment: Thanks for helping make WXT better! 22 | with: 23 | script: | 24 | github.rest.issues.createComment({ 25 | issue_number: context.issue.number, 26 | owner: context.repo.owner, 27 | repo: context.repo.repo, 28 | body: process.env.comment 29 | }) 30 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: 🛡️ Check PR Title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited] 6 | 7 | jobs: 8 | lint-pr-title: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | with: 15 | # Only fetch the config file from the repository 16 | sparse-checkout-cone-mode: false 17 | sparse-checkout: .commitlintrc.yml 18 | 19 | - name: Install dependencies 20 | run: npm install --global @commitlint/config-conventional commitlint 21 | 22 | - name: Check PR title with commitlint 23 | env: 24 | PR_TITLE: ${{ github.event.pull_request.title }} 25 | HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles 26 | run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL 27 | -------------------------------------------------------------------------------- /.github/workflows/sync-releases.yml: -------------------------------------------------------------------------------- 1 | name: 🔄 Sync Releases 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | package: 6 | description: Package to sync 7 | default: wxt 8 | type: choice 9 | options: 10 | - analytics 11 | - auto-icons 12 | - i18n 13 | - module-react 14 | - module-solid 15 | - module-svelte 16 | - module-vue 17 | - storage 18 | - webextension-polyfill 19 | - wxt 20 | 21 | permissions: 22 | contents: read 23 | 24 | jobs: 25 | sync: 26 | name: Sync Releases 27 | runs-on: ubuntu-22.04 28 | permissions: 29 | contents: write 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup 35 | uses: ./.github/actions/setup 36 | with: 37 | installArgs: --ignore-scripts 38 | 39 | - name: Sync Releases 40 | run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }} 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/vhs.yml: -------------------------------------------------------------------------------- 1 | name: 📼 VHS 2 | on: 3 | push: 4 | paths: 5 | - 'docs/tapes/*.tape' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | vhs: 13 | name: Create VHS 14 | runs-on: ubuntu-22.04 15 | if: ${{ github.repository == 'wxt-dev/wxt' }} 16 | permissions: 17 | contents: write 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup 23 | uses: ./.github/actions/setup 24 | with: 25 | install: false 26 | 27 | # This prevents pnpm dlx from downloading WXT in the video 28 | - name: Pre-install WXT 29 | run: | 30 | pnpm store add wxt@latest 31 | pnpm dlx wxt@latest --version 32 | 33 | - name: Record VHS 34 | uses: charmbracelet/vhs-action@v2.1.0 35 | with: 36 | path: 'docs/tapes/init-demo.tape' 37 | 38 | - name: Save recorded GIF 39 | uses: stefanzweifel/git-auto-commit-action@v5 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | with: 43 | commit_message: 'docs: Update `wxt init` GIF' 44 | # https://github.com/charmbracelet/vhs#output 45 | file_pattern: 'docs/assets/*.gif' 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .env.* 4 | .idea 5 | .output 6 | .webextrc 7 | .wxt 8 | *.log 9 | /docs/.vitepress/cache 10 | docs/.vitepress/.temp 11 | coverage 12 | dist 13 | node_modules 14 | TODOs.md 15 | web-ext.config.js 16 | web-ext.config.ts 17 | templates/*/pnpm-lock.yaml 18 | templates/*/yarn.lock 19 | templates/*/package-lock.json 20 | docs/api/reference 21 | stats.html 22 | .tool-versions 23 | .cache 24 | *-stats.txt 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .output 2 | coverage 3 | dist 4 | .wxt 5 | docs/.vitepress/cache 6 | pnpm-lock.yaml 7 | CHANGELOG.md 8 | packages/browser/src/gen 9 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | endOfLine: lf 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "davidanson.vscode-markdownlint", 4 | "esbenp.prettier-vscode", 5 | "github.vscode-github-actions" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Set default formatter 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | 5 | "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, 6 | "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, 7 | "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, 8 | "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, 9 | 10 | // Additional guidelines for Copilot 11 | "github.copilot.chat.codeGeneration.instructions": [ 12 | { "file": "CONTRIBUTING.md" } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Aaron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | While WXT is in prerelease, only the latest version will receive security updates. The latest version is: 4 | 5 | npm version 6 | 7 | 16 | -------------------------------------------------------------------------------- /docs/.vitepress/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lipanski/docker-static-website:latest 2 | COPY dist . 3 | -------------------------------------------------------------------------------- /docs/.vitepress/components/EntrypointPatterns.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /docs/.vitepress/components/Icon.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /docs/.vitepress/composables/useBlogDate.ts: -------------------------------------------------------------------------------- 1 | import { computed, toValue, MaybeRefOrGetter } from 'vue'; 2 | 3 | const MONTH_FORMATTER = new Intl.DateTimeFormat( 4 | globalThis?.navigator?.language, 5 | { 6 | month: 'long', 7 | }, 8 | ); 9 | 10 | export default function (date: MaybeRefOrGetter) { 11 | return computed(() => { 12 | const d = new Date(toValue(date)); 13 | return `${MONTH_FORMATTER.format(d)} ${d.getDate()}, ${d.getFullYear()}`; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /docs/.vitepress/loaders/blog.data.ts: -------------------------------------------------------------------------------- 1 | import { createContentLoader } from 'vitepress'; 2 | 3 | export default createContentLoader('blog/*.md'); 4 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import Icon from '../components/Icon.vue'; 3 | import EntrypointPatterns from '../components/EntrypointPatterns.vue'; 4 | import UsingWxtSection from '../components/UsingWxtSection.vue'; 5 | import ExampleSearch from '../components/ExampleSearch.vue'; 6 | import BlogLayout from '../components/BlogLayout.vue'; 7 | import './custom.css'; 8 | import 'virtual:group-icons.css'; 9 | 10 | export default { 11 | extends: DefaultTheme, 12 | enhanceApp(ctx) { 13 | ctx.app 14 | .component('Icon', Icon) 15 | .component('EntrypointPatterns', EntrypointPatterns) 16 | .component('UsingWxtSection', UsingWxtSection) 17 | .component('ExampleSearch', ExampleSearch) 18 | .component('blog', BlogLayout); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /docs/.vitepress/utils/head.ts: -------------------------------------------------------------------------------- 1 | import { HeadConfig } from 'vitepress/types/shared'; 2 | 3 | export function meta( 4 | property: string, 5 | content: string, 6 | options?: { useName: boolean }, 7 | ): HeadConfig { 8 | return [ 9 | 'meta', 10 | { 11 | [options?.useName ? 'name' : 'property']: property, 12 | content, 13 | }, 14 | ]; 15 | } 16 | 17 | export function script( 18 | src: string, 19 | props: Record = {}, 20 | ): HeadConfig { 21 | return [ 22 | 'script', 23 | { 24 | ...props, 25 | src, 26 | }, 27 | ]; 28 | } 29 | -------------------------------------------------------------------------------- /docs/.vitepress/utils/types.ts: -------------------------------------------------------------------------------- 1 | export interface Example { 2 | name: string; 3 | description?: string; 4 | url: string; 5 | searchText: string; 6 | apis: string[]; 7 | permissions: string[]; 8 | packages: string[]; 9 | } 10 | 11 | export type ExamplesMetadata = { 12 | examples: Example[]; 13 | allApis: string[]; 14 | allPermissions: string[]; 15 | allPackages: string[]; 16 | }; 17 | 18 | export type KeySelectedObject = Record; 19 | -------------------------------------------------------------------------------- /docs/analytics.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-build.md: -------------------------------------------------------------------------------- 1 | # `wxt build` 2 | 3 | 6 | 7 |
{{ data.build }}
8 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-clean.md: -------------------------------------------------------------------------------- 1 | # `wxt clean` 2 | 3 | 6 | 7 |
{{ data.clean }}
8 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-init.md: -------------------------------------------------------------------------------- 1 | # `wxt init` 2 | 3 | 6 | 7 |
{{ data.init }}
8 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-prepare.md: -------------------------------------------------------------------------------- 1 | # `wxt prepare` 2 | 3 | 6 | 7 |
{{ data.prepare }}
8 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-submit-init.md: -------------------------------------------------------------------------------- 1 | # `wxt submit init` 2 | 3 | > Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension) 4 | 5 | 8 | 9 |
{{ data.submitInit }}
10 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-submit.md: -------------------------------------------------------------------------------- 1 | # `wxt submit` 2 | 3 | > Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension) 4 | 5 | 8 | 9 |
{{ data.submit }}
10 | -------------------------------------------------------------------------------- /docs/api/cli/wxt-zip.md: -------------------------------------------------------------------------------- 1 | # `wxt zip` 2 | 3 | 6 | 7 |
{{ data.zip }}
8 | -------------------------------------------------------------------------------- /docs/api/cli/wxt.md: -------------------------------------------------------------------------------- 1 | # `wxt` 2 | 3 | 6 | 7 |
{{ data.wxt }}
8 | -------------------------------------------------------------------------------- /docs/assets/cli-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/assets/cli-output.png -------------------------------------------------------------------------------- /docs/assets/init-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/assets/init-demo.gif -------------------------------------------------------------------------------- /docs/auto-icons.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/blog/.drafts/2024-10-19-real-world-messaging.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: blog 3 | title: Real World Messaging 4 | description: | 5 | The extension messaging APIs are difficult to learn. Let's go beyond the simple examples from Chrome and Firefox's documentation to build our own simple messaging system from scratch. 6 | authors: 7 | - name: Aaron Klinker 8 | github: aklinker1 9 | date: 2024-10-20T04:54:23.601Z 10 | --- 11 | 12 | Test content **bold** _italic_ 13 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | 10 | 11 |
12 |
13 |

Examples

14 |
15 | 16 |
17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /docs/guide/essentials/config/build-mode.md: -------------------------------------------------------------------------------- 1 | # Build Modes 2 | 3 | Because WXT is powered by Vite, it supports [modes](https://vite.dev/guide/env-and-mode.html#modes) in the same way. 4 | 5 | When running any dev or build commands, pass the `--mode` flag: 6 | 7 | ```sh 8 | wxt --mode production 9 | wxt build --mode development 10 | wxt zip --mode testing 11 | ``` 12 | 13 | By default, `--mode` is `development` for the dev command and `production` for all other commands (build, zip, etc). 14 | 15 | ## Get Mode at Runtime 16 | 17 | You can access the current mode in your extension using `import.meta.env.MODE`: 18 | 19 | ```ts 20 | switch (import.meta.env.MODE) { 21 | case 'development': // ... 22 | case 'production': // ... 23 | 24 | // Custom modes specified with --mode 25 | case 'testing': // ... 26 | case 'staging': // ... 27 | // ... 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/guide/essentials/e2e-testing.md: -------------------------------------------------------------------------------- 1 | # E2E Testing 2 | 3 | ## Playwright 4 | 5 | [Playwright](https://playwright.dev) is the only good option for writing Chrome Extension end-to-end tests. 6 | 7 | To add E2E tests to your project, follow Playwright's [Chrome Extension docs](https://playwright.dev/docs/chrome-extensions). When you have to pass the path to your extension, pass the output directory, `/path/to/project/.output/chrome-mv3`. 8 | 9 | For a complete example, see the [WXT's Playwright Example](https://github.com/wxt-dev/examples/tree/main/examples/playwright-e2e-testing). 10 | -------------------------------------------------------------------------------- /docs/guide/essentials/remote-code.md: -------------------------------------------------------------------------------- 1 | # Remote Code 2 | 3 | WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend on remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code). 4 | 5 | ## Google Analytics 6 | 7 | For example, you can import Google Analytics: 8 | 9 | ```ts 10 | // utils/google-analytics.ts 11 | import 'url:https://www.googletagmanager.com/gtag/js?id=G-XXXXXX'; 12 | 13 | window.dataLayer = window.dataLayer || []; 14 | // NOTE: This line is different from Google's documentation 15 | window.gtag = function () { 16 | dataLayer.push(arguments); 17 | }; 18 | gtag('js', new Date()); 19 | gtag('config', 'G-XXXXXX'); 20 | ``` 21 | 22 | Then you can import this in your HTML files to enable Google Analytics: 23 | 24 | ```ts 25 | // popup/main.ts 26 | import '~/utils/google-analytics'; 27 | 28 | gtag('event', 'event_name', { 29 | key: 'value', 30 | }); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/guide/essentials/scripting.md: -------------------------------------------------------------------------------- 1 | # Scripting 2 | 3 | [Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/scripting) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting) 4 | 5 | Refer to the browser docs above for basics on how the API works. 6 | 7 | ## Execute Script Return Values 8 | 9 | When using `browser.scripting.executeScript`, you can execute content scripts or unlisted scripts. To return a value, just return a value from the script's `main` function. 10 | 11 | ```ts 12 | // entrypoints/background.ts 13 | const res = await browser.scripting.executeScript({ 14 | target: { tabId }, 15 | files: ['content-scripts/example.js'], 16 | }); 17 | console.log(res); // "Hello John!" 18 | ``` 19 | 20 | ```ts 21 | // entrypoints/example.content.ts 22 | export default defineContentScript({ 23 | registration: 'runtime', 24 | main(ctx) { 25 | console.log('Script was executed!'); 26 | return 'Hello John!'; 27 | }, 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/guide/essentials/storage.md: -------------------------------------------------------------------------------- 1 | # Storage 2 | 3 | [Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/storage) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage) 4 | 5 | You can use the vanilla APIs (see docs above), use [WXT's built-in storage API](/storage), or install a package from NPM. 6 | 7 | ## Alternatives 8 | 9 | 1. [`wxt/utils/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases 10 | 11 | 2. DIY: If you're migrating to WXT and already have a storage wrapper, keep using it. In the future, if you want to delete that code, you can use one of these alternatives, but there's no reason to replace working code during a migration. 12 | 13 | 3. Any other NPM package: [There are lots of wrappers around the storage API](https://www.npmjs.com/search?q=chrome%20storage), you can find one you like. Here's some popular ones: 14 | - [`webext-storage`](https://www.npmjs.com/package/webext-storage) - A more usable typed storage API for Web Extensions 15 | - [`@webext-core/storage`](https://www.npmjs.com/package/@webext-core/storage) - A type-safe, localStorage-esque wrapper around the web extension storage APIs 16 | -------------------------------------------------------------------------------- /docs/guide/essentials/testing-updates.md: -------------------------------------------------------------------------------- 1 | # Testing Updates 2 | 3 | ## Testing Permission Changes 4 | 5 | When `permissions`/`host_permissions` change during an update, depending on what exactly changed, the browser will disable your extension until the user accepts the new permissions. 6 | 7 | You can test if your permission changes will result in a disabled extension: 8 | 9 | - Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) 10 | - Firefox: See their [Test Permission Requests](https://extensionworkshop.com/documentation/develop/test-permission-requests/) page 11 | - Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier 12 | 13 | ## Update Event 14 | 15 | You can setup a callback that runs after your extension updates like so: 16 | 17 | ```ts 18 | browser.runtime.onInstalled.addListener(({ reason }) => { 19 | if (reason === 'update') { 20 | // Do something 21 | } 22 | }); 23 | ``` 24 | 25 | If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either: 26 | 27 | 1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions` 28 | 2. Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) 29 | -------------------------------------------------------------------------------- /docs/guide/resources/community.md: -------------------------------------------------------------------------------- 1 | # Community 2 | 3 | This page is dedicated to all the awesome people how have made something for WXT or that works with WXT. Blog posts, YouTube videos, NPM packages, etc. If a section doesn't exist for the thing you made, add one! 4 | 5 | [[toc]] 6 | 7 | ## Blog Posts 8 | 9 | - [Building Modern Cross Browser Web Extensions](https://aabidk.dev/tags/wxt/) by Aabid ([@aabidk20](https://github.com/aabidk20)) 10 | 11 | ## NPM Packages 12 | 13 | - [`@webext-core/*`](https://webext-core.aklinker1.io/): Easy-to-use utilities for writing and testing web extensions that work on all browsers. 14 | - [`Comctx`](https://github.com/molvqingtai/comctx): Cross-context RPC solution with type safety and flexible adapters. 15 | -------------------------------------------------------------------------------- /docs/guide/resources/how-wxt-works.md: -------------------------------------------------------------------------------- 1 | # How WXT Works 2 | 3 | :::warning 🚧 Under construction 4 | These docs will be coming soon! 5 | ::: 6 | -------------------------------------------------------------------------------- /docs/i18n.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/content-script-ui-alignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/public/content-script-ui-alignment.png -------------------------------------------------------------------------------- /docs/public/content-script-ui-append.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/public/content-script-ui-append.png -------------------------------------------------------------------------------- /docs/public/content-script-ui-position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/public/content-script-ui-position.png -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/hero-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /api.html 3 | Disallow: /config.html 4 | 5 | Sitemap: https://wxt.dev/sitemap.xml 6 | -------------------------------------------------------------------------------- /docs/public/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/docs/public/social-preview.png -------------------------------------------------------------------------------- /docs/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPointStrategy": "packages", 4 | "entryPoints": ["../packages/wxt"], 5 | "plugin": [ 6 | "typedoc-plugin-markdown", 7 | "typedoc-vitepress-theme", 8 | "typedoc-plugin-frontmatter" 9 | ], 10 | "out": "./api/reference", 11 | "githubPages": false, 12 | "excludePrivate": true, 13 | "excludeProtected": true, 14 | "excludeInternal": true, 15 | "readme": "none", 16 | "frontmatterGlobals": { 17 | "editLink": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/unocss.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/analytics/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.5.0 4 | [⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) • [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0) 5 | 6 | ### 🩹 Fixes 7 | 8 | - ⚠️ Update min WXT version to 0.20 ([2e8baf0](https://github.com/wxt-dev/wxt/commit/2e8baf0)) 9 | 10 | ### ❤️ Contributors 11 | 12 | - Aaron ([@aklinker1](https://github.com/aklinker1)) 13 | -------------------------------------------------------------------------------- /packages/analytics/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineAppConfig } from 'wxt/utils/define-app-config'; 2 | import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4'; 3 | import { umami } from './modules/analytics/providers/umami'; 4 | 5 | export default defineAppConfig({ 6 | analytics: { 7 | debug: true, 8 | providers: [ 9 | googleAnalytics4({ 10 | apiSecret: '...', 11 | measurementId: '...', 12 | }), 13 | umami({ 14 | apiUrl: 'https://umami.aklinker1.io/api', 15 | domain: 'analytics.wxt.dev', 16 | websiteId: '8f1c2aa4-fad3-406e-a5b2-33e8d4501716', 17 | }), 18 | ], 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/analytics/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | import { resolve } from 'node:path'; 3 | 4 | // Build module and plugins 5 | export default defineBuildConfig({ 6 | rootDir: resolve(__dirname, 'modules/analytics'), 7 | outDir: resolve(__dirname, 'dist'), 8 | entries: [ 9 | { input: 'index.ts', name: 'module' }, 10 | { input: 'client.ts', name: 'index' }, 11 | 'background-plugin.ts', 12 | 'types.ts', 13 | 'providers/google-analytics-4.ts', 14 | 'providers/umami.ts', 15 | ], 16 | externals: ['#analytics'], 17 | replace: { 18 | 'import.meta.env.NPM': 'true', 19 | }, 20 | declaration: true, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/analytics/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Popup 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/analytics/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import { analytics } from '#analytics'; 2 | 3 | declare const enabledCheckbox: HTMLInputElement; 4 | 5 | analytics.autoTrack(document); 6 | 7 | enabledCheckbox.oninput = () => { 8 | void analytics.setEnabled(enabledCheckbox.checked); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/analytics/modules/analytics/background-plugin.ts: -------------------------------------------------------------------------------- 1 | import '#analytics'; 2 | 3 | export default () => {}; 4 | -------------------------------------------------------------------------------- /packages/analytics/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/analytics/public/.keep -------------------------------------------------------------------------------- /packages/analytics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], 3 | "compilerOptions": { 4 | "paths": { 5 | "#analytics": ["./.wxt/analytics/index.ts"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/analytics/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | export default defineConfig({ 4 | // Unimport doesn't look for imports in node_modules, so when developing a 5 | // WXT module, we need to disable this to simplify the build process 6 | imports: false, 7 | 8 | manifest: { 9 | name: 'Analytics Demo', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/auto-icons/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.2 4 | 5 | [compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2) 6 | 7 | ### 📖 Documentation 8 | 9 | - **auto-icons:** Fix configuration example typo ([#905](https://github.com/wxt-dev/wxt/pull/905)) 10 | 11 | ### 🏡 Chore 12 | 13 | - Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885)) 14 | 15 | ### ❤️ Contributors 16 | 17 | - Uncenter ([@uncenter](http://github.com/uncenter)) 18 | - Florian Metz ([@Timeraa](http://github.com/Timeraa)) 19 | 20 | ## v1.0.1 21 | 22 | [compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.0...auto-icons-v1.0.1) 23 | 24 | ### 🩹 Fixes 25 | 26 | - **auto-icons:** Path option ([#880](https://github.com/wxt-dev/wxt/pull/880)) 27 | 28 | ### 🏡 Chore 29 | 30 | - **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869)) 31 | 32 | ### ❤️ Contributors 33 | 34 | - Florian Metz ([@Timeraa](http://github.com/Timeraa)) -------------------------------------------------------------------------------- /packages/auto-icons/README.md: -------------------------------------------------------------------------------- 1 | # WXT Auto Icons 2 | 3 | [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/CHANGELOG.md) 4 | 5 | ## Features 6 | 7 | - Generate extension icons with the correct sizes 8 | - Make the icon greyscale during development 9 | 10 | ## Usage 11 | 12 | Install the package: 13 | 14 | ```sh 15 | npm i --save-dev @wxt-dev/auto-icons 16 | pnpm i -D @wxt-dev/auto-icons 17 | yarn add --dev @wxt-dev/auto-icons 18 | bun i -D @wxt-dev/auto-icons 19 | ``` 20 | 21 | Add the module to `wxt.config.ts`: 22 | 23 | ```ts 24 | export default defineConfig({ 25 | modules: ['@wxt-dev/auto-icons'], 26 | }); 27 | ``` 28 | 29 | And finally, save the base icon to `/assets/icon.png`. 30 | 31 | ## Configuration 32 | 33 | The module can be configured via the `autoIcons` config: 34 | 35 | ```ts 36 | export default defineConfig({ 37 | modules: ['@wxt-dev/auto-icons'], 38 | autoIcons: { 39 | // ... 40 | }, 41 | }); 42 | ``` 43 | 44 | Options have JSDocs available in your editor, or you can read them in the source code: [`AutoIconsOptions`](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/src/index.ts). 45 | -------------------------------------------------------------------------------- /packages/auto-icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "exclude": ["node_modules/**", "dist/**"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wxt-dev/browser", 3 | "description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome", 4 | "version": "0.0.326", 5 | "type": "module", 6 | "main": "src/index.mjs", 7 | "types": "src/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/wxt-dev/wxt.git", 11 | "directory": "packages/browser" 12 | }, 13 | "scripts": { 14 | "check": "check", 15 | "gen": "tsx scripts/generate.ts" 16 | }, 17 | "author": { 18 | "name": "Aaron Klinker", 19 | "email": "aaronklinker1+wxt@gmail.com" 20 | }, 21 | "license": "MIT", 22 | "files": [ 23 | "src" 24 | ], 25 | "devDependencies": { 26 | "@types/chrome": "0.0.326", 27 | "fs-extra": "^11.3.0", 28 | "nano-spawn": "^0.2.0", 29 | "tsx": "4.19.4", 30 | "typescript": "^5.8.3", 31 | "vitest": "^3.1.2" 32 | }, 33 | "dependencies": { 34 | "@types/filesystem": "*", 35 | "@types/har-format": "*" 36 | }, 37 | "peerDependencies": {} 38 | } 39 | -------------------------------------------------------------------------------- /packages/browser/src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { describe, expectTypeOf, it } from 'vitest'; 3 | import { browser, type Browser } from '../index'; 4 | 5 | describe('browser', () => { 6 | describe('types', () => { 7 | it('should provide types via the Browser import', () => { 8 | expectTypeOf().toMatchTypeOf(); 9 | expectTypeOf().toMatchTypeOf(); 10 | expectTypeOf().toMatchTypeOf(); 11 | }); 12 | 13 | it('should provide values via the browser import', () => { 14 | expectTypeOf(browser.runtime.id).toMatchTypeOf(); 15 | expectTypeOf( 16 | browser.storage.local, 17 | ).toMatchTypeOf(); 18 | expectTypeOf( 19 | browser.i18n.detectLanguage('Hello, world!'), 20 | ).resolves.toMatchTypeOf(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/browser/src/gen/har-format/index.d.ts: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT - generated by scripts/generate.ts */ 2 | 3 | import { Entry, Log } from "har-format"; 4 | 5 | declare global { 6 | export type HARFormatEntry = Entry; 7 | export type HARFormatLog = Log; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/browser/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Browser } from './gen'; 2 | 3 | export const browser: typeof Browser; 4 | export { Browser }; 5 | -------------------------------------------------------------------------------- /packages/browser/src/index.mjs: -------------------------------------------------------------------------------- 1 | // #region snippet 2 | export const browser = globalThis.browser?.runtime?.id 3 | ? globalThis.browser 4 | : globalThis.chrome; 5 | // #endregion snippet 6 | -------------------------------------------------------------------------------- /packages/browser/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wxt-dev/browser", 3 | "description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome", 4 | "version": "{{chromeTypesVersion}}", 5 | "type": "module", 6 | "main": "src/index.mjs", 7 | "types": "src/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/wxt-dev/wxt.git", 11 | "directory": "packages/browser" 12 | }, 13 | "scripts": { 14 | "check": "check", 15 | "gen": "tsx scripts/generate.ts" 16 | }, 17 | "author": { 18 | "name": "Aaron Klinker", 19 | "email": "aaronklinker1+wxt@gmail.com" 20 | }, 21 | "license": "MIT", 22 | "files": [ 23 | "src" 24 | ], 25 | "devDependencies": { 26 | "@types/chrome": "{{chromeTypesVersion}}", 27 | "fs-extra": "^11.3.0", 28 | "nano-spawn": "^0.2.0", 29 | "tsx": "4.19.4", 30 | "typescript": "^5.8.3", 31 | "vitest": "^3.1.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/i18n/src/supported-locales.ts: -------------------------------------------------------------------------------- 1 | /** From https://developer.chrome.com/docs/extensions/reference/api/i18n#locales */ 2 | export const SUPPORTED_LOCALES = new Set([ 3 | 'ar', 4 | 'am', 5 | 'bg', 6 | 'bn', 7 | 'ca', 8 | 'cs', 9 | 'da', 10 | 'de', 11 | 'el', 12 | 'en', 13 | 'en_AU', 14 | 'en_GB', 15 | 'en_US', 16 | 'es', 17 | 'es_419', 18 | 'et', 19 | 'fa', 20 | 'fi', 21 | 'fil', 22 | 'fr', 23 | 'gu', 24 | 'he', 25 | 'hi', 26 | 'hr', 27 | 'hu', 28 | 'id', 29 | 'it', 30 | 'ja', 31 | 'kn', 32 | 'ko', 33 | 'lt', 34 | 'lv', 35 | 'ml', 36 | 'mr', 37 | 'ms', 38 | 'nl', 39 | 'no', 40 | 'pl', 41 | 'pt_BR', 42 | 'pt_PT', 43 | 'ro', 44 | 'ru', 45 | 'sk', 46 | 'sl', 47 | 'sr', 48 | 'sv', 49 | 'sw', 50 | 'ta', 51 | 'te', 52 | 'th', 53 | 'tr', 54 | 'uk', 55 | 'vi', 56 | 'zh_CN', 57 | 'zh_TW', 58 | ]); 59 | -------------------------------------------------------------------------------- /packages/i18n/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ChromeMessage } from './build'; 2 | 3 | export function applyChromeMessagePlaceholders(message: ChromeMessage): string { 4 | if (message.placeholders == null) return message.message; 5 | 6 | return Object.entries(message.placeholders ?? {}).reduce( 7 | (text, [name, value]) => { 8 | return text.replaceAll(new RegExp(`\\$${name}\\$`, 'gi'), value.content); 9 | }, 10 | message.message, 11 | ); 12 | } 13 | 14 | export function getSubstitutionCount(message: string): number { 15 | return ( 16 | 1 + 17 | Array.from({ length: MAX_SUBSTITUTIONS }).findLastIndex((_, i) => 18 | message.match(new RegExp(`(? setCount((count) => count + 1); 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /packages/module-react/entrypoints/content/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineContentScript, 3 | ContentScriptContext, 4 | createShadowRootUi, 5 | } from '#imports'; 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom/client'; 8 | 9 | export default defineContentScript({ 10 | matches: ['*://*/*'], 11 | 12 | async main(ctx) { 13 | const ui = await createUi(ctx); 14 | ui.mount(); 15 | }, 16 | }); 17 | 18 | function createUi(ctx: ContentScriptContext) { 19 | return createShadowRootUi(ctx, { 20 | name: 'react-ui', 21 | position: 'inline', 22 | append: 'first', 23 | onMount(container) { 24 | const root = ReactDOM.createRoot(container); 25 | root.render( 26 | 27 | 28 | , 29 | ); 30 | return root; 31 | }, 32 | onRemove(root) { 33 | root?.unmount(); 34 | }, 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/module-react/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/module-react/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | const root = document.getElementById('app')!; 5 | 6 | ReactDOM.createRoot(root).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /packages/module-react/modules/react.ts: -------------------------------------------------------------------------------- 1 | import 'wxt'; 2 | import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules'; 3 | import react, { Options as PluginOptions } from '@vitejs/plugin-react'; 4 | 5 | export default defineWxtModule({ 6 | name: '@wxt-dev/module-react', 7 | configKey: 'react', 8 | setup(wxt, options) { 9 | const { vite } = options ?? {}; 10 | 11 | addViteConfig(wxt, () => ({ 12 | plugins: [react(vite)], 13 | })); 14 | 15 | addImportPreset(wxt, 'react'); 16 | 17 | // Enable auto-imports for JSX files 18 | wxt.hook('config:resolved', (wxt) => { 19 | // In older versions of WXT, `wxt.config.imports` could be false 20 | if (!wxt.config.imports) return; 21 | 22 | wxt.config.imports.dirsScanOptions ??= {}; 23 | wxt.config.imports.dirsScanOptions.filePatterns = [ 24 | // Default plus JSX/TSX 25 | '*.{ts,js,mjs,cjs,mts,cts,jsx,tsx}', 26 | ]; 27 | }); 28 | }, 29 | }); 30 | 31 | export interface ReactModuleOptions { 32 | vite?: PluginOptions; 33 | } 34 | 35 | declare module 'wxt' { 36 | export interface InlineConfig { 37 | react?: ReactModuleOptions; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/module-react/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/module-react/public/.keep -------------------------------------------------------------------------------- /packages/module-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | }, 7 | "exclude": ["node_modules/**", "dist/**"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/module-solid/README.md: -------------------------------------------------------------------------------- 1 | # `@wxt-dev/module-solid` 2 | 3 | Enables the use of [SolidJS](https://www.solidjs.com/) in your web extension, in HTML pages and content scripts. 4 | 5 | This plugin makes a few changes: 6 | 7 | 1. Adds `vite-plugin-solid` to and sets `build.target: esnext` in the vite config 8 | 2. Adds the [`solid-js` preset](https://github.com/unjs/unimport/blob/main/src/presets/solid.ts) to auto-imports 9 | 10 | ## Usage 11 | 12 | ```sh 13 | pnpm i solid-js 14 | pnpm i -D @wxt-dev/module-solid 15 | ``` 16 | 17 | Then add the module to your config: 18 | 19 | ```ts 20 | // wxt.config.ts 21 | export default defineConfig({ 22 | // Required 23 | modules: ['@wxt-dev/module-solid'], 24 | 25 | // Optional: Pass options to the module: 26 | solid: { 27 | vite: { 28 | // ... 29 | }, 30 | }, 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/module-solid/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | import { resolve } from 'node:path'; 3 | 4 | export default defineBuildConfig({ 5 | rootDir: resolve(__dirname, 'modules'), 6 | outDir: resolve(__dirname, 'dist'), 7 | entries: [{ input: 'solid.ts', name: 'index' }], 8 | rollup: { 9 | emitCJS: true, 10 | }, 11 | declaration: true, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/module-solid/components/App.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js'; 2 | 3 | export const App: Component = () => { 4 | const [count, setCount] = createSignal(0); 5 | const increment = () => setCount((count) => count + 1); 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/module-solid/entrypoints/content/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineContentScript, 3 | ContentScriptContext, 4 | createShadowRootUi, 5 | } from '#imports'; 6 | import { render } from 'solid-js/web'; 7 | 8 | export default defineContentScript({ 9 | matches: ['*://*/*'], 10 | 11 | async main(ctx) { 12 | const ui = await createUi(ctx); 13 | ui.mount(); 14 | }, 15 | }); 16 | 17 | function createUi(ctx: ContentScriptContext) { 18 | return createShadowRootUi(ctx, { 19 | name: 'solid-ui', 20 | position: 'inline', 21 | append: 'first', 22 | onMount(container) { 23 | return render(() => , container); 24 | }, 25 | onRemove(unmount) { 26 | unmount?.(); 27 | }, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /packages/module-solid/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/module-solid/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | 3 | const root = document.getElementById('app')!; 4 | 5 | render(() => , root); 6 | -------------------------------------------------------------------------------- /packages/module-solid/modules/solid.ts: -------------------------------------------------------------------------------- 1 | import 'wxt'; 2 | import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules'; 3 | import solid, { Options as PluginOptions } from 'vite-plugin-solid'; 4 | 5 | export default defineWxtModule({ 6 | name: '@wxt-dev/module-solid', 7 | configKey: 'solid', 8 | setup(wxt, options) { 9 | const { vite } = options ?? {}; 10 | 11 | addViteConfig(wxt, () => ({ 12 | plugins: [solid(vite)], 13 | build: { 14 | target: 'esnext', 15 | }, 16 | })); 17 | 18 | addImportPreset(wxt, 'solid-js'); 19 | 20 | // Enable auto-imports for JSX files 21 | wxt.hook('config:resolved', (wxt) => { 22 | // In older versions of WXT, `wxt.config.imports` could be false 23 | if (!wxt.config.imports) return; 24 | 25 | wxt.config.imports.dirsScanOptions ??= {}; 26 | wxt.config.imports.dirsScanOptions.filePatterns = [ 27 | // Default plus JSX/TSX 28 | '*.{ts,js,mjs,cjs,mts,cts,jsx,tsx}', 29 | ]; 30 | }); 31 | }, 32 | }); 33 | 34 | export interface SolidModuleOptions { 35 | vite?: PluginOptions; 36 | } 37 | 38 | declare module 'wxt' { 39 | export interface InlineConfig { 40 | solid?: SolidModuleOptions; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/module-solid/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/module-solid/public/.keep -------------------------------------------------------------------------------- /packages/module-solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | }, 7 | "exclude": ["node_modules/**", "dist/**"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/module-svelte/README.md: -------------------------------------------------------------------------------- 1 | # `@wxt-dev/module-svelte` 2 | 3 | Enables the use of [Svelte](https://svelte.dev/) in your web extension, in HTML pages and content scripts. 4 | 5 | This plugin makes a few changes: 6 | 7 | 1. Adds `@sveltejs/vite-plugin-svelte` to vite 8 | 2. Adds the [`svelte` preset](https://github.com/unjs/unimport/blob/main/src/presets/svelte.ts) to auto-imports 9 | 10 | ## Usage 11 | 12 | ```sh 13 | pnpm i svelte 14 | pnpm i -D @wxt-dev/module-svelte 15 | ``` 16 | 17 | Then add the module to your config: 18 | 19 | ```ts 20 | // wxt.config.ts 21 | export default defineConfig({ 22 | // Required 23 | modules: ['@wxt-dev/module-svelte'], 24 | 25 | // Optional: Pass options to the module: 26 | svelte: { 27 | vite: { 28 | // ... 29 | }, 30 | }, 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/module-svelte/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'wxt'; 2 | import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules'; 3 | import { 4 | svelte, 5 | vitePreprocess, 6 | Options as PluginOptions, 7 | } from '@sveltejs/vite-plugin-svelte'; 8 | 9 | export default defineWxtModule({ 10 | name: '@wxt-dev/module-svelte', 11 | configKey: 'svelte', 12 | setup(wxt, options) { 13 | const { vite } = options ?? {}; 14 | 15 | addViteConfig(wxt, ({ mode }) => ({ 16 | plugins: [ 17 | svelte({ 18 | // Using a svelte.config.js file causes a segmentation fault when importing the file 19 | configFile: false, 20 | preprocess: [vitePreprocess()], 21 | ...vite, 22 | }), 23 | ], 24 | resolve: { 25 | conditions: ['browser', mode], 26 | }, 27 | })); 28 | 29 | addImportPreset(wxt, 'svelte'); 30 | }, 31 | }); 32 | 33 | export interface SvelteModuleOptions { 34 | vite?: Partial; 35 | } 36 | 37 | declare module 'wxt' { 38 | export interface InlineConfig { 39 | svelte?: SvelteModuleOptions; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/module-svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "exclude": ["node_modules/**", "dist/**"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/module-vue/README.md: -------------------------------------------------------------------------------- 1 | # `@wxt-dev/module-vue` 2 | 3 | Enables the use of [Vue](https://vuejs.org/) in your web extension, in HTML pages and content scripts. 4 | 5 | This plugin makes a few changes: 6 | 7 | 1. Adds `@vitejs/plugin-vue` to vite config 8 | 2. Adds the [`vue` preset](https://github.com/unjs/unimport/blob/main/src/presets/vue.ts) to auto-imports 9 | 3. Applies sourcemap fix to prevent HMR errors during development 10 | 4. Enable auto-imports in `.vue` files 11 | 12 | ## Usage 13 | 14 | ```sh 15 | pnpm i vue 16 | pnpm i -D @wxt-dev/module-vue 17 | ``` 18 | 19 | Then add the module to your config: 20 | 21 | ```ts 22 | // wxt.config.ts 23 | export default defineConfig({ 24 | // Required 25 | modules: ['@wxt-dev/module-vue'], 26 | 27 | // Optional: Pass options to the module: 28 | vue: { 29 | vite: { 30 | script: { 31 | propsDestructure: true, 32 | }, 33 | }, 34 | }, 35 | }); 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/module-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "exclude": ["node_modules/**", "dist/**"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/storage/README.md: -------------------------------------------------------------------------------- 1 | # WXT Storage 2 | 3 | [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/storage/CHANGELOG.md) • [Docs](https://wxt.dev/storage.html) 4 | 5 | A simplified wrapper around the extension storage APIs. 6 | 7 | ## Installation 8 | 9 | ### With WXT 10 | 11 | This module is built-in to WXT, so you don't need to install anything. 12 | 13 | ```ts 14 | import { storage } from 'wxt/storage'; 15 | ``` 16 | 17 | If you use auto-imports, `storage` is auto-imported for you, so you don't even need to import it! 18 | 19 | ### Without WXT 20 | 21 | Install the NPM package: 22 | 23 | ```sh 24 | npm i @wxt-dev/storage 25 | pnpm add @wxt-dev/storage 26 | yarn add @wxt-dev/storage 27 | bun add @wxt-dev/storage 28 | ``` 29 | 30 | ```ts 31 | import { storage } from '@wxt-dev/storage'; 32 | ``` 33 | 34 | ## Usage 35 | 36 | Read full docs on the [documentation website](https://wxt.dev/storage.html). 37 | -------------------------------------------------------------------------------- /packages/storage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": true 5 | }, 6 | "exclude": ["node_modules/**", "dist/**"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/storage/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | test: { 5 | mockReset: true, 6 | restoreMocks: true, 7 | setupFiles: ['vitest.setup.ts'], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/storage/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { fakeBrowser } from '@webext-core/fake-browser'; 2 | import { vi } from 'vitest'; 3 | 4 | vi.stubGlobal('chrome', fakeBrowser); 5 | -------------------------------------------------------------------------------- /packages/unocss/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.1 4 | 5 | [compare changes](https://github.com/wxt-dev/wxt/compare/unocss-v1.0.0...unocss-v1.0.1) 6 | 7 | ### 🩹 Fixes 8 | 9 | - Respect `configOrPath` for dev server ([#1169](https://github.com/wxt-dev/wxt/pull/1169)) 10 | 11 | ### 📖 Documentation 12 | 13 | - Use full URLs in README so they work on the docs site ([d20793d](https://github.com/wxt-dev/wxt/commit/d20793d)) 14 | - Fix unocss readme ([#1329](https://github.com/wxt-dev/wxt/pull/1329)) 15 | 16 | ### 🏡 Chore 17 | 18 | - **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164)) 19 | - **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167)) 20 | - Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493)) 21 | - Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494)) 22 | 23 | ### ❤️ Contributors 24 | 25 | - Aaron ([@aklinker1](http://github.com/aklinker1)) 26 | - Ntnyq ([@ntnyq](http://github.com/ntnyq)) -------------------------------------------------------------------------------- /packages/unocss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "exclude": ["node_modules/**", "dist/**"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/README.md: -------------------------------------------------------------------------------- 1 | # `@wxt-dev/webextension-polyfill` 2 | 3 | Configures `wxt/browser` to import `browser` from [`webextension-polyfill`](https://github.com/mozilla/webextension-polyfill) instead of using the regular `chrome`/`browser` globals WXT normally provides. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | pnpm i @wxt-dev/webextension-polyfill webextension-polyfill 9 | ``` 10 | 11 | Then add the module to your config: 12 | 13 | ```ts 14 | // wxt.config.ts 15 | export default defineConfig({ 16 | modules: ['@wxt-dev/webextension-polyfill'], 17 | }); 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | import { resolve } from 'node:path'; 3 | 4 | export default defineBuildConfig({ 5 | rootDir: resolve(__dirname, 'modules/webextension-polyfill'), 6 | outDir: resolve(__dirname, 'dist'), 7 | entries: [ 8 | { input: 'index.ts', name: 'index' }, 9 | { input: 'browser.ts', name: 'browser' }, 10 | ], 11 | replace: { 12 | 'process.env.NPM': 'true', 13 | }, 14 | declaration: true, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/entrypoints/content/index.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*/*'], 3 | async main() { 4 | console.log(browser.runtime.id); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | const root = document.getElementById('app')!; 2 | 3 | root.textContent = browser.runtime.id; 4 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/modules/webextension-polyfill/browser.ts: -------------------------------------------------------------------------------- 1 | export { default as browser } from 'webextension-polyfill'; 2 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/modules/webextension-polyfill/index.ts: -------------------------------------------------------------------------------- 1 | import 'wxt'; 2 | import { addViteConfig, defineWxtModule } from 'wxt/modules'; 3 | import { resolve } from 'node:path'; 4 | 5 | export default defineWxtModule({ 6 | name: '@wxt-dev/webextension-polyfill', 7 | setup(wxt) { 8 | addViteConfig(wxt, () => ({ 9 | resolve: { 10 | alias: { 11 | 'wxt/browser': process.env.NPM 12 | ? '@wxt-dev/webextension-polyfill/browser' 13 | : resolve(__dirname, 'browser.ts'), 14 | }, 15 | }, 16 | })); 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /packages/webextension-polyfill/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/webextension-polyfill/public/.keep -------------------------------------------------------------------------------- /packages/webextension-polyfill/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], 3 | "exclude": ["node_modules/**", "dist/**"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/wxt-demo/eslint.config.js: -------------------------------------------------------------------------------- 1 | import autoImports from './.wxt/eslintrc-auto-import.js'; 2 | 3 | export default [ 4 | { 5 | languageOptions: { 6 | globals: { 7 | ...autoImports.globals, 8 | }, 9 | sourceType: 'module', 10 | }, 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /packages/wxt-demo/modules/auto-icons.ts: -------------------------------------------------------------------------------- 1 | import autoIcons from '@wxt-dev/auto-icons'; 2 | 3 | export default autoIcons; 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/modules/example.ts: -------------------------------------------------------------------------------- 1 | import { defineWxtModule } from 'wxt/modules'; 2 | 3 | // Example of adding option types to wxt config file 4 | export interface ExampleModuleOptions { 5 | a: string; 6 | b?: string; 7 | } 8 | declare module 'wxt' { 9 | interface InlineConfig { 10 | example?: ExampleModuleOptions; 11 | } 12 | } 13 | 14 | export default defineWxtModule({ 15 | configKey: 'example', 16 | setup(wxt, options) { 17 | wxt.logger.info('Example module with options:', options); 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/wxt-demo/modules/i18n.ts: -------------------------------------------------------------------------------- 1 | import module from '@wxt-dev/i18n/module'; 2 | 3 | export default module; 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/modules/unocss.ts: -------------------------------------------------------------------------------- 1 | export { default } from '@wxt-dev/unocss'; 2 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineAppConfig } from '#imports'; 2 | 3 | declare module 'wxt/utils/define-app-config' { 4 | export interface WxtAppConfig { 5 | example: string; 6 | } 7 | } 8 | 9 | export default defineAppConfig({ 10 | example: 'value', 11 | }); 12 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/wxt-demo/src/assets/icon.png -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/__tests__/background.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it, vi } from 'vitest'; 2 | import background from '../background'; 3 | 4 | browser.i18n.getMessage = () => 'fake-message'; 5 | 6 | const logMock = vi.fn(); 7 | console.log = logMock; 8 | 9 | describe('Background Entrypoint', () => { 10 | beforeEach(() => { 11 | fakeBrowser.reset(); 12 | }); 13 | 14 | it("should log the extension's runtime ID", () => { 15 | const id = 'some-id'; 16 | fakeBrowser.runtime.id = id; 17 | 18 | background.main(); 19 | 20 | expect(logMock).toBeCalledWith(id); 21 | }); 22 | 23 | it('should set the start time in storage', async () => { 24 | background.main(); 25 | await new Promise((res) => setTimeout(res)); 26 | 27 | expect(await storage.getItem('session:startTime')).toBeDefined(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/automount.content/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: dark; 3 | } 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/example-2.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/example-tsx.content.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | 3 | export default defineContentScript({ 4 | matches: [''], 5 | async main(ctx) { 6 | console.log(browser.runtime.id); 7 | logId(); 8 | 9 | console.log('WXT MODE:', { 10 | MODE: import.meta.env.MODE, 11 | DEV: import.meta.env.DEV, 12 | PROD: import.meta.env.PROD, 13 | }); 14 | 15 | const n = (Math.random() * 100).toFixed(1); 16 | ctx.setInterval(() => { 17 | console.log(n, browser.runtime.id); 18 | }, 1e3); 19 | 20 | const container = document.createElement('div'); 21 | document.body.append(container); 22 | 23 | ReactDOM.createRoot(container).render(); 24 | }, 25 | }); 26 | 27 | function SomeComponent() { 28 | return
Some component
; 29 | } 30 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/example.sandbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/iframe-src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Iframe Example 7 | 8 | 9 |

Hello iframe page!

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/iframe-src/main.ts: -------------------------------------------------------------------------------- 1 | console.log('iframe 2'); 2 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/iframe.content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | 4 | main(ctx) { 5 | const ui = createIframeUi(ctx, { 6 | page: '/iframe-src.html', 7 | position: 'overlay', 8 | anchor: 'form[action="/search"]', 9 | }); 10 | ui.mount(); 11 | console.log('Mounted iframe'); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/injected.content/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/location-change.content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | // Site that uses HTML5 history 3 | matches: ['*://*.crunchyroll.com/*'], 4 | 5 | main(ctx) { 6 | ctx.addEventListener(window, 'wxt:locationchange', ({ newUrl, oldUrl }) => { 7 | console.log('Location changed:', newUrl.href, oldUrl.href); 8 | }); 9 | console.log('Watching for location change...'); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/main-world.content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*/*'], 3 | world: 'MAIN', 4 | 5 | main() { 6 | console.log(`Hello from ${location.hostname}!`); 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Extension Settings 7 | 8 | 9 | 10 |

Hello options page!

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/options/main.ts: -------------------------------------------------------------------------------- 1 | import 'url:https://code.jquery.com/jquery-3.7.1.slim.min.js'; 2 | 3 | console.log(browser.runtime.id); 4 | logId(); 5 | console.log(2); 6 | 7 | console.log('WXT MODE:', { 8 | MODE: import.meta.env.MODE, 9 | DEV: import.meta.env.DEV, 10 | PROD: import.meta.env.PROD, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/options/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: black; 3 | color: white; 4 | } 5 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Popup 7 | 8 | 9 | 10 |

Hello popup!

11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/sidepanel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sidebar 7 | 8 | 9 |

Example

10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/ui.content/index.ts: -------------------------------------------------------------------------------- 1 | import 'uno.css'; 2 | import './style.css'; 3 | import manualStyle from './manual-style.css?inline'; 4 | 5 | export default defineContentScript({ 6 | matches: ['https://*.duckduckgo.com/*'], 7 | cssInjectionMode: 'ui', 8 | 9 | async main(ctx) { 10 | const style = document.createElement('style'); 11 | style.textContent = manualStyle; 12 | document.head.append(style); 13 | 14 | const ui = await createShadowRootUi(ctx, { 15 | name: 'demo-ui', 16 | position: 'inline', 17 | append: 'before', 18 | anchor: 'form[role=search]', 19 | onMount: (container) => { 20 | const app = document.createElement('div'); 21 | app.classList.add('m-4', 'text-red-500'); 22 | app.textContent = i18n.t('prompt_for_name'); 23 | container.append(app); 24 | }, 25 | }); 26 | ui.mount(); 27 | 28 | setTimeout(ui.remove, 5000); 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/ui.content/manual-style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 2rem; 3 | background-color: blanchedalmond; 4 | } 5 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/ui.content/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: dark; 3 | } 4 | html { 5 | background-color: black; 6 | } 7 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/entrypoints/unlisted.ts: -------------------------------------------------------------------------------- 1 | export default defineUnlistedScript(() => { 2 | console.log('injected'); 3 | }); 4 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/locales/en.yml: -------------------------------------------------------------------------------- 1 | prompt_for_name: 2 | message: What's your name? 3 | description: Ask for the user's name 4 | hello: 5 | message: Hello, $USER$ 6 | description: Greet the user 7 | placeholders: 8 | user: 9 | content: $1 10 | example: Cira 11 | bye: 12 | message: Goodbye, $USER$. Come back to $OUR_SITE$ soon! 13 | description: Say goodbye to the user 14 | placeholders: 15 | our_site: 16 | content: Example.com 17 | user: 18 | content: $1 19 | example: Cira 20 | deep: 21 | example: 'this is deep' 22 | items: 23 | 0: Zero items 24 | 1: 1 item 25 | n: $1 items 26 | -------------------------------------------------------------------------------- /packages/wxt-demo/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | export default console; 2 | 3 | export function logId() { 4 | console.log('logId', browser.runtime.id); 5 | } 6 | -------------------------------------------------------------------------------- /packages/wxt-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx", 6 | "types": ["vitest-plugin-random-seed/types"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/wxt-demo/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | import { WxtVitest } from 'wxt/testing'; 3 | 4 | export default defineProject({ 5 | test: { 6 | mockReset: true, 7 | restoreMocks: true, 8 | }, 9 | plugins: [WxtVitest()], 10 | }); 11 | -------------------------------------------------------------------------------- /packages/wxt/.oxlintignore: -------------------------------------------------------------------------------- 1 | src/core/utils/building/__tests__/test-entrypoints 2 | -------------------------------------------------------------------------------- /packages/wxt/bin/wxt-publish-extension.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * A alias around `publish-extension` that is always installed on the path without having to install 4 | * `publish-browser-extension` as a direct dependency (like for PNPM, which doesn't link 5 | * sub-dependency binaries to "node_modules/.bin") 6 | */ 7 | require('publish-browser-extension/cli'); 8 | -------------------------------------------------------------------------------- /packages/wxt/bin/wxt.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import '../dist/cli/index.mjs'; 3 | -------------------------------------------------------------------------------- /packages/wxt/e2e/tests/dev.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { TestProject } from '../utils'; 3 | 4 | describe('Dev Mode', () => { 5 | it('should not change ports when restarting the server', async () => { 6 | const project = new TestProject(); 7 | project.addFile( 8 | 'entrypoints/background.ts', 9 | 'export default defineBackground(() => {})', 10 | ); 11 | 12 | const server = await project.startServer({ 13 | runner: { 14 | disabled: true, 15 | }, 16 | }); 17 | const initialPort = server.port; 18 | await server.restart(); 19 | const finalPort = server.port; 20 | await server.stop(); 21 | 22 | expect(finalPort).toBe(initialPort); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/wxt/e2e/tests/manifest-content.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { TestProject } from '../utils'; 3 | 4 | describe('Manifest Content', () => { 5 | it.each([ 6 | { browser: undefined, outDir: 'chrome-mv3', expected: undefined }, 7 | { browser: 'chrome', outDir: 'chrome-mv3', expected: undefined }, 8 | { browser: 'firefox', outDir: 'firefox-mv2', expected: true }, 9 | { browser: 'safari', outDir: 'safari-mv2', expected: false }, 10 | ])( 11 | 'should respect the per-browser entrypoint option with %j', 12 | async ({ browser, expected, outDir }) => { 13 | const project = new TestProject(); 14 | 15 | project.addFile( 16 | 'entrypoints/background.ts', 17 | `export default defineBackground({ 18 | persistent: { 19 | firefox: true, 20 | safari: false, 21 | }, 22 | main: () => {}, 23 | })`, 24 | ); 25 | await project.build({ browser }); 26 | 27 | const safariManifest = await project.getOutputManifest( 28 | `.output/${outDir}/manifest.json`, 29 | ); 30 | expect(safariManifest.background.persistent).toBe(expected); 31 | }, 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/wxt/e2e/tests/remote-code.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { TestProject } from '../utils'; 3 | 4 | describe('Remote Code', () => { 5 | it('should download "url:*" modules and include them in the final bundle', async () => { 6 | const url = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js'; 7 | const project = new TestProject(); 8 | project.addFile( 9 | 'entrypoints/popup.ts', 10 | `import "url:${url}" 11 | export default defineUnlistedScript(() => {})`, 12 | ); 13 | 14 | await project.build(); 15 | 16 | const output = await project.serializeFile('.output/chrome-mv3/popup.js'); 17 | expect(output).toContain( 18 | // Some text that will hopefully be in future versions of this script 19 | 'lodash.com', 20 | ); 21 | expect(output).not.toContain(url); 22 | expect( 23 | await project.fileExists(`.wxt/cache/${encodeURIComponent(url)}`), 24 | ).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/wxt/src/@types/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV_SERVER_ORIGIN__: string; 2 | 3 | // Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts 4 | interface ImportMetaEnv { 5 | readonly COMMAND: WxtCommand; 6 | readonly MANIFEST_VERSION: 2 | 3; 7 | readonly ENTRYPOINT: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/wxt/src/@types/project-types.d.ts: -------------------------------------------------------------------------------- 1 | // Types generated in the .wxt directory available after wxt prepare 2 | 3 | declare type PublicPath = string; 4 | -------------------------------------------------------------------------------- /packages/wxt/src/browser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the `browser` export which you should use to access the extension APIs in your project: 3 | * ```ts 4 | * import { browser } from 'wxt/browser'; 5 | * 6 | * browser.runtime.onInstalled.addListener(() => { 7 | * // ... 8 | * }) 9 | * ``` 10 | * @module wxt/browser 11 | */ 12 | import { browser as _browser, type Browser } from '@wxt-dev/browser'; 13 | 14 | /** 15 | * This interface is empty because it is generated per-project when running `wxt prepare`. See: 16 | * - `.wxt/types/paths.d.ts` 17 | */ 18 | export interface WxtRuntime {} 19 | 20 | /** 21 | * This interface is empty because it is generated per-project when running `wxt prepare`. See: 22 | * - `.wxt/types/i18n.d.ts` 23 | */ 24 | export interface WxtI18n {} 25 | 26 | export type WxtBrowser = Omit & { 27 | runtime: WxtRuntime & Omit<(typeof _browser)['runtime'], 'getURL'>; 28 | i18n: WxtI18n & Omit<(typeof _browser)['i18n'], 'getMessage'>; 29 | }; 30 | 31 | export const browser: WxtBrowser = _browser; 32 | 33 | export { Browser }; 34 | -------------------------------------------------------------------------------- /packages/wxt/src/builtin-modules/index.ts: -------------------------------------------------------------------------------- 1 | import { WxtModule } from '../types'; 2 | import unimport from './unimport'; 3 | 4 | export const builtinModules: WxtModule[] = [unimport]; 5 | -------------------------------------------------------------------------------- /packages/wxt/src/cli/index.ts: -------------------------------------------------------------------------------- 1 | import cli from './commands'; 2 | import { version } from '../version'; 3 | import { isAliasedCommand } from './cli-utils'; 4 | 5 | // Grab the command that we're trying to run 6 | cli.parse(process.argv, { run: false }); 7 | 8 | // If it's not an alias, add the help and version options, then parse again 9 | if (!isAliasedCommand(cli.matchedCommand)) { 10 | cli.help(); 11 | cli.version(version); 12 | cli.parse(process.argv, { run: false }); 13 | } 14 | 15 | // Run the alias or command 16 | await cli.runMatchedCommand(); 17 | -------------------------------------------------------------------------------- /packages/wxt/src/core/build.ts: -------------------------------------------------------------------------------- 1 | import { BuildOutput, InlineConfig } from '../types'; 2 | import { internalBuild } from './utils/building'; 3 | import { registerWxt } from './wxt'; 4 | 5 | /** 6 | * Bundles the extension for production. Returns a promise of the build result. Discovers the `wxt.config.ts` file in 7 | * the root directory, and merges that config with what is passed in. 8 | * 9 | * @example 10 | * // Use config from `wxt.config.ts` 11 | * const res = await build() 12 | * 13 | * // or override config `from wxt.config.ts` 14 | * const res = await build({ 15 | * // Override config... 16 | * }) 17 | */ 18 | export async function build(config?: InlineConfig): Promise { 19 | await registerWxt('build', config); 20 | 21 | return await internalBuild(); 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/__tests__/fixtures/module.ts: -------------------------------------------------------------------------------- 1 | import { a } from './test'; 2 | 3 | function defineSomething(config: T): T { 4 | return config; 5 | } 6 | 7 | export default defineSomething({ 8 | option: 'some value', 9 | main: () => { 10 | console.log('main', a); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/__tests__/fixtures/test.ts: -------------------------------------------------------------------------------- 1 | console.log('Side-effect in test.ts'); 2 | export const a = 'a'; 3 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/bundleAnalysis.ts: -------------------------------------------------------------------------------- 1 | import type * as vite from 'vite'; 2 | import { visualizer } from '@aklinker1/rollup-plugin-visualizer'; 3 | import { ResolvedConfig } from '../../../../types'; 4 | import path from 'node:path'; 5 | 6 | let increment = 0; 7 | 8 | export function bundleAnalysis(config: ResolvedConfig): vite.Plugin { 9 | return visualizer({ 10 | template: 'raw-data', 11 | filename: path.resolve( 12 | config.analysis.outputDir, 13 | `${config.analysis.outputName}-${increment++}.json`, 14 | ), 15 | }); 16 | } 17 | 18 | /** 19 | * @deprecated FOR TESTING ONLY. 20 | */ 21 | export function resetBundleIncrement() { 22 | increment = 0; 23 | } 24 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/cssEntrypoints.ts: -------------------------------------------------------------------------------- 1 | import type * as vite from 'vite'; 2 | import { Entrypoint, ResolvedConfig } from '../../../../types'; 3 | import { getEntrypointBundlePath } from '../../../utils/entrypoints'; 4 | 5 | /** 6 | * Rename CSS entrypoint outputs to ensure a JS file is not generated, and that the CSS file is 7 | * placed in the correct place. 8 | * 9 | * It: 10 | * 1. Renames CSS files to their final paths 11 | * 2. Removes the JS file that get's output by lib mode 12 | * 13 | * THIS PLUGIN SHOULD ONLY BE APPLIED TO CSS LIB MODE BUILDS. It should not be added to every build. 14 | */ 15 | export function cssEntrypoints( 16 | entrypoint: Entrypoint, 17 | config: ResolvedConfig, 18 | ): vite.Plugin { 19 | return { 20 | name: 'wxt:css-entrypoint', 21 | config() { 22 | return { 23 | build: { 24 | rollupOptions: { 25 | output: { 26 | assetFileNames: () => 27 | getEntrypointBundlePath(entrypoint, config.outDir, '.css'), 28 | }, 29 | }, 30 | }, 31 | }; 32 | }, 33 | generateBundle(_, bundle) { 34 | Object.keys(bundle).forEach((file) => { 35 | if (file.endsWith('.js')) delete bundle[file]; 36 | }); 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/defineImportMeta.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Overrides definitions for `import.meta.*` 3 | * 4 | * - `import.meta.url`: Without this, background service workers crash trying to access 5 | * `document.location`, see https://github.com/wxt-dev/wxt/issues/392 6 | */ 7 | export function defineImportMeta() { 8 | return { 9 | name: 'wxt:define', 10 | config() { 11 | return { 12 | define: { 13 | // This works for all extension contexts, including background service worker 14 | 'import.meta.url': 'self.location.href', 15 | }, 16 | }; 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/devServerGlobals.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite'; 2 | import { ResolvedConfig, WxtDevServer } from '../../../../types'; 3 | 4 | /** 5 | * Defines global constants about the dev server. Helps scripts connect to the server's web socket. 6 | */ 7 | export function devServerGlobals( 8 | config: ResolvedConfig, 9 | server: WxtDevServer | undefined, 10 | ): Plugin { 11 | return { 12 | name: 'wxt:dev-server-globals', 13 | config() { 14 | if (server == null || config.command == 'build') return; 15 | 16 | return { 17 | define: { 18 | __DEV_SERVER_ORIGIN__: JSON.stringify( 19 | server.origin.replace(/^http(s):/, 'ws$1:'), 20 | ), 21 | }, 22 | }; 23 | }, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/download.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite'; 2 | import { ResolvedConfig } from '../../../../types'; 3 | import { fetchCached } from '../../../utils/network'; 4 | 5 | /** 6 | * Downloads any URL imports, like Google Analytics, into virtual modules so they are bundled with 7 | * the extension instead of depending on remote code at runtime. 8 | * 9 | * @example 10 | * import "url:https://google-tagmanager.com/gtag?id=XYZ"; 11 | */ 12 | export function download(config: ResolvedConfig): Plugin { 13 | return { 14 | name: 'wxt:download', 15 | resolveId(id) { 16 | if (id.startsWith('url:')) return '\0' + id; 17 | }, 18 | async load(id) { 19 | if (!id.startsWith('\0url:')) return; 20 | 21 | // Load file from network or cache 22 | const url = id.replace('\0url:', ''); 23 | return await fetchCached(url, config); 24 | }, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/entrypointGroupGlobals.ts: -------------------------------------------------------------------------------- 1 | import type * as vite from 'vite'; 2 | import { EntrypointGroup } from '../../../../types'; 3 | import { getEntrypointGlobals } from '../../../utils/globals'; 4 | 5 | /** 6 | * Define a set of global variables specific to an entrypoint. 7 | */ 8 | export function entrypointGroupGlobals( 9 | entrypointGroup: EntrypointGroup, 10 | ): vite.PluginOption { 11 | return { 12 | name: 'wxt:entrypoint-group-globals', 13 | config() { 14 | const define: vite.InlineConfig['define'] = {}; 15 | let name = Array.isArray(entrypointGroup) ? 'html' : entrypointGroup.name; 16 | for (const global of getEntrypointGlobals(name)) { 17 | define[`import.meta.env.${global.name}`] = JSON.stringify(global.value); 18 | } 19 | return { 20 | define, 21 | }; 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/globals.ts: -------------------------------------------------------------------------------- 1 | import type * as vite from 'vite'; 2 | import { ResolvedConfig } from '../../../../types'; 3 | import { getGlobals } from '../../../utils/globals'; 4 | 5 | export function globals(config: ResolvedConfig): vite.PluginOption { 6 | return { 7 | name: 'wxt:globals', 8 | config() { 9 | const define: vite.InlineConfig['define'] = {}; 10 | for (const global of getGlobals(config)) { 11 | define[`import.meta.env.${global.name}`] = JSON.stringify(global.value); 12 | } 13 | return { 14 | define, 15 | }; 16 | }, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './devHtmlPrerender'; 2 | export * from './devServerGlobals'; 3 | export * from './download'; 4 | export * from './multipageMove'; 5 | export * from './resolveVirtualModules'; 6 | export * from './tsconfigPaths'; 7 | export * from './noopBackground'; 8 | export * from './cssEntrypoints'; 9 | export * from './bundleAnalysis'; 10 | export * from './globals'; 11 | export * from './extensionApiMock'; 12 | export * from './entrypointGroupGlobals'; 13 | export * from './defineImportMeta'; 14 | export * from './removeEntrypointMainFunction'; 15 | export * from './wxtPluginLoader'; 16 | export * from './resolveAppConfig'; 17 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/noopBackground.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite'; 2 | import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../../utils/constants'; 3 | 4 | /** 5 | * In dev mode, if there's not a background script listed, we need to add one so that the web socket 6 | * connection is setup and the extension reloads HTML pages and content scripts correctly. 7 | */ 8 | export function noopBackground(): Plugin { 9 | const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID; 10 | const resolvedVirtualModuleId = '\0' + virtualModuleId; 11 | return { 12 | name: 'wxt:noop-background', 13 | resolveId(id) { 14 | if (id === virtualModuleId) return resolvedVirtualModuleId; 15 | }, 16 | load(id) { 17 | if (id === resolvedVirtualModuleId) { 18 | return `import { defineBackground } from 'wxt/utils/define-background';\nexport default defineBackground(() => void 0)`; 19 | } 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts: -------------------------------------------------------------------------------- 1 | import { ResolvedConfig } from '../../../../types'; 2 | import * as vite from 'vite'; 3 | import { normalizePath } from '../../../utils/paths'; 4 | import { removeMainFunctionCode } from '../../../utils/transform'; 5 | import { resolve } from 'node:path'; 6 | 7 | /** 8 | * Transforms entrypoints, removing the main function from the entrypoint if it exists. 9 | */ 10 | export function removeEntrypointMainFunction( 11 | config: ResolvedConfig, 12 | path: string, 13 | ): vite.Plugin { 14 | const absPath = normalizePath(resolve(config.root, path)); 15 | return { 16 | name: 'wxt:remove-entrypoint-main-function', 17 | transform: { 18 | order: 'pre', 19 | handler(code, id) { 20 | if (id === absPath) { 21 | const newCode = removeMainFunctionCode(code); 22 | config.logger.debug('vite-node transformed entrypoint', path); 23 | config.logger.debug(`Original:\n---\n${code}\n---`); 24 | config.logger.debug(`Transformed:\n---\n${newCode.code}\n---`); 25 | return newCode; 26 | } 27 | }, 28 | }, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/resolveAppConfig.ts: -------------------------------------------------------------------------------- 1 | import { exists } from 'fs-extra'; 2 | import { resolve } from 'node:path'; 3 | import type * as vite from 'vite'; 4 | import { ResolvedConfig } from '../../../../types'; 5 | 6 | /** 7 | * When importing `virtual:app-config`, resolve it to the `app.config.ts` file in the project. 8 | */ 9 | export function resolveAppConfig(config: ResolvedConfig): vite.Plugin { 10 | const virtualModuleId = 'virtual:app-config'; 11 | const resolvedVirtualModuleId = '\0' + virtualModuleId; 12 | const appConfigFile = resolve(config.srcDir, 'app.config.ts'); 13 | 14 | return { 15 | name: 'wxt:resolve-app-config', 16 | config() { 17 | return { 18 | optimizeDeps: { 19 | // Prevent ESBuild from attempting to resolve the virtual module 20 | // while optimizing WXT. 21 | exclude: [virtualModuleId], 22 | }, 23 | }; 24 | }, 25 | async resolveId(id) { 26 | if (id !== virtualModuleId) return; 27 | 28 | return (await exists(appConfigFile)) 29 | ? appConfigFile 30 | : resolvedVirtualModuleId; 31 | }, 32 | load(id) { 33 | if (id === resolvedVirtualModuleId) return `export default {}`; 34 | }, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/wxt/src/core/builders/vite/plugins/tsconfigPaths.ts: -------------------------------------------------------------------------------- 1 | import { ResolvedConfig } from '../../../../types'; 2 | import type * as vite from 'vite'; 3 | 4 | export function tsconfigPaths(config: ResolvedConfig): vite.Plugin { 5 | return { 6 | name: 'wxt:aliases', 7 | async config() { 8 | return { 9 | resolve: { 10 | alias: config.alias, 11 | }, 12 | }; 13 | }, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/wxt/src/core/define-config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfig } from '../types'; 2 | 3 | export function defineConfig(config: UserConfig): UserConfig { 4 | return config; 5 | } 6 | -------------------------------------------------------------------------------- /packages/wxt/src/core/define-web-ext-config.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola'; 2 | import { WebExtConfig } from '../types'; 3 | 4 | /** 5 | * @deprecated Use `defineWebExtConfig` instead. Same function, different name. 6 | */ 7 | export function defineRunnerConfig(config: WebExtConfig): WebExtConfig { 8 | consola.warn( 9 | '`defineRunnerConfig` is deprecated, use `defineWebExtConfig` instead. See https://wxt.dev/guide/resources/upgrading.html#v0-19-0-rarr-v0-20-0', 10 | ); 11 | return defineWebExtConfig(config); 12 | } 13 | 14 | /** 15 | * Configure how [`web-ext`](https://github.com/mozilla/web-ext) starts the browser during development. 16 | */ 17 | export function defineWebExtConfig(config: WebExtConfig): WebExtConfig { 18 | return config; 19 | } 20 | -------------------------------------------------------------------------------- /packages/wxt/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './build'; 2 | export * from './clean'; 3 | export * from './define-config'; 4 | export * from './define-web-ext-config'; 5 | export * from './create-server'; 6 | export * from './initialize'; 7 | export * from './prepare'; 8 | export * from './zip'; 9 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/bun.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import path from 'node:path'; 3 | import { bun } from '../bun'; 4 | 5 | describe.skipIf(() => process.platform === 'win32')( 6 | 'Bun Package Management Utils', 7 | () => { 8 | describe('listDependencies', () => { 9 | const cwd = path.resolve(__dirname, 'fixtures/simple-bun-project'); 10 | 11 | it('should list direct dependencies', async () => { 12 | const actual = await bun.listDependencies({ cwd }); 13 | expect(actual).toEqual([ 14 | { name: 'flatten', version: '1.0.3' }, 15 | { name: 'mime-types', version: '2.1.35' }, 16 | ]); 17 | }); 18 | 19 | it('should list all dependencies', async () => { 20 | const actual = await bun.listDependencies({ cwd, all: true }); 21 | expect(actual).toEqual([ 22 | { name: 'flatten', version: '1.0.3' }, 23 | { name: 'mime-db', version: '1.52.0' }, 24 | { name: 'mime-types', version: '2.1.35' }, 25 | ]); 26 | }); 27 | }); 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-bun-project/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-bun-project/bun.lockb -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-bun-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bun-ls", 3 | "dependencies": { 4 | "mime-types": "2.1.35" 5 | }, 6 | "devDependencies": { 7 | "flatten": "1.0.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-npm-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-ls", 3 | "dependencies": { 4 | "mime-types": "2.1.35" 5 | }, 6 | "devDependencies": { 7 | "flatten": "1.0.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-pnpm-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pnpm-ls", 3 | "dependencies": { 4 | "mime-types": "2.1.35" 5 | }, 6 | "devDependencies": { 7 | "flatten": "1.0.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-pnpm-project/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | mime-types: 12 | specifier: 2.1.35 13 | version: 2.1.35 14 | devDependencies: 15 | flatten: 16 | specifier: 1.0.3 17 | version: 1.0.3 18 | 19 | packages: 20 | 21 | flatten@1.0.3: 22 | resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==} 23 | deprecated: flatten is deprecated in favor of utility frameworks such as lodash. 24 | 25 | mime-db@1.52.0: 26 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 27 | engines: {node: '>= 0.6'} 28 | 29 | mime-types@2.1.35: 30 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 31 | engines: {node: '>= 0.6'} 32 | 33 | snapshots: 34 | 35 | flatten@1.0.3: {} 36 | 37 | mime-db@1.52.0: {} 38 | 39 | mime-types@2.1.35: 40 | dependencies: 41 | mime-db: 1.52.0 42 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yarn-ls", 3 | "packageManager": "yarn@1.22.22", 4 | "dependencies": { 5 | "mime-types": "2.1.35" 6 | }, 7 | "devDependencies": { 8 | "flatten": "1.0.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | flatten@1.0.3: 6 | version "1.0.3" 7 | resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" 8 | integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== 9 | 10 | mime-db@1.52.0: 11 | version "1.52.0" 12 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 13 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 14 | 15 | mime-types@2.1.35: 16 | version "2.1.35" 17 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 18 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 19 | dependencies: 20 | mime-db "1.52.0" 21 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/pnpm.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll, describe, expect, it } from 'vitest'; 2 | import path from 'node:path'; 3 | import { pnpm } from '../pnpm'; 4 | import spawn from 'nano-spawn'; 5 | 6 | process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true'; 7 | 8 | describe('PNPM Package Management Utils', () => { 9 | describe('listDependencies', () => { 10 | const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project'); 11 | beforeAll(async () => { 12 | // PNPM needs the modules installed, or 'pnpm ls' will return a blank list. 13 | await spawn('pnpm', ['i', '--ignore-workspace'], { cwd }); 14 | }); 15 | 16 | it('should list direct dependencies', async () => { 17 | const actual = await pnpm.listDependencies({ cwd }); 18 | expect(actual).toEqual([ 19 | { name: 'flatten', version: '1.0.3' }, 20 | { name: 'mime-types', version: '2.1.35' }, 21 | ]); 22 | }); 23 | 24 | it('should list all dependencies', async () => { 25 | const actual = await pnpm.listDependencies({ cwd, all: true }); 26 | expect(actual).toEqual([ 27 | { name: 'flatten', version: '1.0.3' }, 28 | { name: 'mime-types', version: '2.1.35' }, 29 | { name: 'mime-db', version: '1.52.0' }, 30 | ]); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/__tests__/yarn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import path from 'node:path'; 3 | import { yarn } from '../yarn'; 4 | 5 | describe('Yarn Package Management Utils', () => { 6 | describe('listDependencies', () => { 7 | const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-project'); 8 | 9 | it('should list direct dependencies', async () => { 10 | const actual = await yarn.listDependencies({ cwd }); 11 | expect(actual).toEqual([ 12 | { name: 'mime-db', version: '1.52.0' }, 13 | { name: 'flatten', version: '1.0.3' }, 14 | { name: 'mime-types', version: '2.1.35' }, 15 | ]); 16 | }); 17 | 18 | it('should list all dependencies', async () => { 19 | const actual = await yarn.listDependencies({ cwd, all: true }); 20 | expect(actual).toEqual([ 21 | { name: 'mime-db', version: '1.52.0' }, 22 | { name: 'flatten', version: '1.0.3' }, 23 | { name: 'mime-types', version: '2.1.35' }, 24 | ]); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/bun.ts: -------------------------------------------------------------------------------- 1 | import { dedupeDependencies, npm } from './npm'; 2 | import { WxtPackageManagerImpl } from './types'; 3 | import spawn from 'nano-spawn'; 4 | 5 | export const bun: WxtPackageManagerImpl = { 6 | overridesKey: 'overrides', // But also supports "resolutions" 7 | downloadDependency(...args) { 8 | return npm.downloadDependency(...args); 9 | }, 10 | async listDependencies(options) { 11 | const args = ['pm', 'ls']; 12 | if (options?.all) { 13 | args.push('--all'); 14 | } 15 | const res = await spawn('bun', args, { cwd: options?.cwd }); 16 | return dedupeDependencies( 17 | res.stdout 18 | .split('\n') 19 | .slice(1) // Skip the first line, is not a dependency 20 | .map((line) => line.trim()) 21 | .map((line) => /.* (@?\S+)@(\S+)$/.exec(line)) 22 | .filter((match) => !!match) 23 | .map(([_, name, version]) => ({ name, version })), 24 | ); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/deno.ts: -------------------------------------------------------------------------------- 1 | import { WxtPackageManagerImpl } from './types'; 2 | 3 | export const deno: WxtPackageManagerImpl = { 4 | overridesKey: 'na', 5 | downloadDependency() { 6 | throw Error('Deno not supported'); 7 | }, 8 | listDependencies() { 9 | throw Error('Deno not supported'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/pnpm.ts: -------------------------------------------------------------------------------- 1 | import { NpmListProject, flattenNpmListOutput, npm } from './npm'; 2 | import { WxtPackageManagerImpl } from './types'; 3 | import spawn from 'nano-spawn'; 4 | 5 | export const pnpm: WxtPackageManagerImpl = { 6 | overridesKey: 'resolutions', // "pnpm.overrides" has a higher priority, but I don't want to deal with nesting 7 | downloadDependency(...args) { 8 | return npm.downloadDependency(...args); 9 | }, 10 | async listDependencies(options) { 11 | const args = ['ls', '-r', '--json']; 12 | if (options?.all) { 13 | args.push('--depth', 'Infinity'); 14 | } 15 | // Helper for testing - since WXT uses pnpm workspaces, folders inside it don't behave like 16 | // standalone projects unless you pass the --ignore-workspace flag. 17 | if ( 18 | typeof process !== 'undefined' && 19 | process.env.WXT_PNPM_IGNORE_WORKSPACE === 'true' 20 | ) { 21 | args.push('--ignore-workspace'); 22 | } 23 | const res = await spawn('pnpm', args, { cwd: options?.cwd }); 24 | const projects: NpmListProject[] = JSON.parse(res.stdout); 25 | 26 | return flattenNpmListOutput(projects); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/wxt/src/core/package-managers/types.ts: -------------------------------------------------------------------------------- 1 | import { WxtPackageManager } from '../../types'; 2 | 3 | export type WxtPackageManagerImpl = Pick< 4 | WxtPackageManager, 5 | 'downloadDependency' | 'listDependencies' | 'overridesKey' 6 | >; 7 | -------------------------------------------------------------------------------- /packages/wxt/src/core/prepare.ts: -------------------------------------------------------------------------------- 1 | import { InlineConfig } from '../types'; 2 | import { findEntrypoints } from './utils/building'; 3 | import { generateWxtDir } from './generate-wxt-dir'; 4 | import { registerWxt, wxt } from './wxt'; 5 | 6 | export async function prepare(config: InlineConfig) { 7 | await registerWxt('build', config); 8 | wxt.logger.info('Generating types...'); 9 | 10 | const entrypoints = await findEntrypoints(); 11 | await generateWxtDir(entrypoints); 12 | } 13 | -------------------------------------------------------------------------------- /packages/wxt/src/core/runners/index.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionRunner } from '../../types'; 2 | import { createWslRunner } from './wsl'; 3 | import { createWebExtRunner } from './web-ext'; 4 | import { createSafariRunner } from './safari'; 5 | import { createManualRunner } from './manual'; 6 | import { isWsl } from '../utils/wsl'; 7 | import { wxt } from '../wxt'; 8 | 9 | export async function createExtensionRunner(): Promise { 10 | if (wxt.config.browser === 'safari') return createSafariRunner(); 11 | 12 | if (await isWsl()) return createWslRunner(); 13 | if (wxt.config.runnerConfig.config?.disabled) return createManualRunner(); 14 | 15 | return createWebExtRunner(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/wxt/src/core/runners/manual.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionRunner } from '../../types'; 2 | import { relative } from 'node:path'; 3 | import { wxt } from '../wxt'; 4 | 5 | /** 6 | * The manual runner tells the user to load the unpacked extension manually. 7 | */ 8 | export function createManualRunner(): ExtensionRunner { 9 | return { 10 | async openBrowser() { 11 | wxt.logger.info( 12 | `Load "${relative( 13 | process.cwd(), 14 | wxt.config.outDir, 15 | )}" as an unpacked extension manually`, 16 | ); 17 | }, 18 | async closeBrowser() { 19 | // noop 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/runners/safari.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionRunner } from '../../types'; 2 | import { relative } from 'node:path'; 3 | import { wxt } from '../wxt'; 4 | 5 | /** 6 | * The Safari runner just logs a warning message because `web-ext` doesn't work with Safari. 7 | */ 8 | export function createSafariRunner(): ExtensionRunner { 9 | return { 10 | async openBrowser() { 11 | wxt.logger.warn( 12 | `Cannot Safari using web-ext. Load "${relative( 13 | process.cwd(), 14 | wxt.config.outDir, 15 | )}" as an unpacked extension manually`, 16 | ); 17 | }, 18 | async closeBrowser() { 19 | // noop 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/runners/wsl.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionRunner } from '../../types'; 2 | import { relative } from 'node:path'; 3 | import { wxt } from '../wxt'; 4 | 5 | /** 6 | * The WSL runner just logs a warning message because `web-ext` doesn't work in WSL. 7 | */ 8 | export function createWslRunner(): ExtensionRunner { 9 | return { 10 | async openBrowser() { 11 | wxt.logger.warn( 12 | `Cannot open browser when using WSL. Load "${relative( 13 | process.cwd(), 14 | wxt.config.outDir, 15 | )}" as an unpacked extension manually`, 16 | ); 17 | }, 18 | async closeBrowser() { 19 | // noop 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/__tests__/arrays.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { every, some } from '../arrays'; 3 | 4 | describe('Array Utils', () => { 5 | describe('every', () => { 6 | it('should return true when the array is empty', () => { 7 | expect(every([], () => false)).toBe(true); 8 | }); 9 | 10 | it("should return true when all item predicate's return true", () => { 11 | expect(every([1, 1, 1], (item) => item === 1)).toBe(true); 12 | }); 13 | 14 | it("should return false when a single item predicate's return false", () => { 15 | expect(every([1, 2, 1], (item) => item === 1)).toBe(false); 16 | }); 17 | }); 18 | 19 | describe('some', () => { 20 | it('should return true if one value returns true', () => { 21 | const array = [1, 2, 3]; 22 | const predicate = (item: number) => item === 2; 23 | 24 | expect(some(array, predicate)).toBe(true); 25 | }); 26 | 27 | it('should return false if no values match', () => { 28 | const array = [1, 2, 3]; 29 | const predicate = (item: number) => item === 4; 30 | 31 | expect(some(array, predicate)).toBe(false); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/__tests__/number.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { safeStringToNumber } from '../number'; 3 | 4 | describe('Number Utils', () => { 5 | describe('safeStringToNumber', () => { 6 | it.each([ 7 | { arg: '1000', expected: 1000 }, 8 | { arg: '', expected: 0 }, 9 | { arg: '12abc', expected: null }, 10 | { arg: undefined, expected: null }, 11 | ])( 12 | 'should be safely converted from string to number: safeStringToNumber($arg) -> $expected', 13 | ({ arg, expected }) => { 14 | expect(safeStringToNumber(arg)).toBe(expected); 15 | }, 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/__tests__/package.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { getPackageJson } from '../package'; 3 | import { setFakeWxt } from '../testing/fake-objects'; 4 | import { mock } from 'vitest-mock-extended'; 5 | import { Logger } from '../../../types'; 6 | import { WXT_PACKAGE_DIR } from '../../../../e2e/utils'; 7 | 8 | describe('Package JSON Utils', () => { 9 | describe('getPackageJson', () => { 10 | it('should return the package.json inside /package.json', async () => { 11 | setFakeWxt({ 12 | config: { root: WXT_PACKAGE_DIR }, 13 | }); 14 | 15 | const actual = await getPackageJson(); 16 | 17 | expect(actual).toMatchObject({ 18 | name: 'wxt', 19 | }); 20 | }); 21 | 22 | it("should return an empty object when /package.json doesn't exist", async () => { 23 | const root = '/some/path/that/does/not/exist'; 24 | const logger = mock(); 25 | setFakeWxt({ 26 | config: { root, logger }, 27 | logger, 28 | }); 29 | 30 | const actual = await getPackageJson(); 31 | 32 | expect(actual).toEqual({}); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/__tests__/paths.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { normalizePath } from '../paths'; 3 | 4 | describe('Path Utils', () => { 5 | describe('normalizePath', () => { 6 | it.each([ 7 | // Relative paths 8 | ['../test.sh', '../test.sh'], 9 | ['..\\test.sh', '../test.sh'], 10 | ['test.png', 'test.png'], 11 | // Absolute paths 12 | ['C:\\\\path\\to\\file', 'C:/path/to/file'], 13 | ['/path/to/file', '/path/to/file'], 14 | // Strip trailing slash 15 | ['C:\\\\path\\to\\folder\\', 'C:/path/to/folder'], 16 | ['/path/to/folder/', '/path/to/folder'], 17 | // Dedupe slashes 18 | ['path\\\\\\file', 'path/file'], 19 | ['path//file', 'path/file'], 20 | ])('should normalize "%s" to "%s"', (input, expected) => { 21 | expect(normalizePath(input)).toBe(expected); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/__tests__/virtual-modules.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | import { 3 | VirtualModuleId, 4 | VirtualModuleName, 5 | VirtualEntrypointType, 6 | VirtualEntrypointModuleName, 7 | } from '../virtual-modules'; 8 | 9 | describe('Virtual Modules', () => { 10 | it('should resolve types to litteral values, not string', () => { 11 | // @ts-expect-error 12 | const _c: VirtualEntrypointType = ''; 13 | // @ts-expect-error 14 | const _d: VirtualEntrypointModuleName = ''; 15 | // @ts-expect-error 16 | const _b: VirtualModuleName = ''; 17 | // @ts-expect-error 18 | const _a: VirtualModuleId = ''; 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/arrays.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `predicate` returns truthy for all elements of the array. 3 | */ 4 | export function every( 5 | array: T[], 6 | predicate: (item: T, index: number) => boolean, 7 | ): boolean { 8 | for (let i = 0; i < array.length; i++) 9 | if (!predicate(array[i], i)) return false; 10 | return true; 11 | } 12 | 13 | /** 14 | * Returns true when any of the predicates return true; 15 | */ 16 | export function some( 17 | array: T[], 18 | predicate: (item: T, index: number) => boolean, 19 | ): boolean { 20 | for (let i = 0; i < array.length; i++) 21 | if (predicate(array[i], i)) return true; 22 | return false; 23 | } 24 | 25 | /** 26 | * Convert an item or array to an array. 27 | */ 28 | export function toArray(a: T | T[]): T[] { 29 | return Array.isArray(a) ? a : [a]; 30 | } 31 | 32 | export function filterTruthy(array: Array): T[] { 33 | return array.filter((item) => !!item) as T[]; 34 | } 35 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | import { defineBackground } from '../../../../../utils/define-background'; 2 | 3 | export default defineBackground({ 4 | main() {}, 5 | }); 6 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | import { defineContentScript } from '../../../../../utils/define-content-script'; 2 | 3 | export default defineContentScript({ 4 | matches: [''], 5 | main() {}, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/imported-option.ts: -------------------------------------------------------------------------------- 1 | import { defineContentScript } from '../../../../../utils/define-content-script'; 2 | import { faker } from '@faker-js/faker'; 3 | 4 | export default defineContentScript({ 5 | matches: [faker.string.nanoid()], 6 | main() {}, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/no-default-export.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/packages/wxt/src/core/utils/building/__tests__/test-entrypoints/no-default-export.ts -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/react.tsx: -------------------------------------------------------------------------------- 1 | import { defineUnlistedScript } from '../../../../../utils/define-unlisted-script'; 2 | 3 | export default defineUnlistedScript(() => {}); 4 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/unlisted.ts: -------------------------------------------------------------------------------- 1 | import { defineUnlistedScript } from '../../../../../utils/define-unlisted-script'; 2 | 3 | export default defineUnlistedScript(() => {}); 4 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/__tests__/test-entrypoints/with-named.ts: -------------------------------------------------------------------------------- 1 | import { defineBackground } from '../../../../../utils/define-background'; 2 | 3 | export const a = {}; 4 | 5 | export default defineBackground(() => {}); 6 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/building/index.ts: -------------------------------------------------------------------------------- 1 | export * from './build-entrypoints'; 2 | export * from './detect-dev-changes'; 3 | export * from './find-entrypoints'; 4 | export * from './group-entrypoints'; 5 | export * from './internal-build'; 6 | export * from './rebuild'; 7 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import fs, { ensureDir } from 'fs-extra'; 2 | import { FsCache } from '../../types'; 3 | import { dirname, resolve } from 'path'; 4 | import { writeFileIfDifferent } from './fs'; 5 | 6 | /** 7 | * A basic file system cache stored at `/.wxt/cache/`. Just caches a string in a 8 | * file for the given key. 9 | * 10 | * @param srcDir Absolute path to source directory. See `InternalConfig.srcDir` 11 | */ 12 | export function createFsCache(wxtDir: string): FsCache { 13 | const getPath = (key: string) => 14 | resolve(wxtDir, 'cache', encodeURIComponent(key)); 15 | 16 | return { 17 | async set(key: string, value: string): Promise { 18 | const path = getPath(key); 19 | await ensureDir(dirname(path)); 20 | await writeFileIfDifferent(path, value); 21 | }, 22 | async get(key: string): Promise { 23 | const path = getPath(key); 24 | try { 25 | return await fs.readFile(path, 'utf-8'); 26 | } catch { 27 | return undefined; 28 | } 29 | }, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/cli.ts: -------------------------------------------------------------------------------- 1 | import { LogLevels, consola } from 'consola'; 2 | import { printHeader } from './log'; 3 | import { formatDuration } from './time'; 4 | 5 | export function defineCommand( 6 | cb: (...args: TArgs) => void | boolean | Promise, 7 | options?: { 8 | disableFinishedLog?: boolean; 9 | }, 10 | ) { 11 | return async (...args: TArgs) => { 12 | // Enable consola's debug mode globally at the start of all commands when the `--debug` flag is 13 | // passed 14 | const isDebug = !!args.find((arg) => arg?.debug); 15 | if (isDebug) { 16 | consola.level = LogLevels.debug; 17 | } 18 | 19 | const startTime = Date.now(); 20 | try { 21 | printHeader(); 22 | 23 | const ongoing = await cb(...args); 24 | 25 | if (!ongoing && !options?.disableFinishedLog) 26 | consola.success( 27 | `Finished in ${formatDuration(Date.now() - startTime)}`, 28 | ); 29 | } catch (err) { 30 | consola.fail( 31 | `Command failed after ${formatDuration(Date.now() - startTime)}`, 32 | ); 33 | consola.error(err); 34 | process.exit(1); 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module ID used to build the background in dev mode if the extension doesn't include a background 3 | * script/service worker. 4 | */ 5 | export const VIRTUAL_NOOP_BACKGROUND_MODULE_ID = 'virtual:user-background'; 6 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/env.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import { expand } from 'dotenv-expand'; 3 | import type { TargetBrowser } from '../../types'; 4 | 5 | /** 6 | * Load environment files based on the current mode and browser. 7 | */ 8 | export function loadEnv(mode: string, browser: TargetBrowser) { 9 | return expand( 10 | config({ 11 | // Files on top override files below 12 | path: [ 13 | `.env.${mode}.${browser}.local`, 14 | `.env.${mode}.${browser}`, 15 | `.env.${browser}.local`, 16 | `.env.${browser}`, 17 | `.env.${mode}.local`, 18 | `.env.${mode}`, 19 | `.env.local`, 20 | `.env`, 21 | ], 22 | }), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/environments/browser-environment.ts: -------------------------------------------------------------------------------- 1 | import { parseHTML } from 'linkedom'; 2 | import { createEnvironment, Environment, EnvGlobals } from './environment'; 3 | 4 | export function createBrowserEnvironment(): Environment { 5 | return createEnvironment(getBrowserEnvironmentGlobals); 6 | } 7 | 8 | export function getBrowserEnvironmentGlobals(): EnvGlobals { 9 | const { window, document, global } = parseHTML(` 10 | 11 | 12 | 13 | 14 | `); 15 | return { 16 | ...global, 17 | window, 18 | document, 19 | self: global, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/environments/extension-environment.ts: -------------------------------------------------------------------------------- 1 | import { fakeBrowser } from '@webext-core/fake-browser'; 2 | import { getBrowserEnvironmentGlobals } from './browser-environment'; 3 | import { createEnvironment, Environment } from './environment'; 4 | 5 | export function createExtensionEnvironment(): Environment { 6 | return createEnvironment(getExtensionEnvironmentGlobals); 7 | } 8 | 9 | export function getExtensionEnvironmentGlobals() { 10 | return { 11 | ...getBrowserEnvironmentGlobals(), 12 | chrome: fakeBrowser, 13 | browser: fakeBrowser, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/environments/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browser-environment'; 2 | export * from './extension-environment'; 3 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/eslint.ts: -------------------------------------------------------------------------------- 1 | export async function getEslintVersion(): Promise { 2 | try { 3 | const require = (await import('node:module')).default.createRequire( 4 | import.meta.url, 5 | ); 6 | const { ESLint } = require('eslint'); 7 | return ESLint.version?.split('.') ?? []; 8 | } catch { 9 | // Return an empty version when there's an error importing ESLint 10 | return []; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import glob from 'fast-glob'; 3 | import { unnormalizePath } from './paths'; 4 | import { wxt } from '../wxt'; 5 | 6 | /** 7 | * Only write the contents to a file if it results in a change. This prevents unnecessary file 8 | * watchers from being triggered, like WXT's dev server or the TS language server in editors. 9 | * 10 | * @param file The file to write to. 11 | * @param newContents The new text content to write. 12 | */ 13 | export async function writeFileIfDifferent( 14 | file: string, 15 | newContents: string, 16 | ): Promise { 17 | const existingContents = await fs 18 | .readFile(file, 'utf-8') 19 | .catch(() => undefined); 20 | 21 | if (existingContents !== newContents) { 22 | await fs.writeFile(file, newContents); 23 | } 24 | } 25 | 26 | /** 27 | * Get all the files in the project's public directory. Returned paths are relative to the 28 | * `config.publicDir`. 29 | */ 30 | export async function getPublicFiles(): Promise { 31 | if (!(await fs.exists(wxt.config.publicDir))) return []; 32 | 33 | const files = await glob('**/*', { cwd: wxt.config.publicDir }); 34 | return files.map(unnormalizePath); 35 | } 36 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/log/index.ts: -------------------------------------------------------------------------------- 1 | export * from './printBuildSummary'; 2 | export * from './printFileList'; 3 | export * from './printHeader'; 4 | export * from './printTable'; 5 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/log/printHeader.ts: -------------------------------------------------------------------------------- 1 | import pc from 'picocolors'; 2 | import { version } from '../../../version'; 3 | import { consola } from 'consola'; 4 | 5 | export function printHeader() { 6 | console.log(); 7 | consola.log(`${pc.gray('WXT')} ${pc.gray(pc.bold(version))}`); 8 | } 9 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/log/printTable.ts: -------------------------------------------------------------------------------- 1 | export function printTable( 2 | log: (message: string) => void, 3 | header: string, 4 | rows: string[][], 5 | gap = 2, 6 | ): void { 7 | if (rows.length === 0) return; 8 | 9 | const columnWidths = rows.reduce( 10 | (widths, row) => { 11 | for (let i = 0; i < Math.max(widths.length, row.length); i++) { 12 | widths[i] = Math.max(row[i]?.length ?? 0, widths[i] ?? 0); 13 | } 14 | return widths; 15 | }, 16 | rows[0].map((column) => column.length), 17 | ); 18 | 19 | let str = ''; 20 | rows.forEach((row, i) => { 21 | row.forEach((col, j) => { 22 | str += col.padEnd(columnWidths[j], ' '); 23 | if (j !== row.length - 1) str += ''.padEnd(gap, ' '); 24 | }); 25 | if (i !== rows.length - 1) str += '\n'; 26 | }); 27 | 28 | log(`${header}\n${str}`); 29 | } 30 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/number.ts: -------------------------------------------------------------------------------- 1 | export function safeStringToNumber(str: string | undefined): number | null { 2 | const num = Number(str); 3 | return isNaN(num) ? null : num; 4 | } 5 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/package.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path'; 2 | import fs from 'fs-extra'; 3 | import { wxt } from '../wxt'; 4 | 5 | /** 6 | * Read the project's package.json. 7 | * 8 | * TODO: look in root and up directories until it's found 9 | */ 10 | export async function getPackageJson(): Promise< 11 | Partial> | undefined 12 | > { 13 | const file = resolve(wxt.config.root, 'package.json'); 14 | try { 15 | return await fs.readJson(file); 16 | } catch (err) { 17 | wxt.logger.debug( 18 | `Failed to read package.json at: ${file}. Returning undefined.`, 19 | err, 20 | ); 21 | return {}; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import systemPath from 'node:path'; 2 | import normalize from 'normalize-path'; 3 | 4 | /** 5 | * Converts system paths to normalized bundler path. On windows and unix, this returns paths with / 6 | * instead of \. 7 | */ 8 | export function normalizePath(path: string): string { 9 | return normalize(path); 10 | } 11 | 12 | /** 13 | * Given a normalized path, convert it to the system path style. On Windows, switch to \, otherwise use /. 14 | */ 15 | export function unnormalizePath(path: string): string { 16 | return systemPath.normalize(path); 17 | } 18 | 19 | export const CSS_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'styl', 'stylus']; 20 | 21 | // .module.css files are not supported because these are global CSS files, so using CSS modules doesn't make sense. 22 | export const CSS_EXTENSIONS_PATTERN = `+(${CSS_EXTENSIONS.join('|')})`; 23 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/strings.ts: -------------------------------------------------------------------------------- 1 | import { camelCase } from 'scule'; 2 | 3 | export function kebabCaseAlphanumeric(str: string): string { 4 | return str 5 | .toLowerCase() 6 | .replace(/[^a-z0-9-\s]/g, '') // Remove all non-alphanumeric, non-hyphen characters 7 | .replace(/\s+/g, '-'); // Replace spaces with hyphens 8 | } 9 | 10 | /** 11 | * Return a safe variable name for a given string. 12 | */ 13 | export function safeVarName(str: string): string { 14 | const name = camelCase(kebabCaseAlphanumeric(str)); 15 | if (name.match(/^[a-z]/)) return name; 16 | // _ prefix to ensure it doesn't start with a number or other invalid symbol 17 | return '_' + name; 18 | } 19 | 20 | /** 21 | * Converts a string to a valid filename (NOT path), stripping out invalid characters. 22 | */ 23 | export function safeFilename(str: string): string { 24 | return kebabCaseAlphanumeric(str); 25 | } 26 | 27 | /** 28 | * Removes import statements from the top of a file. Keeps import.meta and inline, async `import()` 29 | * calls. 30 | */ 31 | export function removeImportStatements(text: string): string { 32 | return text.replace( 33 | /(import\s?[\s\S]*?from\s?["'][\s\S]*?["'];?|import\s?["'][\s\S]*?["'];?)/gm, 34 | '', 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/syntax-errors.ts: -------------------------------------------------------------------------------- 1 | import { relative } from 'node:path'; 2 | import pc from 'picocolors'; 3 | import { wxt } from '../wxt'; 4 | 5 | export interface BabelSyntaxError extends SyntaxError { 6 | code: 'BABEL_PARSER_SYNTAX_ERROR'; 7 | frame?: string; 8 | id: string; 9 | loc: { line: number; column: number }; 10 | } 11 | 12 | export function isBabelSyntaxError(error: unknown): error is BabelSyntaxError { 13 | return ( 14 | error instanceof SyntaxError && 15 | (error as any).code === 'BABEL_PARSER_SYNTAX_ERROR' 16 | ); 17 | } 18 | 19 | export function logBabelSyntaxError(error: BabelSyntaxError) { 20 | let filename = relative(wxt.config.root, error.id); 21 | if (filename.startsWith('..')) { 22 | filename = error.id; 23 | } 24 | let message = error.message.replace( 25 | /\(\d+:\d+\)$/, 26 | `(${filename}:${error.loc.line}:${error.loc.column + 1})`, 27 | ); 28 | if (error.frame) { 29 | message += '\n\n' + pc.red(error.frame); 30 | } 31 | wxt.logger.error(message); 32 | } 33 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function formatDuration(duration: number): string { 2 | if (duration < 1e3) return `${duration} ms`; 3 | if (duration < 10e3) return `${(duration / 1e3).toFixed(3)} s`; 4 | if (duration < 60e3) return `${(duration / 1e3).toFixed(1)} s`; 5 | return `${(duration / 1e3).toFixed(0)} s`; 6 | } 7 | 8 | /** 9 | * Add a timeout to a promise. 10 | */ 11 | export function withTimeout( 12 | promise: Promise, 13 | duration: number, 14 | ): Promise { 15 | return new Promise((res, rej) => { 16 | const timeout = setTimeout(() => { 17 | rej(`Promise timed out after ${duration}ms`); 18 | }, duration); 19 | promise 20 | .then(res) 21 | .catch(rej) 22 | .finally(() => clearTimeout(timeout)); 23 | }); 24 | } 25 | 26 | /** 27 | * @deprecated Don't use in production, just for testing and slowing things down. 28 | */ 29 | export function sleep(ms: number): Promise { 30 | return new Promise((res) => setTimeout(res, ms)); 31 | } 32 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/types.ts: -------------------------------------------------------------------------------- 1 | import type { Browser } from '@wxt-dev/browser'; 2 | 3 | /** 4 | * Remove optional from key, but keep undefined if present 5 | * 6 | * @example 7 | * type Test = NullablyRequired<{a?: string, b: number}> 8 | * // type Test = {a: string | undefined, b: number} 9 | */ 10 | export type NullablyRequired = { [K in keyof Required]: T[K] }; 11 | 12 | export type ManifestContentScript = NonNullable< 13 | Browser.runtime.Manifest['content_scripts'] 14 | >[number]; 15 | 16 | export type ManifestV3WebAccessibleResource = NonNullable< 17 | Browser.runtime.ManifestV3['web_accessible_resources'] 18 | >[number]; 19 | -------------------------------------------------------------------------------- /packages/wxt/src/core/utils/wsl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true when running on WSL or WSL2. 3 | */ 4 | export async function isWsl(): Promise { 5 | const { default: isWsl } = await import('is-wsl'); // ESM only, requires dynamic import 6 | return isWsl; 7 | } 8 | -------------------------------------------------------------------------------- /packages/wxt/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module contains: 3 | * - JS APIs used by the CLI to build extensions or start dev mode. 4 | * - Helper functions for defining project config. 5 | * - Types for building and extension or configuring WXT. 6 | * 7 | * @module wxt 8 | */ 9 | export * from './core'; 10 | export * from './types'; 11 | export * from './version'; 12 | -------------------------------------------------------------------------------- /packages/wxt/src/testing/fake-browser.ts: -------------------------------------------------------------------------------- 1 | export { fakeBrowser, type FakeBrowser } from '@webext-core/fake-browser'; 2 | -------------------------------------------------------------------------------- /packages/wxt/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for unit testing WXT extensions. 3 | * @module wxt/testing 4 | */ 5 | export * from './fake-browser'; 6 | export * from './wxt-vitest-plugin'; 7 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/README.md: -------------------------------------------------------------------------------- 1 | This folder is for public utils, not internal utils. Put generic helpers and other utils in the core/utils folder. 2 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/__tests__/define-background.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { defineBackground } from '../define-background'; 3 | import { BackgroundDefinition } from '../../types'; 4 | 5 | describe('defineBackground', () => { 6 | it('should return the object definition when given an object', () => { 7 | const definition: BackgroundDefinition = { 8 | include: [''], 9 | persistent: false, 10 | main: vi.fn(), 11 | }; 12 | 13 | const actual = defineBackground(definition); 14 | 15 | expect(actual).toEqual(definition); 16 | }); 17 | 18 | it('should return the object definition when given a main function', () => { 19 | const main = vi.fn(); 20 | 21 | const actual = defineBackground(main); 22 | 23 | expect(actual).toEqual({ main }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/__tests__/define-content-script.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { defineContentScript } from '../define-content-script'; 3 | import { ContentScriptDefinition } from '../../types'; 4 | 5 | describe('defineContentScript', () => { 6 | it('should return the object passed in', () => { 7 | const definition: ContentScriptDefinition = { 8 | matches: [], 9 | include: [''], 10 | main: vi.fn(), 11 | }; 12 | 13 | const actual = defineContentScript(definition); 14 | 15 | expect(actual).toEqual(definition); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/__tests__/define-unlisted-script.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { defineUnlistedScript } from '../define-unlisted-script'; 3 | import { UnlistedScriptDefinition } from '../../types'; 4 | 5 | describe('defineUnlistedScript', () => { 6 | it('should return the object definition when given an object', () => { 7 | const definition: UnlistedScriptDefinition = { 8 | include: [''], 9 | main: vi.fn(), 10 | }; 11 | 12 | const actual = defineUnlistedScript(definition); 13 | 14 | expect(actual).toEqual(definition); 15 | }); 16 | 17 | it('should return the object definition when given a main function', () => { 18 | const main = vi.fn(); 19 | 20 | const actual = defineUnlistedScript(main); 21 | 22 | expect(actual).toEqual({ main }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/app-config.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/app-config */ 2 | // @ts-expect-error: Untyped virtual module 3 | import appConfig from 'virtual:app-config'; 4 | import type { WxtAppConfig } from '../utils/define-app-config'; 5 | 6 | export function useAppConfig(): WxtAppConfig { 7 | return appConfig; 8 | } 9 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/define-app-config.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/define-app-config */ 2 | export interface WxtAppConfig {} 3 | 4 | /** 5 | * Runtime app config defined in `/app.config.ts`. 6 | * 7 | * You can add fields to this interface via ["Module Augmentation"](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation): 8 | * 9 | * ```ts 10 | * // app.config.ts 11 | * import 'wxt/utils/define-app-config'; 12 | * 13 | * declare module "wxt/utils/define-app-config" { 14 | * export interface WxtAppConfig { 15 | * analytics: AnalyticsConfig 16 | * } 17 | * } 18 | * ``` 19 | */ 20 | export function defineAppConfig(config: WxtAppConfig): WxtAppConfig { 21 | return config; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/define-background.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/define-background */ 2 | import type { BackgroundDefinition } from '../types'; 3 | 4 | export function defineBackground(main: () => void): BackgroundDefinition; 5 | export function defineBackground( 6 | definition: BackgroundDefinition, 7 | ): BackgroundDefinition; 8 | export function defineBackground( 9 | arg: (() => void) | BackgroundDefinition, 10 | ): BackgroundDefinition { 11 | if (arg == null || typeof arg === 'function') return { main: arg }; 12 | return arg; 13 | } 14 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/define-content-script.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/define-content-script */ 2 | import type { ContentScriptDefinition } from '../types'; 3 | 4 | export function defineContentScript( 5 | definition: ContentScriptDefinition, 6 | ): ContentScriptDefinition { 7 | return definition; 8 | } 9 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/define-unlisted-script.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/define-unlisted-script */ 2 | import type { UnlistedScriptDefinition } from '../types'; 3 | 4 | export function defineUnlistedScript( 5 | main: () => void, 6 | ): UnlistedScriptDefinition; 7 | export function defineUnlistedScript( 8 | definition: UnlistedScriptDefinition, 9 | ): UnlistedScriptDefinition; 10 | export function defineUnlistedScript( 11 | arg: (() => void) | UnlistedScriptDefinition, 12 | ): UnlistedScriptDefinition { 13 | if (arg == null || typeof arg === 'function') return { main: arg }; 14 | return arg; 15 | } 16 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/define-wxt-plugin.ts: -------------------------------------------------------------------------------- 1 | /** @module wxt/utils/define-wxt-plugin */ 2 | import type { WxtPlugin } from '../types'; 3 | 4 | export function defineWxtPlugin(plugin: WxtPlugin): WxtPlugin { 5 | return plugin; 6 | } 7 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/internal/custom-events.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'wxt/browser'; 2 | 3 | export class WxtLocationChangeEvent extends Event { 4 | static EVENT_NAME = getUniqueEventName('wxt:locationchange'); 5 | 6 | constructor( 7 | readonly newUrl: URL, 8 | readonly oldUrl: URL, 9 | ) { 10 | super(WxtLocationChangeEvent.EVENT_NAME, {}); 11 | } 12 | } 13 | 14 | /** 15 | * Returns an event name unique to the extension and content script that's running. 16 | */ 17 | export function getUniqueEventName(eventName: string): string { 18 | return `${browser?.runtime?.id}:${import.meta.env.ENTRYPOINT}:${eventName}`; 19 | } 20 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/internal/location-watcher.ts: -------------------------------------------------------------------------------- 1 | import { ContentScriptContext } from '../content-script-context'; 2 | import { WxtLocationChangeEvent } from './custom-events'; 3 | 4 | /** 5 | * Create a util that watches for URL changes, dispatching the custom event when detected. Stops 6 | * watching when content script is invalidated. 7 | */ 8 | export function createLocationWatcher(ctx: ContentScriptContext) { 9 | let interval: number | undefined; 10 | let oldUrl: URL; 11 | 12 | return { 13 | /** 14 | * Ensure the location watcher is actively looking for URL changes. If it's already watching, 15 | * this is a noop. 16 | */ 17 | run() { 18 | if (interval != null) return; 19 | 20 | oldUrl = new URL(location.href); 21 | interval = ctx.setInterval(() => { 22 | let newUrl = new URL(location.href); 23 | if (newUrl.href !== oldUrl.href) { 24 | window.dispatchEvent(new WxtLocationChangeEvent(newUrl, oldUrl)); 25 | oldUrl = newUrl; 26 | } 27 | }, 1e3); 28 | }, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/internal/logger.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | function print(method: (...args: any[]) => void, ...args: any[]) { 4 | if (import.meta.env.MODE === 'production') return; 5 | 6 | if (typeof args[0] === 'string') { 7 | const message = args.shift(); 8 | method(`[wxt] ${message}`, ...args); 9 | } else { 10 | method('[wxt]', ...args); 11 | } 12 | } 13 | 14 | /** 15 | * Wrapper around `console` with a "[wxt]" prefix 16 | */ 17 | export const logger = { 18 | debug: (...args: any[]) => print(console.debug, ...args), 19 | log: (...args: any[]) => print(console.log, ...args), 20 | warn: (...args: any[]) => print(console.warn, ...args), 21 | error: (...args: any[]) => print(console.error, ...args), 22 | }; 23 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/match-patterns.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Re-export the [`@webext-core/match-patterns` package](https://www.npmjs.com/package/@webext-core/match-patterns). 3 | * @module wxt/utils/match-patterns 4 | */ 5 | export * from '@webext-core/match-patterns'; 6 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/split-shadow-root-css.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a CSS string that will be loaded into a shadow root, split it into two parts: 3 | * - `documentCss`: CSS that needs to be applied to the document (like `@property`) 4 | * - `shadowCss`: CSS that needs to be applied to the shadow root 5 | * @param css 6 | */ 7 | export function splitShadowRootCss(css: string): { 8 | documentCss: string; 9 | shadowCss: string; 10 | } { 11 | let shadowCss = css; 12 | let documentCss = ''; 13 | 14 | const rulesRegex = /(\s*@(property|font-face)[\s\S]*?{[\s\S]*?})/gm; 15 | let match; 16 | while ((match = rulesRegex.exec(css)) !== null) { 17 | documentCss += match[1]; 18 | shadowCss = shadowCss.replace(match[1], ''); 19 | } 20 | 21 | return { 22 | documentCss: documentCss.trim(), 23 | shadowCss: shadowCss.trim(), 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/wxt/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Re-export the [`@wxt-dev/storage` package](https://www.npmjs.com/package/@wxt-dev/storage). 3 | * @module wxt/utils/storage 4 | */ 5 | export * from '@wxt-dev/storage'; 6 | -------------------------------------------------------------------------------- /packages/wxt/src/version.ts: -------------------------------------------------------------------------------- 1 | export const version = '{{version}}'; 2 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/README.md: -------------------------------------------------------------------------------- 1 | # WXT Virtual Entrypoints 2 | 3 | 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. 4 | 5 | While they are bundled and shipped inside WXT, Vite considers them a part 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. 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | See https://github.com/wxt-dev/wxt/issues/286#issuecomment-1858888390 for more details. 12 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/content-script-isolated-world-entrypoint.ts: -------------------------------------------------------------------------------- 1 | import definition from 'virtual:user-content-script-isolated-world-entrypoint'; 2 | import { logger } from '../utils/internal/logger'; 3 | import { ContentScriptContext } from 'wxt/utils/content-script-context'; 4 | import { initPlugins } from 'virtual:wxt-plugins'; 5 | 6 | const result = (async () => { 7 | try { 8 | initPlugins(); 9 | const { main, ...options } = definition; 10 | const ctx = new ContentScriptContext(import.meta.env.ENTRYPOINT, options); 11 | 12 | return await main(ctx); 13 | } catch (err) { 14 | logger.error( 15 | `The content script "${import.meta.env.ENTRYPOINT}" crashed on startup!`, 16 | err, 17 | ); 18 | throw err; 19 | } 20 | })(); 21 | 22 | // Return the main function's result to the background when executed via the 23 | // scripting API. Default export causes the IIFE to return a value. 24 | // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value 25 | // Tested on both Chrome and Firefox 26 | export default result; 27 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/content-script-main-world-entrypoint.ts: -------------------------------------------------------------------------------- 1 | import definition from 'virtual:user-content-script-main-world-entrypoint'; 2 | import { logger } from '../utils/internal/logger'; 3 | import { initPlugins } from 'virtual:wxt-plugins'; 4 | 5 | const result = (async () => { 6 | try { 7 | initPlugins(); 8 | return await definition.main(); 9 | } catch (err) { 10 | logger.error( 11 | `The content script "${import.meta.env.ENTRYPOINT}" crashed on startup!`, 12 | err, 13 | ); 14 | throw err; 15 | } 16 | })(); 17 | 18 | // Return the main function's result to the background when executed via the 19 | // scripting API. Default export causes the IIFE to return a value. 20 | // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value 21 | // Tested on both Chrome and Firefox 22 | export default result; 23 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/mock-browser.ts: -------------------------------------------------------------------------------- 1 | import { fakeBrowser } from 'wxt/testing'; 2 | 3 | export const browser = fakeBrowser; 4 | export default fakeBrowser; 5 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/reload-html.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '../utils/internal/logger'; 2 | import { getDevServerWebSocket } from '../utils/internal/dev-server-websocket'; 3 | 4 | if (import.meta.env.COMMAND === 'serve') { 5 | try { 6 | const ws = getDevServerWebSocket(); 7 | ws.addWxtEventListener('wxt:reload-page', (event) => { 8 | // "popup.html" === "/popup.html".substring(1) 9 | if (event.detail === location.pathname.substring(1)) location.reload(); 10 | }); 11 | } catch (err) { 12 | logger.error('Failed to setup web socket connection with dev server', err); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["vite/client", "../@types/globals.d.ts"] 5 | }, 6 | "include": ["./*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/unlisted-script-entrypoint.ts: -------------------------------------------------------------------------------- 1 | import definition from 'virtual:user-unlisted-script-entrypoint'; 2 | import { logger } from '../utils/internal/logger'; 3 | import { initPlugins } from 'virtual:wxt-plugins'; 4 | 5 | const result = (async () => { 6 | try { 7 | initPlugins(); 8 | return await definition.main(); 9 | } catch (err) { 10 | logger.error( 11 | `The unlisted script "${import.meta.env.ENTRYPOINT}" crashed on startup!`, 12 | err, 13 | ); 14 | throw err; 15 | } 16 | })(); 17 | 18 | // Return the main function's result to the background when executed via the 19 | // scripting API. Default export causes the IIFE to return a value. 20 | // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value 21 | // Tested on both Chrome and Firefox 22 | export default result; 23 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/utils/keep-service-worker-alive.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'wxt/browser'; 2 | 3 | /** 4 | * https://developer.chrome.com/blog/longer-esw-lifetimes/ 5 | */ 6 | export function keepServiceWorkerAlive() { 7 | setInterval(async () => { 8 | // Calling an async browser API resets the service worker's timeout 9 | await browser.runtime.getPlatformInfo(); 10 | }, 5e3); 11 | } 12 | -------------------------------------------------------------------------------- /packages/wxt/src/virtual/virtual-module-globals.d.ts: -------------------------------------------------------------------------------- 1 | // Types required to make the virtual modules happy. 2 | 3 | declare module 'virtual:user-background-entrypoint' { 4 | const definition: import('wxt').BackgroundDefinition; 5 | export default definition; 6 | } 7 | 8 | declare module 'virtual:user-content-script-isolated-world-entrypoint' { 9 | const definition: import('wxt').IsolatedWorldContentScriptDefinition; 10 | export default definition; 11 | } 12 | 13 | declare module 'virtual:user-content-script-main-world-entrypoint' { 14 | const definition: import('wxt').MainWorldContentScriptDefinition; 15 | export default definition; 16 | } 17 | 18 | declare module 'virtual:user-unlisted-script-entrypoint' { 19 | const definition: import('wxt').UnlistedScriptDefinition; 20 | export default definition; 21 | } 22 | 23 | declare module 'wxt/browser' { 24 | export { browser, Browser } from '@wxt-dev/browser'; 25 | } 26 | 27 | declare module 'virtual:wxt-plugins' { 28 | export function initPlugins(): void; 29 | } 30 | -------------------------------------------------------------------------------- /packages/wxt/src/vite-builder-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/wxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["vitest-plugin-random-seed/types"] 5 | }, 6 | "exclude": ["node_modules", "src/virtual", "e2e/dist", "dist"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/wxt/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "src/index.ts", 4 | "src/utils/app-config.ts", 5 | "src/utils/inject-script.ts", 6 | "src/utils/content-script-context.ts", 7 | "src/utils/content-script-ui/types.ts", 8 | "src/utils/content-script-ui/integrated.ts", 9 | "src/utils/content-script-ui/shadow-root.ts", 10 | "src/utils/content-script-ui/iframe.ts", 11 | "src/utils/define-app-config.ts", 12 | "src/utils/define-background.ts", 13 | "src/utils/define-content-script.ts", 14 | "src/utils/define-unlisted-script.ts", 15 | "src/utils/define-wxt-plugin.ts", 16 | "src/utils/match-patterns.ts", 17 | "src/utils/split-shadow-root-css.ts", 18 | "src/utils/storage.ts", 19 | "src/testing/index.ts", 20 | "src/modules.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/wxt/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import path from 'node:path'; 3 | import RandomSeed from 'vitest-plugin-random-seed'; 4 | 5 | export default defineConfig({ 6 | test: { 7 | mockReset: true, 8 | restoreMocks: true, 9 | testTimeout: 120e3, 10 | coverage: { 11 | include: ['src/**'], 12 | exclude: ['**/dist', '**/__tests__', 'src/utils/testing'], 13 | }, 14 | setupFiles: ['./vitest.setup.ts'], 15 | globalSetup: ['./vitest.globalSetup.ts'], 16 | }, 17 | server: { 18 | watch: { 19 | ignored: '**/dist/**', 20 | }, 21 | }, 22 | plugins: [RandomSeed()], 23 | resolve: { 24 | alias: { 25 | 'wxt/testing': path.resolve('src/testing'), 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/wxt/vitest.globalSetup.ts: -------------------------------------------------------------------------------- 1 | import { exists, rm } from 'fs-extra'; 2 | 3 | let setupHappened = false; 4 | 5 | export async function setup() { 6 | if (setupHappened) { 7 | throw new Error('setup called twice'); 8 | } 9 | 10 | setupHappened = true; 11 | 12 | // @ts-expect-error 13 | globalThis.__ENTRYPOINT__ = 'test'; 14 | 15 | const e2eDistPath = './e2e/dist/'; 16 | if (await exists(e2eDistPath)) { 17 | await rm(e2eDistPath, { recursive: true, force: true }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/wxt/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { fakeBrowser } from '@webext-core/fake-browser'; 2 | import { vi } from 'vitest'; 3 | 4 | vi.stubGlobal('chrome', fakeBrowser); 5 | vi.stubGlobal('browser', fakeBrowser); 6 | -------------------------------------------------------------------------------- /patches/markdown-it-footnote.md: -------------------------------------------------------------------------------- 1 | Removed sub-ids from rendered links. When you link to the same footnote multiple times, the link would look like `[3.2]` instead of just `[3]`. Didn't like how that looked, so this patch removes that function. 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - docs 3 | - packages/* 4 | onlyBuiltDependencies: 5 | - '@parcel/watcher' 6 | - dtrace-provider 7 | - esbuild 8 | - sharp 9 | - simple-git-hooks 10 | - spawn-sync 11 | -------------------------------------------------------------------------------- /scripts/benchmarks/browser-startup.patch: -------------------------------------------------------------------------------- 1 | diff --git a/packages/wxt/src/core/runners/web-ext.ts b/packages/wxt/src/core/runners/web-ext.ts 2 | index 09819c3..a0f0df2 100644 3 | --- a/packages/wxt/src/core/runners/web-ext.ts 4 | +++ b/packages/wxt/src/core/runners/web-ext.ts 5 | @@ -3,6 +3,8 @@ import { ExtensionRunner } from '../../types'; 6 | import { formatDuration } from '../utils/time'; 7 | import defu from 'defu'; 8 | import { wxt } from '../wxt'; 9 | +import fs from 'node:fs'; 10 | +import stream from 'node:stream/promises'; 11 | 12 | /** 13 | * Create an `ExtensionRunner` backed by `web-ext`. 14 | @@ -78,6 +80,12 @@ export function createWebExtRunner(): ExtensionRunner { 15 | 16 | const duration = Date.now() - startTime; 17 | wxt.logger.success(`Opened browser in ${formatDuration(duration)}`); 18 | + await runner.exit(); 19 | + const s = fs.createWriteStream('stats.txt', { flags: 'a' }); 20 | + s.write(duration + ' '); 21 | + s.end(); 22 | + await stream.finished(s); 23 | + process.exit(0); 24 | }, 25 | 26 | async closeBrowser() { 27 | -------------------------------------------------------------------------------- /scripts/benchmarks/browser-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # 5 | # Benchmark how long it takes for the browser to open in dev mode. 6 | # 7 | # To run: 8 | # cd 9 | # ./scripts/benchmarks/browser-startup.sh 10 | # 11 | # You can set N below to change the number of samples per git ref. 12 | # 13 | 14 | N=20 15 | 16 | function benchmark_ref() { 17 | # Prep 18 | git checkout $1 19 | pnpm buildc clean 20 | git apply scripts/benchmarks/browser-startup.patch 21 | pnpm i --ignore-scripts 22 | pnpm -r --filter wxt build 23 | echo -n "$1 " >> stats.txt 24 | 25 | # Run benchmark 26 | for i in $(seq $N); do 27 | pnpm wxt packages/wxt-demo 28 | done 29 | git checkout HEAD -- packages/wxt/src/core/runners/web-ext.ts pnpm-lock.yaml 30 | echo "" >> stats.txt 31 | } 32 | 33 | rm -f stats.txt 34 | 35 | benchmark_ref "HEAD" 36 | 37 | # Benchmark a commit: 38 | # benchmark_ref "3109bba" 39 | # 40 | # Benchmark a version: 41 | # benchmark_ref "v0.19.0" 42 | -------------------------------------------------------------------------------- /scripts/create-github-release.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createGithubRelease, 3 | loadChangelogConfig, 4 | parseChangelogMarkdown, 5 | } from 'changelogen'; 6 | import fs from 'fs-extra'; 7 | import { grabPackageDetails } from './git'; 8 | import consola from 'consola'; 9 | 10 | const pkg = process.argv[2]; 11 | if (pkg == null) { 12 | throw Error( 13 | 'Package name missing. Usage: tsx create-github-release.ts ', 14 | ); 15 | } 16 | 17 | const { pkgName, prevTag, currentVersion, changelogPath } = 18 | await grabPackageDetails(pkg); 19 | consola.info('Creating release for:', { pkg, pkgName, prevTag }); 20 | 21 | const { releases } = await fs 22 | .readFile(changelogPath, 'utf8') 23 | .then(parseChangelogMarkdown) 24 | .catch(() => ({ releases: [] })); 25 | 26 | const config = await loadChangelogConfig(process.cwd()); 27 | config.tokens.github = process.env.GITHUB_TOKEN; 28 | await createGithubRelease(config, { 29 | tag_name: prevTag, 30 | name: `${pkgName} v${currentVersion}`, 31 | body: releases[0].body, 32 | // @ts-expect-error: Not typed in changelogen, but present: https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release 33 | make_latest: String(pkg === 'wxt'), 34 | }); 35 | consola.success('Created release'); 36 | -------------------------------------------------------------------------------- /scripts/generate-readmes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Generate code in README.md and sync it with wxt/packages/README.md for NPM 5 | # 6 | 7 | pnpm dlx automd 8 | echo "" > packages/wxt/README.md 9 | cat README.md >> packages/wxt/README.md 10 | -------------------------------------------------------------------------------- /taze.config.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/antfu-collective/taze?tab=readme-ov-file#config-file 2 | export default { 3 | exclude: [ 4 | // Very touchy, don't change: 5 | 'typedoc', 6 | 'typedoc-plugin-markdown', 7 | 'typedoc-vitepress-theme', 8 | // Manually manage version so a single version is used: 9 | 'esbuild', 10 | // Maintained manually to match min-node version 11 | '@types/node', 12 | // License changed in newer versions 13 | 'ua-parser-js', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /templates/react/README.md: -------------------------------------------------------------------------------- 1 | # WXT + React 2 | 3 | This template should help get you started developing with React in WXT. 4 | -------------------------------------------------------------------------------- /templates/react/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /templates/react/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log('Hello background!', { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/react/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | main() { 4 | console.log('Hello content.'); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /templates/react/entrypoints/popup/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #54bc4ae0); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /templates/react/entrypoints/popup/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import reactLogo from '@/assets/react.svg'; 3 | import wxtLogo from '/wxt.svg'; 4 | import './App.css'; 5 | 6 | function App() { 7 | const [count, setCount] = useState(0); 8 | 9 | return ( 10 | <> 11 | 19 |

WXT + React

20 |
21 | 24 |

25 | Edit src/App.tsx and save to test HMR 26 |

27 |
28 |

29 | Click on the WXT and React logos to learn more 30 |

31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /templates/react/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/react/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './style.css'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /templates/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-react-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "dev:firefox": "wxt -b firefox", 10 | "build": "wxt build", 11 | "build:firefox": "wxt build -b firefox", 12 | "zip": "wxt zip", 13 | "zip:firefox": "wxt zip -b firefox", 14 | "compile": "tsc --noEmit", 15 | "postinstall": "wxt prepare" 16 | }, 17 | "dependencies": { 18 | "react": "^19.1.0", 19 | "react-dom": "^19.1.0" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^19.1.2", 23 | "@types/react-dom": "^19.1.3", 24 | "@wxt-dev/module-react": "^1.1.3", 25 | "typescript": "^5.8.3", 26 | "wxt": "^0.20.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /templates/react/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/react/public/icon/128.png -------------------------------------------------------------------------------- /templates/react/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/react/public/icon/16.png -------------------------------------------------------------------------------- /templates/react/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/react/public/icon/32.png -------------------------------------------------------------------------------- /templates/react/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/react/public/icon/48.png -------------------------------------------------------------------------------- /templates/react/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/react/public/icon/96.png -------------------------------------------------------------------------------- /templates/react/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /templates/react/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ['@wxt-dev/module-react'], 6 | }); 7 | -------------------------------------------------------------------------------- /templates/solid/README.md: -------------------------------------------------------------------------------- 1 | # WXT + SolidJS 2 | 3 | This template should help get you started developing with SolidJS in WXT. 4 | -------------------------------------------------------------------------------- /templates/solid/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log('Hello background!', { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | main() { 4 | console.log('Hello content.'); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/popup/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #54bc4ae0); 16 | } 17 | .logo.solid:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | .card { 22 | padding: 2em; 23 | } 24 | 25 | .read-the-docs { 26 | color: #888; 27 | } 28 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/popup/App.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal } from 'solid-js'; 2 | import solidLogo from '@/assets/solid.svg'; 3 | import wxtLogo from '/wxt.svg'; 4 | import './App.css'; 5 | 6 | function App() { 7 | const [count, setCount] = createSignal(0); 8 | 9 | return ( 10 | <> 11 | 19 |

WXT + Solid

20 |
21 | 24 |

25 | Edit popup/App.tsx and save to test HMR 26 |

27 |
28 |

29 | Click on the WXT and Solid logos to learn more 30 |

31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/solid/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | 3 | import './style.css'; 4 | import App from './App'; 5 | 6 | render(() => , document.getElementById('root')!); 7 | -------------------------------------------------------------------------------- /templates/solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-solid-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "dev:firefox": "wxt -b firefox", 10 | "build": "wxt build", 11 | "build:firefox": "wxt build -b firefox", 12 | "zip": "wxt zip", 13 | "zip:firefox": "wxt zip -b firefox", 14 | "compile": "tsc --noEmit", 15 | "postinstall": "wxt prepare" 16 | }, 17 | "dependencies": { 18 | "solid-js": "^1.9.6" 19 | }, 20 | "devDependencies": { 21 | "@wxt-dev/module-solid": "^1.1.3", 22 | "typescript": "^5.8.3", 23 | "wxt": "^0.20.6" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/solid/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/solid/public/icon/128.png -------------------------------------------------------------------------------- /templates/solid/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/solid/public/icon/16.png -------------------------------------------------------------------------------- /templates/solid/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/solid/public/icon/32.png -------------------------------------------------------------------------------- /templates/solid/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/solid/public/icon/48.png -------------------------------------------------------------------------------- /templates/solid/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/solid/public/icon/96.png -------------------------------------------------------------------------------- /templates/solid/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /templates/solid/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ['@wxt-dev/module-solid'], 6 | }); 7 | -------------------------------------------------------------------------------- /templates/svelte/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /templates/svelte/README.md: -------------------------------------------------------------------------------- 1 | # WXT + Svelte 2 | 3 | This template should help get you started developing with Svelte in WXT. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | -------------------------------------------------------------------------------- /templates/svelte/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /templates/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-svelte-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "dev:firefox": "wxt -b firefox", 10 | "build": "wxt build", 11 | "build:firefox": "wxt build -b firefox", 12 | "zip": "wxt zip", 13 | "zip:firefox": "wxt zip -b firefox", 14 | "check": "svelte-check --tsconfig ./tsconfig.json", 15 | "postinstall": "wxt prepare" 16 | }, 17 | "devDependencies": { 18 | "@tsconfig/svelte": "^5.0.4", 19 | "@wxt-dev/module-svelte": "^2.0.3", 20 | "svelte": "^5.28.2", 21 | "svelte-check": "^4.1.6", 22 | "tslib": "^2.8.1", 23 | "typescript": "^5.8.3", 24 | "wxt": "^0.20.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/svelte/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/svelte/public/icon/128.png -------------------------------------------------------------------------------- /templates/svelte/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/svelte/public/icon/16.png -------------------------------------------------------------------------------- /templates/svelte/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/svelte/public/icon/32.png -------------------------------------------------------------------------------- /templates/svelte/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/svelte/public/icon/48.png -------------------------------------------------------------------------------- /templates/svelte/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/svelte/public/icon/96.png -------------------------------------------------------------------------------- /templates/svelte/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/svelte/src/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log('Hello background!', { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/svelte/src/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | main() { 4 | console.log('Hello content.'); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /templates/svelte/src/entrypoints/popup/App.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 15 |

WXT + Svelte

16 | 17 |
18 | 19 |
20 | 21 |

22 | Click on the WXT and Svelte logos to learn more 23 |

24 |
25 | 26 | 43 | -------------------------------------------------------------------------------- /templates/svelte/src/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/svelte/src/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte'; 2 | import App from './App.svelte'; 3 | import './app.css'; 4 | 5 | const app = mount(App, { 6 | target: document.getElementById('app')!, 7 | }); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /templates/svelte/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /templates/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "useDefineForClassFields": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /templates/svelte/wxt-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /templates/svelte/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | srcDir: 'src', 6 | modules: ['@wxt-dev/module-svelte'], 7 | }); 8 | -------------------------------------------------------------------------------- /templates/vanilla/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /templates/vanilla/components/counter.ts: -------------------------------------------------------------------------------- 1 | export function setupCounter(element: HTMLButtonElement) { 2 | let counter = 0; 3 | const setCounter = (count: number) => { 4 | counter = count; 5 | element.innerHTML = `count is ${counter}`; 6 | }; 7 | element.addEventListener('click', () => setCounter(counter + 1)); 8 | setCounter(0); 9 | } 10 | -------------------------------------------------------------------------------- /templates/vanilla/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log('Hello background!', { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/vanilla/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | main() { 4 | console.log('Hello content.'); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /templates/vanilla/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/vanilla/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | import typescriptLogo from '@/assets/typescript.svg'; 3 | import viteLogo from '/wxt.svg'; 4 | import { setupCounter } from '@/components/counter'; 5 | 6 | document.querySelector('#app')!.innerHTML = ` 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |

WXT + TypeScript

15 |
16 | 17 |
18 |

19 | Click on the WXT and TypeScript logos to learn more 20 |

21 |
22 | `; 23 | 24 | setupCounter(document.querySelector('#counter')!); 25 | -------------------------------------------------------------------------------- /templates/vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "dev:firefox": "wxt -b firefox", 10 | "build": "wxt build", 11 | "build:firefox": "wxt build -b firefox", 12 | "zip": "wxt zip", 13 | "zip:firefox": "wxt zip -b firefox", 14 | "compile": "tsc --noEmit", 15 | "postinstall": "wxt prepare" 16 | }, 17 | "devDependencies": { 18 | "typescript": "^5.8.3", 19 | "wxt": "^0.20.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/vanilla/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vanilla/public/icon/128.png -------------------------------------------------------------------------------- /templates/vanilla/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vanilla/public/icon/16.png -------------------------------------------------------------------------------- /templates/vanilla/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vanilla/public/icon/32.png -------------------------------------------------------------------------------- /templates/vanilla/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vanilla/public/icon/48.png -------------------------------------------------------------------------------- /templates/vanilla/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vanilla/public/icon/96.png -------------------------------------------------------------------------------- /templates/vanilla/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/vanilla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /templates/vanilla/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /templates/vue/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /templates/vue/README.md: -------------------------------------------------------------------------------- 1 | # WXT + Vue 3 2 | 3 | This template should help get you started developing with Vue 3 in WXT. 4 | 5 | ## Recommended IDE Setup 6 | 7 | - [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar). 8 | -------------------------------------------------------------------------------- /templates/vue/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /templates/vue/assets/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/vue/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /templates/vue/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log('Hello background!', { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/vue/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ['*://*.google.com/*'], 3 | main() { 4 | console.log('Hello content.'); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /templates/vue/entrypoints/popup/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 31 | -------------------------------------------------------------------------------- /templates/vue/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/vue/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import './style.css'; 3 | import App from './App.vue'; 4 | 5 | createApp(App).mount('#app'); 6 | -------------------------------------------------------------------------------- /templates/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-vue-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "dev:firefox": "wxt -b firefox", 10 | "build": "wxt build", 11 | "build:firefox": "wxt build -b firefox", 12 | "zip": "wxt zip", 13 | "zip:firefox": "wxt zip -b firefox", 14 | "compile": "vue-tsc --noEmit", 15 | "postinstall": "wxt prepare" 16 | }, 17 | "dependencies": { 18 | "vue": "^3.5.13" 19 | }, 20 | "devDependencies": { 21 | "@wxt-dev/module-vue": "^1.0.2", 22 | "typescript": "5.6.3", 23 | "vue-tsc": "^2.2.10", 24 | "wxt": "^0.20.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/vue/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vue/public/icon/128.png -------------------------------------------------------------------------------- /templates/vue/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vue/public/icon/16.png -------------------------------------------------------------------------------- /templates/vue/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vue/public/icon/32.png -------------------------------------------------------------------------------- /templates/vue/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vue/public/icon/48.png -------------------------------------------------------------------------------- /templates/vue/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/wxt/f02400a1fb58b7c9cd5354c965aeb9a3da2f7abe/templates/vue/public/icon/96.png -------------------------------------------------------------------------------- /templates/vue/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /templates/vue/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ['@wxt-dev/module-vue'], 6 | }); 7 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | 10 | /* Type Checking */ 11 | "strict": true, 12 | "lib": ["DOM", "WebWorker", "ESNext"], 13 | 14 | /* Completeness */ 15 | "skipLibCheck": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "exclude": ["packages", "templates", "node_modules"] 4 | } 5 | --------------------------------------------------------------------------------