Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34dd2c1487 | |||
| a5667f85d0 | |||
| ec3dd52f83 | |||
| 87ebde3abb | |||
| dc5b55bffb | |||
| df20993917 | |||
| caa0fb31e6 | |||
| 95da4fb054 | |||
| a917f32a0e | |||
| 6ca376749c | |||
| 792e3032b9 | |||
| 38a5a2c440 | |||
| 21106ce188 | |||
| ec7263e7de | |||
| e7c3fc2a5e | |||
| f808cb7526 | |||
| c4795e7479 | |||
| b44a96ab81 | |||
| 3bfdc5c755 | |||
| acb75543ff | |||
| 25b45b9c09 | |||
| efd5c40043 | |||
| e47519fb76 | |||
| 171e32c272 | |||
| 54e5ed0bb4 | |||
| a94d673f31 | |||
| b7f7222c26 | |||
| aa5a9d4fa9 | |||
| 77da2d6ade | |||
| 3119c04e10 | |||
| c09994263e | |||
| 2a4fa9ae5c | |||
| c833d3b15b | |||
| d6e095bfce | |||
| 825160a3cc | |||
| e29ca33f82 | |||
| ca5a52c6f1 | |||
| 982c69f8ae | |||
| 6fc7d9254a | |||
| 2e0e104d48 | |||
| 02430fd7b8 | |||
| 2e6d35d193 | |||
| 7cf412ac1f | |||
| 9398cbb596 | |||
| ca9b96142e | |||
| db57c8e079 | |||
| 7e37e11caf | |||
| bb90029c54 | |||
| d3b14afbce | |||
| ba2197a609 | |||
| 4c558d3eb6 | |||
| dcd175444a | |||
| 16fc58404a | |||
| 28c7e7d9ed | |||
| 5a3b42a4b0 | |||
| c9c5d721ec | |||
| 1556b9ad73 | |||
| ca15b1f321 | |||
| 448436cb08 | |||
| c9ddc3d0f2 | |||
| 6b72f883b6 | |||
| cd3446bd7f | |||
| 829b371bba | |||
| 36b399dd49 | |||
| 08f32aa685 | |||
| 05db264e02 | |||
| de5a01834c | |||
| 1d260dbacb | |||
| 53c63b4556 | |||
| 816878bc15 | |||
| e087a0d6ef | |||
| 56b71491a6 | |||
| 121b521157 | |||
| 16c2e3218b | |||
| d9cba55d34 | |||
| 832cd5d640 | |||
| bb95e08d71 | |||
| 35269dad9e | |||
| 64d1498460 | |||
| 0d540a6df9 | |||
| b47c150daa | |||
| 38345481df | |||
| da92449e0f | |||
| 23b3980645 | |||
| 81b4b629c9 | |||
| 4f7e9cb050 | |||
| b226d9b947 | |||
| aabba41e86 | |||
| f19e6908b0 | |||
| 5f651cc3fe | |||
| 8a6acbaaf2 | |||
| 13ab8e4d01 | |||
| bd039dcbab | |||
| 9cd4e8342b | |||
| c9633cefec | |||
| 1dd1370ed7 | |||
| 9ff90d0acf | |||
| b754435589 | |||
| 195d2cceaf | |||
| 420d78b638 | |||
| b35e499355 | |||
| 41c3d6d015 | |||
| 64610bad41 | |||
| 45b7d7c67f | |||
| 0bb2746869 | |||
| f9b0aa45f8 | |||
| 6f6ce33bbd | |||
| e1c6020997 | |||
| 012bd7e67f | |||
| 35778f70cb | |||
| cb4f9aa752 | |||
| f80fb42483 | |||
| 02c79ca244 | |||
| 50e8c86ce9 | |||
| 6fbfc15be7 | |||
| c793d3cf6b | |||
| b5f4d8cc1b | |||
| 77e6d1f5a4 |
@@ -1,2 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
@@ -25,11 +25,14 @@ jobs:
|
||||
pnpx changelogen@latest --release
|
||||
git push
|
||||
git push --tags
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm publish
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: GitHub Release
|
||||
run: pnpx changelogen@latest gh release --token ${{ secrets.GITHUB_TOKEN }}
|
||||
working-directory: packages/wxt
|
||||
|
||||
@@ -20,3 +20,4 @@ jobs:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- run: pnpm sync-releases all --token ${{ secrets.GITHUB_TOKEN }}
|
||||
working-directory: packages/wxt
|
||||
|
||||
@@ -7,42 +7,27 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
formatting:
|
||||
checks:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm format:check
|
||||
lint:
|
||||
- run: pnpm check
|
||||
build-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm lint
|
||||
type-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm compile
|
||||
validate-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm tsc --noEmit
|
||||
working-directory: demo
|
||||
- run: pnpm build:all
|
||||
working-directory: demo
|
||||
working-directory: packages/wxt-demo
|
||||
- run: pnpm wxt zip
|
||||
working-directory: demo
|
||||
- run: pnpm vitest run
|
||||
working-directory: demo
|
||||
working-directory: packages/wxt-demo
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v4
|
||||
@@ -68,9 +53,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
working-directory: packages/wxt
|
||||
- run: npm i
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: npm i -D ../../wxt-*.tgz
|
||||
- run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm compile
|
||||
if: matrix.template != 'svelte'
|
||||
|
||||
@@ -29,20 +29,22 @@
|
||||
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
|
||||
•
|
||||
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
|
||||
•
|
||||
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/07359e53-f491-43b6-8e8f-fae94aec8063
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
```sh
|
||||
pnpx wxt@latest init <project-name>
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"description": "Demo extension for WXT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm -w build && wxt",
|
||||
"build": "pnpm -w build && wxt build",
|
||||
"build:all": "pnpm -w build && run-s -s 'build:all:*'",
|
||||
"build:all:chrome-mv3": "wxt build",
|
||||
"build:all:chrome-mv2": "wxt build --mv2",
|
||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||
"test": "pnpm -w build && vitest",
|
||||
"zip": "pnpm -w build && wxt zip",
|
||||
"compile": "pnpm -w build && tsc --noEmit",
|
||||
"postinstall": "pnpm -w build && wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.34",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"sass": "^1.69.5",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,14 @@ const chromeExtensionIds = [
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
||||
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
||||
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
||||
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
||||
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -19,7 +19,7 @@ const title = 'Next-gen Web Extension Framework';
|
||||
const titleSuffix = ' – WXT';
|
||||
|
||||
const description =
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilties for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
const ogTitle = `${title}${titleSuffix}`;
|
||||
const ogUrl = 'https://wxt.dev';
|
||||
const ogImage = 'https://wxt.dev/social-preview.png';
|
||||
@@ -95,6 +95,7 @@ export default defineConfig({
|
||||
{ text: 'Vite', link: '/guide/vite.md' },
|
||||
{ text: 'Remote Code', link: '/guide/remote-code.md' },
|
||||
{ text: 'Publishing', link: '/guide/publishing.md' },
|
||||
{ text: 'Handling Updates', link: '/guide/handling-updates.md' },
|
||||
{ text: 'Development', link: '/guide/development.md' },
|
||||
{ text: 'Testing', link: '/guide/testing.md' },
|
||||
],
|
||||
@@ -124,7 +125,7 @@ export default defineConfig({
|
||||
{ text: 'Options', link: '/entrypoints/options.md' },
|
||||
{ text: 'Popup', link: '/entrypoints/popup.md' },
|
||||
{ text: 'Sandbox', link: '/entrypoints/sandbox.md' },
|
||||
{ text: 'Sidepanel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Side Panel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Unlisted Pages', link: '/entrypoints/unlisted-pages.md' },
|
||||
{
|
||||
text: 'Unlisted Scripts',
|
||||
@@ -146,6 +147,9 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' }],
|
||||
socialLinks: [
|
||||
{ icon: 'discord', link: 'https://discord.gg/ZFsZqGery9' },
|
||||
{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { execaCommand } from 'execa';
|
||||
|
||||
let hasGenerated = false;
|
||||
|
||||
const cliDir = resolve('src/cli/commands');
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
const cliTemplatePath = resolve('docs/.vitepress/plugins/cli.tpl.md');
|
||||
const cliPath = resolve('docs/api/cli.md');
|
||||
@@ -23,7 +23,9 @@ export function generateCliDocs() {
|
||||
const generateDocs = async () => {
|
||||
consola.info(`Generating ${relative(process.cwd(), cliPath)}`);
|
||||
try {
|
||||
const res = await execaCommand('pnpm -s wxt --help');
|
||||
const res = await execaCommand('pnpm -s wxt --help', {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
const dev = splitInfo(res.stdout);
|
||||
const lines: Array<string | string[]> = [
|
||||
`## \`wxt\``,
|
||||
@@ -32,14 +34,22 @@ export function generateCliDocs() {
|
||||
'```',
|
||||
];
|
||||
|
||||
const commands = await Promise.all(
|
||||
const commands = await Promise.allSettled(
|
||||
extractCommands(dev.info).map(async (command) => {
|
||||
const res = await execaCommand(`pnpm -s wxt ${command} --help`);
|
||||
const res = await execaCommand(`pnpm -s wxt ${command} --help`, {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
const { rest: docs } = splitInfo(res.stdout);
|
||||
return [`## \`wxt ${command}\``, '```sh', docs, '```'];
|
||||
}),
|
||||
);
|
||||
lines.push(...commands);
|
||||
lines.push(
|
||||
...commands
|
||||
.filter((res) => res.status === 'fulfilled')
|
||||
.map(
|
||||
(res) => (res as unknown as PromiseFulfilledResult<string>).value,
|
||||
),
|
||||
);
|
||||
|
||||
const text = await format(
|
||||
PREFACE +
|
||||
|
||||
@@ -40,7 +40,7 @@ export default defineContentScript({
|
||||
// Configure how/when content script will be registered
|
||||
registration: undefined | "manifest" | "runtime",
|
||||
|
||||
main(ctx) {
|
||||
main(ctx: ContentScriptContext) {
|
||||
// Executed when content script is loaded
|
||||
},
|
||||
});
|
||||
@@ -50,43 +50,6 @@ export default defineContentScript({
|
||||
|
||||
When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single `content_script` item in the manifest.
|
||||
|
||||
## Context
|
||||
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a extension API.
|
||||
|
||||
WXT provides a utility for managing this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of your content script.
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
// ...
|
||||
main(ctx: ContentScriptContext) {
|
||||
// Add custom listeners for stopping work
|
||||
ctx.onInvalidated(() => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// Stop fetch requests
|
||||
fetch('...url', { signal: ctx.signal });
|
||||
|
||||
// Timeout utilities
|
||||
ctx.setTimeout(() => {
|
||||
// ...
|
||||
}, 5e3);
|
||||
ctx.setInterval(() => {
|
||||
// ...
|
||||
}, 60e3);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The class extends [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and provides other utilities for stopping a content script's logic once it becomes invalidated.
|
||||
|
||||
:::tip
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from the console in production.
|
||||
:::
|
||||
|
||||
## CSS
|
||||
|
||||
To include CSS with your content script, import the CSS file at the top of your entrypoint.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
HTML pages that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
If you plan on using the page in an iframe, don't forget to add the page to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
### Examples
|
||||
|
||||
- Onboarding
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
TypeScript files that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
You are responsible for loading/running these scripts where needed.
|
||||
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
## Filenames
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Files in the assets directory will be processed by Vite. They are imported in yo
|
||||
<!-- ... -->
|
||||
</head>
|
||||
<body>
|
||||
<img src="~/illustration.svg" />
|
||||
<img src="~/assets/illustration.svg" />
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
# Compare
|
||||
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework), another web extension framework.
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another web extension framework) and [CRXJS](https://crxjs.dev/vite-plugin) (the most popular bundler plugin).
|
||||
|
||||
## Overview
|
||||
|
||||
| Features | WXT | Plasmo |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ |
|
||||
| MV2 Support | ✅ | ✅ |
|
||||
| MV3 Support | ✅ | ✅ |
|
||||
| Create Extension ZIPs | ✅ | ✅ |
|
||||
| Create Firefox Sources ZIP | ✅ | ❌ |
|
||||
| First-class TypeScript support | ✅ | ✅ |
|
||||
| File based entrypoint discovery | ✅ | ✅ |
|
||||
| Inline entrypoint config | ✅ | ✅ |
|
||||
| Auto-imports | ✅ | ❌ |
|
||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte |
|
||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` |
|
||||
| Automated publishing | 🟡 Coming soon | ✅ |
|
||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ |
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
|
||||
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| Create Extension ZIPs | ✅ | ✅ | ❌ |
|
||||
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
|
||||
| First-class TypeScript support | ✅ | ✅ | ✅ |
|
||||
| Entrypoint discovery | File based | File based | ❌ |
|
||||
| Inline entrypoint config | ✅ | ✅ | Manifest based |
|
||||
| Auto-imports | ✅ | ❌ | ❌ |
|
||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
|
||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
|
||||
| Automated publishing | ✅ | ✅ | ❌ |
|
||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
|
||||
| <strong style="opacity: 50%">Dev Mode</strong> | | |
|
||||
| `.env` Files | ✅ | ✅ |
|
||||
| Opens browser and install extension | ✅ | ❌ |
|
||||
| HMR for UIs | ✅ | 🟡 React only |
|
||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension |
|
||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension |
|
||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | |
|
||||
| Storage | ✅ | ✅ |
|
||||
| Messaging | 🟡 Coming soon | ✅ |
|
||||
| Content Script UI | ✅ | ✅ |
|
||||
| `.env` Files | ✅ | ✅ | ✅ |
|
||||
| Opens browser and install extension | ✅ | ❌ | ❌ |
|
||||
| HMR for UIs | ✅ | 🟡 React only | ✅ |
|
||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
|
||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
|
||||
| Storage | ✅ | ✅ | ❌ |
|
||||
| Messaging | ❌ | ✅ | ❌ |
|
||||
| Content Script UI | ✅ | ✅ | ❌ |
|
||||
|
||||
## Dev Mode
|
||||
## Why use WXT?
|
||||
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions. There are two things WXT does differently:
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions, while not sacrificing support. There are two things WXT does differently:
|
||||
|
||||
1. Automatically opens a browser with the extension installed when starting development
|
||||
2. Reload each part of the extension individually rather than reloading the entire extension
|
||||
|
||||
@@ -89,7 +89,7 @@ import { defineRunnerConfig } from 'wxt';
|
||||
export default defineRunnerConfig({
|
||||
startUrls: ['https://google.com', 'https://duckduckgo.com'],
|
||||
chromiumProfile: '/path/to/profile/to/use',
|
||||
chromiumArgs: ['--window-size=400x300'],
|
||||
chromiumArgs: ['--window-size=400,300'],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -99,7 +99,17 @@ For a full list of options, see the [API Reference](/api/wxt/interfaces/Extensio
|
||||
|
||||
Normally, to manually reload an extension, you have to visit `chrome://extensions` and click the reload button for your extension.
|
||||
|
||||
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut, `ctrl+E` for Windows/Linux and `cmd+E` for Mac, that reloads the extension when pressed, without visiting `chrome://extensions`.
|
||||
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut `Alt+R`, that reloads the extension when pressed, without visiting `chrome://extensions`. This can also be customized or disabled:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
dev: {
|
||||
reloadCommand: 'Alt+T', // false, to disable
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
|
||||
|
||||
@@ -89,7 +89,7 @@ For a full list of entrypoints and each of their options, see the [`/entrypoints
|
||||
|
||||
### Side Effects
|
||||
|
||||
You cannot use imported variables outside the `main` function is JS entrypoints. This includes options, as shown below:
|
||||
You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
@@ -114,7 +114,7 @@ WXT 0.14.1
|
||||
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects
|
||||
```
|
||||
|
||||
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `run_at`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
|
||||
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
|
||||
|
||||
:::details Why?
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ Then we can use `browser.storage` to save the install date to local storage.
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onInstall.addEventListener(({ reason }) => {
|
||||
browser.runtime.onInstalled.addListener(({ reason }) => {
|
||||
if (reason === 'install') {
|
||||
browser.storage.local.setItem({ installDate: Date.now() });
|
||||
browser.storage.local.set({ installDate: Date.now() });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Handling Extension Updates
|
||||
|
||||
When releasing an update to your extension, there's a couple of things you need to keep in mind:
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Content Script Cleanup
|
||||
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a web extension API (ie, the `browser` or `chrome` globals).
|
||||
|
||||
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of each content script.
|
||||
|
||||
When your extension updates or reloads, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
main(ctx) {
|
||||
ctx.onInvalidated(() => {
|
||||
// Do something
|
||||
});
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
The `ctx` also provides other convenient APIs for stopping your content script without manually calling `onInvalidated` to add a listener:
|
||||
|
||||
1. Setting timers:
|
||||
```ts
|
||||
ctx.setTimeout(() => { ... }, ...);
|
||||
ctx.setInterval(() => { ... }, ...);
|
||||
ctx.requestAnimationFrame(() => { ... });
|
||||
```
|
||||
1. Adding DOM events:
|
||||
```ts
|
||||
ctx.addEventListener(window, "mousemove", (event) => { ... });
|
||||
```
|
||||
1. Implements [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling standard APIs:
|
||||
```ts
|
||||
fetch('...', {
|
||||
signal: ctx.signal,
|
||||
});
|
||||
```
|
||||
|
||||
Other WXT APIs require a `ctx` object so they can clean themselves up. For example, [`createIntegratedUi`](/guide/content-script-ui#integrated), [`createShadowRootUi`](/guide/content-script-ui#shadow-root), and [`createIframeUi`](/guide/content-script-ui#iframe) automatically unmount and stop a UI when the script is invalidated.
|
||||
|
||||
:::warning
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from being logged to the console in production.
|
||||
:::
|
||||
|
||||
## Testing Permission Changes
|
||||
|
||||
When `permissions`/`host_permissions` change during an update, depending on what exactly changed, Chrome will disable your extension until the user accepts the new permissions.
|
||||
|
||||
It is possible to test this before you release an update, but it's not a simple process:
|
||||
|
||||
1. Get 2 ZIPs of your extension, both generated by `wxt zip`. The first contains a previous version of your extension, the second contains the latest version. Make sure the second ZIP's version is higher than the first's.
|
||||
2. Unzip the two ZIP files somewhere next to each other that's easy to locate.
|
||||
3. In Chrome, open `chrome://extensions` and make sure developer mode is enabled
|
||||
4. Pack the first extension into a CRX, generating a new private key:
|
||||
1. Click "Pack Extension" in the top left
|
||||
2. For "Extension root directory", enter the path to the first extracted zip directory. The directory should contain a `manifest.json` file
|
||||
3. Leave "Private key file" blank
|
||||
4. Click "Pack Extension". This will generate a `.crx` and `.pem` file
|
||||
5. Pack the second extension into a CRX, reusing the private key generated by the previous step
|
||||
1. Click "Pack Extension" in the top left
|
||||
2. For "Extension root directory", enter the path to the second extracted zip directory.
|
||||
3. For "Private key file", enter the path to the generated `.pem` private key file
|
||||
4. Click "Pack Extension". This will generate a second `.crx` file.
|
||||
6. Install the first CRX file by dragging and dropping it onto the `chrome://extensions` page
|
||||
7. Install the second CRX file by dragging and dropping it onthe the `chrome://extensions` page
|
||||
|
||||
If a new permission must be accepted, you'll be prompted to accept it after dropping the second CRX file onto the page.
|
||||
|
||||
:::Info Note
|
||||
Note: Chrome no longer allows self-signed CRX extensions to run, but that's OK for this test case. We're still prompted to accept new permissions, even if we can't interact with the installed extension.
|
||||
|
||||
To validate this, you can create a third ZIP file with a rare permission like `geolocation` in the manifest, that's guarenteed to reprompt permissions when added.
|
||||
:::
|
||||
|
||||
## Update Event
|
||||
|
||||
You can setup a callback that runs after your extension updates like so:
|
||||
|
||||
```ts
|
||||
browser.runtime.onInstalled.addEventListener(({ reason }) => {
|
||||
if (reason === 'update') {
|
||||
// Do something
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions`
|
||||
2. Build two ZIPs with the same runtime ID and actually update the extension
|
||||
|
||||
The first approach is very straightforward. The second is more complicated...
|
||||
|
||||
Here are the steps:
|
||||
|
||||
So the steps:
|
||||
|
||||
1. Checkout an old commit.
|
||||
2. [Add a `key`](https://developer.chrome.com/docs/extensions/reference/manifest/key#keep-consistent-id) to the `manifest` in your `wxt.config.ts`.
|
||||
3. Run `wxt zip` to create the first ZIP.
|
||||
4. Stash or reset changes and checkout your latest code.
|
||||
5. Add back the same `key` to your manifest.
|
||||
6. Make sure the extension's version is higher than the first zip. It can be any version that's higher, since you won't be releasing this version.
|
||||
7. Run `wxt zip` to create the second ZIP.
|
||||
8. In a fresh chrome profile, go to `chrome://extensions`, enable dev mode, and drag and drop the first zip onto the page to install it.
|
||||
9. In the extension, play around and setup your test case.
|
||||
10. Back on `chrome://extensions`, drag and drop your second zip onto the page.
|
||||
|
||||
If you setup the `key` correctly, it will cause the extension to act like it was updated instead of installing a second version of your extension.
|
||||
@@ -7,13 +7,17 @@ Bootstrap a new project, start from scratch, or [migrate an existing project](/g
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpx wxt@latest init <project-name>
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bunx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
@@ -55,6 +59,12 @@ cd project-name
|
||||
yarn init
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
bun init
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Then install `wxt`:
|
||||
@@ -73,6 +83,10 @@ npm i --save-dev wxt
|
||||
yarn add --dev wxt
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add --dev wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Add your first entrypoint:
|
||||
@@ -102,7 +116,7 @@ Finally, add scripts to your `package.json`:
|
||||
|
||||
## Migrate an Existing Project
|
||||
|
||||
Before starting the migration, it is recommended to run `pnpx wxt@latest init` to see what a basic project looks like. Once you have an understanding of how WXT projects are structured, you're ready to convert the project, using the initialized project as a reference.
|
||||
Before starting the migration, it is recommended to run `pnpm dlx wxt@latest init` to see what a basic project looks like. Once you have an understanding of how WXT projects are structured, you're ready to convert the project, using the initialized project as a reference.
|
||||
|
||||
Migrating a project to WXT comes down to a few steps:
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Manifest.json
|
||||
|
||||
The manifest.json is generated at build-time based on files in the `entrypoints/` directory and `wxt.config.ts`.
|
||||
@@ -7,7 +11,7 @@ The manifest.json is generated at build-time based on files in the `entrypoints/
|
||||
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
|
||||
|
||||
```ts
|
||||
// wxt.config.tsentrypoint of your extension
|
||||
// wxt.config.ts entrypoint of your extension
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -20,11 +24,11 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## `name`
|
||||
### `name`
|
||||
|
||||
If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your package.json's `name` property.
|
||||
|
||||
## `version` and `version_name`
|
||||
### `version` and `version_name`
|
||||
|
||||
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) properties are based on the `version` field listed in your `package.json` or `wxt.config.ts`.
|
||||
|
||||
@@ -33,7 +37,7 @@ The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/
|
||||
|
||||
If a version is not found, a warning is logged and the version defaults to `"0.0.0"`.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```json
|
||||
// package.json
|
||||
@@ -50,7 +54,7 @@ If a version is not found, a warning is logged and the version defaults to `"0.0
|
||||
}
|
||||
```
|
||||
|
||||
## `icons`
|
||||
### `icons`
|
||||
|
||||
By default, WXT will discover icons in your [`public` directory](/guide/assets#public-directory) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
|
||||
|
||||
@@ -65,7 +69,7 @@ public/
|
||||
|
||||
Icon files need to match the following regex to be automatically included in the manifest. Most design software can output icons in one of these formats
|
||||
|
||||
<<< @/../src/core/utils/manifest.ts#snippet
|
||||
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
|
||||
|
||||
If you prefer to use filenames in a different format, you can add the icons manually in your `wxt.config.ts` file:
|
||||
|
||||
@@ -83,7 +87,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## `permissions`
|
||||
### `permissions`
|
||||
|
||||
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
|
||||
|
||||
@@ -125,3 +129,109 @@ export default defineConfig({
|
||||
See the official localization examples for more details:
|
||||
|
||||
<ExampleList tag="i18n" />
|
||||
|
||||
## Per-Manifest Version Config
|
||||
|
||||
WXT applies several transformations to your manifest to simplify managing both MV2 and MV3 keys in your `wxt.config.ts` file:
|
||||
|
||||
1. Top level MV2-only or MV3-only keys are stripped from the final manifest when targeting the other manifest version
|
||||
2. Some keys, are automatically converted between versions when possible:
|
||||
- Define `web_accessible_resources` in it's MV3 style and it will be converted to the MV2 style automatically
|
||||
- `action` will automatically be converted to `browser_action` for MV3. To use `page_action` instead, add both `action` and `page_action` entries to your manifest
|
||||
|
||||
For example, a `wxt.config.ts` file that looks like this:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
mainfest: {
|
||||
action: {
|
||||
default_title: 'Some Title',
|
||||
},
|
||||
web_accessible_resources: [
|
||||
{
|
||||
matches: ['*://*.google.com/*'],
|
||||
resources: ['icon/*.png'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Will be output differently for each manifest version:
|
||||
|
||||
:::code-group
|
||||
|
||||
```json [MV2]
|
||||
{
|
||||
"manifest_version": 2,
|
||||
// ...
|
||||
"browser_action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": ["icon/*.png"]
|
||||
}
|
||||
```
|
||||
|
||||
```json [MV3]
|
||||
{
|
||||
"manifest_version": 3,
|
||||
// ...
|
||||
"action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"matches": ["*://*.google.com/*"],
|
||||
"resources": ["icon/*.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
If this isn't enough control for your use-case, remember you can use a function for the `manifest` key and generate it however you'd like, or you can use the `build:manifestGenerated` hook to apply additional transformations.
|
||||
:::
|
||||
|
||||
## Per-Browser Configuration
|
||||
|
||||
The `manifest` field can be a function. If you are building and extension for multiple browsers, and need to modify the manifest per browser, using a function instead of an object is very useful.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser, manifestVersion, mode, command }) => {
|
||||
return {
|
||||
// Your manifest
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
The first argument is of type `ConfigEnv`. See the [API reference](/api/wxt/interfaces/ConfigEnv) for info about each property.
|
||||
:::
|
||||
|
||||
For example, say you use OAuth, and you need to provide a different `oauth.client_id` for each browser:
|
||||
|
||||
```ts
|
||||
const clientIds = {
|
||||
chrome: '<your-chrome-client-id>',
|
||||
edge: '<your-edge-client-id>',
|
||||
firefox: '<your-firefox-client-id>',
|
||||
opera: '<your-opera-client-id>',
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
oauth: {
|
||||
client_id: clientIds[browser],
|
||||
scopes: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ Always start by generating a new vanilla project and merging it into your projec
|
||||
|
||||
```sh
|
||||
cd path/to/your/project
|
||||
pnpx wxt@latest init example-wxt --template vanilla
|
||||
pnpm dlx wxt@latest init example-wxt --template vanilla
|
||||
```
|
||||
|
||||
In general, you'll need to:
|
||||
|
||||
@@ -64,12 +64,59 @@ See the [Firefox Addon Store](#firefox-addon-store) section for more details abo
|
||||
|
||||
## GitHub Action
|
||||
|
||||
Here's an example of a GitHub Action to automate submitting new versions of your extension for review. Ensure that you've added all required secrets used in the workflow to the repo's settings.
|
||||
Here's an example of a GitHub Action that submits new versions of an extension for review. Ensure that you've added all required secrets used in the workflow to the repo's settings.
|
||||
|
||||
```yml
|
||||
# TODO
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 'latest'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Zip extensions
|
||||
run: |
|
||||
pnpm zip
|
||||
pnpm zip:firefox
|
||||
|
||||
- name: Submit to stores
|
||||
run: |
|
||||
pnpm wxt submit \
|
||||
--chrome-zip .output/*-chrome.zip \
|
||||
--firefox-zip .output/*-firefox.zip --firefox-sources-zip .output/*-sources.zip
|
||||
env:
|
||||
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
|
||||
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
|
||||
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
|
||||
```
|
||||
|
||||
The action above lays the foundation for a basic workflow, including `zip` and `submit` steps. To further enhance your GitHub Action and delve into more complex scenarios, consider exploring the following examples from real projects. They introduce advanced features such as version management, changelog generation, and GitHub releases, tailored for different needs:
|
||||
|
||||
- [`aklinker1/github-better-line-counts`](https://github.com/aklinker1/github-better-line-counts/blob/main/.github/workflows/submit.yml) - Conventional commits, automated version bump and changelog generation, triggered manually, optional dry run for testing
|
||||
- [`GuiEpi/plex-skipper`](https://github.com/GuiEpi/plex-skipper/blob/main/.github/workflows/deploy.yml) - Triggered automatically when `package.json` version is changed, creates and uploads artifacts to GitHub release.
|
||||
|
||||
> These examples are designed to provide clear insights and are a good starting point for customizing your own workflows. Feel free to explore and adapt them to your project needs.
|
||||
|
||||
## Stores
|
||||
|
||||
### Chrome Web Store
|
||||
@@ -124,6 +171,11 @@ yarn
|
||||
yarn zip:firefox
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun i
|
||||
bun zip:firefox
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Make sure the build output is the exact same when running `wxt build -b firefox` in your main project and inside the zipped sources.
|
||||
@@ -136,6 +188,28 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
|
||||
|
||||
Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the above commands so that the Firefox team knows how to build your extension.
|
||||
|
||||
#### Private Packages
|
||||
|
||||
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: [
|
||||
'@mycompany/some-package',
|
||||
//...
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Depending on your package manager, the `package.json` in the sources zip will be modified to use the downloaded dependencies via the `overrides` or `resolutions` field.
|
||||
|
||||
:::warning
|
||||
WXT uses the command `npm pack <package-name>` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add an `.npmrc` file.
|
||||
:::
|
||||
|
||||
### Safari
|
||||
|
||||
> 🚧 Not supported yet
|
||||
|
||||
@@ -10,6 +10,21 @@ WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the `
|
||||
import { storage } from 'wxt/storage';
|
||||
```
|
||||
|
||||
:::warning
|
||||
To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
More info on permissions [here](/guide/manifest#permissions).
|
||||
:::
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Basic Usage
|
||||
@@ -117,7 +132,7 @@ Now, instead of using the `storage` variable, you can use the helper functions o
|
||||
await showChangelogOnUpdate.getValue();
|
||||
await showChangelogOnUpdate.setValue(false);
|
||||
await showChangelogOnUpdate.removeValue();
|
||||
const unwatch = showChangelogOnUpdate.watch(() => {
|
||||
const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
@@ -263,27 +278,4 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code
|
||||
|
||||
### Running Migrations
|
||||
|
||||
To run migrations, you have two options:
|
||||
|
||||
1. Import all versioned storage items into your background script and they will run automatically whenever your extension updates
|
||||
2. Manually call `item.migrate()`
|
||||
|
||||
The first approach is recommended. To make importing all your storage items easy, you can define all of them in a single file, `utils/storage.ts`, and import that file into your background entrypoint:
|
||||
|
||||
```ts
|
||||
// utils/storage.ts
|
||||
export countStorage = storage.defineItem(...);
|
||||
export themeStorage = storage.defineItem(...);
|
||||
export someOtherStorage = storage.defineItem(...);
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/background.ts
|
||||
import '@/utils/storage'; // This import runs migrations on updates
|
||||
|
||||
export default defineBackground({
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
> When you call `storage.defineItem`, a `browser.runtime.onInstalled` listener is added. `onInstalled` listeners are only triggered in the background, which is why you must import them into the background.
|
||||
As soon as `storage.defineItem` is called, WXT checks if migrations need to be ran, and if so, runs them. Calls to get or update the storage item's value or metadata (`getValue`, `setValue`, `removeValue`, `getMeta`, etc) will automatically wait for the migration process to finish before actually reading or writing values.
|
||||
|
||||
@@ -70,14 +70,14 @@ features:
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
<section class="vp-doc">
|
||||
<section class="vp-doc" style="padding: 0 48px">
|
||||
<div class="container">
|
||||
<h2>Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First</h2>
|
||||
<p>
|
||||
WXT's simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
|
||||
</p>
|
||||
<div style="margin: auto; width: 100%; max-width: 900px; text-align: center">
|
||||
<video src="https://github.com/wxt-dev/wxt/assets/10101283/b32e6766-ec11-45a4-9677-226ee4718e1c" controls></video>
|
||||
<video src="https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94" controls></video>
|
||||
<br />
|
||||
<small>
|
||||
And who doesn't appreciate a beautiful CLI?
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
{
|
||||
"entryPoints": [
|
||||
"../src",
|
||||
"../src/client",
|
||||
"../src/browser.ts",
|
||||
"../src/sandbox",
|
||||
"../src/storage.ts",
|
||||
"../src/testing"
|
||||
],
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPointStrategy": "packages",
|
||||
"entryPoints": ["../packages/wxt"],
|
||||
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
|
||||
"out": "./api",
|
||||
"githubPages": false,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
describe('Manifest Content', () => {
|
||||
it.each([
|
||||
{ browser: undefined, outDir: 'chrome-mv3', expected: undefined },
|
||||
{ browser: 'chrome', outDir: 'chrome-mv3', expected: undefined },
|
||||
{ browser: 'firefox', outDir: 'firefox-mv2', expected: true },
|
||||
{ browser: 'safari', outDir: 'safari-mv2', expected: false },
|
||||
])(
|
||||
'should respect the per-browser entrypoint option with %j',
|
||||
async ({ browser, expected, outDir }) => {
|
||||
const project = new TestProject();
|
||||
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
`export default defineBackground({
|
||||
persistent: {
|
||||
firefox: true,
|
||||
safari: false,
|
||||
},
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
await project.build({ browser });
|
||||
|
||||
const safariManifest = await project.getOutputManifest(
|
||||
`.output/${outDir}/manifest.json`,
|
||||
);
|
||||
expect(safariManifest.background.persistent).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1,188 +1,48 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.17.2",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wxt-dev/wxt"
|
||||
},
|
||||
"homepage": "https://wxt.dev",
|
||||
"keywords": [
|
||||
"vite",
|
||||
"chrome",
|
||||
"web",
|
||||
"extension",
|
||||
"browser",
|
||||
"bundler",
|
||||
"framework"
|
||||
],
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"wxt": "./bin/wxt.mjs",
|
||||
"wxt-publish-extension": "./bin/wxt-publish-extension.cjs"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./client": {
|
||||
"types": "./dist/client.d.ts",
|
||||
"import": "./dist/client.js"
|
||||
},
|
||||
"./sandbox": {
|
||||
"types": "./dist/sandbox.d.ts",
|
||||
"import": "./dist/sandbox.js"
|
||||
},
|
||||
"./browser": {
|
||||
"types": "./dist/browser.d.ts",
|
||||
"import": "./dist/browser.js"
|
||||
},
|
||||
"./testing": {
|
||||
"import": {
|
||||
"types": "./dist/testing.d.ts",
|
||||
"default": "./dist/testing.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/testing.d.cts",
|
||||
"default": "./dist/testing.cjs"
|
||||
}
|
||||
},
|
||||
"./storage": {
|
||||
"import": {
|
||||
"types": "./dist/storage.d.ts",
|
||||
"default": "./dist/storage.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/storage.d.cts",
|
||||
"default": "./dist/storage.cjs"
|
||||
}
|
||||
},
|
||||
"./vite-builder-env": {
|
||||
"types": "./dist/vite-builder-env.d.ts"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.6.3",
|
||||
"scripts": {
|
||||
"wxt": "tsx src/cli/index.ts",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"lint": "run-p -c -s lint:*",
|
||||
"lint:eslint": "echo 'ESLint: TODO'",
|
||||
"lint:package": "publint",
|
||||
"compile": "run-s -c compile:*",
|
||||
"compile:wxt": "tsc --noEmit",
|
||||
"compile:virtual": "tsc --noEmit -p src/virtual",
|
||||
"check": "check && pnpm -r run check",
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage.enabled \"--coverage.include=src/**\" \"--coverage.exclude=src/core/utils/testing/**\" \"--coverage.exclude=**/*.d.ts\"",
|
||||
"test:coverage": "vitest run --coverage.enabled \"--coverage.include=packages/wxt/src/**\" \"--coverage.exclude=packages/wxt/src/core/utils/testing/**\" \"--coverage.exclude=**/*.d.ts\" \"--coverage.exclude=**/fixtures/**\"",
|
||||
"prepare": "simple-git-hooks",
|
||||
"prepublish": "pnpm -s build",
|
||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs",
|
||||
"sync-releases": "pnpx changelogen@latest gh release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||
"@types/webextension-polyfill": "^0.10.5",
|
||||
"@webext-core/fake-browser": "^1.3.1",
|
||||
"@webext-core/isolated-element": "^1.1.1",
|
||||
"@webext-core/match-patterns": "^1.0.3",
|
||||
"async-mutex": "^0.4.0",
|
||||
"c12": "^1.5.1",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^3.5.3",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.3",
|
||||
"dequal": "^2.0.3",
|
||||
"esbuild": "^0.19.5",
|
||||
"fast-glob": "^3.3.1",
|
||||
"filesize": "^10.0.8",
|
||||
"fs-extra": "^11.1.1",
|
||||
"get-port": "^7.0.0",
|
||||
"giget": "^1.1.3",
|
||||
"hookable": "^5.5.3",
|
||||
"is-wsl": "^3.0.0",
|
||||
"jiti": "^1.21.0",
|
||||
"json5": "^2.2.3",
|
||||
"linkedom": "^0.16.1",
|
||||
"minimatch": "^9.0.3",
|
||||
"natural-compare": "^1.4.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"ora": "^7.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.3",
|
||||
"unimport": "^3.4.0",
|
||||
"vite": "^5.1.3",
|
||||
"web-ext-run": "^0.2.0",
|
||||
"webextension-polyfill": "^0.10.0",
|
||||
"zip-dir": "^2.0.0"
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/natural-compare": "^1.4.3",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@aklinker1/check": "^1.1.1",
|
||||
"@vitest/coverage-v8": "^1.0.1",
|
||||
"consola": "^3.2.3",
|
||||
"execa": "^8.0.1",
|
||||
"happy-dom": "^13.3.8",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"p-map": "^7.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"publint": "^0.2.6",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"tsup": "^8.0.1",
|
||||
"tsx": "^4.6.2",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.3.2",
|
||||
"vitepress": "1.0.0-rc.34",
|
||||
"vitest": "^1.2.2",
|
||||
"vitest": "^1.5.3",
|
||||
"vitest-mock-extended": "^1.3.1",
|
||||
"vue": "^3.3.10"
|
||||
"vitest-plugin-random-seed": "^1.0.2",
|
||||
"vue": "^3.3.10",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.3",
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "prettier --ignore-unknown --write"
|
||||
},
|
||||
"changelog": {
|
||||
"excludeAuthors": [
|
||||
"aaronklinker1@gmail.com"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "wxt-demo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm build:deps && wxt",
|
||||
"build:deps": "pnpm --filter wxt build",
|
||||
"build": "pnpm build:deps && wxt build",
|
||||
"build:all": "pnpm build:deps && run-s -s 'build:all:*'",
|
||||
"build:all:chrome-mv3": "wxt build",
|
||||
"build:all:chrome-mv2": "wxt build --mv2",
|
||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||
"test": "pnpm build:deps && vitest",
|
||||
"zip": "pnpm build:deps && wxt zip",
|
||||
"check": "pnpm build:deps && check",
|
||||
"postinstall": "pnpm build:deps && wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.34",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.3.2",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import messages from 'public/_locales/en/messages.json';
|
||||
import messages from '~/public/_locales/en/messages.json';
|
||||
|
||||
export default defineBackground({
|
||||
// type: 'module',
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 504 B |
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 936 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": ["../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"jsx": "react-jsx"
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { defineProject } from 'vitest/config';
|
||||
import { WxtVitest } from 'wxt/testing';
|
||||
|
||||
export default defineConfig({
|
||||
export default defineProject({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
@@ -12,7 +12,13 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
alias: {
|
||||
public: 'src/public',
|
||||
zip: {
|
||||
downloadPackages: ['sass'],
|
||||
},
|
||||
analysis: {
|
||||
open: true,
|
||||
},
|
||||
experimental: {
|
||||
viteRuntime: true,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,304 @@
|
||||
# Changelog
|
||||
|
||||
## v0.18.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.18.0...v0.18.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- `_background` is not defined ([#649](https://github.com/wxt-dev/wxt/pull/649))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add root README back ([ec3dd52](https://github.com/wxt-dev/wxt/commit/ec3dd52))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Fix sync releases workflow ([dc5b55b](https://github.com/wxt-dev/wxt/commit/dc5b55b))
|
||||
|
||||
## v0.18.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.12...v0.18.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add zip compression settings ([#605](https://github.com/wxt-dev/wxt/pull/605))
|
||||
- Support returning values from scripts executed with the scripting API ([#624](https://github.com/wxt-dev/wxt/pull/624))
|
||||
- **experimental:** Load entrypoint options with Vite Runtime API ([#648](https://github.com/wxt-dev/wxt/pull/648))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- ⚠️ Automatically move `host_permissions` to `permissions` for MV2 ([#626](https://github.com/wxt-dev/wxt/pull/626))
|
||||
- **dep:** Upgrade `@webext-core/isolated-element` to v1.1.2 ([#625](https://github.com/wxt-dev/wxt/pull/625))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add Fluent Read to homepage ([#600](https://github.com/wxt-dev/wxt/pull/600))
|
||||
- Fix typo on example for wxt.config.ts. ([#609](https://github.com/wxt-dev/wxt/pull/609))
|
||||
- Tix typo in `entrypoints.md` ([#614](https://github.com/wxt-dev/wxt/pull/614))
|
||||
- Add Facebook Video Controls to homepage ([#615](https://github.com/wxt-dev/wxt/pull/615))
|
||||
- Fix typo in assets page ([a94d673](https://github.com/wxt-dev/wxt/commit/a94d673))
|
||||
- Add ElemSnap to homepage ([#621](https://github.com/wxt-dev/wxt/pull/621))
|
||||
- Update content script registration JSDoc ([e47519f](https://github.com/wxt-dev/wxt/commit/e47519f))
|
||||
- Add docs about handling updates ([acb7554](https://github.com/wxt-dev/wxt/commit/acb7554))
|
||||
- Add MS Edge TTS to homepage ([#647](https://github.com/wxt-dev/wxt/pull/647))
|
||||
- Document required permission for storage API ([#632](https://github.com/wxt-dev/wxt/pull/632))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update vue template config ([#607](https://github.com/wxt-dev/wxt/pull/607))
|
||||
- **deps-dev:** Bump lint-staged from 15.2.1 to 15.2.2 ([#637](https://github.com/wxt-dev/wxt/pull/637))
|
||||
- **deps-dev:** Bump publint from 0.2.6 to 0.2.7 ([#639](https://github.com/wxt-dev/wxt/pull/639))
|
||||
- **deps-dev:** Bump simple-git-hooks from 2.9.0 to 2.11.1 ([#640](https://github.com/wxt-dev/wxt/pull/640))
|
||||
- Refactor repo to a standard monorepo ([#646](https://github.com/wxt-dev/wxt/pull/646))
|
||||
- Fix formatting after monorepo refactor ([6ca3767](https://github.com/wxt-dev/wxt/commit/6ca3767))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Automatically move `host_permissions` to `permissions` for MV2 ([#626](https://github.com/wxt-dev/wxt/pull/626))
|
||||
|
||||
Out of an abundance of caution, I've marked this as a breaking change because permission generation has changed. **_If you list `host_permissions` in your `wxt.config.ts`'s manifest and have released your extension_**, double check that your `permissions` and `host_permissions` have not changed for all browsers you target in your `.output/*/manifest.json` files. Permission changes can cause the extension to be disabled on update, and can cause a drop in users, so be sure to double check for differences compared to the previous manifest version.
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Alegal200 ([@alegal200](https://github.com/alegal200))
|
||||
- Yacine-bens ([@yacine-bens](http://github.com/yacine-bens))
|
||||
- Ayden ([@AydenGen](https://github.com/AydenGen))
|
||||
- Wuzequanyouzi ([@wuzequanyouzi](http://github.com/wuzequanyouzi))
|
||||
- Can Rau ([@CanRau](http://github.com/CanRau))
|
||||
- 日高 凌 ([@ryohidaka](http://github.com/ryohidaka))
|
||||
- Bas Van Zanten ([@Bas950](http://github.com/Bas950))
|
||||
- ThinkStu ([@Bistutu](http://github.com/Bistutu))
|
||||
|
||||
## v0.17.12
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.11...v0.17.12)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add hooks for extending vite config ([#599](https://github.com/wxt-dev/wxt/pull/599))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **content-script-ui:** Properly assign and unassign mounted value ([#598](https://github.com/wxt-dev/wxt/pull/598))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add discord server link ([#593](https://github.com/wxt-dev/wxt/pull/593))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Remove unnecssary 'Omit' types ([db57c8e](https://github.com/wxt-dev/wxt/commit/db57c8e))
|
||||
- **deps-dev:** Bump @aklinker1/check from 1.1.1 to 1.2.0 ([#588](https://github.com/wxt-dev/wxt/pull/588))
|
||||
- **deps-dev:** Bump vue from 3.3.10 to 3.4.21 ([#589](https://github.com/wxt-dev/wxt/pull/589))
|
||||
- **deps-dev:** Bump sass from 1.69.5 to 1.72.0 ([#591](https://github.com/wxt-dev/wxt/pull/591))
|
||||
|
||||
## v0.17.11
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.10...v0.17.11)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Resolve absolute paths from the public directory properly ([#583](https://github.com/wxt-dev/wxt/pull/583))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix `zip.includeSources` example ([16fc584](https://github.com/wxt-dev/wxt/commit/16fc584))
|
||||
- Add "GitHub Custom Notifier" to homepage ([#580](https://github.com/wxt-dev/wxt/pull/580))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Simplify virtual module setup ([#581](https://github.com/wxt-dev/wxt/pull/581))
|
||||
- Add some array utils ([#582](https://github.com/wxt-dev/wxt/pull/582))
|
||||
- Extract helper function ([d3b14af](https://github.com/wxt-dev/wxt/commit/d3b14af))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Qiwei Yang ([@qiweiii](http://github.com/qiweiii))
|
||||
|
||||
## v0.17.10
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.9...v0.17.10)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `dev.server.port` config ([#577](https://github.com/wxt-dev/wxt/pull/577))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Use `@aklinker1/check` to simplify checks setup ([#550](https://github.com/wxt-dev/wxt/pull/550))
|
||||
- Refactor order of config resolution ([#578](https://github.com/wxt-dev/wxt/pull/578))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Zizheng Tai ([@zizhengtai](http://github.com/zizhengtai))
|
||||
|
||||
## v0.17.9
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.8...v0.17.9)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `{{mode}}` Template Variable ([#566](https://github.com/wxt-dev/wxt/pull/566))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Don't override `wxt.config.ts` options when CLI flags are not passed ([#567](https://github.com/wxt-dev/wxt/pull/567))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Merge user config using `defu` ([#568](https://github.com/wxt-dev/wxt/pull/568))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Guillaume ([@GuiEpi](http://github.com/GuiEpi))
|
||||
|
||||
## v0.17.8
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.7...v0.17.8)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **analysis:** Open `stats.html` file automatically ([#564](https://github.com/wxt-dev/wxt/pull/564))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **init:** Better error logging when templates fail to load ([b47c150](https://github.com/wxt-dev/wxt/commit/b47c150))
|
||||
- Remove deprecated extension from Vue template ([#534](https://github.com/wxt-dev/wxt/pull/534))
|
||||
- Append option description error ([#546](https://github.com/wxt-dev/wxt/pull/546))
|
||||
- **init:** Don't overwrite existing files when initializing a new project ([#556](https://github.com/wxt-dev/wxt/pull/556))
|
||||
- **dev:** Don't crash dev mode when rebuild fails ([#565](https://github.com/wxt-dev/wxt/pull/565))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Replace `pnpx` with `pnpm dlx` ([#527](https://github.com/wxt-dev/wxt/pull/527))
|
||||
- Update homepage demo video ([35269da](https://github.com/wxt-dev/wxt/commit/35269da))
|
||||
- Update README demo video ([#539](https://github.com/wxt-dev/wxt/pull/539))
|
||||
- Add Plex skipper to "Using WXT" section ([#541](https://github.com/wxt-dev/wxt/pull/541))
|
||||
- Add documentation for Bun.sh ([#543](https://github.com/wxt-dev/wxt/pull/543))
|
||||
- Mention adding unlisted scripts and pages to `web_accessible_resources` ([121b521](https://github.com/wxt-dev/wxt/commit/121b521))
|
||||
- Add examples for GitHub Actions ([#540](https://github.com/wxt-dev/wxt/pull/540))
|
||||
- Fixed "Reload the Extension" section ([#559](https://github.com/wxt-dev/wxt/pull/559))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Increase unit test timeout ([d9cba55](https://github.com/wxt-dev/wxt/commit/d9cba55))
|
||||
- Increase hook timeout for Windows/NPM tests ([56b7149](https://github.com/wxt-dev/wxt/commit/56b7149))
|
||||
- Switch to seed plugin for testing ([#547](https://github.com/wxt-dev/wxt/pull/547))
|
||||
- **vue-template:** Upgrade to `vue-tsc` v2 ([#549](https://github.com/wxt-dev/wxt/pull/549))
|
||||
- Add a test covering `wxt init` in a non-empty directory ([#563](https://github.com/wxt-dev/wxt/pull/563))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Btea ([@btea](http://github.com/btea))
|
||||
- Vlad Fedosov ([@StyleT](http://github.com/StyleT))
|
||||
- Lpmvb ([@Lpmvb](http://github.com/Lpmvb))
|
||||
- Guillaume ([@GuiEpi](http://github.com/GuiEpi))
|
||||
- Sunshio ([@MPB-Tech](http://github.com/MPB-Tech))
|
||||
- Luca Dalli ([@lucadalli](http://github.com/lucadalli))
|
||||
|
||||
## v0.17.7
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.6...v0.17.7)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **zip:** List `.wxt/local_modules` overrides relative to the `package.json` they're added to ([#526](https://github.com/wxt-dev/wxt/pull/526))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Bump templates to v0.17 ([#524](https://github.com/wxt-dev/wxt/pull/524))
|
||||
- Increase test timeout for windows NPM tests ([#525](https://github.com/wxt-dev/wxt/pull/525))
|
||||
|
||||
## v0.17.6
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.5...v0.17.6)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add warnings when important directories are missing ([#516](https://github.com/wxt-dev/wxt/pull/516))
|
||||
- Automatically remove top-level MV2-only or MV3-only keys ([#518](https://github.com/wxt-dev/wxt/pull/518))
|
||||
- Automatically generate `browser_action` based on `action` for MV2 ([#519](https://github.com/wxt-dev/wxt/pull/519))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **zip:** List all private packages correctly in a PNPM workspace ([#520](https://github.com/wxt-dev/wxt/pull/520))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Mentions moving folders into `srcDir` ([9cd4e83](https://github.com/wxt-dev/wxt/commit/9cd4e83))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps-dev:** Bump @types/react from 18.2.34 to 18.2.61 ([#510](https://github.com/wxt-dev/wxt/pull/510))
|
||||
|
||||
## v0.17.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.4...v0.17.5)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Expose package management utils under `wxt.pm` ([#502](https://github.com/wxt-dev/wxt/pull/502))
|
||||
- Download and override private packages for Firefox code review ([#507](https://github.com/wxt-dev/wxt/pull/507))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix typos ([#503](https://github.com/wxt-dev/wxt/pull/503))
|
||||
- Add docs about configuring the manifest as a function ([195d2cc](https://github.com/wxt-dev/wxt/commit/195d2cc))
|
||||
- Fix CLI generation ([b754435](https://github.com/wxt-dev/wxt/commit/b754435))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Use JSZip for `wxt zip`, enabling future features ([#501](https://github.com/wxt-dev/wxt/pull/501))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Btea ([@btea](http://github.com/btea))
|
||||
|
||||
## v0.17.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.3...v0.17.4)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add basic content script to templates ([#495](https://github.com/wxt-dev/wxt/pull/495))
|
||||
- Add `ResolvedConfig.wxtModuleDir`, resolving the directory once ([#497](https://github.com/wxt-dev/wxt/pull/497))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Resolve the path to `node_modules/wxt` correctly ([#498](https://github.com/wxt-dev/wxt/pull/498))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added DocVersionRedirector to "Using WXT" section ([#492](https://github.com/wxt-dev/wxt/pull/492))
|
||||
- Fix typos ([f80fb42](https://github.com/wxt-dev/wxt/commit/f80fb42))
|
||||
- Add CRXJS to comparison page ([cb4f9aa](https://github.com/wxt-dev/wxt/commit/cb4f9aa))
|
||||
- Update comparison page ([35778f7](https://github.com/wxt-dev/wxt/commit/35778f7))
|
||||
- Update context usage ([012bd7e](https://github.com/wxt-dev/wxt/commit/012bd7e))
|
||||
- Add testing example for `ContentScriptContext` ([e1c6020](https://github.com/wxt-dev/wxt/commit/e1c6020))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix tests after template change ([f9b0aa4](https://github.com/wxt-dev/wxt/commit/f9b0aa4))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Btea ([@btea](http://github.com/btea))
|
||||
- Leo Shklovskii <leo@thermopylae.net>
|
||||
|
||||
## v0.17.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.2...v0.17.3)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **storage:** Guarantee `storage.getItems` returns values in the same order as requested ([b5f4d8c](https://github.com/wxt-dev/wxt/commit/b5f4d8c))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Content scripts crash when using `storage.defineItem` ([77e6d1f](https://github.com/wxt-dev/wxt/commit/77e6d1f))
|
||||
- **storage:** Revert #478 and run migrations when item is defined and properly wait for migrations before allowing read/writes ([#487](https://github.com/wxt-dev/wxt/pull/487), [#478](https://github.com/wxt-dev/wxt/issues/478))
|
||||
|
||||
## v0.17.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.1...v0.17.2)
|
||||
@@ -30,11 +329,11 @@
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **storage:** ⚠️ Improved support for default values on storage items ([#477](https://github.com/wxt-dev/wxt/pull/477))
|
||||
- **storage:** ⚠️ Improved support for default values on storage items ([#477](https://github.com/wxt-dev/wxt/pull/477))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **storage:** ⚠️ Only run migrations when the extension is updated ([#478](https://github.com/wxt-dev/wxt/pull/478))
|
||||
- **storage:** ⚠️ Only run migrations when the extension is updated ([#478](https://github.com/wxt-dev/wxt/pull/478))
|
||||
- Improve dev mode for content scripts registered at runtime ([#474](https://github.com/wxt-dev/wxt/pull/474))
|
||||
|
||||
### 📖 Documentation
|
||||
@@ -62,12 +361,13 @@ const item = storage.defineItem<number | null>("local:count", {
|
||||
})
|
||||
```
|
||||
|
||||
The `defaultValue` property is now required if passing in the second options argument.
|
||||
The `defaultValue` property is now required if passing in the second options argument.
|
||||
|
||||
If you exclude the second options argument, it will default to being nullable, as before.
|
||||
|
||||
```ts
|
||||
const item: WxtStorageItem<number | null> = storage.defineItem<number>("local:count");
|
||||
const item: WxtStorageItem<number | null> =
|
||||
storage.defineItem<number>('local:count');
|
||||
const value: number | null = await item.getValue();
|
||||
```
|
||||
|
||||
@@ -77,13 +377,13 @@ For storage items that are not nullable, the `watch` callback types has improved
|
||||
|
||||
```ts
|
||||
// >=0.17
|
||||
const item = storage.defineItem<number>("local:count", { defaultValue: 0 });
|
||||
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
|
||||
item.watch((newValue: number | null, oldValue: number | null) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// >=0.17
|
||||
const item = storage.defineItem<number>("local:count", { defaultValue: 0 });
|
||||
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
|
||||
item.watch((newValue: number, oldValue: number) => {
|
||||
// ...
|
||||
});
|
||||
@@ -313,7 +613,7 @@ item.migrate();
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ ESM background support ([#398](https://github.com/wxt-dev/wxt/pull/398))
|
||||
- ⚠️ ESM background support ([#398](https://github.com/wxt-dev/wxt/pull/398))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -330,23 +630,23 @@ item.migrate();
|
||||
In [#398](https://github.com/wxt-dev/wxt/pull/398), HTML pages' JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
|
||||
|
||||
- Before:
|
||||
```
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
some-shared-chunk-<hash>.js
|
||||
popup-<hash>.js
|
||||
popup.html
|
||||
```
|
||||
```
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
some-shared-chunk-<hash>.js
|
||||
popup-<hash>.js
|
||||
popup.html
|
||||
```
|
||||
- After:
|
||||
```
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
some-shared-chunk-<hash>.js
|
||||
popup.html
|
||||
popup.js
|
||||
```
|
||||
```
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
some-shared-chunk-<hash>.js
|
||||
popup.html
|
||||
popup.js
|
||||
```
|
||||
|
||||
This effects all HTML files, not just the Popup. The hash has been removed, and it's been moved to the root of the build target folder, not inside the `chunks/` directory. Moving files like this has not historically increased review times or triggered in-depth reviews when submitting updates to the stores.
|
||||
|
||||
@@ -412,12 +712,12 @@ This effects all HTML files, not just the Popup. The hash has been removed, and
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **zip:** ⚠️ Add `includeSources` and rename `ignoredSources` to `excludeSources` ([#378](https://github.com/wxt-dev/wxt/pull/378))
|
||||
- **zip:** ⚠️ Add `includeSources` and rename `ignoredSources` to `excludeSources` ([#378](https://github.com/wxt-dev/wxt/pull/378))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Generate missing sourcemap in `wxt:unimport` plugin ([#381](https://github.com/wxt-dev/wxt/pull/381))
|
||||
- ⚠️ Move browser constants to `import.meta.env` ([#380](https://github.com/wxt-dev/wxt/pull/380))
|
||||
- ⚠️ Move browser constants to `import.meta.env` ([#380](https://github.com/wxt-dev/wxt/pull/380))
|
||||
- Enable inline sourcemaps by default during development ([#382](https://github.com/wxt-dev/wxt/pull/382))
|
||||
|
||||
### 📖 Documentation
|
||||
@@ -567,7 +867,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Refactor content script UI functions and add helper for "integrated" UIs ([#333](https://github.com/wxt-dev/wxt/pull/333))
|
||||
- ⚠️ Refactor content script UI functions and add helper for "integrated" UIs ([#333](https://github.com/wxt-dev/wxt/pull/333))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
@@ -576,7 +876,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
- `createContentScriptUi({ ... })` → `createShadowRootUi({ ... })`
|
||||
- `createContentScriptIframe({ ... })` → `createIframeUi({ ... })`
|
||||
- `type: "inline" | "overlay" | "modal"` has been changed to `position: "inline" | "overlay" | "modal"`
|
||||
- `onRemove` is now called ***before*** the UI is removed from the DOM, previously it was called after the UI was removed
|
||||
- `onRemove` is now called **_before_** the UI is removed from the DOM, previously it was called after the UI was removed
|
||||
- `mount` option has been renamed to `onMount`, to better match the related option, `onRemove`.
|
||||
|
||||
## v0.13.5
|
||||
@@ -672,7 +972,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ New `wxt/storage` APIs ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
- ⚠️ New `wxt/storage` APIs ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
@@ -729,7 +1029,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Add support for "main world" content scripts ([#284](https://github.com/wxt-dev/wxt/pull/284))
|
||||
- ⚠️ Add support for "main world" content scripts ([#284](https://github.com/wxt-dev/wxt/pull/284))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
@@ -751,10 +1051,10 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
- If you use auto-imports, no changes are required.
|
||||
- If you have disabled auto-imports, you'll need to manually update your import statements:
|
||||
```diff
|
||||
- import { defineBackground, defineContentScript } from 'wxt/client';
|
||||
+ import { defineBackground, defineContentScript } from 'wxt/sandbox';
|
||||
```
|
||||
```diff
|
||||
- import { defineBackground, defineContentScript } from 'wxt/client';
|
||||
+ import { defineBackground, defineContentScript } from 'wxt/sandbox';
|
||||
```
|
||||
|
||||
## v0.11.2
|
||||
|
||||
@@ -792,7 +1092,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Vite 5 support ([#261](https://github.com/wxt-dev/wxt/pull/261))
|
||||
- ⚠️ Vite 5 support ([#261](https://github.com/wxt-dev/wxt/pull/261))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -884,7 +1184,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
### 🚀 Enhancements
|
||||
|
||||
- List `bun` as an experimental option in `wxt init` ([#233](https://github.com/wxt-dev/wxt/pull/233))
|
||||
- ⚠️ Allow plural directory and only png's for manifest icons ([#237](https://github.com/wxt-dev/wxt/pull/237))
|
||||
- ⚠️ Allow plural directory and only png's for manifest icons ([#237](https://github.com/wxt-dev/wxt/pull/237))
|
||||
- Add `wxt/storage` API ([#234](https://github.com/wxt-dev/wxt/pull/234))
|
||||
|
||||
### 🩹 Fixes
|
||||
@@ -956,7 +1256,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- ⚠️ Remove `lib` from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209))
|
||||
- ⚠️ Remove `lib` from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -969,15 +1269,15 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Removed [`"WebWorker"` types](https://www.typescriptlang.org/tsconfig/lib.html) from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209)). These types are useful for MV3 projects using a service worker. To add them back to your project, add the following to your project's TSConfig:
|
||||
```diff
|
||||
{
|
||||
"extends": "./.wxt/tsconfig.json",
|
||||
+ "compilerOptions": {
|
||||
+ "lib": ["ESNext", "DOM", "WebWorker"]
|
||||
+ }
|
||||
}
|
||||
```
|
||||
- ⚠️ Removed [`"WebWorker"` types](https://www.typescriptlang.org/tsconfig/lib.html) from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209)). These types are useful for MV3 projects using a service worker. To add them back to your project, add the following to your project's TSConfig:
|
||||
```diff
|
||||
{
|
||||
"extends": "./.wxt/tsconfig.json",
|
||||
+ "compilerOptions": {
|
||||
+ "lib": ["ESNext", "DOM", "WebWorker"]
|
||||
+ }
|
||||
}
|
||||
```
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
@@ -1091,7 +1391,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Use `defineUnlistedScript` to define unlisted scripts ([#167](https://github.com/wxt-dev/wxt/pull/167))
|
||||
- ⚠️ Use `defineUnlistedScript` to define unlisted scripts ([#167](https://github.com/wxt-dev/wxt/pull/167))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -1099,12 +1399,12 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🌊 Types
|
||||
|
||||
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
|
||||
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Unlisted scripts must now `export default defineUnlistedScript(...)` ([#167](https://github.com/wxt-dev/wxt/pull/167))
|
||||
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
|
||||
- ⚠️ Unlisted scripts must now `export default defineUnlistedScript(...)` ([#167](https://github.com/wxt-dev/wxt/pull/167))
|
||||
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
@@ -1197,7 +1497,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
### 🩹 Fixes
|
||||
|
||||
- Validate transformed manifest correctly ([4b2012c](https://github.com/wxt-dev/wxt/commit/4b2012c))
|
||||
- ⚠️ Output content script CSS to `content-scripts/<name>.css` ([#140](https://github.com/wxt-dev/wxt/pull/140))
|
||||
- ⚠️ Output content script CSS to `content-scripts/<name>.css` ([#140](https://github.com/wxt-dev/wxt/pull/140))
|
||||
- Reorder typescript paths to give priority to `@` and `~` over `@@` and `~~` ([#142](https://github.com/wxt-dev/wxt/pull/142))
|
||||
|
||||
### 🏡 Chore
|
||||
@@ -1295,7 +1595,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Export `ContentScriptContext` from `wxt/client` ([1f448d1](https://github.com/wxt-dev/wxt/commit/1f448d1))
|
||||
- ⚠️ Require a function for `vite` configuration ([#121](https://github.com/wxt-dev/wxt/pull/121))
|
||||
- ⚠️ Require a function for `vite` configuration ([#121](https://github.com/wxt-dev/wxt/pull/121))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
@@ -1304,7 +1604,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ The `vite` config option must now be a function. If you were using an object before, change it from `vite: { ... }` to `vite: () => ({ ... })`. ([#121](https://github.com/wxt-dev/wxt/pull/121))
|
||||
- ⚠️ The `vite` config option must now be a function. If you were using an object before, change it from `vite: { ... }` to `vite: () => ({ ... })`. ([#121](https://github.com/wxt-dev/wxt/pull/121))
|
||||
|
||||
## v0.5.6
|
||||
|
||||
@@ -1371,7 +1671,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
### 🩹 Fixes
|
||||
|
||||
- **types:** Don't write to files if nothing changes ([#107](https://github.com/wxt-dev/wxt/pull/107))
|
||||
- ⚠️ Change default `publicDir` to `<srcDir>/public` ([5f15f9c](https://github.com/wxt-dev/wxt/commit/5f15f9c))
|
||||
- ⚠️ Change default `publicDir` to `<srcDir>/public` ([5f15f9c](https://github.com/wxt-dev/wxt/commit/5f15f9c))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -1391,7 +1691,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Change default `publicDir` to `<srcDir>/public` ([5f15f9c](https://github.com/wxt-dev/wxt/commit/5f15f9c))
|
||||
- ⚠️ Change default `publicDir` to `<srcDir>/public` ([5f15f9c](https://github.com/wxt-dev/wxt/commit/5f15f9c))
|
||||
|
||||
## v0.4.1
|
||||
|
||||
@@ -1419,7 +1719,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
- Allow adding custom content scripts ([b428a62](https://github.com/wxt-dev/wxt/commit/b428a62))
|
||||
- Don't overwrite `wxt.config.ts` content scripts, append entrypoints to it ([5f5f1d9](https://github.com/wxt-dev/wxt/commit/5f5f1d9))
|
||||
- ⚠️ Use relative path aliases inside `.wxt/tsconfig.json` ([#102](https://github.com/wxt-dev/wxt/pull/102))
|
||||
- ⚠️ Use relative path aliases inside `.wxt/tsconfig.json` ([#102](https://github.com/wxt-dev/wxt/pull/102))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -1452,7 +1752,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Use relative path aliases inside `.wxt/tsconfig.json` ([#102](https://github.com/wxt-dev/wxt/pull/102))
|
||||
- ⚠️ Use relative path aliases inside `.wxt/tsconfig.json` ([#102](https://github.com/wxt-dev/wxt/pull/102))
|
||||
|
||||
## v0.3.2
|
||||
|
||||
@@ -1524,8 +1824,8 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Add type safety to `browser.runtime.getURL` ([58a84ec](https://github.com/wxt-dev/wxt/commit/58a84ec))
|
||||
- ⚠️ Change default `publicDir` to `<rootDir>/public` ([19c0948](https://github.com/wxt-dev/wxt/commit/19c0948))
|
||||
- ⚠️ Add type safety to `browser.runtime.getURL` ([58a84ec](https://github.com/wxt-dev/wxt/commit/58a84ec))
|
||||
- ⚠️ Change default `publicDir` to `<rootDir>/public` ([19c0948](https://github.com/wxt-dev/wxt/commit/19c0948))
|
||||
- Windows support ([#50](https://github.com/wxt-dev/wxt/pull/50))
|
||||
|
||||
### 🩹 Fixes
|
||||
@@ -1544,10 +1844,10 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
- Improve checks against `demo/` extension ([9cc464f](https://github.com/wxt-dev/wxt/commit/9cc464f))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Add type safety to `browser.runtime.getURL` ([58a84ec](https://github.com/wxt-dev/wxt/commit/58a84ec))
|
||||
- ⚠️ Change default `publicDir` to `<rootDir>/public` ([19c0948](https://github.com/wxt-dev/wxt/commit/19c0948))
|
||||
- ⚠️ Add type safety to `browser.runtime.getURL` ([58a84ec](https://github.com/wxt-dev/wxt/commit/58a84ec))
|
||||
- ⚠️ Change default `publicDir` to `<rootDir>/public` ([19c0948](https://github.com/wxt-dev/wxt/commit/19c0948))
|
||||
|
||||
## v0.2.5
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<h1 align="center">
|
||||
<img style="vertical-align:middle" width="44" src="./docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<span>Next-gen framework for developing web extensions.</span>
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Chrome Extensions</i></q>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://wxt.dev" target="_blank">Get Started</a>
|
||||
•
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Installation</a>
|
||||
•
|
||||
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
|
||||
•
|
||||
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
|
||||
•
|
||||
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
```sh
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌐 Supports all browsers
|
||||
- ✅ Supports both MV2 and MV3
|
||||
- ⚡ Dev mode with HMR & fast reload
|
||||
- 📂 File based entrypoints
|
||||
- 🚔 TypeScript
|
||||
- 🦾 Auto-imports
|
||||
- 🤖 Automated publishing
|
||||
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
|
||||
- 🖍️ Quickly bootstrap a new project
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
</a>
|
||||
@@ -1,6 +1,14 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { resetBundleIncrement } from '~/core/builders/vite/plugins';
|
||||
import open from 'open';
|
||||
|
||||
vi.mock('open');
|
||||
const openMock = vi.mocked(open);
|
||||
|
||||
vi.mock('ci-info', () => ({
|
||||
isCI: false,
|
||||
}));
|
||||
|
||||
describe('Analysis', () => {
|
||||
beforeEach(() => {
|
||||
@@ -89,4 +97,24 @@ describe('Analysis', () => {
|
||||
expect(await project.fileExists('stats/bundle-0.json')).toBe(true);
|
||||
expect(await project.fileExists('stats/bundle-1.json')).toBe(true);
|
||||
});
|
||||
|
||||
it('should open the stats in the browser when requested', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html');
|
||||
project.addFile('entrypoints/options.html');
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
|
||||
await project.build({
|
||||
analysis: {
|
||||
enabled: true,
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(openMock).toBeCalledTimes(1);
|
||||
expect(openMock).toBeCalledWith(project.resolvePath('stats.html'));
|
||||
});
|
||||
});
|
||||
@@ -9,16 +9,21 @@ const hooks: WxtHooks = {
|
||||
'build:manifestGenerated': vi.fn(),
|
||||
'entrypoints:resolved': vi.fn(),
|
||||
'entrypoints:grouped': vi.fn(),
|
||||
'vite:build:extendConfig': vi.fn(),
|
||||
'vite:devServer:extendConfig': vi.fn(),
|
||||
};
|
||||
|
||||
function expectHooksToBeCalled(called: Record<keyof WxtHooks, boolean>) {
|
||||
function expectHooksToBeCalled(
|
||||
called: Record<keyof WxtHooks, boolean | number>,
|
||||
) {
|
||||
Object.keys(hooks).forEach((key) => {
|
||||
const hookName = key as keyof WxtHooks;
|
||||
const times = called[hookName] ? 1 : 0;
|
||||
const value = called[hookName];
|
||||
const times = typeof value === 'number' ? value : value ? 1 : 0;
|
||||
expect(
|
||||
hooks[hookName],
|
||||
`Expected "${hookName}" to be called ${times} time(s)`,
|
||||
).toBeCalledTimes(called[hookName] ? 1 : 0);
|
||||
).toBeCalledTimes(times);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,6 +45,8 @@ describe('Hooks', () => {
|
||||
'build:manifestGenerated': false,
|
||||
'entrypoints:grouped': false,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': false,
|
||||
'vite:devServer:extendConfig': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,6 +63,8 @@ describe('Hooks', () => {
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,6 +81,8 @@ describe('Hooks', () => {
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,6 +105,8 @@ describe('Hooks', () => {
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 2,
|
||||
'vite:devServer:extendConfig': 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { execaCommand } from 'execa';
|
||||
import glob from 'fast-glob';
|
||||
import { mkdir, writeJson } from 'fs-extra';
|
||||
import { WXT_PACKAGE_DIR } from '../utils';
|
||||
|
||||
describe('Init command', () => {
|
||||
it('should download and create a template', async () => {
|
||||
@@ -10,6 +12,7 @@ describe('Init command', () => {
|
||||
await execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
|
||||
env: { ...process.env, CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
});
|
||||
const files = await glob('**/*', {
|
||||
cwd: project.root,
|
||||
@@ -25,6 +28,7 @@ describe('Init command', () => {
|
||||
"assets/vue.svg",
|
||||
"components/HelloWorld.vue",
|
||||
"entrypoints/background.ts",
|
||||
"entrypoints/content.ts",
|
||||
"entrypoints/popup/App.vue",
|
||||
"entrypoints/popup/index.html",
|
||||
"entrypoints/popup/main.ts",
|
||||
@@ -41,4 +45,18 @@ describe('Init command', () => {
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should throw an error if the directory is not empty', async () => {
|
||||
const project = new TestProject();
|
||||
await mkdir(project.root, { recursive: true });
|
||||
await writeJson(project.resolvePath('package.json'), {});
|
||||
|
||||
await expect(() =>
|
||||
execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
|
||||
env: { ...process.env, CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
}),
|
||||
).rejects.toThrowError('Command failed with exit code 1:');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
describe.each([true, false])(
|
||||
'Manifest Content (Vite runtime? %s)',
|
||||
(viteRuntime) => {
|
||||
it.each([
|
||||
{ browser: undefined, outDir: 'chrome-mv3', expected: undefined },
|
||||
{ browser: 'chrome', outDir: 'chrome-mv3', expected: undefined },
|
||||
{ browser: 'firefox', outDir: 'firefox-mv2', expected: true },
|
||||
{ browser: 'safari', outDir: 'safari-mv2', expected: false },
|
||||
])(
|
||||
'should respect the per-browser entrypoint option with %j',
|
||||
async ({ browser, expected, outDir }) => {
|
||||
const project = new TestProject();
|
||||
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
`export default defineBackground({
|
||||
persistent: {
|
||||
firefox: true,
|
||||
safari: false,
|
||||
},
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
await project.build({ browser, experimental: { viteRuntime } });
|
||||
|
||||
const safariManifest = await project.getOutputManifest(
|
||||
`.output/${outDir}/manifest.json`,
|
||||
);
|
||||
expect(safariManifest.background.persistent).toBe(expected);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -272,7 +272,7 @@ describe('Output Directory Structure', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/background.js
|
||||
----------------------------------------
|
||||
import { l as logHello } from "./chunks/log-bezs0tt4.js";
|
||||
import { l as logHello } from "./chunks/log-BsZv2eRn.js";
|
||||
function defineBackground(arg) {
|
||||
if (typeof arg === "function")
|
||||
return { main: arg };
|
||||
@@ -294,9 +294,10 @@ describe('Output Directory Structure', () => {
|
||||
warn: (...args) => print(console.warn, ...args),
|
||||
error: (...args) => print(console.error, ...args)
|
||||
};
|
||||
var result;
|
||||
try {
|
||||
const res = definition.main();
|
||||
if (res instanceof Promise) {
|
||||
result = definition.main();
|
||||
if (result instanceof Promise) {
|
||||
console.warn(
|
||||
"The background's main() function return a promise, but it must be synchronous"
|
||||
);
|
||||
@@ -352,7 +353,7 @@ describe('Output Directory Structure', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/background.js
|
||||
----------------------------------------
|
||||
(function() {
|
||||
var _background = function() {
|
||||
"use strict";
|
||||
function defineBackground(arg) {
|
||||
if (typeof arg === "function")
|
||||
@@ -362,11 +363,13 @@ describe('Output Directory Structure', () => {
|
||||
function logHello(name) {
|
||||
console.log(\`Hello \${name}!\`);
|
||||
}
|
||||
_background;
|
||||
const definition = defineBackground({
|
||||
main() {
|
||||
logHello("background");
|
||||
}
|
||||
});
|
||||
_background;
|
||||
chrome;
|
||||
function print(method, ...args) {
|
||||
return;
|
||||
@@ -377,9 +380,10 @@ describe('Output Directory Structure', () => {
|
||||
warn: (...args) => print(console.warn, ...args),
|
||||
error: (...args) => print(console.error, ...args)
|
||||
};
|
||||
var result;
|
||||
try {
|
||||
const res = definition.main();
|
||||
if (res instanceof Promise) {
|
||||
result = definition.main();
|
||||
if (result instanceof Promise) {
|
||||
console.warn(
|
||||
"The background's main() function return a promise, but it must be synchronous"
|
||||
);
|
||||
@@ -388,7 +392,10 @@ describe('Output Directory Structure', () => {
|
||||
logger.error("The background crashed on startup!");
|
||||
throw err;
|
||||
}
|
||||
})();
|
||||
var background_entrypoint_default = result;
|
||||
return background_entrypoint_default;
|
||||
}();
|
||||
_background;
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -119,7 +119,7 @@ describe('User Config', () => {
|
||||
project.addFile(
|
||||
'test.config.ts',
|
||||
`import { defineConfig } from 'wxt';
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
outDir: ".custom-output",
|
||||
srcDir: "src",
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import extract from 'extract-zip';
|
||||
import { execaCommand } from 'execa';
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
describe('Zipping', () => {
|
||||
it('should download packages and produce a valid build when zipping sources', async () => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
flatten: '1.0.3',
|
||||
},
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
|
||||
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
|
||||
|
||||
await project.zip({
|
||||
browser: 'firefox',
|
||||
zip: { downloadPackages: ['flatten'] },
|
||||
});
|
||||
expect(await project.fileExists('.output/')).toBe(true);
|
||||
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
// Update package json wxt path
|
||||
const packageJsonPath = project.resolvePath(unzipDir, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
||||
packageJson.dependencies.wxt = '../../../../..';
|
||||
await writeFile(
|
||||
packageJsonPath,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// Build zipped extension
|
||||
await expect(
|
||||
execaCommand('pnpm i --ignore-workspace --frozen-lockfile false', {
|
||||
cwd: unzipDir,
|
||||
}),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
await expect(
|
||||
execaCommand('pnpm wxt build -b firefox', { cwd: unzipDir }),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
|
||||
await expect(project.fileExists(unzipDir, '.output')).resolves.toBe(true);
|
||||
expect(
|
||||
await project.serializeFile(
|
||||
project.resolvePath(unzipDir, 'package.json'),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".output/test-1.0.0-sources/package.json
|
||||
----------------------------------------
|
||||
{
|
||||
"name": "test",
|
||||
"description": "Example description",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"wxt": "../../../../..",
|
||||
"flatten": "1.0.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"flatten@1.0.3": "file://./.wxt/local_modules/flatten-1.0.3.tgz"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should correctly apply template variables for zip file names based on provided config', async () => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
const artifactZip = '.output/test-1.0.0-firefox-development.zip';
|
||||
const sourcesZip = '.output/test-1.0.0-development-sources.zip';
|
||||
|
||||
await project.zip({
|
||||
browser: 'firefox',
|
||||
mode: 'development',
|
||||
zip: {
|
||||
artifactTemplate: '{{name}}-{{version}}-{{browser}}-{{mode}}.zip',
|
||||
sourcesTemplate: '{{name}}-{{version}}-{{mode}}-sources.zip',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(artifactZip)).toBe(true);
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dirname, join, relative, resolve } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import fs, { mkdir } from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { execaCommand } from 'execa';
|
||||
import {
|
||||
@@ -13,6 +13,13 @@ import {
|
||||
import { normalizePath } from '../src/core/utils/paths';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
// Run "pnpm wxt" to use the "wxt" dev script, not the "wxt" binary from the
|
||||
// wxt package. This uses the TS files instead of the compiled JS package
|
||||
// files.
|
||||
export const WXT_PACKAGE_DIR = resolve(__dirname, '..');
|
||||
|
||||
export const E2E_DIR = resolve(WXT_PACKAGE_DIR, 'e2e');
|
||||
|
||||
export class TestProject {
|
||||
files: Array<[string, string]> = [];
|
||||
config: UserConfig | undefined;
|
||||
@@ -23,7 +30,7 @@ export class TestProject {
|
||||
// file is cached and cannot be different between each test. Instead, we add a random ID to the
|
||||
// end to make each test's path unique.
|
||||
const id = Math.random().toString(32).substring(3);
|
||||
this.root = join('e2e/dist', id);
|
||||
this.root = resolve(E2E_DIR, 'dist', id);
|
||||
this.files.push([
|
||||
'package.json',
|
||||
JSON.stringify(
|
||||
@@ -113,6 +120,9 @@ export class TestProject {
|
||||
await execaCommand('pnpm --ignore-workspace i --ignore-scripts', {
|
||||
cwd: this.root,
|
||||
});
|
||||
await mkdir(resolve(this.root, 'public'), { recursive: true }).catch(
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,9 +141,7 @@ export class TestProject {
|
||||
* that can be used in a snapshot.
|
||||
*/
|
||||
serializeWxtDir(): Promise<string> {
|
||||
return this.serializeDir(
|
||||
resolve(this.config?.srcDir ?? this.root, '.wxt/types'),
|
||||
);
|
||||
return this.serializeDir(resolve(this.root, '.wxt/types'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,8 +183,8 @@ export class TestProject {
|
||||
].join(`\n${''.padEnd(40, '-')}\n`);
|
||||
}
|
||||
|
||||
fileExists(path: string): Promise<boolean> {
|
||||
return fs.exists(this.resolvePath(path));
|
||||
fileExists(...path: string[]): Promise<boolean> {
|
||||
return fs.exists(this.resolvePath(...path));
|
||||
}
|
||||
|
||||
async getOutputManifest(
|
||||
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.18.1",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wxt-dev/wxt"
|
||||
},
|
||||
"homepage": "https://wxt.dev",
|
||||
"keywords": [
|
||||
"vite",
|
||||
"chrome",
|
||||
"web",
|
||||
"extension",
|
||||
"browser",
|
||||
"bundler",
|
||||
"framework"
|
||||
],
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"wxt": "./bin/wxt.mjs",
|
||||
"wxt-publish-extension": "./bin/wxt-publish-extension.cjs"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./client": {
|
||||
"types": "./dist/client.d.ts",
|
||||
"import": "./dist/client.js"
|
||||
},
|
||||
"./sandbox": {
|
||||
"types": "./dist/sandbox.d.ts",
|
||||
"import": "./dist/sandbox.js"
|
||||
},
|
||||
"./browser": {
|
||||
"types": "./dist/browser.d.ts",
|
||||
"import": "./dist/browser.js"
|
||||
},
|
||||
"./testing": {
|
||||
"import": {
|
||||
"types": "./dist/testing.d.ts",
|
||||
"default": "./dist/testing.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/testing.d.cts",
|
||||
"default": "./dist/testing.cjs"
|
||||
}
|
||||
},
|
||||
"./storage": {
|
||||
"import": {
|
||||
"types": "./dist/storage.d.ts",
|
||||
"default": "./dist/storage.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/storage.d.cts",
|
||||
"default": "./dist/storage.cjs"
|
||||
}
|
||||
},
|
||||
"./vite-builder-env": {
|
||||
"types": "./dist/vite-builder-env.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"wxt": "tsx src/cli/index.ts",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"check": "run-s -c check:*",
|
||||
"check:default": "check",
|
||||
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
|
||||
"test": "vitest",
|
||||
"test:e2e": "vitest -r e2e",
|
||||
"sync-releases": "pnpx changelogen@latest gh release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||
"@types/webextension-polyfill": "^0.10.5",
|
||||
"@webext-core/fake-browser": "^1.3.1",
|
||||
"@webext-core/isolated-element": "^1.1.2",
|
||||
"@webext-core/match-patterns": "^1.0.3",
|
||||
"async-mutex": "^0.4.0",
|
||||
"c12": "^1.5.1",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^3.5.3",
|
||||
"ci-info": "^4.0.0",
|
||||
"defu": "^6.1.3",
|
||||
"dequal": "^2.0.3",
|
||||
"esbuild": "^0.19.5",
|
||||
"fast-glob": "^3.3.1",
|
||||
"filesize": "^10.0.8",
|
||||
"fs-extra": "^11.1.1",
|
||||
"get-port": "^7.0.0",
|
||||
"giget": "^1.1.3",
|
||||
"hookable": "^5.5.3",
|
||||
"is-wsl": "^3.0.0",
|
||||
"jiti": "^1.21.0",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
"linkedom": "^0.16.1",
|
||||
"magicast": "^0.3.4",
|
||||
"minimatch": "^9.0.3",
|
||||
"natural-compare": "^1.4.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nypm": "^0.3.6",
|
||||
"open": "^10.1.0",
|
||||
"ora": "^7.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.3",
|
||||
"unimport": "^3.4.0",
|
||||
"vite": "^5.2.8",
|
||||
"web-ext-run": "^0.2.0",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.1.1",
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/natural-compare": "^1.4.3",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"execa": "^8.0.1",
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^13.3.8",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"p-map": "^7.0.0",
|
||||
"publint": "^0.2.6",
|
||||
"tsup": "^8.0.1",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.3.2"
|
||||
},
|
||||
"changelog": {
|
||||
"excludeAuthors": [
|
||||
"aaronklinker1@gmail.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import { consola } from 'consola';
|
||||
import pMap from 'p-map';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
virtualModuleNames,
|
||||
virtualEntrypointModuleNames,
|
||||
} from '~/core/utils/virtual-modules';
|
||||
|
||||
const spinnerText = 'Building WXT';
|
||||
const spinner = ora(spinnerText).start();
|
||||
@@ -20,12 +24,7 @@ const preset = {
|
||||
dts: true,
|
||||
silent: true,
|
||||
sourcemap: false,
|
||||
external: [
|
||||
'virtual:user-unlisted-script',
|
||||
'virtual:user-content-script-isolated-world',
|
||||
'virtual:user-content-script-main-world',
|
||||
'virtual:user-background',
|
||||
],
|
||||
external: virtualEntrypointModuleNames.map((name) => `virtual:user-${name}`),
|
||||
} satisfies tsup.Options;
|
||||
|
||||
function spinnerPMap(configs: tsup.Options[]) {
|
||||
@@ -74,17 +73,13 @@ const config: tsup.Options[] = [
|
||||
},
|
||||
{
|
||||
...preset,
|
||||
entry: {
|
||||
'virtual/background-entrypoint': 'src/virtual/background-entrypoint.ts',
|
||||
'virtual/content-script-isolated-world-entrypoint':
|
||||
'src/virtual/content-script-isolated-world-entrypoint.ts',
|
||||
'virtual/content-script-main-world-entrypoint':
|
||||
'src/virtual/content-script-main-world-entrypoint.ts',
|
||||
'virtual/mock-browser': 'src/virtual/mock-browser.ts',
|
||||
'virtual/reload-html': 'src/virtual/reload-html.ts',
|
||||
'virtual/unlisted-script-entrypoint':
|
||||
'src/virtual/unlisted-script-entrypoint.ts',
|
||||
},
|
||||
entry: virtualModuleNames.reduce<Record<string, string>>(
|
||||
(acc, moduleName) => {
|
||||
acc[`virtual/${moduleName}`] = `src/virtual/${moduleName}.ts`;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
),
|
||||
format: ['esm'],
|
||||
splitting: false,
|
||||
dts: false,
|
||||
@@ -3,11 +3,12 @@ import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
|
||||
import { browser } from '~/browser';
|
||||
import { WxtStorageItem, storage } from '~/storage';
|
||||
|
||||
async function triggerUpdate() {
|
||||
await fakeBrowser.runtime.onInstalled.trigger({
|
||||
reason: 'update',
|
||||
temporary: false,
|
||||
});
|
||||
/**
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
||||
* calls within a single tick of the event loop, ie: a timeout of 0.
|
||||
*/
|
||||
async function waitForMigrations() {
|
||||
return new Promise((res) => setTimeout(res));
|
||||
}
|
||||
|
||||
describe('Storage Utils', () => {
|
||||
@@ -75,10 +76,7 @@ describe('Storage Utils', () => {
|
||||
|
||||
const actual = await storage.getItems(params);
|
||||
|
||||
expect(actual).toHaveLength(3);
|
||||
expected.forEach((item) => {
|
||||
expect(actual).toContainEqual(item);
|
||||
});
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -412,7 +410,7 @@ describe('Storage Utils', () => {
|
||||
3: migrateToV3,
|
||||
},
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
const actualValue = await item.getValue();
|
||||
const actualMeta = await item.getMeta();
|
||||
@@ -439,7 +437,7 @@ describe('Storage Utils', () => {
|
||||
3: migrateToV3,
|
||||
},
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
const actualValue = await item.getValue();
|
||||
const actualMeta = await item.getMeta();
|
||||
@@ -464,7 +462,7 @@ describe('Storage Utils', () => {
|
||||
2: migrateToV2,
|
||||
},
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
const actualValue = await item.getValue();
|
||||
const actualMeta = await item.getMeta();
|
||||
@@ -492,7 +490,7 @@ describe('Storage Utils', () => {
|
||||
3: migrateToV3,
|
||||
},
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
expect(migrateToV2).not.toBeCalled();
|
||||
expect(migrateToV3).not.toBeCalled();
|
||||
@@ -514,7 +512,7 @@ describe('Storage Utils', () => {
|
||||
3: migrateToV3,
|
||||
},
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
const actualValue = await item.getValue();
|
||||
const actualMeta = await item.getMeta();
|
||||
@@ -541,7 +539,7 @@ describe('Storage Utils', () => {
|
||||
defaultValue: 0,
|
||||
version: nextVersion,
|
||||
});
|
||||
await triggerUpdate();
|
||||
await waitForMigrations();
|
||||
|
||||
await expect(item.migrate()).rejects.toThrow(
|
||||
'Version downgrade detected (v2 -> v1) for "local:count"',
|
||||
@@ -51,7 +51,7 @@ export type {
|
||||
Identity,
|
||||
Idle,
|
||||
Management,
|
||||
Manifest,
|
||||
Manifest, // TODO: Export custom manifest types that are valid for both Chrome and Firefox.
|
||||
ContextMenus,
|
||||
Menus,
|
||||
NetworkStatus,
|
||||
@@ -116,6 +116,20 @@ describe('CLI', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect passing --port', async () => {
|
||||
const expectedPort = 3100;
|
||||
mockArgv('--port', String(expectedPort));
|
||||
await importCli();
|
||||
|
||||
expect(createServerMock).toBeCalledWith({
|
||||
dev: {
|
||||
server: {
|
||||
port: expectedPort,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect passing --debug', async () => {
|
||||
mockArgv('--debug');
|
||||
await importCli();
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CAC, Command } from 'cac';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
import { filterTruthy, toArray } from '~/core/utils/arrays';
|
||||
import { printHeader } from '~/core/utils/log';
|
||||
import { formatDuration } from '~/core/utils/time';
|
||||
import { ValidationError } from '~/core/utils/validation';
|
||||
@@ -56,8 +57,8 @@ export function getArrayFromFlags<T>(
|
||||
flags: any,
|
||||
name: string,
|
||||
): T[] | undefined {
|
||||
const array = [flags[name]].flat() as Array<T | undefined>;
|
||||
const result = array.filter((item) => item != null) as T[];
|
||||
const array = toArray<T | undefined>(flags[name]);
|
||||
const result = filterTruthy(array);
|
||||
return result.length ? result : undefined;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ cli
|
||||
.option('-c, --config <file>', 'use specified config file')
|
||||
.option('-m, --mode <mode>', 'set env mode')
|
||||
.option('-b, --browser <browser>', 'specify a browser')
|
||||
.option('-p, --port <port>', 'specify a port for the dev server')
|
||||
.option(
|
||||
'-e, --filter-entrypoint <entrypoint>',
|
||||
'only build specific entrypoints',
|
||||
@@ -35,6 +36,14 @@ cli
|
||||
configFile: flags.config,
|
||||
debug: flags.debug,
|
||||
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
|
||||
dev:
|
||||
flags.port == null
|
||||
? undefined
|
||||
: {
|
||||
server: {
|
||||
port: parseInt(flags.port),
|
||||
},
|
||||
},
|
||||
});
|
||||
await server.start();
|
||||
return { isOngoing: true };
|
||||
@@ -57,6 +66,7 @@ cli
|
||||
.option('--mv3', 'target manifest v3')
|
||||
.option('--mv2', 'target manifest v2')
|
||||
.option('--analyze', 'visualize extension bundle')
|
||||
.option('--analyze-open', 'automatically open stats.html in browser')
|
||||
.action(
|
||||
wrapAction(async (root, flags) => {
|
||||
await build({
|
||||
@@ -66,7 +76,12 @@ cli
|
||||
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : undefined,
|
||||
configFile: flags.config,
|
||||
debug: flags.debug,
|
||||
analysis: flags.analyze ? { enabled: true } : undefined,
|
||||
analysis: flags.analyze
|
||||
? {
|
||||
enabled: true,
|
||||
open: flags.analyzeOpen,
|
||||
}
|
||||
: undefined,
|
||||
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
|
||||
});
|
||||
}),
|
||||
@@ -10,6 +10,29 @@ import { createLocationWatcher } from './location-watcher';
|
||||
*
|
||||
* It also provides several utilities like `ctx.setTimeout` and `ctx.setInterval` that should be used in
|
||||
* content scripts instead of `window.setTimeout` or `window.setInterval`.
|
||||
*
|
||||
* To create context for testing, you can use the class's constructor:
|
||||
*
|
||||
* ```ts
|
||||
* import { ContentScriptContext } from 'wxt/client';
|
||||
*
|
||||
* test("storage listener should be removed when context is invalidated", () => {
|
||||
* const ctx = new ContentScriptContext('test');
|
||||
* const item = storage.defineItem("local:count", { defaultValue: 0 });
|
||||
* const watcher = vi.fn();
|
||||
*
|
||||
* const unwatch = item.watch(watcher);
|
||||
* ctx.onInvalidated(unwatch); // Listen for invalidate here
|
||||
*
|
||||
* await item.setValue(1);
|
||||
* expect(watcher).toBeCalledTimes(1);
|
||||
* expect(watcher).toBeCalledWith(1, 0);
|
||||
*
|
||||
* ctx.notifyInvalidated(); // Use this function to invalidate the context
|
||||
* await item.setValue(2);
|
||||
* expect(watcher).toBeCalledTimes(1);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class ContentScriptContext implements AbortController {
|
||||
private static SCRIPT_STARTED_MESSAGE_TYPE = 'wxt:content-script-started';
|
||||
@@ -72,7 +72,7 @@ describe('Content Script UIs', () => {
|
||||
it('should load a shadow root to the page', async () => {
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
position: 'inline',
|
||||
name: 'test',
|
||||
name: 'test-component',
|
||||
onMount(uiContainer) {
|
||||
appendTestApp(uiContainer);
|
||||
},
|
||||
@@ -80,7 +80,7 @@ describe('Content Script UIs', () => {
|
||||
ui.mount();
|
||||
|
||||
expect(
|
||||
document.querySelector('test[data-wxt-shadow-root]'),
|
||||
document.querySelector('test-component[data-wxt-shadow-root]'),
|
||||
).not.toBeNull();
|
||||
expect(ui.shadow.querySelector('app')).not.toBeNull();
|
||||
});
|
||||
@@ -94,7 +94,7 @@ describe('Content Script UIs', () => {
|
||||
async (input, expected) => {
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
position: 'inline',
|
||||
name: 'test',
|
||||
name: 'test-component',
|
||||
mode: input,
|
||||
onMount: appendTestApp,
|
||||
});
|
||||
@@ -404,4 +404,62 @@ describe('Content Script UIs', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mounted value', () => {
|
||||
describe('integrated', () => {
|
||||
it('should set the mounted value based on the onMounted return value', () => {
|
||||
const expected = Symbol();
|
||||
|
||||
const ui = createIntegratedUi(new ContentScriptContext('test'), {
|
||||
position: 'inline',
|
||||
onMount: () => expected,
|
||||
});
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
|
||||
ui.mount();
|
||||
expect(ui.mounted).toBe(expected);
|
||||
|
||||
ui.remove();
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('iframe', () => {
|
||||
it('should set the mounted value based on the onMounted return value', async () => {
|
||||
const expected = Symbol();
|
||||
|
||||
const ui = createIframeUi(new ContentScriptContext('test'), {
|
||||
page: '',
|
||||
position: 'inline',
|
||||
onMount: () => expected,
|
||||
});
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
|
||||
ui.mount();
|
||||
expect(ui.mounted).toBe(expected);
|
||||
|
||||
ui.remove();
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shadow-root', () => {
|
||||
it('should set the mounted value based on the onMounted return value', async () => {
|
||||
const expected = Symbol();
|
||||
|
||||
const ui = await createShadowRootUi(new ContentScriptContext('test'), {
|
||||
name: 'test-component',
|
||||
position: 'inline',
|
||||
onMount: () => expected,
|
||||
});
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
|
||||
ui.mount();
|
||||
expect(ui.mounted).toBe(expected);
|
||||
|
||||
ui.remove();
|
||||
expect(ui.mounted).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -35,12 +35,15 @@ export function createIntegratedUi<TMounted>(
|
||||
const remove = () => {
|
||||
options.onRemove?.(mounted);
|
||||
wrapper.remove();
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
|
||||
return {
|
||||
mounted,
|
||||
get mounted() {
|
||||
return mounted;
|
||||
},
|
||||
wrapper,
|
||||
mount,
|
||||
remove,
|
||||
@@ -71,12 +74,15 @@ export function createIframeUi<TMounted>(
|
||||
const remove = () => {
|
||||
options.onRemove?.(mounted);
|
||||
wrapper.remove();
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
|
||||
return {
|
||||
mounted,
|
||||
get mounted() {
|
||||
return mounted;
|
||||
},
|
||||
iframe,
|
||||
wrapper,
|
||||
mount,
|
||||
@@ -116,7 +122,7 @@ export async function createShadowRootUi<TMounted>(
|
||||
});
|
||||
shadowHost.setAttribute('data-wxt-shadow-root', '');
|
||||
|
||||
let mounted: TMounted;
|
||||
let mounted: TMounted | undefined;
|
||||
|
||||
const mount = () => {
|
||||
// Add shadow root element to DOM
|
||||
@@ -134,6 +140,8 @@ export async function createShadowRootUi<TMounted>(
|
||||
// Remove children from uiContainer
|
||||
while (uiContainer.lastChild)
|
||||
uiContainer.removeChild(uiContainer.lastChild);
|
||||
// Clear mounted value
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
@@ -144,7 +152,9 @@ export async function createShadowRootUi<TMounted>(
|
||||
uiContainer,
|
||||
mount,
|
||||
remove,
|
||||
mounted: mounted!,
|
||||
get mounted() {
|
||||
return mounted;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ export interface ContentScriptAnchoredOptions {
|
||||
* In combination with `anchor`, decide how to add the UI to the DOM.
|
||||
*
|
||||
* - `"last"` (default) - Add the UI as the last child of the `anchor` element
|
||||
* - `"first"` - Add the UI as the last child of the `anchor` element
|
||||
* - `"first"` - Add the UI as the first child of the `anchor` element
|
||||
* - `"replace"` - Replace the `anchor` element with the UI.
|
||||
* - `"before"` - Add the UI as the sibling before the `anchor` element
|
||||
* - `"after"` - Add the UI as the sibling after the `anchor` element
|
||||
@@ -0,0 +1,12 @@
|
||||
import { a } from './test';
|
||||
|
||||
function defineSomething<T>(config: T): T {
|
||||
return config;
|
||||
}
|
||||
|
||||
export default defineSomething({
|
||||
option: 'some value',
|
||||
main: () => {
|
||||
console.log('main', a);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
console.log('Side-effect in test.ts');
|
||||
export const a = 'a';
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createViteBuilder } from '../index';
|
||||
import { fakeResolvedConfig } from '~/core/utils/testing/fake-objects';
|
||||
import { createHooks } from 'hookable';
|
||||
|
||||
describe('Vite Builder', () => {
|
||||
describe('importEntrypoint', () => {
|
||||
it('should import entrypoints, removing runtime values (like the main function)', async () => {
|
||||
const {
|
||||
default: { main: _, ...expected },
|
||||
} = await import('./fixtures/module');
|
||||
const builder = await createViteBuilder(
|
||||
fakeResolvedConfig({ root: __dirname }),
|
||||
createHooks(),
|
||||
);
|
||||
const actual = await builder.importEntrypoint<{ default: any }>(
|
||||
'./fixtures/module.ts',
|
||||
);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,23 +2,29 @@ import type * as vite from 'vite';
|
||||
import {
|
||||
BuildStepOutput,
|
||||
Entrypoint,
|
||||
InlineConfig,
|
||||
ResolvedConfig,
|
||||
UserConfig,
|
||||
VirtualEntrypointType,
|
||||
WxtBuilder,
|
||||
WxtBuilderServer,
|
||||
WxtDevServer,
|
||||
WxtHooks,
|
||||
} from '~/types';
|
||||
import * as wxtPlugins from './plugins';
|
||||
import {
|
||||
getEntrypointBundlePath,
|
||||
isHtmlEntrypoint,
|
||||
} from '~/core/utils/entrypoints';
|
||||
import {
|
||||
VirtualEntrypointType,
|
||||
VirtualModuleId,
|
||||
} from '~/core/utils/virtual-modules';
|
||||
import { Hookable } from 'hookable';
|
||||
import { toArray } from '~/core/utils/arrays';
|
||||
import { safeVarName } from '~/core/utils/strings';
|
||||
|
||||
export async function createViteBuilder(
|
||||
inlineConfig: InlineConfig,
|
||||
userConfig: UserConfig,
|
||||
wxtConfig: Omit<ResolvedConfig, 'builder'>,
|
||||
wxtConfig: ResolvedConfig,
|
||||
hooks: Hookable<WxtHooks>,
|
||||
server?: WxtDevServer,
|
||||
): Promise<WxtBuilder> {
|
||||
const vite = await import('vite');
|
||||
|
||||
@@ -26,14 +32,7 @@ export async function createViteBuilder(
|
||||
* Returns the base vite config shared by all builds based on the inline and user config.
|
||||
*/
|
||||
const getBaseConfig = async () => {
|
||||
const resolvedInlineConfig =
|
||||
(await inlineConfig.vite?.(wxtConfig.env)) ?? {};
|
||||
const resolvedUserConfig = (await userConfig.vite?.(wxtConfig.env)) ?? {};
|
||||
|
||||
const config: vite.InlineConfig = vite.mergeConfig(
|
||||
resolvedUserConfig,
|
||||
resolvedInlineConfig,
|
||||
);
|
||||
const config: vite.InlineConfig = await wxtConfig.vite(wxtConfig.env);
|
||||
|
||||
config.root = wxtConfig.root;
|
||||
config.configFile = false;
|
||||
@@ -41,6 +40,8 @@ export async function createViteBuilder(
|
||||
config.mode = wxtConfig.mode;
|
||||
|
||||
config.build ??= {};
|
||||
config.publicDir = wxtConfig.publicDir;
|
||||
config.build.copyPublicDir = false;
|
||||
config.build.outDir = wxtConfig.outDir;
|
||||
config.build.emptyOutDir = false;
|
||||
// Disable minification for the dev command
|
||||
@@ -55,13 +56,10 @@ export async function createViteBuilder(
|
||||
config.plugins ??= [];
|
||||
config.plugins.push(
|
||||
wxtPlugins.download(wxtConfig),
|
||||
wxtPlugins.devHtmlPrerender(wxtConfig),
|
||||
wxtPlugins.devHtmlPrerender(wxtConfig, server),
|
||||
wxtPlugins.unimport(wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('background', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig),
|
||||
wxtPlugins.devServerGlobals(wxtConfig),
|
||||
wxtPlugins.resolveVirtualModules(wxtConfig),
|
||||
wxtPlugins.devServerGlobals(wxtConfig, server),
|
||||
wxtPlugins.tsconfigPaths(wxtConfig),
|
||||
wxtPlugins.noopBackground(),
|
||||
wxtPlugins.globals(wxtConfig),
|
||||
@@ -90,14 +88,22 @@ export async function createViteBuilder(
|
||||
plugins.push(wxtPlugins.cssEntrypoints(entrypoint, wxtConfig));
|
||||
}
|
||||
|
||||
const iifeReturnValueName = safeVarName(entrypoint.name);
|
||||
const libMode: vite.UserConfig = {
|
||||
mode: wxtConfig.mode,
|
||||
plugins,
|
||||
esbuild: {
|
||||
// Add a footer with the returned value so it can return values to `scripting.executeScript`
|
||||
// Footer is added apart of esbuild to make sure it's not minified. It
|
||||
// get's removed if added to `build.rollupOptions.output.footer`
|
||||
// See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
|
||||
footer: iifeReturnValueName + ';',
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry,
|
||||
formats: ['iife'],
|
||||
name: '_',
|
||||
name: iifeReturnValueName,
|
||||
fileName: entrypoint.name,
|
||||
},
|
||||
rollupOptions: {
|
||||
@@ -197,6 +203,22 @@ export async function createViteBuilder(
|
||||
return {
|
||||
name: 'Vite',
|
||||
version: vite.version,
|
||||
async importEntrypoint(url) {
|
||||
const baseConfig = await getBaseConfig();
|
||||
const envConfig: vite.InlineConfig = {
|
||||
plugins: [
|
||||
wxtPlugins.webextensionPolyfillMock(wxtConfig),
|
||||
wxtPlugins.removeEntrypointMainFunction(wxtConfig, url),
|
||||
],
|
||||
};
|
||||
const config = vite.mergeConfig(baseConfig, envConfig);
|
||||
const server = await vite.createServer(config);
|
||||
await server.listen();
|
||||
const runtime = await vite.createViteRuntime(server, { hmr: false });
|
||||
const module = await runtime.executeUrl(url);
|
||||
await server.close();
|
||||
return module.default;
|
||||
},
|
||||
async build(group) {
|
||||
let entryConfig;
|
||||
if (Array.isArray(group)) entryConfig = getMultiPageConfig(group);
|
||||
@@ -205,6 +227,11 @@ export async function createViteBuilder(
|
||||
else entryConfig = getLibModeConfig(group);
|
||||
|
||||
const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
|
||||
await hooks.callHook(
|
||||
'vite:build:extendConfig',
|
||||
toArray(group),
|
||||
buildConfig,
|
||||
);
|
||||
const result = await vite.build(buildConfig);
|
||||
return {
|
||||
entrypoints: group,
|
||||
@@ -221,9 +248,9 @@ export async function createViteBuilder(
|
||||
},
|
||||
};
|
||||
const baseConfig = await getBaseConfig();
|
||||
const viteServer = await vite.createServer(
|
||||
vite.mergeConfig(baseConfig, serverConfig),
|
||||
);
|
||||
const finalConfig = vite.mergeConfig(baseConfig, serverConfig);
|
||||
await hooks.callHook('vite:devServer:extendConfig', finalConfig);
|
||||
const viteServer = await vite.createServer(finalConfig);
|
||||
|
||||
const server: WxtBuilderServer = {
|
||||
async listen() {
|
||||
@@ -277,7 +304,10 @@ function getRollupEntry(entrypoint: Entrypoint): string {
|
||||
: 'content-script-isolated-world';
|
||||
break;
|
||||
}
|
||||
return virtualEntrypointType
|
||||
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
|
||||
: entrypoint.inputPath;
|
||||
|
||||
if (virtualEntrypointType) {
|
||||
const moduleId: VirtualModuleId = `virtual:wxt-${virtualEntrypointType}-entrypoint`;
|
||||
return `${moduleId}?${entrypoint.inputPath}`;
|
||||
}
|
||||
return entrypoint.inputPath;
|
||||
}
|
||||
@@ -5,9 +5,7 @@ import path from 'node:path';
|
||||
|
||||
let increment = 0;
|
||||
|
||||
export function bundleAnalysis(
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
export function bundleAnalysis(config: ResolvedConfig): vite.Plugin {
|
||||
return visualizer({
|
||||
template: 'raw-data',
|
||||
filename: path.resolve(
|
||||
@@ -14,7 +14,7 @@ import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
*/
|
||||
export function cssEntrypoints(
|
||||
entrypoint: Entrypoint,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
config: ResolvedConfig,
|
||||
): vite.Plugin {
|
||||
return {
|
||||
name: 'wxt:css-entrypoint',
|
||||