Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d97164c20 | |||
| 6d526e7043 | |||
| 946072fc5a | |||
| 6b8dfdf02d | |||
| ba85fdf470 | |||
| 295f8601e3 | |||
| a38ccd8624 | |||
| b2dcec2f8a | |||
| e8355029d9 | |||
| 2b374b98d2 | |||
| d14d76a0c8 | |||
| d0f1c7065c | |||
| 3edb66d76d | |||
| bba8cdace1 | |||
| 22f82a67ee | |||
| b32c60ca73 | |||
| c93620cba5 | |||
| dd05ed4d4c | |||
| 21636defda | |||
| bb022646e3 | |||
| 37ad2c7ceb | |||
| b992631e00 | |||
| 35a5cc6717 | |||
| 310b56fe5d | |||
| 148234382e | |||
| c1a97b77b0 | |||
| 557ccb4854 | |||
| 83e7e8aab9 | |||
| 7bf490c59d | |||
| 9670babdbb | |||
| 42dfc2d5cf | |||
| 1527f0e275 | |||
| 66bd957ee0 | |||
| 72e3c5baa2 | |||
| 9318346ab3 | |||
| 1d6aceab0a | |||
| c1b5a9d270 | |||
| f0b2a12724 | |||
| c45677ed48 | |||
| 7a3f075e21 | |||
| 1e12ce2a0d | |||
| eca30296a7 | |||
| 41fa320815 | |||
| 873c24fa50 | |||
| d92db5ba8f | |||
| 1a6fe33f97 |
@@ -9,3 +9,7 @@ updates:
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
@@ -10,25 +10,25 @@ jobs:
|
||||
formatting:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm format:check
|
||||
lint:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm lint
|
||||
type-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm compile
|
||||
validate-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm tsc --noEmit
|
||||
working-directory: demo
|
||||
@@ -41,17 +41,17 @@ jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v3
|
||||
- uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
windows-tests:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm test run
|
||||
template:
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
- vanilla
|
||||
- vue
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
- run: npm i
|
||||
|
||||
+159
@@ -1,5 +1,164 @@
|
||||
# Changelog
|
||||
|
||||
## v0.16.6
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.5...v0.16.6)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add option to customize the analysis artifacts output ([#431](https://github.com/wxt-dev/wxt/pull/431))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use `insertBefore` on mounting content script UI ([ba85fdf](https://github.com/wxt-dev/wxt/commit/ba85fdf))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Use `Element.prepend` on mounting UI ([295f860](https://github.com/wxt-dev/wxt/commit/295f860))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix `createShadowRootUi` unmount calls ([946072f](https://github.com/wxt-dev/wxt/commit/946072f))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Enable skipped test since it works now ([6b8dfdf](https://github.com/wxt-dev/wxt/commit/6b8dfdf))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Lionelhorn
|
||||
- Okou
|
||||
|
||||
## v0.16.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.4...v0.16.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Support node 20 when running `wxt submit` ([e835502](https://github.com/wxt-dev/wxt/commit/e835502))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Remove "coming soon" from automated publishing feature ([2b374b9](https://github.com/wxt-dev/wxt/commit/2b374b9))
|
||||
|
||||
## v0.16.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.3...v0.16.4)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Automatically convert MV3 `web_accessible_resources` to MV2 ([#423](https://github.com/wxt-dev/wxt/pull/423))
|
||||
- Add option to customize the analysis output filename ([#426](https://github.com/wxt-dev/wxt/pull/426))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Don't use immer for `transformManifest` ([#424](https://github.com/wxt-dev/wxt/pull/424))
|
||||
- Exclude analysis files from the build summary ([#425](https://github.com/wxt-dev/wxt/pull/425))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix fake path in test data generator ([d0f1c70](https://github.com/wxt-dev/wxt/commit/d0f1c70))
|
||||
|
||||
## v0.16.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.2...v0.16.3)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Hooks ([#419](https://github.com/wxt-dev/wxt/pull/419))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **init:** Use `ungh` to prevent rate limits when loading templates ([37ad2c7](https://github.com/wxt-dev/wxt/commit/37ad2c7))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix typo of intuitive ([#415](https://github.com/wxt-dev/wxt/pull/415))
|
||||
- Fix typo of opinionated ([#416](https://github.com/wxt-dev/wxt/pull/416))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add dependabot for github actions ([#404](https://github.com/wxt-dev/wxt/pull/404))
|
||||
- **deps-dev:** Bump happy-dom from 12.10.3 to 13.3.8 ([#411](https://github.com/wxt-dev/wxt/pull/411))
|
||||
- **deps-dev:** Bump typescript from 5.3.2 to 5.3.3 ([#409](https://github.com/wxt-dev/wxt/pull/409))
|
||||
- Register global `wxt` instance ([#418](https://github.com/wxt-dev/wxt/pull/418))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Chen Hua ([@hcljsq](https://github.com/hcljsq))
|
||||
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||
|
||||
## v0.16.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.1...v0.16.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Don't crash background service worker when using `import.meta.url` ([#402](https://github.com/wxt-dev/wxt/pull/402))
|
||||
|
||||
## v0.16.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.0...v0.16.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Don't require config to run `wxt submit init` ([9318346](https://github.com/wxt-dev/wxt/commit/9318346))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add premid extension to homepage ([#399](https://github.com/wxt-dev/wxt/pull/399))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **templates:** Upgrade to wxt `^0.16.0` ([f0b2a12](https://github.com/wxt-dev/wxt/commit/f0b2a12))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||
|
||||
## v0.16.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.4...v0.16.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ ESM background support ([#398](https://github.com/wxt-dev/wxt/pull/398))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Document how to opt into ESM ([1e12ce2](https://github.com/wxt-dev/wxt/commit/1e12ce2))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps-dev:** Bump lint-staged from 15.2.0 to 15.2.1 ([#395](https://github.com/wxt-dev/wxt/pull/395))
|
||||
- **deps-dev:** Bump p-map from 7.0.0 to 7.0.1 ([#396](https://github.com/wxt-dev/wxt/pull/396))
|
||||
- **deps-dev:** Bump @vitest/coverage-v8 from 1.0.1 to 1.2.2 ([#397](https://github.com/wxt-dev/wxt/pull/397))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
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
|
||||
```
|
||||
- After:
|
||||
```
|
||||
.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.
|
||||
|
||||
## v0.15.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.3...v0.15.4)
|
||||
|
||||
@@ -55,14 +55,11 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📂 File based entrypoints
|
||||
- 🚔 TypeScript
|
||||
- 🦾 Auto-imports
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
- 🤖 Automated publishing
|
||||
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
|
||||
- 🖍️ Quickly bootstrap a new project
|
||||
- 📏 Bundle analysis
|
||||
|
||||
### Coming Soon
|
||||
|
||||
- 🤖 Automated publishing
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
import messages from 'public/_locales/en/messages.json';
|
||||
|
||||
export default defineBackground(() => {
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
console.log({
|
||||
browser: import.meta.env.BROWSER,
|
||||
chrome: import.meta.env.CHROME,
|
||||
firefox: import.meta.env.FIREFOX,
|
||||
manifestVersion: import.meta.env.MANIFEST_VERSION,
|
||||
messages,
|
||||
});
|
||||
export default defineBackground({
|
||||
// type: 'module',
|
||||
|
||||
// @ts-expect-error: should only accept entrypoints or public assets
|
||||
browser.runtime.getURL('/');
|
||||
browser.runtime.getURL('/background.js');
|
||||
browser.runtime.getURL('/icon/128.png');
|
||||
browser.runtime.getURL('/example.html#hash');
|
||||
browser.runtime.getURL('/example.html?query=param');
|
||||
// @ts-expect-error: should only allow hashes/query params on HTML files
|
||||
browser.runtime.getURL('/icon-128.png?query=param');
|
||||
main() {
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
console.log({
|
||||
url: import.meta.url,
|
||||
browser: import.meta.env.BROWSER,
|
||||
chrome: import.meta.env.CHROME,
|
||||
firefox: import.meta.env.FIREFOX,
|
||||
manifestVersion: import.meta.env.MANIFEST_VERSION,
|
||||
messages,
|
||||
});
|
||||
|
||||
// @ts-expect-error: should only accept known message names
|
||||
browser.i18n.getMessage('test');
|
||||
browser.i18n.getMessage('prompt_for_name');
|
||||
browser.i18n.getMessage('hello', 'Aaron');
|
||||
browser.i18n.getMessage('bye', ['Aaron']);
|
||||
browser.i18n.getMessage('@@extension_id');
|
||||
// @ts-expect-error: should only accept entrypoints or public assets
|
||||
browser.runtime.getURL('/');
|
||||
browser.runtime.getURL('/background.js');
|
||||
browser.runtime.getURL('/icon/128.png');
|
||||
browser.runtime.getURL('/example.html#hash');
|
||||
browser.runtime.getURL('/example.html?query=param');
|
||||
// @ts-expect-error: should only allow hashes/query params on HTML files
|
||||
browser.runtime.getURL('/icon-128.png?query=param');
|
||||
|
||||
console.log('WXT MODE:', {
|
||||
MODE: import.meta.env.MODE,
|
||||
DEV: import.meta.env.DEV,
|
||||
PROD: import.meta.env.PROD,
|
||||
});
|
||||
// @ts-expect-error: should only accept known message names
|
||||
browser.i18n.getMessage('test');
|
||||
browser.i18n.getMessage('prompt_for_name');
|
||||
browser.i18n.getMessage('hello', 'Aaron');
|
||||
browser.i18n.getMessage('bye', ['Aaron']);
|
||||
browser.i18n.getMessage('@@extension_id');
|
||||
|
||||
storage.setItem('session:startTime', Date.now());
|
||||
console.log('WXT MODE:', {
|
||||
MODE: import.meta.env.MODE,
|
||||
DEV: import.meta.env.DEV,
|
||||
PROD: import.meta.env.PROD,
|
||||
});
|
||||
|
||||
storage.setItem('session:startTime', Date.now());
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ const chromeExtensionIds = [
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
];
|
||||
|
||||
const { data } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -90,6 +90,7 @@ export default defineConfig({
|
||||
{ text: 'Assets', link: '/guide/assets.md' },
|
||||
{ text: 'Content Script UI', link: '/guide/content-script-ui.md' },
|
||||
{ text: 'Multiple Browsers', link: '/guide/multiple-browsers.md' },
|
||||
{ text: 'ES Modules', link: '/guide/esm.md' },
|
||||
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
|
||||
{ text: 'Vite', link: '/guide/vite.md' },
|
||||
{ text: 'Remote Code', link: '/guide/remote-code.md' },
|
||||
|
||||
@@ -233,7 +233,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Unmount the app when the UI is removed
|
||||
app.unmount();
|
||||
app?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -268,7 +268,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (root) => {
|
||||
// Unmount the root when the UI is removed
|
||||
root.unmount();
|
||||
root?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -303,7 +303,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app.$destroy();
|
||||
app?.$destroy();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -335,7 +335,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (unmount) => {
|
||||
// Unmount the app when the UI is removed
|
||||
unmount();
|
||||
unmount?.();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -83,6 +83,6 @@ Normally, to manually reload an extension, you have to visit `chrome://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`.
|
||||
|
||||
:::note
|
||||
:::info
|
||||
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
|
||||
:::
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# ES Modules
|
||||
|
||||
Configure entrypoints to use ESM at runtime.
|
||||
|
||||
Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind.
|
||||
|
||||
## HTML Pages <Badge type="warning" text="≥0.0.1" />
|
||||
|
||||
In general, you should always make HTML pages import ESM scripts, unless you need to support old browsers.
|
||||
|
||||
To make a script ESM, add `type="module"`:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<script src="./main.ts"></script> <!-- [!code --] -->
|
||||
<script src="./main.ts" type="module"></script> <!-- [!code ++] -->
|
||||
```
|
||||
|
||||
## Background <Badge type="warning" text="≥0.16.0" />
|
||||
|
||||
In your background script, set `type: "module"`:
|
||||
|
||||
```ts
|
||||
export default defineBackground({
|
||||
type: 'module', // !code ++
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
Only MV3 support ESM background scripts/service workers. When targetting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
|
||||
:::
|
||||
|
||||
## Content Scripts
|
||||
|
||||
Coming soon. Follow [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for updates.
|
||||
@@ -145,5 +145,6 @@ If you're an experienced web extension developer and think the dev manifest look
|
||||
You're ready to build your web extension!
|
||||
|
||||
- Learn how to [add entrypoints](./entrypoints) like the popup, options page, or content scripts
|
||||
- Configure your entrypoints to [use ESM](./esm) at runtime
|
||||
- [Configure WXT](./configuration) by creating a `wxt.config.ts` file
|
||||
- Checkout [example projects](https://github.com/wxt-dev/wxt-examples) to see how to perfom common tasks with WXT
|
||||
|
||||
@@ -7,7 +7,7 @@ head:
|
||||
|
||||
# Introduction
|
||||
|
||||
WXT is a free and open source framework for building web extensions in an conventional, intuative, and safe way **_for all browsers_**.
|
||||
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
|
||||
|
||||
WXT is based of [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
|
||||
|
||||
@@ -15,7 +15,7 @@ WXT is based of [Nuxt](https://nuxt.com), and aims to provide the same great DX
|
||||
|
||||
## Conventions
|
||||
|
||||
WXT is an optionated framework. This helps keep projects consistent and easy to pick up.
|
||||
WXT is an opinionated framework. This helps keep projects consistent and easy to pick up.
|
||||
|
||||
- **Generated manifest**: Based on your project's file structure
|
||||
- **Entrypoint configuration**: Configure entrypoints from the same file they're declare in
|
||||
|
||||
+8
-8
@@ -47,11 +47,9 @@ features:
|
||||
details: Nuxt-like auto-imports to speed up development.
|
||||
link: /guide/auto-imports
|
||||
linkText: Read docs
|
||||
- icon: ⬇️
|
||||
title: Bundle Remote Code
|
||||
details: Downloads and bundles remote code imported from URLs.
|
||||
link: /guide/remote-code
|
||||
linkText: Read docs
|
||||
- icon: 🤖
|
||||
title: Automated Publishing
|
||||
details: Automatically zip, upload, submit, and publish extensions.
|
||||
- icon: 🎨
|
||||
title: Frontend Framework Agnostic
|
||||
details: Works with any front-end framework with a Vite plugin.
|
||||
@@ -65,9 +63,11 @@ features:
|
||||
- icon: 📏
|
||||
title: Bundle Analysis
|
||||
details: Tools for analyizing the final extension bundle and minimizing your extension's size.
|
||||
- icon: 🤖
|
||||
title: Automated Publishing
|
||||
details: 'Coming soon. Automatically zip, upload, and release extensions.'
|
||||
- icon: ⬇️
|
||||
title: Bundle Remote Code
|
||||
details: Downloads and bundles remote code imported from URLs.
|
||||
link: /guide/remote-code
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
<section class="vp-doc">
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { resetBundleIncrement } from '~/core/builders/vite/plugins';
|
||||
|
||||
describe('Analysis', () => {
|
||||
beforeEach(() => {
|
||||
resetBundleIncrement();
|
||||
});
|
||||
|
||||
it('should outptut a stats.html with no part files by default', 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,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats.html')).toBe(true);
|
||||
expect(await project.fileExists('.output/chrome-mv3/stats-0.json')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should save part files 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,
|
||||
keepArtifacts: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats.html')).toBe(true);
|
||||
expect(await project.fileExists('stats-0.json')).toBe(true);
|
||||
expect(await project.fileExists('stats-1.json')).toBe(true);
|
||||
});
|
||||
|
||||
it('should support customizing the stats output directory', 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,
|
||||
outputFile: 'stats/bundle.html',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats/bundle.html')).toBe(true);
|
||||
});
|
||||
|
||||
it('should place artifacts next to the custom output file', 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,
|
||||
outputFile: 'stats/bundle.html',
|
||||
keepArtifacts: true,
|
||||
},
|
||||
});
|
||||
console.log(project.root);
|
||||
|
||||
expect(await project.fileExists('stats/bundle.html')).toBe(true);
|
||||
expect(await project.fileExists('stats/bundle-0.json')).toBe(true);
|
||||
expect(await project.fileExists('stats/bundle-1.json')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { WxtHooks } from '~/types';
|
||||
|
||||
const hooks: WxtHooks = {
|
||||
ready: vi.fn(),
|
||||
'build:before': vi.fn(),
|
||||
'build:done': vi.fn(),
|
||||
'build:manifestGenerated': vi.fn(),
|
||||
'entrypoints:resolved': vi.fn(),
|
||||
'entrypoints:grouped': vi.fn(),
|
||||
};
|
||||
|
||||
function expectHooksToBeCalled(called: Record<keyof WxtHooks, boolean>) {
|
||||
Object.keys(hooks).forEach((key) => {
|
||||
const hookName = key as keyof WxtHooks;
|
||||
const times = called[hookName] ? 1 : 0;
|
||||
expect(
|
||||
hooks[hookName],
|
||||
`Expected "${hookName}" to be called ${times} time(s)`,
|
||||
).toBeCalledTimes(called[hookName] ? 1 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Hooks', () => {
|
||||
beforeEach(() => {
|
||||
Object.values(hooks).forEach((fn) => fn.mockReset());
|
||||
});
|
||||
|
||||
it('prepare should call hooks', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
await project.prepare({ hooks });
|
||||
|
||||
expectHooksToBeCalled({
|
||||
ready: true,
|
||||
'build:before': false,
|
||||
'build:done': false,
|
||||
'build:manifestGenerated': false,
|
||||
'entrypoints:grouped': false,
|
||||
'entrypoints:resolved': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('build should call hooks', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
await project.build({ hooks });
|
||||
|
||||
expectHooksToBeCalled({
|
||||
ready: true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('zip should call hooks', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
await project.zip({ hooks });
|
||||
|
||||
expectHooksToBeCalled({
|
||||
ready: true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('server.start should call hooks', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
const server = await project.startServer({
|
||||
hooks,
|
||||
runner: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
await server.stop();
|
||||
|
||||
expectHooksToBeCalled({
|
||||
ready: true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -227,4 +227,169 @@ describe('Output Directory Structure', () => {
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate ESM background script when type=module', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'utils/log.ts',
|
||||
`export function logHello(name: string) {
|
||||
console.log(\`Hello \${name}!\`);
|
||||
}`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
`export default defineBackground({
|
||||
type: "module",
|
||||
main() {
|
||||
logHello("background");
|
||||
},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/popup/index.html',
|
||||
`<html>
|
||||
<head>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</head>
|
||||
</html>`,
|
||||
);
|
||||
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
|
||||
|
||||
await project.build({
|
||||
experimental: {
|
||||
// Simplify the build output for comparison
|
||||
includeBrowserPolyfill: false,
|
||||
},
|
||||
vite: () => ({
|
||||
build: {
|
||||
// Make output for snapshot readible
|
||||
minify: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/background.js
|
||||
----------------------------------------
|
||||
import { l as logHello } from "./chunks/log-bezs0tt4.js";
|
||||
function defineBackground(arg) {
|
||||
if (typeof arg === "function")
|
||||
return { main: arg };
|
||||
return arg;
|
||||
}
|
||||
const definition = defineBackground({
|
||||
type: "module",
|
||||
main() {
|
||||
logHello("background");
|
||||
}
|
||||
});
|
||||
chrome;
|
||||
function print(method, ...args) {
|
||||
return;
|
||||
}
|
||||
var logger = {
|
||||
debug: (...args) => print(console.debug, ...args),
|
||||
log: (...args) => print(console.log, ...args),
|
||||
warn: (...args) => print(console.warn, ...args),
|
||||
error: (...args) => print(console.error, ...args)
|
||||
};
|
||||
try {
|
||||
const res = definition.main();
|
||||
if (res instanceof Promise) {
|
||||
console.warn(
|
||||
"The background's main() function return a promise, but it must be synchonous"
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("The background crashed on startup!");
|
||||
throw err;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should generate IIFE background script when type=undefined', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'utils/log.ts',
|
||||
`export function logHello(name: string) {
|
||||
console.log(\`Hello \${name}!\`);
|
||||
}`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
`export default defineBackground({
|
||||
main() {
|
||||
logHello("background");
|
||||
},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/popup/index.html',
|
||||
`<html>
|
||||
<head>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</head>
|
||||
</html>`,
|
||||
);
|
||||
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
|
||||
|
||||
await project.build({
|
||||
experimental: {
|
||||
// Simplify the build output for comparison
|
||||
includeBrowserPolyfill: false,
|
||||
},
|
||||
vite: () => ({
|
||||
build: {
|
||||
// Make output for snapshot readible
|
||||
minify: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/background.js
|
||||
----------------------------------------
|
||||
(function() {
|
||||
"use strict";
|
||||
function defineBackground(arg) {
|
||||
if (typeof arg === "function")
|
||||
return { main: arg };
|
||||
return arg;
|
||||
}
|
||||
function logHello(name) {
|
||||
console.log(\`Hello \${name}!\`);
|
||||
}
|
||||
const definition = defineBackground({
|
||||
main() {
|
||||
logHello("background");
|
||||
}
|
||||
});
|
||||
chrome;
|
||||
function print(method, ...args) {
|
||||
return;
|
||||
}
|
||||
var logger = {
|
||||
debug: (...args) => print(console.debug, ...args),
|
||||
log: (...args) => print(console.log, ...args),
|
||||
warn: (...args) => print(console.warn, ...args),
|
||||
error: (...args) => print(console.error, ...args)
|
||||
};
|
||||
try {
|
||||
const res = definition.main();
|
||||
if (res instanceof Promise) {
|
||||
console.warn(
|
||||
"The background's main() function return a promise, but it must be synchonous"
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("The background crashed on startup!");
|
||||
throw err;
|
||||
}
|
||||
})();
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
+30
-5
@@ -2,7 +2,14 @@ import { dirname, join, relative, resolve } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { execaCommand } from 'execa';
|
||||
import { InlineConfig, UserConfig, build } from '../src';
|
||||
import {
|
||||
InlineConfig,
|
||||
UserConfig,
|
||||
build,
|
||||
createServer,
|
||||
prepare,
|
||||
zip,
|
||||
} from '../src';
|
||||
import { normalizePath } from '../src/core/utils/paths';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
@@ -63,10 +70,29 @@ export class TestProject {
|
||||
if (filename === 'wxt.config.ts') this.config = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the files to the test directory install dependencies, and build the project.
|
||||
*/
|
||||
async prepare(config: InlineConfig = {}) {
|
||||
await this.writeProjectToDisk();
|
||||
await prepare({ ...config, root: this.root });
|
||||
}
|
||||
|
||||
async build(config: InlineConfig = {}) {
|
||||
await this.writeProjectToDisk();
|
||||
await build({ ...config, root: this.root });
|
||||
}
|
||||
|
||||
async zip(config: InlineConfig = {}) {
|
||||
await this.writeProjectToDisk();
|
||||
await zip({ ...config, root: this.root });
|
||||
}
|
||||
|
||||
async startServer(config: InlineConfig = {}) {
|
||||
await this.writeProjectToDisk();
|
||||
const server = await createServer({ ...config, root: this.root });
|
||||
await server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
private async writeProjectToDisk() {
|
||||
if (this.config == null) this.setConfigFileConfig();
|
||||
|
||||
for (const file of this.files) {
|
||||
@@ -80,7 +106,6 @@ export class TestProject {
|
||||
await execaCommand('pnpm --ignore-workspace i --ignore-scripts', {
|
||||
cwd: this.root,
|
||||
});
|
||||
await build({ ...config, root: this.root });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.15.4",
|
||||
"version": "0.16.6",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
@@ -120,7 +120,7 @@
|
||||
"fs-extra": "^11.1.1",
|
||||
"get-port": "^7.0.0",
|
||||
"giget": "^1.1.3",
|
||||
"immer": "^10.0.3",
|
||||
"hookable": "^5.5.3",
|
||||
"is-wsl": "^3.0.0",
|
||||
"jiti": "^1.21.0",
|
||||
"json5": "^2.2.3",
|
||||
@@ -132,7 +132,7 @@
|
||||
"ora": "^7.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.0",
|
||||
"publish-browser-extension": "^2.1.2",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"unimport": "^3.4.0",
|
||||
"vite": "^5.0.12",
|
||||
@@ -150,7 +150,7 @@
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@vitest/coverage-v8": "^1.0.1",
|
||||
"execa": "^8.0.1",
|
||||
"happy-dom": "^12.10.3",
|
||||
"happy-dom": "^13.3.8",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
||||
Generated
+90
-122
@@ -10,7 +10,7 @@ importers:
|
||||
dependencies:
|
||||
'@types/webextension-polyfill':
|
||||
specifier: ^0.10.5
|
||||
version: 0.10.5
|
||||
version: 0.10.7
|
||||
'@webext-core/fake-browser':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
@@ -37,7 +37,7 @@ importers:
|
||||
version: 3.2.3
|
||||
defu:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
version: 6.1.4
|
||||
dequal:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
@@ -46,7 +46,7 @@ importers:
|
||||
version: 0.19.8
|
||||
fast-glob:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
version: 3.3.2
|
||||
filesize:
|
||||
specifier: ^10.0.8
|
||||
version: 10.1.0
|
||||
@@ -59,9 +59,9 @@ importers:
|
||||
giget:
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
immer:
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3
|
||||
hookable:
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3
|
||||
is-wsl:
|
||||
specifier: ^3.0.0
|
||||
version: 3.1.0
|
||||
@@ -96,11 +96,11 @@ importers:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
publish-browser-extension:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.3
|
||||
version: 5.12.0
|
||||
unimport:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
@@ -140,16 +140,16 @@ importers:
|
||||
version: 2.4.9
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(vitest@1.1.0)
|
||||
version: 1.2.2(vitest@1.1.0)
|
||||
execa:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
happy-dom:
|
||||
specifier: ^12.10.3
|
||||
version: 12.10.3
|
||||
specifier: ^13.3.8
|
||||
version: 13.3.8
|
||||
lint-staged:
|
||||
specifier: ^15.2.0
|
||||
version: 15.2.0
|
||||
version: 15.2.1
|
||||
lodash.merge:
|
||||
specifier: ^4.6.2
|
||||
version: 4.6.2
|
||||
@@ -158,7 +158,7 @@ importers:
|
||||
version: 4.1.5
|
||||
p-map:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
version: 7.0.1
|
||||
prettier:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.1
|
||||
@@ -170,13 +170,13 @@ importers:
|
||||
version: 2.9.0
|
||||
tsup:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(typescript@5.3.2)
|
||||
version: 8.0.1(typescript@5.3.3)
|
||||
tsx:
|
||||
specifier: ^4.6.2
|
||||
version: 4.7.0
|
||||
typedoc:
|
||||
specifier: ^0.25.4
|
||||
version: 0.25.4(typescript@5.3.2)
|
||||
version: 0.25.4(typescript@5.3.3)
|
||||
typedoc-plugin-markdown:
|
||||
specifier: 4.0.0-next.23
|
||||
version: 4.0.0-next.23(typedoc@0.25.4)
|
||||
@@ -185,19 +185,19 @@ importers:
|
||||
version: 1.0.0-next.3(typedoc-plugin-markdown@4.0.0-next.23)
|
||||
typescript:
|
||||
specifier: ^5.3.2
|
||||
version: 5.3.2
|
||||
version: 5.3.3
|
||||
vitepress:
|
||||
specifier: 1.0.0-rc.34
|
||||
version: 1.0.0-rc.34(@types/node@20.10.3)(typescript@5.3.2)
|
||||
version: 1.0.0-rc.34(@types/node@20.10.3)(typescript@5.3.3)
|
||||
vitest:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5)
|
||||
version: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
vitest-mock-extended:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(typescript@5.3.2)(vitest@1.1.0)
|
||||
version: 1.3.1(typescript@5.3.3)(vitest@1.1.0)
|
||||
vue:
|
||||
specifier: ^3.3.10
|
||||
version: 3.3.10(typescript@5.3.2)
|
||||
version: 3.3.10(typescript@5.3.3)
|
||||
|
||||
demo:
|
||||
dependencies:
|
||||
@@ -209,7 +209,7 @@ importers:
|
||||
version: 18.2.0(react@18.2.0)
|
||||
vitest:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5)
|
||||
version: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.2.34
|
||||
@@ -399,20 +399,12 @@ packages:
|
||||
js-tokens: 4.0.0
|
||||
dev: false
|
||||
|
||||
/@babel/parser@7.23.5:
|
||||
resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.23.5
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.23.6:
|
||||
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.23.5
|
||||
'@babel/types': 7.23.9
|
||||
dev: true
|
||||
|
||||
/@babel/runtime@7.23.9:
|
||||
@@ -422,8 +414,8 @@ packages:
|
||||
regenerator-runtime: 0.14.0
|
||||
dev: false
|
||||
|
||||
/@babel/types@7.23.5:
|
||||
resolution: {integrity: sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==}
|
||||
/@babel/types@7.23.9:
|
||||
resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.23.4
|
||||
@@ -1164,8 +1156,8 @@ packages:
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
dev: true
|
||||
|
||||
/@types/webextension-polyfill@0.10.5:
|
||||
resolution: {integrity: sha512-LlmbFLUB7+BDrb9nMuM0wlqtx9LZbBV2x3W98o02cD7Y8i10+sBenTlhG56vr47dzC7WIVXbURii+5jMJsyjLw==}
|
||||
/@types/webextension-polyfill@0.10.7:
|
||||
resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==}
|
||||
dev: false
|
||||
|
||||
/@types/yauzl@2.10.3:
|
||||
@@ -1184,11 +1176,11 @@ packages:
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
|
||||
vue: 3.4.3(typescript@5.3.2)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/@vitest/coverage-v8@1.0.1(vitest@1.1.0):
|
||||
resolution: {integrity: sha512-Z4a7ig4VjUCT/P+LRB3IZrBRXb9xWRUM8rSBH9cKgfrU1Oe01/K2WJKtGshOnQwXZoSfQtwCGpbnHmB/qJwjcw==}
|
||||
/@vitest/coverage-v8@1.2.2(vitest@1.1.0):
|
||||
resolution: {integrity: sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==}
|
||||
peerDependencies:
|
||||
vitest: ^1.0.0
|
||||
dependencies:
|
||||
@@ -1200,12 +1192,12 @@ packages:
|
||||
istanbul-lib-source-maps: 4.0.1
|
||||
istanbul-reports: 3.1.6
|
||||
magic-string: 0.30.5
|
||||
magicast: 0.3.2
|
||||
magicast: 0.3.3
|
||||
picocolors: 1.0.0
|
||||
std-env: 3.6.0
|
||||
test-exclude: 6.0.0
|
||||
v8-to-istanbul: 9.2.0
|
||||
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5)
|
||||
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -1279,7 +1271,7 @@ packages:
|
||||
/@vue/compiler-sfc@3.3.10:
|
||||
resolution: {integrity: sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.5
|
||||
'@babel/parser': 7.23.6
|
||||
'@vue/compiler-core': 3.3.10
|
||||
'@vue/compiler-dom': 3.3.10
|
||||
'@vue/compiler-ssr': 3.3.10
|
||||
@@ -1382,7 +1374,7 @@ packages:
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.3.10
|
||||
'@vue/shared': 3.3.10
|
||||
vue: 3.3.10(typescript@5.3.2)
|
||||
vue: 3.3.10(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/@vue/server-renderer@3.4.3(vue@3.4.3):
|
||||
@@ -1392,7 +1384,7 @@ packages:
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.4.3
|
||||
'@vue/shared': 3.4.3
|
||||
vue: 3.4.3(typescript@5.3.2)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/@vue/shared@3.3.10:
|
||||
@@ -1752,7 +1744,7 @@ packages:
|
||||
resolution: {integrity: sha512-BWZRJgDEveT8uI+cliCwvYSSSSvb4xKoiiu5S0jaDbKBopQLQF7E+bq9xKk1pTcG+mUa3yXuFO7bD9d8Lr9Xxg==}
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
defu: 6.1.3
|
||||
defu: 6.1.4
|
||||
dotenv: 16.3.1
|
||||
giget: 1.1.3
|
||||
jiti: 1.21.0
|
||||
@@ -2061,9 +2053,6 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dev: false
|
||||
|
||||
/css.escape@1.5.1:
|
||||
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
|
||||
|
||||
/cssom@0.5.0:
|
||||
resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
|
||||
dev: false
|
||||
@@ -2167,8 +2156,8 @@ packages:
|
||||
object-keys: 1.1.1
|
||||
dev: true
|
||||
|
||||
/defu@6.1.3:
|
||||
resolution: {integrity: sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==}
|
||||
/defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
dev: false
|
||||
|
||||
/dequal@2.0.3:
|
||||
@@ -2243,10 +2232,6 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
||||
/emoji-regex@10.2.1:
|
||||
resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==}
|
||||
dev: false
|
||||
|
||||
/emoji-regex@10.3.0:
|
||||
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
|
||||
|
||||
@@ -2481,8 +2466,8 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/fast-glob@3.3.1:
|
||||
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
|
||||
/fast-glob@3.3.2:
|
||||
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -2686,7 +2671,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
colorette: 2.0.20
|
||||
defu: 6.1.3
|
||||
defu: 6.1.4
|
||||
https-proxy-agent: 7.0.2
|
||||
mri: 1.2.0
|
||||
node-fetch-native: 1.4.1
|
||||
@@ -2759,7 +2744,7 @@ packages:
|
||||
dependencies:
|
||||
array-union: 2.1.0
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.3.1
|
||||
fast-glob: 3.3.2
|
||||
ignore: 5.2.4
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
@@ -2803,14 +2788,12 @@ packages:
|
||||
resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==}
|
||||
dev: false
|
||||
|
||||
/happy-dom@12.10.3:
|
||||
resolution: {integrity: sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg==}
|
||||
/happy-dom@13.3.8:
|
||||
resolution: {integrity: sha512-RAbq4oYfJNkVan1m1F3jfA4YEyRY0/ASoNvZsNJbuX85jIypidmsz9jQZD7Tqz0VXA2MhAGfcsh5oshwmwNYSg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
dependencies:
|
||||
css.escape: 1.5.1
|
||||
entities: 4.5.0
|
||||
iconv-lite: 0.6.3
|
||||
webidl-conversions: 7.0.0
|
||||
whatwg-encoding: 2.0.0
|
||||
whatwg-mimetype: 3.0.0
|
||||
|
||||
/has-bigints@1.0.2:
|
||||
@@ -2864,6 +2847,10 @@ packages:
|
||||
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
||||
dev: false
|
||||
|
||||
/hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
dev: false
|
||||
|
||||
/hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
dev: true
|
||||
@@ -2920,12 +2907,6 @@ packages:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
/iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: false
|
||||
@@ -2946,10 +2927,6 @@ packages:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
dev: false
|
||||
|
||||
/immer@10.0.3:
|
||||
resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==}
|
||||
dev: false
|
||||
|
||||
/immutable@4.3.4:
|
||||
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
|
||||
|
||||
@@ -3403,8 +3380,8 @@ packages:
|
||||
uhyphen: 0.2.0
|
||||
dev: false
|
||||
|
||||
/lint-staged@15.2.0:
|
||||
resolution: {integrity: sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==}
|
||||
/lint-staged@15.2.1:
|
||||
resolution: {integrity: sha512-dhwAPnM85VdshybV9FWI/9ghTvMLoQLEXgVMx+ua2DN7mdfzd/tRfoU2yhMcBac0RHkofoxdnnJUokr8s4zKmQ==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -3413,7 +3390,7 @@ packages:
|
||||
debug: 4.3.4
|
||||
execa: 8.0.1
|
||||
lilconfig: 3.0.0
|
||||
listr2: 8.0.0
|
||||
listr2: 8.0.1
|
||||
micromatch: 4.0.5
|
||||
pidtree: 0.6.0
|
||||
string-argv: 0.3.2
|
||||
@@ -3422,8 +3399,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/listr2@8.0.0:
|
||||
resolution: {integrity: sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==}
|
||||
/listr2@8.0.1:
|
||||
resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
cli-truncate: 4.0.0
|
||||
@@ -3543,11 +3520,11 @@ packages:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
/magicast@0.3.2:
|
||||
resolution: {integrity: sha512-Fjwkl6a0syt9TFN0JSYpOybxiMCkYNEeOTnOTNRbjphirLakznZXAqrXgj/7GG3D1dvETONNwrBfinvAbpunDg==}
|
||||
/magicast@0.3.3:
|
||||
resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.5
|
||||
'@babel/types': 7.23.5
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.9
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
@@ -3974,8 +3951,8 @@ packages:
|
||||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
|
||||
/p-map@7.0.0:
|
||||
resolution: {integrity: sha512-EZl03dLKv3RypkrjlevZoNwQMSy4bAblWcR18zhonktnN4fUs3asFQKSe0awn982omGxamvbejqQKQYDJYHCEg==}
|
||||
/p-map@7.0.1:
|
||||
resolution: {integrity: sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==}
|
||||
engines: {node: '>=18'}
|
||||
dev: true
|
||||
|
||||
@@ -4184,9 +4161,9 @@ packages:
|
||||
sade: 1.8.1
|
||||
dev: true
|
||||
|
||||
/publish-browser-extension@2.1.0:
|
||||
resolution: {integrity: sha512-OdnhaGTZ5d3PPNh8gUjTvLhrjH4H0lawe+y5gUvGHAt3hVS33FBTt0r/NyP5qgNtN9TM3iHfTduN4GydXiXt8w==}
|
||||
engines: {node: '18', pnpm: '8'}
|
||||
/publish-browser-extension@2.1.2:
|
||||
resolution: {integrity: sha512-g6+mtdR4Z+GYHPIrfaAwC7Kbt1oQlpJ8r0x1PAytScy33OFdK+HVUeDDYorBpPAiQlmYJRQga7rY9QVTyTw34g==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@@ -4238,7 +4215,7 @@ packages:
|
||||
/rc9@2.1.1:
|
||||
resolution: {integrity: sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==}
|
||||
dependencies:
|
||||
defu: 6.1.3
|
||||
defu: 6.1.4
|
||||
destr: 2.0.2
|
||||
flat: 5.0.2
|
||||
dev: false
|
||||
@@ -4400,8 +4377,8 @@ packages:
|
||||
glob: 7.1.6
|
||||
dev: false
|
||||
|
||||
/rollup-plugin-visualizer@5.9.3:
|
||||
resolution: {integrity: sha512-ieGM5UAbMVqThX67GCuFHu/GkaSXIUZwFKJsSzE+7+k9fibU/6gbUz7SL+9BBzNtv5bIFHj7kEu0TWcqEnT/sQ==}
|
||||
/rollup-plugin-visualizer@5.12.0:
|
||||
resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4472,9 +4449,6 @@ packages:
|
||||
is-regex: 1.1.4
|
||||
dev: true
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
/sass@1.69.5:
|
||||
resolution: {integrity: sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -4739,7 +4713,7 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 10.2.1
|
||||
emoji-regex: 10.3.0
|
||||
strip-ansi: 7.1.0
|
||||
dev: false
|
||||
|
||||
@@ -4953,12 +4927,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ts-essentials@9.3.2(typescript@5.3.2):
|
||||
/ts-essentials@9.3.2(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-JxKJzuWqH1MmH4ZFHtJzGEhkfN3QvVR3C3w+4BIoWeoY68UVVoA2Np/Bca9z0IPSErVCWhv439aT0We4Dks8kQ==}
|
||||
peerDependencies:
|
||||
typescript: '>=4.1.0'
|
||||
dependencies:
|
||||
typescript: 5.3.2
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/ts-interface-checker@0.1.13:
|
||||
@@ -4969,7 +4943,7 @@ packages:
|
||||
resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==}
|
||||
dev: false
|
||||
|
||||
/tsup@8.0.1(typescript@5.3.2):
|
||||
/tsup@8.0.1(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
@@ -5002,7 +4976,7 @@ packages:
|
||||
source-map: 0.8.0-beta.0
|
||||
sucrase: 3.32.0
|
||||
tree-kill: 1.2.2
|
||||
typescript: 5.3.2
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- ts-node
|
||||
@@ -5060,7 +5034,7 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: '>=0.25.0'
|
||||
dependencies:
|
||||
typedoc: 0.25.4(typescript@5.3.2)
|
||||
typedoc: 0.25.4(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/typedoc-vitepress-theme@1.0.0-next.3(typedoc-plugin-markdown@4.0.0-next.23):
|
||||
@@ -5071,7 +5045,7 @@ packages:
|
||||
typedoc-plugin-markdown: 4.0.0-next.23(typedoc@0.25.4)
|
||||
dev: true
|
||||
|
||||
/typedoc@0.25.4(typescript@5.3.2):
|
||||
/typedoc@0.25.4(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==}
|
||||
engines: {node: '>= 16'}
|
||||
hasBin: true
|
||||
@@ -5082,11 +5056,11 @@ packages:
|
||||
marked: 4.3.0
|
||||
minimatch: 9.0.3
|
||||
shiki: 0.14.5
|
||||
typescript: 5.3.2
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/typescript@5.3.2:
|
||||
resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
|
||||
/typescript@5.3.3:
|
||||
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
@@ -5115,7 +5089,7 @@ packages:
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.5
|
||||
escape-string-regexp: 5.0.0
|
||||
fast-glob: 3.3.1
|
||||
fast-glob: 3.3.2
|
||||
local-pkg: 0.4.3
|
||||
magic-string: 0.30.5
|
||||
mlly: 1.4.2
|
||||
@@ -5260,7 +5234,7 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
/vitepress@1.0.0-rc.34(@types/node@20.10.3)(typescript@5.3.2):
|
||||
/vitepress@1.0.0-rc.34(@types/node@20.10.3)(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-TUbTiSdAZFni2XlHlpx61KikgkQ5uG4Wtmw2R0SXhIOG6qGqzDJczAFjkMc4i45I9c3KyatwOYe8oEfCnzVYwQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -5287,7 +5261,7 @@ packages:
|
||||
shikiji-core: 0.9.16
|
||||
shikiji-transformers: 0.9.16
|
||||
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
|
||||
vue: 3.4.3(typescript@5.3.2)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- '@algolia/client-search'
|
||||
- '@types/node'
|
||||
@@ -5316,18 +5290,18 @@ packages:
|
||||
- universal-cookie
|
||||
dev: true
|
||||
|
||||
/vitest-mock-extended@1.3.1(typescript@5.3.2)(vitest@1.1.0):
|
||||
/vitest-mock-extended@1.3.1(typescript@5.3.3)(vitest@1.1.0):
|
||||
resolution: {integrity: sha512-OpghYjh4BDuQ/Mzs3lFMQ1QRk9D8/2O9T47MLUA5eLn7K4RWIy+MfIivYOWEyxjTENjsBnzgMihDjyNalN/K0Q==}
|
||||
peerDependencies:
|
||||
typescript: 3.x || 4.x || 5.x
|
||||
vitest: '>=0.31.1'
|
||||
dependencies:
|
||||
ts-essentials: 9.3.2(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5)
|
||||
ts-essentials: 9.3.2(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
dev: true
|
||||
|
||||
/vitest@1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5):
|
||||
/vitest@1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
@@ -5363,7 +5337,7 @@ packages:
|
||||
chai: 4.3.10
|
||||
debug: 4.3.4
|
||||
execa: 8.0.1
|
||||
happy-dom: 12.10.3
|
||||
happy-dom: 13.3.8
|
||||
local-pkg: 0.5.0
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
@@ -5404,10 +5378,10 @@ packages:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
vue: 3.4.3(typescript@5.3.2)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/vue@3.3.10(typescript@5.3.2):
|
||||
/vue@3.3.10(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-zg6SIXZdTBwiqCw/1p+m04VyHjLfwtjwz8N57sPaBhEex31ND0RYECVOC1YrRwMRmxFf5T1dabl6SGUbMKKuVw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
@@ -5420,10 +5394,10 @@ packages:
|
||||
'@vue/runtime-dom': 3.3.10
|
||||
'@vue/server-renderer': 3.3.10(vue@3.3.10)
|
||||
'@vue/shared': 3.3.10
|
||||
typescript: 5.3.2
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/vue@3.4.3(typescript@5.3.2):
|
||||
/vue@3.4.3(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
@@ -5436,7 +5410,7 @@ packages:
|
||||
'@vue/runtime-dom': 3.4.3
|
||||
'@vue/server-renderer': 3.4.3(vue@3.4.3)
|
||||
'@vue/shared': 3.4.3
|
||||
typescript: 5.3.2
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/watchpack@2.4.0:
|
||||
@@ -5508,12 +5482,6 @@ packages:
|
||||
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
|
||||
dev: false
|
||||
|
||||
/whatwg-encoding@2.0.0:
|
||||
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
/whatwg-mimetype@3.0.0:
|
||||
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { CAC, Command } from 'cac';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
import { getInternalConfig } from '~/core/utils/building';
|
||||
import { exec } from '~/core/utils/exec';
|
||||
import { printHeader } from '~/core/utils/log';
|
||||
import { formatDuration } from '~/core/utils/time';
|
||||
import { ValidationError } from '~/core/utils/validation';
|
||||
import { registerWxt } from '~/core/wxt';
|
||||
|
||||
/**
|
||||
* Wrap an action handler to add a timer, error handling, and maybe enable debug mode.
|
||||
@@ -74,11 +74,12 @@ export function createAliasedCommand(
|
||||
.allowUnknownOptions()
|
||||
.action(async () => {
|
||||
try {
|
||||
const config = await getInternalConfig({}, 'build');
|
||||
await registerWxt('build');
|
||||
|
||||
const args = process.argv.slice(
|
||||
process.argv.indexOf(aliasedCommand.name) + 1,
|
||||
);
|
||||
await exec(config, alias, args, {
|
||||
await exec(alias, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
|
||||
@@ -367,7 +367,7 @@ describe('Content Script UIs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.todo('after', () => {
|
||||
describe('after', () => {
|
||||
it('should append the UI after the anchor', () => {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
|
||||
@@ -211,20 +211,16 @@ function mountUi(
|
||||
anchor.append(root);
|
||||
break;
|
||||
case 'first':
|
||||
if (anchor.firstChild) {
|
||||
anchor.insertBefore(root, anchor.firstChild);
|
||||
} else {
|
||||
anchor.append(root);
|
||||
}
|
||||
anchor.prepend(root);
|
||||
break;
|
||||
case 'replace':
|
||||
anchor.replaceWith(root);
|
||||
break;
|
||||
case 'after':
|
||||
anchor.replaceWith(anchor, root);
|
||||
anchor.parentElement?.insertBefore(root, anchor.nextElementSibling);
|
||||
break;
|
||||
case 'before':
|
||||
anchor.replaceWith(root, anchor);
|
||||
anchor.parentElement?.insertBefore(root, anchor);
|
||||
break;
|
||||
default:
|
||||
options.append(anchor, root);
|
||||
|
||||
+5
-3
@@ -1,5 +1,6 @@
|
||||
import { BuildOutput, InlineConfig } from '~/types';
|
||||
import { getInternalConfig, internalBuild } from './utils/building';
|
||||
import { internalBuild } from './utils/building';
|
||||
import { registerWxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Bundles the extension for production. Returns a promise of the build result. Discovers the `wxt.config.ts` file in
|
||||
@@ -15,6 +16,7 @@ import { getInternalConfig, internalBuild } from './utils/building';
|
||||
* })
|
||||
*/
|
||||
export async function build(config?: InlineConfig): Promise<BuildOutput> {
|
||||
const internalConfig = await getInternalConfig(config ?? {}, 'build');
|
||||
return await internalBuild(internalConfig);
|
||||
await registerWxt('build', config);
|
||||
|
||||
return await internalBuild();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
BuildStepOutput,
|
||||
Entrypoint,
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
ResolvedConfig,
|
||||
UserConfig,
|
||||
VirtualEntrypointType,
|
||||
WxtBuilder,
|
||||
@@ -15,7 +15,7 @@ import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
export async function createViteBuilder(
|
||||
inlineConfig: InlineConfig,
|
||||
userConfig: UserConfig,
|
||||
wxtConfig: Omit<InternalConfig, 'builder'>,
|
||||
wxtConfig: Omit<ResolvedConfig, 'builder'>,
|
||||
): Promise<WxtBuilder> {
|
||||
const vite = await import('vite');
|
||||
|
||||
@@ -63,9 +63,10 @@ export async function createViteBuilder(
|
||||
wxtPlugins.noopBackground(),
|
||||
wxtPlugins.globals(wxtConfig),
|
||||
wxtPlugins.excludeBrowserPolyfill(wxtConfig),
|
||||
wxtPlugins.defineImportMeta(),
|
||||
);
|
||||
if (wxtConfig.analysis.enabled) {
|
||||
config.plugins.push(wxtPlugins.bundleAnalysis());
|
||||
config.plugins.push(wxtPlugins.bundleAnalysis(wxtConfig));
|
||||
}
|
||||
|
||||
return config;
|
||||
@@ -75,23 +76,7 @@ export async function createViteBuilder(
|
||||
* Return the basic config for building an entrypoint in [lib mode](https://vitejs.dev/guide/build.html#library-mode).
|
||||
*/
|
||||
const getLibModeConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
|
||||
let virtualEntrypointType: VirtualEntrypointType | undefined;
|
||||
switch (entrypoint.type) {
|
||||
case 'background':
|
||||
case 'unlisted-script':
|
||||
virtualEntrypointType = entrypoint.type;
|
||||
break;
|
||||
case 'content-script':
|
||||
virtualEntrypointType =
|
||||
entrypoint.options.world === 'MAIN'
|
||||
? 'content-script-main-world'
|
||||
: 'content-script-isolated-world';
|
||||
break;
|
||||
}
|
||||
const entry = virtualEntrypointType
|
||||
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
|
||||
: entrypoint.inputPath;
|
||||
|
||||
const entry = getRollupEntry(entrypoint);
|
||||
const plugins: NonNullable<vite.UserConfig['plugins']> = [
|
||||
wxtPlugins.entrypointGroupGlobals(entrypoint),
|
||||
];
|
||||
@@ -157,14 +142,16 @@ export async function createViteBuilder(
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: entrypoints.reduce<Record<string, string>>((input, entry) => {
|
||||
input[entry.name] = entry.inputPath;
|
||||
input[entry.name] = getRollupEntry(entry);
|
||||
return input;
|
||||
}, {}),
|
||||
output: {
|
||||
// Include a hash to prevent conflicts
|
||||
chunkFileNames: 'chunks/[name]-[hash].js',
|
||||
// Include a hash to prevent conflicts
|
||||
entryFileNames: 'chunks/[name]-[hash].js',
|
||||
// Place JS entrypoints in main directory without a hash. (popup.html & popup.js are
|
||||
// next to each other). The unique entrypoint name requirement prevents conflicts with
|
||||
// scripts of the same name
|
||||
entryFileNames: '[name].js',
|
||||
// We can't control the "name", so we need a hash to prevent conflicts
|
||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||
},
|
||||
@@ -263,3 +250,26 @@ function getBuildOutputChunks(
|
||||
if (Array.isArray(result)) return result.flatMap(({ output }) => output);
|
||||
return result.output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input module ID (virtual or real file) for an entrypoint. The returned string should
|
||||
* be passed as an input to rollup.
|
||||
*/
|
||||
function getRollupEntry(entrypoint: Entrypoint): string {
|
||||
let virtualEntrypointType: VirtualEntrypointType | undefined;
|
||||
switch (entrypoint.type) {
|
||||
case 'background':
|
||||
case 'unlisted-script':
|
||||
virtualEntrypointType = entrypoint.type;
|
||||
break;
|
||||
case 'content-script':
|
||||
virtualEntrypointType =
|
||||
entrypoint.options.world === 'MAIN'
|
||||
? 'content-script-main-world'
|
||||
: 'content-script-isolated-world';
|
||||
break;
|
||||
}
|
||||
return virtualEntrypointType
|
||||
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
|
||||
: entrypoint.inputPath;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Window } from 'happy-dom';
|
||||
import { pointToDevServer } from '../devHtmlPrerender';
|
||||
import {
|
||||
fakeDevServer,
|
||||
fakeInternalConfig,
|
||||
fakeResolvedConfig,
|
||||
} from '~/core/utils/testing/fake-objects';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
import { resolve } from 'node:path';
|
||||
@@ -36,7 +36,7 @@ describe('Dev HTML Prerender Plugin', () => {
|
||||
url: 'http://localhost',
|
||||
});
|
||||
const root = '/some/root';
|
||||
const config = fakeInternalConfig({
|
||||
const config = fakeResolvedConfig({
|
||||
root,
|
||||
alias: {
|
||||
'~local': '.',
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import type * as vite from 'vite';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import path from 'node:path';
|
||||
|
||||
let increment = 0;
|
||||
|
||||
export function bundleAnalysis(): vite.Plugin {
|
||||
export function bundleAnalysis(
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
return visualizer({
|
||||
emitFile: true,
|
||||
template: 'raw-data',
|
||||
filename: `stats-${increment++}.json`,
|
||||
}) as vite.Plugin;
|
||||
filename: path.resolve(
|
||||
config.analysis.outputDir,
|
||||
`${config.analysis.outputName}-${increment++}.json`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated FOR TESTING ONLY.
|
||||
*/
|
||||
export function resetBundleIncrement() {
|
||||
increment = 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type * as vite from 'vite';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { Entrypoint, ResolvedConfig } from '~/types';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
*/
|
||||
export function cssEntrypoints(
|
||||
entrypoint: Entrypoint,
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
return {
|
||||
name: 'wxt:css-entrypoint',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Overrides definitions for `import.meta.*`
|
||||
*
|
||||
* - `import.meta.url`: Without this, background service workers crash trying to access
|
||||
* `document.location`, see https://github.com/wxt-dev/wxt/issues/392
|
||||
*/
|
||||
export function defineImportMeta() {
|
||||
return {
|
||||
name: 'wxt:define',
|
||||
config() {
|
||||
return {
|
||||
define: {
|
||||
// This works for all extension contexts, including background service worker
|
||||
'import.meta.url': 'self.location.href',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig, WxtDevServer } from '~/types';
|
||||
import { ResolvedConfig, WxtDevServer } from '~/types';
|
||||
import { getEntrypointName } from '~/core/utils/entrypoints';
|
||||
import { parseHTML } from 'linkedom';
|
||||
import { dirname, relative, resolve } from 'node:path';
|
||||
@@ -12,7 +12,7 @@ let reactRefreshPreamble = '';
|
||||
* Pre-renders the HTML entrypoints when building the extension to connect to the dev server.
|
||||
*/
|
||||
export function devHtmlPrerender(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.PluginOption {
|
||||
const htmlReloadId = '@wxt/reload-html';
|
||||
const resolvedHtmlReloadId = resolve(
|
||||
@@ -134,7 +134,7 @@ export function devHtmlPrerender(
|
||||
}
|
||||
|
||||
export function pointToDevServer(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
server: WxtDevServer,
|
||||
id: string,
|
||||
document: Document,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
|
||||
/**
|
||||
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
|
||||
*/
|
||||
export function devServerGlobals(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): Plugin {
|
||||
return {
|
||||
name: 'wxt:dev-server-globals',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import { fetchCached } from '~/core/utils/network';
|
||||
|
||||
/**
|
||||
@@ -9,7 +9,7 @@ import { fetchCached } from '~/core/utils/network';
|
||||
* @example
|
||||
* import "url:https://google-tagmanager.com/gtag?id=XYZ";
|
||||
*/
|
||||
export function download(config: Omit<InternalConfig, 'builder'>): Plugin {
|
||||
export function download(config: Omit<ResolvedConfig, 'builder'>): Plugin {
|
||||
return {
|
||||
name: 'wxt:download',
|
||||
resolveId(id) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
/**
|
||||
@@ -7,7 +7,7 @@ import type * as vite from 'vite';
|
||||
* virtual module.
|
||||
*/
|
||||
export function excludeBrowserPolyfill(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
const virtualId = 'virtual:wxt-webextension-polyfill-disabled';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import { getGlobals } from '~/core/utils/globals';
|
||||
|
||||
export function globals(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.PluginOption {
|
||||
return {
|
||||
name: 'wxt:globals',
|
||||
|
||||
@@ -12,3 +12,4 @@ export * from './globals';
|
||||
export * from './webextensionPolyfillMock';
|
||||
export * from './excludeBrowserPolyfill';
|
||||
export * from './entrypointGroupGlobals';
|
||||
export * from './defineImportMeta';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type * as vite from 'vite';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { Entrypoint, ResolvedConfig } from '~/types';
|
||||
import { dirname, extname, resolve, join } from 'node:path';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
import fs, { ensureDir } from 'fs-extra';
|
||||
@@ -20,7 +20,7 @@ import { normalizePath } from '~/core/utils/paths';
|
||||
*/
|
||||
export function multipageMove(
|
||||
entrypoints: Entrypoint[],
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
return {
|
||||
name: 'wxt:multipage-move',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
export function tsconfigPaths(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.Plugin {
|
||||
return {
|
||||
name: 'wxt:aliases',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createUnimport } from 'unimport';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
import type * as vite from 'vite';
|
||||
import { extname } from 'path';
|
||||
@@ -17,7 +17,7 @@ const ENABLED_EXTENSIONS = new Set([
|
||||
* Inject any global imports defined by unimport
|
||||
*/
|
||||
export function unimport(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.PluginOption {
|
||||
const options = getUnimportOptions(config);
|
||||
if (options === false) return [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { InternalConfig, VirtualEntrypointType } from '~/types';
|
||||
import { ResolvedConfig, VirtualEntrypointType } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
@@ -9,7 +9,7 @@ import { normalizePath } from '~/core/utils/paths';
|
||||
*/
|
||||
export function virtualEntrypoint(
|
||||
type: VirtualEntrypointType,
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): Plugin {
|
||||
const virtualId = `virtual:wxt-${type}?`;
|
||||
const resolvedVirtualId = `\0${virtualId}`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
|
||||
/**
|
||||
* Mock `webextension-polyfill` by inlining all dependencies that import it and adding a custom
|
||||
@@ -16,7 +16,7 @@ import { InternalConfig } from '~/types';
|
||||
* `npm list` and inline them automatically.
|
||||
*/
|
||||
export function webextensionPolyfillMock(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): vite.PluginOption {
|
||||
return {
|
||||
name: 'wxt:testing-inline-deps',
|
||||
|
||||
+63
-70
@@ -2,13 +2,11 @@ import {
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
ServerInfo,
|
||||
WxtDevServer,
|
||||
} from '~/types';
|
||||
import {
|
||||
getEntrypointBundlePath,
|
||||
getEntrypointOutputFile,
|
||||
resolvePerBrowserOption,
|
||||
} from '~/core/utils/entrypoints';
|
||||
import {
|
||||
@@ -17,7 +15,6 @@ import {
|
||||
} from '~/core/utils/manifest';
|
||||
import {
|
||||
internalBuild,
|
||||
getInternalConfig,
|
||||
detectDevChanges,
|
||||
rebuild,
|
||||
findEntrypoints,
|
||||
@@ -27,6 +24,7 @@ import { consola } from 'consola';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import pc from 'picocolors';
|
||||
import { relative } from 'node:path';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
|
||||
@@ -51,10 +49,10 @@ export async function createServer(
|
||||
|
||||
const buildAndOpenBrowser = async () => {
|
||||
// Build after starting the dev server so it can be used to transform HTML files
|
||||
server.currentOutput = await internalBuild(config);
|
||||
server.currentOutput = await internalBuild();
|
||||
|
||||
// Open browser after everything is ready to go.
|
||||
await runner.openBrowser(config);
|
||||
await runner.openBrowser();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -62,8 +60,8 @@ export async function createServer(
|
||||
*/
|
||||
const closeAndRecreateRunner = async () => {
|
||||
await runner.closeBrowser();
|
||||
config = await getLatestConfig();
|
||||
runner = await createExtensionRunner(config);
|
||||
await wxt.reloadConfig();
|
||||
runner = await createExtensionRunner();
|
||||
};
|
||||
|
||||
// Server instance must be created first so its reference can be added to the internal config used
|
||||
@@ -79,7 +77,7 @@ export async function createServer(
|
||||
currentOutput: undefined,
|
||||
async start() {
|
||||
await builderServer.listen();
|
||||
config.logger.success(`Started dev server @ ${serverInfo.origin}`);
|
||||
wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
|
||||
await buildAndOpenBrowser();
|
||||
},
|
||||
async stop() {
|
||||
@@ -104,34 +102,26 @@ export async function createServer(
|
||||
},
|
||||
async restartBrowser() {
|
||||
await closeAndRecreateRunner();
|
||||
await runner.openBrowser(config);
|
||||
await runner.openBrowser();
|
||||
},
|
||||
};
|
||||
|
||||
const getLatestConfig = () =>
|
||||
getInternalConfig(inlineConfig ?? {}, 'serve', server);
|
||||
let config = await getLatestConfig();
|
||||
await registerWxt('serve', inlineConfig, server);
|
||||
|
||||
let [runner, builderServer] = await Promise.all([
|
||||
createExtensionRunner(config),
|
||||
config.builder.createServer(server),
|
||||
createExtensionRunner(),
|
||||
wxt.config.builder.createServer(server),
|
||||
]);
|
||||
|
||||
// Register content scripts for the first time after the background starts up since they're not
|
||||
// listed in the manifest
|
||||
server.ws.on('wxt:background-initialized', () => {
|
||||
if (server.currentOutput == null) return;
|
||||
reloadContentScripts(server.currentOutput.steps, config, server);
|
||||
reloadContentScripts(server.currentOutput.steps, server);
|
||||
});
|
||||
|
||||
// Listen for file changes and reload different parts of the extension accordingly
|
||||
const reloadOnChange = createFileReloader({
|
||||
server,
|
||||
getLatestConfig,
|
||||
updateConfig(newConfig) {
|
||||
config = newConfig;
|
||||
},
|
||||
});
|
||||
const reloadOnChange = createFileReloader(server);
|
||||
server.watcher.on('all', reloadOnChange);
|
||||
|
||||
return server;
|
||||
@@ -146,21 +136,15 @@ async function getPort(): Promise<number> {
|
||||
* Returns a function responsible for reloading different parts of the extension when a file
|
||||
* changes.
|
||||
*/
|
||||
function createFileReloader(options: {
|
||||
server: WxtDevServer;
|
||||
getLatestConfig: () => Promise<InternalConfig>;
|
||||
updateConfig: (config: InternalConfig) => void;
|
||||
}) {
|
||||
const { server, getLatestConfig, updateConfig } = options;
|
||||
function createFileReloader(server: WxtDevServer) {
|
||||
const fileChangedMutex = new Mutex();
|
||||
const changeQueue: Array<[string, string]> = [];
|
||||
|
||||
return async (event: string, path: string) => {
|
||||
const config = await getLatestConfig();
|
||||
updateConfig(config);
|
||||
await wxt.reloadConfig();
|
||||
|
||||
// Here, "path" is a non-normalized path (ie: C:\\users\\... instead of C:/users/...)
|
||||
if (path.startsWith(config.outBaseDir)) return;
|
||||
if (path.startsWith(wxt.config.outBaseDir)) return;
|
||||
changeQueue.push([event, path]);
|
||||
|
||||
await fileChangedMutex.runExclusive(async () => {
|
||||
@@ -171,45 +155,31 @@ function createFileReloader(options: {
|
||||
.map(([_, file]) => file);
|
||||
if (fileChanges.length === 0) return;
|
||||
|
||||
const changes = detectDevChanges(
|
||||
config,
|
||||
fileChanges,
|
||||
server.currentOutput,
|
||||
);
|
||||
const changes = detectDevChanges(fileChanges, server.currentOutput);
|
||||
if (changes.type === 'no-change') return;
|
||||
|
||||
if (changes.type === 'full-restart') {
|
||||
config.logger.info('Config changed, restarting server...');
|
||||
wxt.logger.info('Config changed, restarting server...');
|
||||
server.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.type === 'browser-restart') {
|
||||
config.logger.info('Runner config changed, restarting browser...');
|
||||
wxt.logger.info('Runner config changed, restarting browser...');
|
||||
server.restartBrowser();
|
||||
return;
|
||||
}
|
||||
|
||||
// Log the entrypoints that were effected
|
||||
config.logger.info(
|
||||
wxt.logger.info(
|
||||
`Changed: ${Array.from(new Set(fileChanges))
|
||||
.map((file) => pc.dim(relative(config.root, file)))
|
||||
.map((file) => pc.dim(relative(wxt.config.root, file)))
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => {
|
||||
return pc.cyan(
|
||||
relative(config.outDir, getEntrypointOutputFile(entry, '')),
|
||||
);
|
||||
})
|
||||
.join(pc.dim(', '));
|
||||
|
||||
// Rebuild entrypoints on change
|
||||
const allEntrypoints = await findEntrypoints(config);
|
||||
const allEntrypoints = await findEntrypoints();
|
||||
const { output: newOutput } = await rebuild(
|
||||
config,
|
||||
allEntrypoints,
|
||||
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
||||
changes.rebuildGroups,
|
||||
@@ -221,15 +191,23 @@ function createFileReloader(options: {
|
||||
switch (changes.type) {
|
||||
case 'extension-reload':
|
||||
server.reloadExtension();
|
||||
consola.success(`Reloaded extension`);
|
||||
break;
|
||||
case 'html-reload':
|
||||
reloadHtmlPages(changes.rebuildGroups, server, config);
|
||||
const { reloadedNames } = reloadHtmlPages(
|
||||
changes.rebuildGroups,
|
||||
server,
|
||||
);
|
||||
consola.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
|
||||
break;
|
||||
case 'content-script-reload':
|
||||
reloadContentScripts(changes.changedSteps, config, server);
|
||||
reloadContentScripts(changes.changedSteps, server);
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => entry.name);
|
||||
consola.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
||||
break;
|
||||
}
|
||||
consola.success(`Reloaded: ${rebuiltNames}`);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -237,35 +215,34 @@ function createFileReloader(options: {
|
||||
/**
|
||||
* From the server, tell the client to reload content scripts from the provided build step outputs.
|
||||
*/
|
||||
function reloadContentScripts(
|
||||
steps: BuildStepOutput[],
|
||||
config: InternalConfig,
|
||||
server: WxtDevServer,
|
||||
) {
|
||||
if (config.manifestVersion === 3) {
|
||||
function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
steps.forEach((step) => {
|
||||
if (server.currentOutput == null) return;
|
||||
|
||||
const entry = step.entrypoints;
|
||||
if (Array.isArray(entry) || entry.type !== 'content-script') return;
|
||||
|
||||
const js = [getEntrypointBundlePath(entry, config.outDir, '.js')];
|
||||
const js = [getEntrypointBundlePath(entry, wxt.config.outDir, '.js')];
|
||||
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
|
||||
const css = getContentScriptCssFiles([entry], cssMap);
|
||||
|
||||
server.reloadContentScript({
|
||||
allFrames: resolvePerBrowserOption(
|
||||
entry.options.allFrames,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
excludeMatches: resolvePerBrowserOption(
|
||||
entry.options.excludeMatches,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
matches: resolvePerBrowserOption(entry.options.matches, config.browser),
|
||||
runAt: resolvePerBrowserOption(entry.options.runAt, config.browser),
|
||||
matches: resolvePerBrowserOption(
|
||||
entry.options.matches,
|
||||
wxt.config.browser,
|
||||
),
|
||||
runAt: resolvePerBrowserOption(entry.options.runAt, wxt.config.browser),
|
||||
// @ts-expect-error: Chrome accepts this, not typed in webextension-polyfill (https://developer.chrome.com/docs/extensions/reference/scripting/#type-RegisteredContentScript)
|
||||
world: resolvePerBrowserOption(entry.options.world, config.browser),
|
||||
world: resolvePerBrowserOption(entry.options.world, wxt.config.browser),
|
||||
js,
|
||||
css,
|
||||
});
|
||||
@@ -278,10 +255,26 @@ function reloadContentScripts(
|
||||
function reloadHtmlPages(
|
||||
groups: EntrypointGroup[],
|
||||
server: WxtDevServer,
|
||||
config: InternalConfig,
|
||||
) {
|
||||
groups.flat().forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(entry, config.outDir, '.html');
|
||||
): { reloadedNames: string[] } {
|
||||
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
|
||||
const htmlEntries = groups
|
||||
.flat()
|
||||
.filter((entry) => entry.inputPath.endsWith('.html'));
|
||||
|
||||
htmlEntries.forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
|
||||
server.reloadPage(path);
|
||||
});
|
||||
|
||||
return {
|
||||
reloadedNames: htmlEntries.map((entry) => entry.name),
|
||||
};
|
||||
}
|
||||
|
||||
function getFilenameList(names: string[]): string {
|
||||
return names
|
||||
.map((name) => {
|
||||
return pc.cyan(name);
|
||||
})
|
||||
.join(pc.dim(', '));
|
||||
}
|
||||
|
||||
+16
-17
@@ -90,26 +90,25 @@ interface Template {
|
||||
|
||||
async function listTemplates(): Promise<Template[]> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
'https://api.github.com/repos/wxt-dev/wxt/contents/templates',
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
},
|
||||
);
|
||||
const res = await fetch('https://ungh.cc/repos/wxt-dev/wxt/files/main');
|
||||
if (res.status >= 300)
|
||||
throw Error(`Request failed with status ${res.status} ${res.statusText}`);
|
||||
|
||||
const data = (await res.json()) as Array<{
|
||||
type: 'file' | 'dir';
|
||||
name: string;
|
||||
path: string;
|
||||
}>;
|
||||
return data
|
||||
.filter((item: any) => item.type === 'dir')
|
||||
.map((item) => ({ name: item.name, path: item.path }))
|
||||
const data = (await res.json()) as {
|
||||
meta: {
|
||||
sha: string;
|
||||
};
|
||||
files: Array<{
|
||||
path: string;
|
||||
mode: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
}>;
|
||||
};
|
||||
return data.files
|
||||
.map((item) => item.path.match(/templates\/(.+)\/package\.json/)?.[1])
|
||||
.filter((name) => name != null)
|
||||
.map((name) => ({ name: name!, path: `templates/${name}` }))
|
||||
.sort((l, r) => {
|
||||
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
|
||||
+6
-10
@@ -1,15 +1,11 @@
|
||||
import { InlineConfig } from '~/types';
|
||||
import {
|
||||
findEntrypoints,
|
||||
generateTypesDir,
|
||||
getInternalConfig,
|
||||
} from '~/core/utils/building';
|
||||
import { findEntrypoints, generateTypesDir } from '~/core/utils/building';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
|
||||
export async function prepare(config: InlineConfig) {
|
||||
const internalConfig = await getInternalConfig(config, 'build');
|
||||
await registerWxt('build', config);
|
||||
wxt.logger.info('Generating types...');
|
||||
|
||||
internalConfig.logger.info('Generating types...');
|
||||
|
||||
const entrypoints = await findEntrypoints(internalConfig);
|
||||
await generateTypesDir(entrypoints, internalConfig);
|
||||
const entrypoints = await findEntrypoints();
|
||||
await generateTypesDir(entrypoints);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createExtensionRunner } from '..';
|
||||
import { fakeInternalConfig } from '~/core/utils/testing/fake-objects';
|
||||
import { setFakeWxt } from '~/core/utils/testing/fake-objects';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { createSafariRunner } from '../safari';
|
||||
import { createWslRunner } from '../wsl';
|
||||
@@ -26,54 +26,62 @@ const createWebExtRunnerMock = vi.mocked(createWebExtRunner);
|
||||
|
||||
describe('createExtensionRunner', () => {
|
||||
it('should return a Safari runner when browser is "safari"', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
browser: 'safari',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'safari',
|
||||
},
|
||||
});
|
||||
const safariRunner = mock<ExtensionRunner>();
|
||||
createSafariRunnerMock.mockReturnValue(safariRunner);
|
||||
|
||||
await expect(createExtensionRunner(config)).resolves.toBe(safariRunner);
|
||||
await expect(createExtensionRunner()).resolves.toBe(safariRunner);
|
||||
});
|
||||
|
||||
it('should return a WSL runner when `is-wsl` is true', async () => {
|
||||
isWslMock.mockResolvedValueOnce(true);
|
||||
const config = fakeInternalConfig({
|
||||
browser: 'chrome',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'chrome',
|
||||
},
|
||||
});
|
||||
const wslRunner = mock<ExtensionRunner>();
|
||||
createWslRunnerMock.mockReturnValue(wslRunner);
|
||||
|
||||
await expect(createExtensionRunner(config)).resolves.toBe(wslRunner);
|
||||
await expect(createExtensionRunner()).resolves.toBe(wslRunner);
|
||||
});
|
||||
|
||||
it('should return a manual runner when `runner.disabled` is true', async () => {
|
||||
isWslMock.mockResolvedValueOnce(false);
|
||||
const config = fakeInternalConfig({
|
||||
browser: 'chrome',
|
||||
runnerConfig: {
|
||||
config: {
|
||||
disabled: true,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'chrome',
|
||||
runnerConfig: {
|
||||
config: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const manualRunner = mock<ExtensionRunner>();
|
||||
createManualRunnerMock.mockReturnValue(manualRunner);
|
||||
|
||||
await expect(createExtensionRunner(config)).resolves.toBe(manualRunner);
|
||||
await expect(createExtensionRunner()).resolves.toBe(manualRunner);
|
||||
});
|
||||
|
||||
it('should return a web-ext runner otherwise', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
browser: 'chrome',
|
||||
runnerConfig: {
|
||||
config: {
|
||||
disabled: undefined,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'chrome',
|
||||
runnerConfig: {
|
||||
config: {
|
||||
disabled: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const manualRunner = mock<ExtensionRunner>();
|
||||
createWebExtRunnerMock.mockReturnValue(manualRunner);
|
||||
|
||||
await expect(createExtensionRunner(config)).resolves.toBe(manualRunner);
|
||||
await expect(createExtensionRunner()).resolves.toBe(manualRunner);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { InternalConfig, ExtensionRunner } from '~/types';
|
||||
import { ExtensionRunner } from '~/types';
|
||||
import { createWslRunner } from './wsl';
|
||||
import { createWebExtRunner } from './web-ext';
|
||||
import { createSafariRunner } from './safari';
|
||||
import { createManualRunner } from './manual';
|
||||
import { isWsl } from '~/core/utils/wsl';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
export async function createExtensionRunner(
|
||||
config: InternalConfig,
|
||||
): Promise<ExtensionRunner> {
|
||||
if (config.browser === 'safari') return createSafariRunner();
|
||||
export async function createExtensionRunner(): Promise<ExtensionRunner> {
|
||||
if (wxt.config.browser === 'safari') return createSafariRunner();
|
||||
|
||||
if (await isWsl()) return createWslRunner();
|
||||
if (config.runnerConfig.config?.disabled) return createManualRunner();
|
||||
if (wxt.config.runnerConfig.config?.disabled) return createManualRunner();
|
||||
|
||||
return createWebExtRunner();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ExtensionRunner } from '~/types';
|
||||
import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The manual runner tells the user to load the unpacked extension manually.
|
||||
*/
|
||||
export function createManualRunner(): ExtensionRunner {
|
||||
return {
|
||||
async openBrowser(config) {
|
||||
config.logger.info(
|
||||
async openBrowser() {
|
||||
wxt.logger.info(
|
||||
`Load "${relative(
|
||||
process.cwd(),
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ExtensionRunner } from '~/types';
|
||||
import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The Safari runner just logs a warning message because `web-ext` doesn't work with Safari.
|
||||
*/
|
||||
export function createSafariRunner(): ExtensionRunner {
|
||||
return {
|
||||
async openBrowser(config) {
|
||||
config.logger.warn(
|
||||
async openBrowser() {
|
||||
wxt.logger.warn(
|
||||
`Cannot Safari using web-ext. Load "${relative(
|
||||
process.cwd(),
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
|
||||
+17
-12
@@ -2,6 +2,7 @@ import type { WebExtRunInstance } from 'web-ext-run';
|
||||
import { ExtensionRunner } from '~/types';
|
||||
import { formatDuration } from '../utils/time';
|
||||
import defu from 'defu';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Create an `ExtensionRunner` backed by `web-ext`.
|
||||
@@ -10,10 +11,13 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
let runner: WebExtRunInstance | undefined;
|
||||
|
||||
return {
|
||||
async openBrowser(config) {
|
||||
async openBrowser() {
|
||||
const startTime = Date.now();
|
||||
|
||||
if (config.browser === 'firefox' && config.manifestVersion === 3) {
|
||||
if (
|
||||
wxt.config.browser === 'firefox' &&
|
||||
wxt.config.manifestVersion === 3
|
||||
) {
|
||||
throw Error(
|
||||
'Dev mode does not support Firefox MV3. For alternatives, see https://github.com/wxt-dev/wxt/issues/230#issuecomment-1806881653',
|
||||
);
|
||||
@@ -22,16 +26,16 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
// Use WXT's logger instead of web-ext's built-in one.
|
||||
const webExtLogger = await import('web-ext-run/util/logger');
|
||||
webExtLogger.consoleStream.write = ({ level, msg, name }) => {
|
||||
if (level >= ERROR_LOG_LEVEL) config.logger.error(name, msg);
|
||||
if (level >= WARN_LOG_LEVEL) config.logger.warn(msg);
|
||||
if (level >= ERROR_LOG_LEVEL) wxt.logger.error(name, msg);
|
||||
if (level >= WARN_LOG_LEVEL) wxt.logger.warn(msg);
|
||||
};
|
||||
|
||||
const wxtUserConfig = config.runnerConfig.config;
|
||||
const wxtUserConfig = wxt.config.runnerConfig.config;
|
||||
const userConfig = {
|
||||
console: wxtUserConfig?.openConsole,
|
||||
devtools: wxtUserConfig?.openDevtools,
|
||||
startUrl: wxtUserConfig?.startUrls,
|
||||
...(config.browser === 'firefox'
|
||||
...(wxt.config.browser === 'firefox'
|
||||
? {
|
||||
firefox: wxtUserConfig?.binaries?.firefox,
|
||||
firefoxProfile: wxtUserConfig?.firefoxProfile,
|
||||
@@ -39,7 +43,7 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
args: wxtUserConfig?.firefoxArgs,
|
||||
}
|
||||
: {
|
||||
chromiumBinary: wxtUserConfig?.binaries?.[config.browser],
|
||||
chromiumBinary: wxtUserConfig?.binaries?.[wxt.config.browser],
|
||||
chromiumProfile: wxtUserConfig?.chromiumProfile,
|
||||
chromiumPref: defu(
|
||||
wxtUserConfig?.chromiumPref,
|
||||
@@ -51,8 +55,9 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
|
||||
const finalConfig = {
|
||||
...userConfig,
|
||||
target: config.browser === 'firefox' ? 'firefox-desktop' : 'chromium',
|
||||
sourceDir: config.outDir,
|
||||
target:
|
||||
wxt.config.browser === 'firefox' ? 'firefox-desktop' : 'chromium',
|
||||
sourceDir: wxt.config.outDir,
|
||||
// WXT handles reloads, so disable auto-reload behaviors in web-ext
|
||||
noReload: true,
|
||||
noInput: true,
|
||||
@@ -61,14 +66,14 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
// Don't call `process.exit(0)` after starting web-ext
|
||||
shouldExitProgram: false,
|
||||
};
|
||||
config.logger.debug('web-ext config:', finalConfig);
|
||||
config.logger.debug('web-ext options:', options);
|
||||
wxt.logger.debug('web-ext config:', finalConfig);
|
||||
wxt.logger.debug('web-ext options:', options);
|
||||
|
||||
const webExt = await import('web-ext-run');
|
||||
runner = await webExt.default.cmd.run(finalConfig, options);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
config.logger.success(`Opened browser in ${formatDuration(duration)}`);
|
||||
wxt.logger.success(`Opened browser in ${formatDuration(duration)}`);
|
||||
},
|
||||
|
||||
async closeBrowser() {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ExtensionRunner } from '~/types';
|
||||
import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The WSL runner just logs a warning message because `web-ext` doesn't work in WSL.
|
||||
*/
|
||||
export function createWslRunner(): ExtensionRunner {
|
||||
return {
|
||||
async openBrowser(config) {
|
||||
config.logger.warn(
|
||||
async openBrowser() {
|
||||
wxt.logger.warn(
|
||||
`Cannot open browser when using WSL. Load "${relative(
|
||||
process.cwd(),
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { hashContentScriptOptions } from '~/core/utils/content-scripts';
|
||||
import { fakeInternalConfig } from '~/core/utils/testing/fake-objects';
|
||||
import { setFakeWxt } from '~/core/utils/testing/fake-objects';
|
||||
|
||||
describe('Content Script Utils', () => {
|
||||
beforeEach(() => {
|
||||
setFakeWxt();
|
||||
});
|
||||
|
||||
describe('hashContentScriptOptions', () => {
|
||||
it('should return a string containing all the options with defaults applied', () => {
|
||||
const hash = hashContentScriptOptions(
|
||||
{ matches: [] },
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
const hash = hashContentScriptOptions({ matches: [] });
|
||||
|
||||
expect(hash).toMatchInlineSnapshot(
|
||||
`"[["all_frames",false],["exclude_globs",[]],["exclude_matches",[]],["include_globs",[]],["match_about_blank",false],["match_origin_as_fallback",false],["matches",[]],["run_at","document_idle"],["world","ISOLATED"]]"`,
|
||||
@@ -16,21 +17,15 @@ describe('Content Script Utils', () => {
|
||||
});
|
||||
|
||||
it('should be consistent regardless of the object ordering and default values', () => {
|
||||
const hash1 = hashContentScriptOptions(
|
||||
{
|
||||
allFrames: true,
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
matchAboutBlank: false,
|
||||
},
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
const hash2 = hashContentScriptOptions(
|
||||
{
|
||||
matches: ['*://duckduckgo.com/*', '*://google.com/*'],
|
||||
allFrames: true,
|
||||
},
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
const hash1 = hashContentScriptOptions({
|
||||
allFrames: true,
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
matchAboutBlank: false,
|
||||
});
|
||||
const hash2 = hashContentScriptOptions({
|
||||
matches: ['*://duckduckgo.com/*', '*://google.com/*'],
|
||||
allFrames: true,
|
||||
});
|
||||
|
||||
expect(hash1).toBe(hash2);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { generateManifest, stripPathFromMatchPattern } from '../manifest';
|
||||
import {
|
||||
fakeArray,
|
||||
fakeBackgroundEntrypoint,
|
||||
fakeBuildOutput,
|
||||
fakeEntrypoint,
|
||||
fakeInternalConfig,
|
||||
fakeManifestCommand,
|
||||
fakeOptionsEntrypoint,
|
||||
fakePopupEntrypoint,
|
||||
setFakeWxt,
|
||||
} from '../testing/fake-objects';
|
||||
import { Manifest } from 'webextension-polyfill';
|
||||
import {
|
||||
@@ -17,11 +17,16 @@ import {
|
||||
Entrypoint,
|
||||
OutputAsset,
|
||||
} from '~/types';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
const outDir = '/output';
|
||||
const contentScriptOutDir = '/output/content-scripts';
|
||||
|
||||
describe('Manifest Utils', () => {
|
||||
beforeEach(() => {
|
||||
setFakeWxt();
|
||||
});
|
||||
|
||||
describe('generateManifest', () => {
|
||||
describe('popup', () => {
|
||||
type ActionType = 'browser_action' | 'page_action';
|
||||
@@ -41,9 +46,12 @@ describe('Manifest Utils', () => {
|
||||
it('should include an action for mv3', async () => {
|
||||
const popup = popupEntrypoint();
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const expected: Partial<Manifest.WebExtensionManifest> = {
|
||||
action: {
|
||||
@@ -56,7 +64,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
@@ -74,9 +81,11 @@ describe('Manifest Utils', () => {
|
||||
async ({ inputType, expectedType }) => {
|
||||
const popup = popupEntrypoint(inputType);
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
manifestVersion: 2,
|
||||
outDir,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 2,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
default_icon: popup.options.defaultIcon,
|
||||
@@ -87,7 +96,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual[expectedType]).toEqual(expected);
|
||||
@@ -98,24 +106,22 @@ describe('Manifest Utils', () => {
|
||||
describe('action without popup', () => {
|
||||
it('should respect the action field in the manifest without a popup', async () => {
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
manifest: {
|
||||
action: {
|
||||
default_icon: 'icon-16.png',
|
||||
default_title: 'Example title',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifest: {
|
||||
action: {
|
||||
default_icon: 'icon-16.png',
|
||||
default_title: 'Example title',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const expected: Partial<Manifest.WebExtensionManifest> = {
|
||||
action: config.manifest.action,
|
||||
action: wxt.config.manifest.action,
|
||||
};
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
const { manifest: actual } = await generateManifest([], buildOutput);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
});
|
||||
@@ -132,10 +138,12 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it('should include a options_ui and chrome_style for chrome', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'chrome',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'chrome',
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -147,17 +155,18 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[options],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.options_ui).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should include a options_ui and browser_style for firefox', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
manifestVersion: 3,
|
||||
browser: 'firefox',
|
||||
outDir,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
browser: 'firefox',
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -169,7 +178,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[options],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.options_ui).toEqual(expected);
|
||||
@@ -189,10 +197,12 @@ describe('Manifest Utils', () => {
|
||||
it.each(['chrome', 'safari'])(
|
||||
'should include a service worker and type for %s',
|
||||
async (browser) => {
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
browser,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
browser,
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -203,7 +213,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -211,10 +220,12 @@ describe('Manifest Utils', () => {
|
||||
);
|
||||
|
||||
it('should include a background script and type for firefox', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
browser: 'firefox',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -225,7 +236,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -236,10 +246,12 @@ describe('Manifest Utils', () => {
|
||||
it.each(['chrome', 'safari'])(
|
||||
'should include scripts and persistent for %s',
|
||||
async (browser) => {
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
manifestVersion: 2,
|
||||
browser,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 2,
|
||||
browser,
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -250,7 +262,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -258,10 +269,12 @@ describe('Manifest Utils', () => {
|
||||
);
|
||||
|
||||
it('should include a background script and persistent for firefox mv2', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
manifestVersion: 2,
|
||||
browser: 'firefox',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 2,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const expected = {
|
||||
@@ -272,7 +285,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -293,12 +305,10 @@ describe('Manifest Utils', () => {
|
||||
{ type: 'asset', fileName: 'icons/128x128.png' },
|
||||
],
|
||||
});
|
||||
const config = fakeInternalConfig();
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.icons).toEqual({
|
||||
@@ -319,12 +329,10 @@ describe('Manifest Utils', () => {
|
||||
{ type: 'asset', fileName: 'icon-16.jpeg' },
|
||||
],
|
||||
});
|
||||
const config = fakeInternalConfig();
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.icons).toBeUndefined();
|
||||
@@ -346,16 +354,17 @@ describe('Manifest Utils', () => {
|
||||
32: 'logo-32.png',
|
||||
48: 'logo-48.png',
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
manifest: {
|
||||
icons: expected,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifest: {
|
||||
icons: expected,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.icons).toEqual(expected);
|
||||
@@ -440,10 +449,12 @@ describe('Manifest Utils', () => {
|
||||
};
|
||||
|
||||
const entrypoints = [cs1, cs2, cs3, cs4, cs5];
|
||||
const config = fakeInternalConfig({
|
||||
command: 'build',
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'build',
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
const buildOutput: Omit<BuildOutput, 'manifest'> = {
|
||||
publicAssets: [],
|
||||
@@ -459,7 +470,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.content_scripts).toContainEqual({
|
||||
@@ -509,18 +519,19 @@ describe('Manifest Utils', () => {
|
||||
|
||||
const entrypoints = [cs];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifest: {
|
||||
content_scripts: [userContentScript],
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifest: {
|
||||
content_scripts: [userContentScript],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.content_scripts).toContainEqual(userContentScript);
|
||||
@@ -552,15 +563,16 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.content_scripts).toEqual([
|
||||
@@ -597,15 +609,16 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.content_scripts).toEqual([
|
||||
@@ -639,16 +652,17 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
@@ -681,16 +695,17 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 2,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 2,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
@@ -720,16 +735,17 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
@@ -765,21 +781,22 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{ resources: ['one.png'], matches: ['*://one.com/*'] },
|
||||
],
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{ resources: ['one.png'], matches: ['*://one.com/*'] },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
@@ -813,19 +830,20 @@ describe('Manifest Utils', () => {
|
||||
publicAssets: [],
|
||||
steps: [{ entrypoints: cs, chunks: [styles] }],
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 2,
|
||||
manifest: {
|
||||
web_accessible_resources: ['one.png'],
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
command: 'build',
|
||||
manifestVersion: 2,
|
||||
manifest: {
|
||||
web_accessible_resources: ['one.png'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
@@ -833,6 +851,55 @@ describe('Manifest Utils', () => {
|
||||
'content-scripts/one.css',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert mv3 items to mv2 strings automatically', async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 2,
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
matches: ['*://*/*'],
|
||||
resources: ['/icon-128.png'],
|
||||
},
|
||||
{
|
||||
matches: ['https://google.com'],
|
||||
resources: ['/icon-128.png', '/icon-32.png'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[],
|
||||
fakeBuildOutput(),
|
||||
);
|
||||
|
||||
expect(actual.web_accessible_resources).toEqual([
|
||||
'/icon-128.png',
|
||||
'/icon-32.png',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert mv2 strings to mv3 items with a warning automatically', async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
outDir,
|
||||
manifestVersion: 3,
|
||||
manifest: {
|
||||
web_accessible_resources: ['/icon.svg'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(() =>
|
||||
generateManifest([], fakeBuildOutput()),
|
||||
).rejects.toThrow(
|
||||
'Non-MV3 web_accessible_resources detected: ["/icon.svg"]. When manually defining web_accessible_resources, define them as MV3 objects ({ matches: [...], resources: [...] }), and WXT will automatically convert them to MV2 when necessary.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformManifest option', () => {
|
||||
@@ -840,9 +907,11 @@ describe('Manifest Utils', () => {
|
||||
const entrypoints: Entrypoint[] = [];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const newAuthor = 'Custom Author';
|
||||
const config = fakeInternalConfig({
|
||||
transformManifest(manifest: any) {
|
||||
manifest.author = newAuthor;
|
||||
setFakeWxt({
|
||||
config: {
|
||||
transformManifest(manifest: any) {
|
||||
manifest.author = newAuthor;
|
||||
},
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
@@ -852,7 +921,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
@@ -867,18 +935,19 @@ describe('Manifest Utils', () => {
|
||||
const versionName = '1.0.0-alpha1';
|
||||
const entrypoints: Entrypoint[] = [];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: versionName,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: versionName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.version).toBe(version);
|
||||
@@ -893,18 +962,19 @@ describe('Manifest Utils', () => {
|
||||
const versionName = '1.0.0-alpha1';
|
||||
const entrypoints: Entrypoint[] = [];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: versionName,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: versionName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.version).toBe(version);
|
||||
@@ -918,18 +988,19 @@ describe('Manifest Utils', () => {
|
||||
const version = '1.0.0';
|
||||
const entrypoints: Entrypoint[] = [];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: version,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser,
|
||||
manifest: {
|
||||
version,
|
||||
version_name: version,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.version).toBe(version);
|
||||
@@ -940,23 +1011,24 @@ describe('Manifest Utils', () => {
|
||||
it('should log a warning if the version could not be detected', async () => {
|
||||
const entrypoints: Entrypoint[] = [];
|
||||
const buildOutput = fakeBuildOutput();
|
||||
const config = fakeInternalConfig({
|
||||
manifest: {
|
||||
// @ts-ignore: Purposefully removing version from fake object
|
||||
version: null,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifest: {
|
||||
// @ts-ignore: Purposefully removing version from fake object
|
||||
version: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.version).toBe('0.0.0');
|
||||
expect(actual.version_name).toBeUndefined();
|
||||
expect(config.logger.warn).toBeCalledTimes(1);
|
||||
expect(config.logger.warn).toBeCalledWith(
|
||||
expect(wxt.logger.warn).toBeCalledTimes(1);
|
||||
expect(wxt.logger.warn).toBeCalledWith(
|
||||
expect.stringContaining('Extension version not found'),
|
||||
);
|
||||
});
|
||||
@@ -972,14 +1044,15 @@ describe('Manifest Utils', () => {
|
||||
};
|
||||
|
||||
it('should include a command for reloading the extension during development', async () => {
|
||||
const config = fakeInternalConfig({ command: 'serve' });
|
||||
setFakeWxt({
|
||||
config: { command: 'serve' },
|
||||
});
|
||||
const output = fakeBuildOutput();
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual({
|
||||
@@ -988,10 +1061,12 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it('should customize the reload commands key binding if passing a custom command', async () => {
|
||||
const config = fakeInternalConfig({
|
||||
command: 'serve',
|
||||
dev: {
|
||||
reloadCommand: 'Ctrl+E',
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'serve',
|
||||
dev: {
|
||||
reloadCommand: 'Ctrl+E',
|
||||
},
|
||||
},
|
||||
});
|
||||
const output = fakeBuildOutput();
|
||||
@@ -1000,7 +1075,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual({
|
||||
@@ -1014,10 +1088,12 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it("should not include the reload command when it's been disabled", async () => {
|
||||
const config = fakeInternalConfig({
|
||||
command: 'serve',
|
||||
dev: {
|
||||
reloadCommand: false,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'serve',
|
||||
dev: {
|
||||
reloadCommand: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
const output = fakeBuildOutput();
|
||||
@@ -1026,7 +1102,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toBeUndefined();
|
||||
@@ -1035,11 +1110,13 @@ describe('Manifest Utils', () => {
|
||||
it('should not override any existing commands when adding the one to reload the extension', async () => {
|
||||
const customCommandName = 'custom-command';
|
||||
const customCommand = fakeManifestCommand();
|
||||
const config = fakeInternalConfig({
|
||||
command: 'serve',
|
||||
manifest: {
|
||||
commands: {
|
||||
[customCommandName]: customCommand,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'serve',
|
||||
manifest: {
|
||||
commands: {
|
||||
[customCommandName]: customCommand,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1049,7 +1126,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual({
|
||||
@@ -1065,9 +1141,11 @@ describe('Manifest Utils', () => {
|
||||
command3: fakeManifestCommand(),
|
||||
command4: fakeManifestCommand(),
|
||||
};
|
||||
const config = fakeInternalConfig({
|
||||
command: 'serve',
|
||||
manifest: { commands },
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'serve',
|
||||
manifest: { commands },
|
||||
},
|
||||
});
|
||||
const output = fakeBuildOutput();
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
@@ -1075,7 +1153,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual, warnings } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual(commands);
|
||||
@@ -1083,14 +1160,15 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it('should not include the command when building an extension', async () => {
|
||||
const config = fakeInternalConfig({ command: 'build' });
|
||||
setFakeWxt({
|
||||
config: { command: 'build' },
|
||||
});
|
||||
const output = fakeBuildOutput();
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toBeUndefined();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getPackageJson } from '../package';
|
||||
import { fakeInternalConfig } from '../testing/fake-objects';
|
||||
import { setFakeWxt } from '../testing/fake-objects';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { Logger } from '~/types';
|
||||
|
||||
@@ -8,7 +8,11 @@ describe('Package JSON Utils', () => {
|
||||
describe('getPackageJson', () => {
|
||||
it('should return the package.json inside <root>/package.json', async () => {
|
||||
const root = process.cwd(); // WXT project directory
|
||||
const actual = await getPackageJson(fakeInternalConfig({ root }));
|
||||
setFakeWxt({
|
||||
config: { root },
|
||||
});
|
||||
|
||||
const actual = await getPackageJson();
|
||||
|
||||
expect(actual).toMatchObject({
|
||||
name: 'wxt',
|
||||
@@ -18,7 +22,12 @@ describe('Package JSON Utils', () => {
|
||||
it("should return an empty object when <root>/package.json doesn't exist", async () => {
|
||||
const root = '/some/path/that/does/not/exist';
|
||||
const logger = mock<Logger>();
|
||||
const actual = await getPackageJson(fakeInternalConfig({ root, logger }));
|
||||
setFakeWxt({
|
||||
config: { root, logger },
|
||||
logger,
|
||||
});
|
||||
|
||||
const actual = await getPackageJson();
|
||||
|
||||
expect(actual).toEqual({});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { DevModeChange, detectDevChanges } from '~/core/utils/building';
|
||||
import {
|
||||
fakeBackgroundEntrypoint,
|
||||
@@ -10,14 +10,19 @@ import {
|
||||
fakePopupEntrypoint,
|
||||
fakeOutputAsset,
|
||||
fakeOutputChunk,
|
||||
fakeInternalConfig,
|
||||
fakeWxt,
|
||||
setFakeWxt,
|
||||
} from '~/core/utils/testing/fake-objects';
|
||||
import { BuildOutput, BuildStepOutput } from '~/types';
|
||||
import { setWxtForTesting } from '../../../wxt';
|
||||
|
||||
describe('Detect Dev Changes', () => {
|
||||
beforeEach(() => {
|
||||
setWxtForTesting(fakeWxt());
|
||||
});
|
||||
|
||||
describe('No changes', () => {
|
||||
it("should return 'no-change' when the changed file isn't used by any of the entrypoints", () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changes = ['/some/path.ts'];
|
||||
const currentOutput: BuildOutput = {
|
||||
manifest: fakeManifest(),
|
||||
@@ -34,7 +39,7 @@ describe('Detect Dev Changes', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, changes, currentOutput);
|
||||
const actual = detectDevChanges(changes, currentOutput);
|
||||
|
||||
expect(actual).toEqual({ type: 'no-change' });
|
||||
});
|
||||
@@ -43,9 +48,11 @@ describe('Detect Dev Changes', () => {
|
||||
describe('wxt.config.ts', () => {
|
||||
it("should return 'full-restart' when one of the changed files is the config file", () => {
|
||||
const configFile = '/root/wxt.config.ts';
|
||||
const config = fakeInternalConfig({
|
||||
userConfigMetadata: {
|
||||
configFile,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
userConfigMetadata: {
|
||||
configFile,
|
||||
},
|
||||
},
|
||||
});
|
||||
const changes = ['/root/src/public/image.svg', configFile];
|
||||
@@ -58,7 +65,7 @@ describe('Detect Dev Changes', () => {
|
||||
type: 'full-restart',
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, changes, currentOutput);
|
||||
const actual = detectDevChanges(changes, currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -67,9 +74,11 @@ describe('Detect Dev Changes', () => {
|
||||
describe('web-ext.config.ts', () => {
|
||||
it("should return 'browser-restart' when one of the changed files is the config file", () => {
|
||||
const runnerFile = '/root/web-ext.config.ts';
|
||||
const config = fakeInternalConfig({
|
||||
runnerConfig: {
|
||||
configFile: runnerFile,
|
||||
setFakeWxt({
|
||||
config: {
|
||||
runnerConfig: {
|
||||
configFile: runnerFile,
|
||||
},
|
||||
},
|
||||
});
|
||||
const changes = ['/root/src/public/image.svg', runnerFile];
|
||||
@@ -82,7 +91,7 @@ describe('Detect Dev Changes', () => {
|
||||
type: 'browser-restart',
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, changes, currentOutput);
|
||||
const actual = detectDevChanges(changes, currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -90,7 +99,6 @@ describe('Detect Dev Changes', () => {
|
||||
|
||||
describe('Public Assets', () => {
|
||||
it("should return 'extension-reload' without any groups to rebuild when the changed file is a public asset", () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changes = ['/root/src/public/image.svg'];
|
||||
const asset1 = fakeOutputAsset({
|
||||
fileName: 'image.svg',
|
||||
@@ -112,7 +120,7 @@ describe('Detect Dev Changes', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, changes, currentOutput);
|
||||
const actual = detectDevChanges(changes, currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -120,7 +128,6 @@ describe('Detect Dev Changes', () => {
|
||||
|
||||
describe('Background', () => {
|
||||
it("should rebuild the background and reload the extension when the changed file in it's chunks' `moduleIds` field", () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changedPath = '/root/utils/shared.ts';
|
||||
const contentScript = fakeContentScriptEntrypoint({
|
||||
inputPath: '/root/overlay.content.ts',
|
||||
@@ -160,7 +167,7 @@ describe('Detect Dev Changes', () => {
|
||||
rebuildGroups: [background],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, [changedPath], currentOutput);
|
||||
const actual = detectDevChanges([changedPath], currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -168,7 +175,6 @@ describe('Detect Dev Changes', () => {
|
||||
|
||||
describe('HTML Pages', () => {
|
||||
it('should detect changes to entrypoints/<name>.html files', async () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changedPath = '/root/page1.html';
|
||||
const htmlPage1 = fakePopupEntrypoint({
|
||||
inputPath: changedPath,
|
||||
@@ -212,13 +218,12 @@ describe('Detect Dev Changes', () => {
|
||||
rebuildGroups: [[htmlPage1, htmlPage2]],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, [changedPath], currentOutput);
|
||||
const actual = detectDevChanges([changedPath], currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should detect changes to entrypoints/<name>/index.html files', async () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changedPath = '/root/page1/index.html';
|
||||
const htmlPage1 = fakePopupEntrypoint({
|
||||
inputPath: changedPath,
|
||||
@@ -262,7 +267,7 @@ describe('Detect Dev Changes', () => {
|
||||
rebuildGroups: [[htmlPage1, htmlPage2]],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, [changedPath], currentOutput);
|
||||
const actual = detectDevChanges([changedPath], currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -270,7 +275,6 @@ describe('Detect Dev Changes', () => {
|
||||
|
||||
describe('Content Scripts', () => {
|
||||
it('should rebuild then reload only the effected content scripts', async () => {
|
||||
const config = fakeInternalConfig();
|
||||
const changedPath = '/root/utils/shared.ts';
|
||||
const script1 = fakeContentScriptEntrypoint({
|
||||
inputPath: '/root/overlay1.content/index.ts',
|
||||
@@ -322,7 +326,7 @@ describe('Detect Dev Changes', () => {
|
||||
rebuildGroups: [script1, script3],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(config, [changedPath], currentOutput);
|
||||
const actual = detectDevChanges([changedPath], currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
BackgroundEntrypoint,
|
||||
ContentScriptEntrypoint,
|
||||
@@ -11,7 +11,10 @@ import { findEntrypoints } from '../find-entrypoints';
|
||||
import fs from 'fs-extra';
|
||||
import { importEntrypointFile } from '../import-entrypoint';
|
||||
import glob from 'fast-glob';
|
||||
import { fakeInternalConfig } from '~/core/utils/testing/fake-objects';
|
||||
import {
|
||||
fakeResolvedConfig,
|
||||
setFakeWxt,
|
||||
} from '~/core/utils/testing/fake-objects';
|
||||
import { unnormalizePath } from '~/core/utils/paths';
|
||||
|
||||
vi.mock('../import-entrypoint');
|
||||
@@ -26,13 +29,18 @@ const readFileMock = vi.mocked(
|
||||
);
|
||||
|
||||
describe('findEntrypoints', () => {
|
||||
const config = fakeInternalConfig({
|
||||
const config = fakeResolvedConfig({
|
||||
manifestVersion: 3,
|
||||
root: '/',
|
||||
entrypointsDir: resolve('/src/entrypoints'),
|
||||
outDir: resolve('.output'),
|
||||
command: 'build',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setFakeWxt({ config });
|
||||
});
|
||||
|
||||
it.each<[string, string, PopupEntrypoint]>([
|
||||
[
|
||||
'popup.html',
|
||||
@@ -82,7 +90,7 @@ describe('findEntrypoints', () => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
readFileMock.mockResolvedValueOnce(content);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual(expected);
|
||||
@@ -135,7 +143,7 @@ describe('findEntrypoints', () => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
readFileMock.mockResolvedValueOnce(content);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual(expected);
|
||||
@@ -202,14 +210,11 @@ describe('findEntrypoints', () => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointFileMock).toBeCalledWith(
|
||||
expected.inputPath,
|
||||
config,
|
||||
);
|
||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -243,24 +248,58 @@ describe('findEntrypoints', () => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointFileMock).toBeCalledWith(
|
||||
expected.inputPath,
|
||||
config,
|
||||
);
|
||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
||||
},
|
||||
);
|
||||
|
||||
it('should remove type=module from MV2 background scripts', async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 2,
|
||||
},
|
||||
});
|
||||
const options: BackgroundEntrypoint['options'] = {
|
||||
type: 'module',
|
||||
};
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints[0].options).toEqual({});
|
||||
});
|
||||
|
||||
it('should allow type=module for MV3 background service workers', async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
const options: BackgroundEntrypoint['options'] = {
|
||||
type: 'module',
|
||||
};
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints[0].options).toEqual(options);
|
||||
});
|
||||
|
||||
it("should include a virtual background script so dev reloading works when there isn't a background entrypoint defined by the user", async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
...config,
|
||||
command: 'serve',
|
||||
},
|
||||
});
|
||||
globMock.mockResolvedValueOnce(['popup.html']);
|
||||
|
||||
const entrypoints = await findEntrypoints({
|
||||
...config,
|
||||
command: 'serve',
|
||||
});
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(2);
|
||||
expect(entrypoints).toContainEqual({
|
||||
@@ -296,14 +335,11 @@ describe('findEntrypoints', () => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointFileMock).toBeCalledWith(
|
||||
expected.inputPath,
|
||||
config,
|
||||
);
|
||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -592,7 +628,7 @@ describe('findEntrypoints', () => {
|
||||
])('should find entrypoint for %s', async (path, expected) => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual(expected);
|
||||
@@ -608,7 +644,7 @@ describe('findEntrypoints', () => {
|
||||
'ui.html',
|
||||
]);
|
||||
|
||||
await expect(() => findEntrypoints(config)).rejects.toThrowError(
|
||||
await expect(() => findEntrypoints()).rejects.toThrowError(
|
||||
[
|
||||
'Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.',
|
||||
'',
|
||||
@@ -626,7 +662,7 @@ describe('findEntrypoints', () => {
|
||||
it('throw an error if there are no entrypoints', async () => {
|
||||
globMock.mockResolvedValueOnce([]);
|
||||
|
||||
await expect(() => findEntrypoints(config)).rejects.toThrowError(
|
||||
await expect(() => findEntrypoints()).rejects.toThrowError(
|
||||
`No entrypoints found in ${unnormalizePath(config.entrypointsDir)}`,
|
||||
);
|
||||
});
|
||||
@@ -638,7 +674,7 @@ describe('findEntrypoints', () => {
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -649,7 +685,7 @@ describe('findEntrypoints', () => {
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -666,7 +702,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -683,7 +719,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -700,7 +736,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -713,7 +749,7 @@ describe('findEntrypoints', () => {
|
||||
exclude: [config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -724,7 +760,7 @@ describe('findEntrypoints', () => {
|
||||
exclude: [config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -739,7 +775,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -754,7 +790,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -769,7 +805,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -785,15 +821,17 @@ describe('findEntrypoints', () => {
|
||||
]);
|
||||
importEntrypointFileMock.mockResolvedValue({});
|
||||
const filterEntrypoints = ['popup', 'ui'];
|
||||
const config = fakeInternalConfig({
|
||||
root: '/',
|
||||
entrypointsDir: resolve('/src/entrypoints'),
|
||||
outDir: resolve('.output'),
|
||||
command: 'build',
|
||||
filterEntrypoints: new Set(filterEntrypoints),
|
||||
setFakeWxt({
|
||||
config: {
|
||||
root: '/',
|
||||
entrypointsDir: resolve('/src/entrypoints'),
|
||||
outDir: resolve('.output'),
|
||||
command: 'build',
|
||||
filterEntrypoints: new Set(filterEntrypoints),
|
||||
},
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
const names = entrypoints.map((item) => item.name);
|
||||
expect(names).toHaveLength(2);
|
||||
expect(names).toEqual(filterEntrypoints);
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { Entrypoint } from '~/types';
|
||||
import { groupEntrypoints } from '../group-entrypoints';
|
||||
import {
|
||||
fakeBackgroundEntrypoint,
|
||||
fakeGenericEntrypoint,
|
||||
fakePopupEntrypoint,
|
||||
} from '../../testing/fake-objects';
|
||||
|
||||
const background: Entrypoint = {
|
||||
type: 'background',
|
||||
@@ -141,4 +146,29 @@ describe('groupEntrypoints', () => {
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should group ESM compatible scripts with extension pages', () => {
|
||||
const background = fakeBackgroundEntrypoint({
|
||||
options: {
|
||||
type: 'module',
|
||||
},
|
||||
});
|
||||
const popup = fakePopupEntrypoint();
|
||||
const sandbox = fakeGenericEntrypoint({
|
||||
inputPath: '/entrypoints/sandbox.html',
|
||||
name: 'sandbox',
|
||||
type: 'sandbox',
|
||||
});
|
||||
|
||||
const actual = groupEntrypoints([background, popup, sandbox]);
|
||||
|
||||
expect(actual).toEqual([[background, popup], [sandbox]]);
|
||||
});
|
||||
|
||||
it.todo(
|
||||
'should group ESM compatible sandbox scripts with sandbox pages',
|
||||
() => {
|
||||
// Main world content scripts
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { importEntrypointFile } from '~/core/utils/building';
|
||||
import { fakeInternalConfig } from '~/core/utils/testing/fake-objects';
|
||||
import { resolve } from 'node:path';
|
||||
import { unnormalizePath } from '../../paths';
|
||||
import { setFakeWxt } from '../../testing/fake-objects';
|
||||
|
||||
const entrypointPath = (filename: string) =>
|
||||
resolve('src/core/utils/__tests__/test-entrypoints', filename);
|
||||
|
||||
const config = fakeInternalConfig({
|
||||
imports: false,
|
||||
debug: false,
|
||||
// Run inside the demo folder so that wxt is in the node_modules
|
||||
// WXT must also be built for these tests to pass
|
||||
root: 'demo',
|
||||
});
|
||||
|
||||
describe('importEntrypointFile', () => {
|
||||
beforeEach(() => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
imports: false,
|
||||
debug: false,
|
||||
// Run inside the demo folder so that wxt is in the node_modules
|
||||
// WXT must also be built for these tests to pass
|
||||
root: 'demo',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['background.ts', { main: expect.any(Function) }],
|
||||
['content.ts', { main: expect.any(Function), matches: ['<all_urls>'] }],
|
||||
@@ -25,7 +29,7 @@ describe('importEntrypointFile', () => {
|
||||
])(
|
||||
'should return the default export of test-entrypoints/%s',
|
||||
async (file, expected) => {
|
||||
const actual = await importEntrypointFile(entrypointPath(file), config);
|
||||
const actual = await importEntrypointFile(entrypointPath(file));
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
},
|
||||
@@ -34,7 +38,6 @@ describe('importEntrypointFile', () => {
|
||||
it('should return undefined when there is no default export', async () => {
|
||||
const actual = await importEntrypointFile(
|
||||
entrypointPath('no-default-export.ts'),
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual).toBeUndefined();
|
||||
@@ -45,7 +48,7 @@ describe('importEntrypointFile', () => {
|
||||
'../src/core/utils/__tests__/test-entrypoints/imported-option.ts',
|
||||
);
|
||||
await expect(() =>
|
||||
importEntrypointFile(entrypointPath('imported-option.ts'), config),
|
||||
importEntrypointFile(entrypointPath('imported-option.ts')),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: ${filePath}: Cannot use imported variable "faker" outside the main function. See https://wxt.dev/guide/entrypoints.html#side-effects]`,
|
||||
);
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import {
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
InternalConfig,
|
||||
} from '~/types';
|
||||
import { BuildOutput, BuildStepOutput, EntrypointGroup } from '~/types';
|
||||
import { getPublicFiles } from '~/core/utils/fs';
|
||||
import fs from 'fs-extra';
|
||||
import { dirname, resolve } from 'path';
|
||||
import type { Ora } from 'ora';
|
||||
import pc from 'picocolors';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
export async function buildEntrypoints(
|
||||
groups: EntrypointGroup[],
|
||||
config: InternalConfig,
|
||||
spinner: Ora,
|
||||
): Promise<Omit<BuildOutput, 'manifest'>> {
|
||||
const steps: BuildStepOutput[] = [];
|
||||
@@ -23,26 +18,25 @@ export async function buildEntrypoints(
|
||||
spinner.text =
|
||||
pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
|
||||
try {
|
||||
steps.push(await config.builder.build(group));
|
||||
steps.push(await wxt.config.builder.build(group));
|
||||
} catch (err) {
|
||||
wxt.logger.error(err);
|
||||
throw Error(`Failed to build ${groupNames.join(', ')}`, { cause: err });
|
||||
}
|
||||
}
|
||||
const publicAssets = await copyPublicDirectory(config);
|
||||
const publicAssets = await copyPublicDirectory();
|
||||
|
||||
return { publicAssets, steps };
|
||||
}
|
||||
|
||||
async function copyPublicDirectory(
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput['publicAssets']> {
|
||||
const files = await getPublicFiles(config);
|
||||
async function copyPublicDirectory(): Promise<BuildOutput['publicAssets']> {
|
||||
const files = await getPublicFiles();
|
||||
if (files.length === 0) return [];
|
||||
|
||||
const publicAssets: BuildOutput['publicAssets'] = [];
|
||||
for (const file of files) {
|
||||
const srcPath = resolve(config.publicDir, file);
|
||||
const outPath = resolve(config.outDir, file);
|
||||
const srcPath = resolve(wxt.config.publicDir, file);
|
||||
const outPath = resolve(wxt.config.outDir, file);
|
||||
|
||||
await fs.ensureDir(dirname(outPath));
|
||||
await fs.copyFile(srcPath, outPath);
|
||||
|
||||
@@ -2,12 +2,12 @@ import {
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
InternalConfig,
|
||||
OutputAsset,
|
||||
OutputFile,
|
||||
} from '~/types';
|
||||
import { every, some } from '~/core/utils/arrays';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Compare the changed files vs the build output and determine what kind of reload needs to happen:
|
||||
@@ -30,19 +30,18 @@ import { normalizePath } from '~/core/utils/paths';
|
||||
* - Config file changed (wxt.config.ts, .env, web-ext.config.ts, etc)
|
||||
*/
|
||||
export function detectDevChanges(
|
||||
config: InternalConfig,
|
||||
changedFiles: string[],
|
||||
currentOutput: BuildOutput,
|
||||
): DevModeChange {
|
||||
const isConfigChange = some(
|
||||
changedFiles,
|
||||
(file) => file === config.userConfigMetadata.configFile,
|
||||
(file) => file === wxt.config.userConfigMetadata.configFile,
|
||||
);
|
||||
if (isConfigChange) return { type: 'full-restart' };
|
||||
|
||||
const isRunnerChange = some(
|
||||
changedFiles,
|
||||
(file) => file === config.runnerConfig.configFile,
|
||||
(file) => file === wxt.config.runnerConfig.configFile,
|
||||
);
|
||||
if (isRunnerChange) return { type: 'browser-restart' };
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
ContentScriptEntrypoint,
|
||||
Entrypoint,
|
||||
GenericEntrypoint,
|
||||
InternalConfig,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
UnlistedScriptDefinition,
|
||||
@@ -25,15 +24,14 @@ import {
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '~/core/utils/constants';
|
||||
import { CSS_EXTENSIONS_PATTERN } from '~/core/utils/paths';
|
||||
import pc from 'picocolors';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Return entrypoints and their configuration by looking through the project's files.
|
||||
*/
|
||||
export async function findEntrypoints(
|
||||
config: InternalConfig,
|
||||
): Promise<Entrypoint[]> {
|
||||
export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
const relativePaths = await glob(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
|
||||
cwd: config.entrypointsDir,
|
||||
cwd: wxt.config.entrypointsDir,
|
||||
});
|
||||
// Ensure consistent output
|
||||
relativePaths.sort();
|
||||
@@ -42,8 +40,8 @@ export async function findEntrypoints(
|
||||
const entrypointInfos: EntrypointInfo[] = relativePaths.reduce<
|
||||
EntrypointInfo[]
|
||||
>((results, relativePath) => {
|
||||
const inputPath = resolve(config.entrypointsDir, relativePath);
|
||||
const name = getEntrypointName(config.entrypointsDir, inputPath);
|
||||
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
|
||||
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
|
||||
const matchingGlob = pathGlobs.find((glob) =>
|
||||
minimatch(relativePath, glob),
|
||||
);
|
||||
@@ -54,16 +52,16 @@ export async function findEntrypoints(
|
||||
inputPath,
|
||||
type,
|
||||
skipped:
|
||||
config.filterEntrypoints != null &&
|
||||
!config.filterEntrypoints.has(name),
|
||||
wxt.config.filterEntrypoints != null &&
|
||||
!wxt.config.filterEntrypoints.has(name),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
// Validation
|
||||
preventNoEntrypoints(config, entrypointInfos);
|
||||
preventDuplicateEntrypointNames(config, entrypointInfos);
|
||||
preventNoEntrypoints(entrypointInfos);
|
||||
preventDuplicateEntrypointNames(entrypointInfos);
|
||||
|
||||
// Import entrypoints to get their config
|
||||
let hasBackground = false;
|
||||
@@ -72,23 +70,23 @@ export async function findEntrypoints(
|
||||
const { type } = info;
|
||||
switch (type) {
|
||||
case 'popup':
|
||||
return await getPopupEntrypoint(config, info);
|
||||
return await getPopupEntrypoint(info);
|
||||
case 'options':
|
||||
return await getOptionsEntrypoint(config, info);
|
||||
return await getOptionsEntrypoint(info);
|
||||
case 'background':
|
||||
hasBackground = true;
|
||||
return await getBackgroundEntrypoint(config, info);
|
||||
return await getBackgroundEntrypoint(info);
|
||||
case 'content-script':
|
||||
return await getContentScriptEntrypoint(config, info);
|
||||
return await getContentScriptEntrypoint(info);
|
||||
case 'unlisted-page':
|
||||
return await getUnlistedPageEntrypoint(config, info);
|
||||
return await getUnlistedPageEntrypoint(info);
|
||||
case 'unlisted-script':
|
||||
return await getUnlistedScriptEntrypoint(config, info);
|
||||
return await getUnlistedScriptEntrypoint(info);
|
||||
case 'content-script-style':
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: resolve(config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options: {
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
@@ -98,7 +96,7 @@ export async function findEntrypoints(
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
@@ -108,9 +106,9 @@ export async function findEntrypoints(
|
||||
}),
|
||||
);
|
||||
|
||||
if (config.command === 'serve' && !hasBackground) {
|
||||
if (wxt.config.command === 'serve' && !hasBackground) {
|
||||
entrypoints.push(
|
||||
await getBackgroundEntrypoint(config, {
|
||||
await getBackgroundEntrypoint({
|
||||
inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
|
||||
name: 'background',
|
||||
type: 'background',
|
||||
@@ -119,12 +117,12 @@ export async function findEntrypoints(
|
||||
);
|
||||
}
|
||||
|
||||
config.logger.debug('All entrypoints:', entrypoints);
|
||||
wxt.logger.debug('All entrypoints:', entrypoints);
|
||||
const skippedEntrypointNames = entrypointInfos
|
||||
.filter((item) => item.skipped)
|
||||
.map((item) => item.name);
|
||||
if (skippedEntrypointNames.length) {
|
||||
config.logger.warn(
|
||||
wxt.logger.warn(
|
||||
`Filter excluded the following entrypoints:\n${skippedEntrypointNames
|
||||
.map((item) => `${pc.dim('-')} ${pc.cyan(item)}`)
|
||||
.join('\n')}`,
|
||||
@@ -133,16 +131,16 @@ export async function findEntrypoints(
|
||||
const targetEntrypoints = entrypoints.filter((entry) => {
|
||||
const { include, exclude } = entry.options;
|
||||
if (include?.length && exclude?.length) {
|
||||
config.logger.warn(
|
||||
wxt.logger.warn(
|
||||
`The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (exclude?.length && !include?.length) {
|
||||
return !exclude.includes(config.browser);
|
||||
return !exclude.includes(wxt.config.browser);
|
||||
}
|
||||
if (include?.length && !exclude?.length) {
|
||||
return include.includes(config.browser);
|
||||
return include.includes(wxt.config.browser);
|
||||
}
|
||||
if (skippedEntrypointNames.includes(entry.name)) {
|
||||
return false;
|
||||
@@ -150,7 +148,9 @@ export async function findEntrypoints(
|
||||
|
||||
return true;
|
||||
});
|
||||
config.logger.debug(`${config.browser} entrypoints:`, targetEntrypoints);
|
||||
wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
|
||||
await wxt.hooks.callHook('entrypoints:resolved', wxt, targetEntrypoints);
|
||||
|
||||
return targetEntrypoints;
|
||||
}
|
||||
|
||||
@@ -164,10 +164,7 @@ interface EntrypointInfo {
|
||||
skipped: boolean;
|
||||
}
|
||||
|
||||
function preventDuplicateEntrypointNames(
|
||||
config: InternalConfig,
|
||||
files: EntrypointInfo[],
|
||||
) {
|
||||
function preventDuplicateEntrypointNames(files: EntrypointInfo[]) {
|
||||
const namesToPaths = files.reduce<Record<string, string[]>>(
|
||||
(map, { name, inputPath }) => {
|
||||
map[name] ??= [];
|
||||
@@ -181,7 +178,7 @@ function preventDuplicateEntrypointNames(
|
||||
if (absolutePaths.length > 1) {
|
||||
lines.push(`- ${name}`);
|
||||
absolutePaths.forEach((absolutePath) => {
|
||||
lines.push(` - ${relative(config.root, absolutePath)}`);
|
||||
lines.push(` - ${relative(wxt.config.root, absolutePath)}`);
|
||||
});
|
||||
}
|
||||
return lines;
|
||||
@@ -196,9 +193,9 @@ function preventDuplicateEntrypointNames(
|
||||
}
|
||||
}
|
||||
|
||||
function preventNoEntrypoints(config: InternalConfig, files: EntrypointInfo[]) {
|
||||
function preventNoEntrypoints(files: EntrypointInfo[]) {
|
||||
if (files.length === 0) {
|
||||
throw Error(`No entrypoints found in ${config.entrypointsDir}`);
|
||||
throw Error(`No entrypoints found in ${wxt.config.entrypointsDir}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +223,11 @@ function getHtmlBaseOptions(document: Document): BaseEntrypointOptions {
|
||||
* @param path Absolute path to the popup HTML file.
|
||||
* @param content String contents of the file at the path.
|
||||
*/
|
||||
async function getPopupEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<PopupEntrypoint> {
|
||||
async function getPopupEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<PopupEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
@@ -245,7 +243,7 @@ async function getPopupEntrypoint(
|
||||
try {
|
||||
options.defaultIcon = JSON5.parse(defaultIconContent);
|
||||
} catch (err) {
|
||||
config.logger.fatal(
|
||||
wxt.logger.fatal(
|
||||
`Failed to parse default_icon meta tag content as JSON5. content=${defaultIconContent}`,
|
||||
err,
|
||||
);
|
||||
@@ -272,7 +270,7 @@ async function getPopupEntrypoint(
|
||||
name: 'popup',
|
||||
options,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
@@ -281,10 +279,11 @@ async function getPopupEntrypoint(
|
||||
* @param path Absolute path to the options HTML file.
|
||||
* @param content String contents of the file at the path.
|
||||
*/
|
||||
async function getOptionsEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<OptionsEntrypoint> {
|
||||
async function getOptionsEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<OptionsEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
@@ -316,7 +315,7 @@ async function getOptionsEntrypoint(
|
||||
name: 'options',
|
||||
options,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
@@ -325,18 +324,19 @@ async function getOptionsEntrypoint(
|
||||
* @param path Absolute path to the HTML file.
|
||||
* @param content String contents of the file at the path.
|
||||
*/
|
||||
async function getUnlistedPageEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<GenericEntrypoint> {
|
||||
async function getUnlistedPageEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<GenericEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
return {
|
||||
type: 'unlisted-page',
|
||||
name: getEntrypointName(config.entrypointsDir, inputPath),
|
||||
name: getEntrypointName(wxt.config.entrypointsDir, inputPath),
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: getHtmlBaseOptions(document),
|
||||
skipped,
|
||||
};
|
||||
@@ -346,14 +346,13 @@ async function getUnlistedPageEntrypoint(
|
||||
* @param path Absolute path to the script's file.
|
||||
* @param content String contents of the file at the path.
|
||||
*/
|
||||
async function getUnlistedScriptEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<GenericEntrypoint> {
|
||||
const defaultExport = await importEntrypointFile<UnlistedScriptDefinition>(
|
||||
inputPath,
|
||||
config,
|
||||
);
|
||||
async function getUnlistedScriptEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<GenericEntrypoint> {
|
||||
const defaultExport =
|
||||
await importEntrypointFile<UnlistedScriptDefinition>(inputPath);
|
||||
if (defaultExport == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`,
|
||||
@@ -365,7 +364,7 @@ async function getUnlistedScriptEntrypoint(
|
||||
type: 'unlisted-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options,
|
||||
skipped,
|
||||
};
|
||||
@@ -374,16 +373,15 @@ async function getUnlistedScriptEntrypoint(
|
||||
/**
|
||||
* @param path Absolute path to the background's TS file.
|
||||
*/
|
||||
async function getBackgroundEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<BackgroundEntrypoint> {
|
||||
async function getBackgroundEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<BackgroundEntrypoint> {
|
||||
let options: Omit<BackgroundDefinition, 'main'> = {};
|
||||
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
|
||||
const defaultExport = await importEntrypointFile<BackgroundDefinition>(
|
||||
inputPath,
|
||||
config,
|
||||
);
|
||||
const defaultExport =
|
||||
await importEntrypointFile<BackgroundDefinition>(inputPath);
|
||||
if (defaultExport == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`,
|
||||
@@ -392,15 +390,23 @@ async function getBackgroundEntrypoint(
|
||||
const { main: _, ...moduleOptions } = defaultExport;
|
||||
options = moduleOptions;
|
||||
}
|
||||
|
||||
if (wxt.config.manifestVersion !== 3) {
|
||||
delete options.type;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'background',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {
|
||||
...options,
|
||||
type: resolvePerBrowserOption(options.type, config.browser),
|
||||
persistent: resolvePerBrowserOption(options.persistent, config.browser),
|
||||
type: resolvePerBrowserOption(options.type, wxt.config.browser),
|
||||
persistent: resolvePerBrowserOption(
|
||||
options.persistent,
|
||||
wxt.config.browser,
|
||||
),
|
||||
},
|
||||
skipped,
|
||||
};
|
||||
@@ -409,12 +415,13 @@ async function getBackgroundEntrypoint(
|
||||
/**
|
||||
* @param path Absolute path to the content script's TS file.
|
||||
*/
|
||||
async function getContentScriptEntrypoint(
|
||||
config: InternalConfig,
|
||||
{ inputPath, name, skipped }: EntrypointInfo,
|
||||
): Promise<ContentScriptEntrypoint> {
|
||||
async function getContentScriptEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
skipped,
|
||||
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
|
||||
const { main: _, ...options } =
|
||||
await importEntrypointFile<ContentScriptDefinition>(inputPath, config);
|
||||
await importEntrypointFile<ContentScriptDefinition>(inputPath);
|
||||
if (options == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
||||
@@ -424,7 +431,7 @@ async function getContentScriptEntrypoint(
|
||||
type: 'content-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: resolve(config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options,
|
||||
skipped,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UnimportOptions, createUnimport } from 'unimport';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { Entrypoint } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { relative, resolve } from 'path';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
@@ -9,40 +9,39 @@ import { normalizePath } from '~/core/utils/paths';
|
||||
import path from 'node:path';
|
||||
import { Message, parseI18nMessages } from '~/core/utils/i18n';
|
||||
import { writeFileIfDifferent, getPublicFiles } from '~/core/utils/fs';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
|
||||
*/
|
||||
export async function generateTypesDir(
|
||||
entrypoints: Entrypoint[],
|
||||
config: InternalConfig,
|
||||
): Promise<void> {
|
||||
await fs.ensureDir(config.typesDir);
|
||||
await fs.ensureDir(wxt.config.typesDir);
|
||||
|
||||
const references: string[] = [];
|
||||
|
||||
const imports = getUnimportOptions(config);
|
||||
const imports = getUnimportOptions(wxt.config);
|
||||
if (imports !== false) {
|
||||
references.push(await writeImportsDeclarationFile(config, imports));
|
||||
references.push(await writeImportsDeclarationFile(imports));
|
||||
}
|
||||
|
||||
references.push(await writePathsDeclarationFile(entrypoints, config));
|
||||
references.push(await writeI18nDeclarationFile(config));
|
||||
references.push(await writeGlobalsDeclarationFile(config));
|
||||
references.push(await writePathsDeclarationFile(entrypoints));
|
||||
references.push(await writeI18nDeclarationFile());
|
||||
references.push(await writeGlobalsDeclarationFile());
|
||||
|
||||
const mainReference = await writeMainDeclarationFile(references, config);
|
||||
await writeTsConfigFile(mainReference, config);
|
||||
const mainReference = await writeMainDeclarationFile(references);
|
||||
await writeTsConfigFile(mainReference);
|
||||
}
|
||||
|
||||
async function writeImportsDeclarationFile(
|
||||
config: InternalConfig,
|
||||
unimportOptions: Partial<UnimportOptions>,
|
||||
): Promise<string> {
|
||||
const filePath = resolve(config.typesDir, 'imports.d.ts');
|
||||
const filePath = resolve(wxt.config.typesDir, 'imports.d.ts');
|
||||
const unimport = createUnimport(unimportOptions);
|
||||
|
||||
// Load project imports into unimport memory so they are output via generateTypeDeclarations
|
||||
await unimport.scanImportsFromDir(undefined, { cwd: config.srcDir });
|
||||
await unimport.scanImportsFromDir(undefined, { cwd: wxt.config.srcDir });
|
||||
|
||||
await writeFileIfDifferent(
|
||||
filePath,
|
||||
@@ -56,18 +55,17 @@ async function writeImportsDeclarationFile(
|
||||
|
||||
async function writePathsDeclarationFile(
|
||||
entrypoints: Entrypoint[],
|
||||
config: InternalConfig,
|
||||
): Promise<string> {
|
||||
const filePath = resolve(config.typesDir, 'paths.d.ts');
|
||||
const filePath = resolve(wxt.config.typesDir, 'paths.d.ts');
|
||||
const unions = entrypoints
|
||||
.map((entry) =>
|
||||
getEntrypointBundlePath(
|
||||
entry,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
entry.inputPath.endsWith('.html') ? '.html' : '.js',
|
||||
),
|
||||
)
|
||||
.concat(await getPublicFiles(config))
|
||||
.concat(await getPublicFiles())
|
||||
.map(normalizePath)
|
||||
.map((path) => ` | "/${path}"`)
|
||||
.sort()
|
||||
@@ -95,11 +93,9 @@ declare module "wxt/browser" {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function writeI18nDeclarationFile(
|
||||
config: InternalConfig,
|
||||
): Promise<string> {
|
||||
const filePath = resolve(config.typesDir, 'i18n.d.ts');
|
||||
const defaultLocale = config.manifest.default_locale;
|
||||
async function writeI18nDeclarationFile(): Promise<string> {
|
||||
const filePath = resolve(wxt.config.typesDir, 'i18n.d.ts');
|
||||
const defaultLocale = wxt.config.manifest.default_locale;
|
||||
const template = `// Generated by wxt
|
||||
import "wxt/browser";
|
||||
|
||||
@@ -123,7 +119,7 @@ declare module "wxt/browser" {
|
||||
let messages: Message[];
|
||||
if (defaultLocale) {
|
||||
const defaultLocalePath = path.resolve(
|
||||
config.publicDir,
|
||||
wxt.config.publicDir,
|
||||
'_locales',
|
||||
defaultLocale,
|
||||
'messages.json',
|
||||
@@ -154,11 +150,9 @@ declare module "wxt/browser" {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function writeGlobalsDeclarationFile(
|
||||
config: InternalConfig,
|
||||
): Promise<string> {
|
||||
const filePath = resolve(config.typesDir, 'globals.d.ts');
|
||||
const globals = [...getGlobals(config), ...getEntrypointGlobals('')];
|
||||
async function writeGlobalsDeclarationFile(): Promise<string> {
|
||||
const filePath = resolve(wxt.config.typesDir, 'globals.d.ts');
|
||||
const globals = [...getGlobals(wxt.config), ...getEntrypointGlobals('')];
|
||||
await writeFileIfDifferent(
|
||||
filePath,
|
||||
[
|
||||
@@ -175,11 +169,8 @@ async function writeGlobalsDeclarationFile(
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function writeMainDeclarationFile(
|
||||
references: string[],
|
||||
config: InternalConfig,
|
||||
): Promise<string> {
|
||||
const dir = config.wxtDir;
|
||||
async function writeMainDeclarationFile(references: string[]): Promise<string> {
|
||||
const dir = wxt.config.wxtDir;
|
||||
const filePath = resolve(dir, 'wxt.d.ts');
|
||||
await writeFileIfDifferent(
|
||||
filePath,
|
||||
@@ -195,13 +186,10 @@ async function writeMainDeclarationFile(
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function writeTsConfigFile(
|
||||
mainReference: string,
|
||||
config: InternalConfig,
|
||||
) {
|
||||
const dir = config.wxtDir;
|
||||
async function writeTsConfigFile(mainReference: string) {
|
||||
const dir = wxt.config.wxtDir;
|
||||
const getTsconfigPath = (path: string) => normalizePath(relative(dir, path));
|
||||
const paths = Object.entries(config.alias)
|
||||
const paths = Object.entries(wxt.config.alias)
|
||||
.flatMap(([alias, absolutePath]) => {
|
||||
const aliasPath = getTsconfigPath(absolutePath);
|
||||
return [
|
||||
@@ -229,10 +217,10 @@ ${paths}
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"${getTsconfigPath(config.root)}/**/*",
|
||||
"${getTsconfigPath(wxt.config.root)}/**/*",
|
||||
"./${getTsconfigPath(mainReference)}"
|
||||
],
|
||||
"exclude": ["${getTsconfigPath(config.outBaseDir)}"]
|
||||
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] {
|
||||
const groups: EntrypointGroup[] = [];
|
||||
|
||||
for (const entry of entrypoints) {
|
||||
const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
|
||||
if (group === 'no-group') {
|
||||
let group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
|
||||
if (entry.type === 'background' && entry.options.type === 'module') {
|
||||
group = 'esm';
|
||||
}
|
||||
if (group === 'individual') {
|
||||
groups.push(entry);
|
||||
} else {
|
||||
let groupIndex = groupIndexMap[group];
|
||||
@@ -28,22 +31,22 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] {
|
||||
}
|
||||
|
||||
const ENTRY_TYPE_TO_GROUP_MAP: Record<Entrypoint['type'], Group> = {
|
||||
sandbox: 'sandbox-page',
|
||||
sandbox: 'sandboxed-esm',
|
||||
|
||||
popup: 'extension-page',
|
||||
newtab: 'extension-page',
|
||||
history: 'extension-page',
|
||||
options: 'extension-page',
|
||||
devtools: 'extension-page',
|
||||
bookmarks: 'extension-page',
|
||||
sidepanel: 'extension-page',
|
||||
'unlisted-page': 'extension-page',
|
||||
popup: 'esm',
|
||||
newtab: 'esm',
|
||||
history: 'esm',
|
||||
options: 'esm',
|
||||
devtools: 'esm',
|
||||
bookmarks: 'esm',
|
||||
sidepanel: 'esm',
|
||||
'unlisted-page': 'esm',
|
||||
|
||||
background: 'no-group',
|
||||
'content-script': 'no-group',
|
||||
'unlisted-script': 'no-group',
|
||||
'unlisted-style': 'no-group',
|
||||
'content-script-style': 'no-group',
|
||||
background: 'individual',
|
||||
'content-script': 'individual',
|
||||
'unlisted-script': 'individual',
|
||||
'unlisted-style': 'individual',
|
||||
'content-script-style': 'individual',
|
||||
};
|
||||
|
||||
type Group = 'extension-page' | 'sandbox-page' | 'no-group';
|
||||
type Group = 'esm' | 'sandboxed-esm' | 'individual';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import createJITI, { TransformOptions as JitiTransformOptions } from 'jiti';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { createUnimport } from 'unimport';
|
||||
import fs from 'fs-extra';
|
||||
import { relative, resolve } from 'node:path';
|
||||
@@ -8,6 +7,7 @@ import { removeProjectImportStatements } from '~/core/utils/strings';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
import { TransformOptions, transformSync } from 'esbuild';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Get the value from the default export of a `path`.
|
||||
@@ -24,16 +24,13 @@ import { fileURLToPath } from 'node:url';
|
||||
* Downside is that code cannot be executed outside of the main fucntion for the entrypoint,
|
||||
* otherwise you will see "xxx is not defined" errors for any imports used outside of main function.
|
||||
*/
|
||||
export async function importEntrypointFile<T>(
|
||||
path: string,
|
||||
config: InternalConfig,
|
||||
): Promise<T> {
|
||||
config.logger.debug('Loading file metadata:', path);
|
||||
export async function importEntrypointFile<T>(path: string): Promise<T> {
|
||||
wxt.logger.debug('Loading file metadata:', path);
|
||||
// JITI & Babel uses normalized paths.
|
||||
const normalPath = normalizePath(path);
|
||||
|
||||
const unimport = createUnimport({
|
||||
...getUnimportOptions(config),
|
||||
...getUnimportOptions(wxt.config),
|
||||
// Only allow specific imports, not all from the project
|
||||
dirs: [],
|
||||
});
|
||||
@@ -42,7 +39,7 @@ export async function importEntrypointFile<T>(
|
||||
const text = await fs.readFile(path, 'utf-8');
|
||||
const textNoImports = removeProjectImportStatements(text);
|
||||
const { code } = await unimport.injectImports(textNoImports);
|
||||
config.logger.debug(
|
||||
wxt.logger.debug(
|
||||
['Text:', text, 'No imports:', textNoImports, 'Code:', code].join('\n'),
|
||||
);
|
||||
|
||||
@@ -52,11 +49,11 @@ export async function importEntrypointFile<T>(
|
||||
: fileURLToPath(import.meta.url),
|
||||
{
|
||||
cache: false,
|
||||
debug: config.debug,
|
||||
debug: wxt.config.debug,
|
||||
esmResolve: true,
|
||||
alias: {
|
||||
'webextension-polyfill': resolve(
|
||||
config.root,
|
||||
wxt.config.root,
|
||||
'node_modules/wxt/dist/virtual/mock-browser.js',
|
||||
),
|
||||
},
|
||||
@@ -89,7 +86,7 @@ export async function importEntrypointFile<T>(
|
||||
const res = await jiti(path);
|
||||
return res.default;
|
||||
} catch (err) {
|
||||
const filePath = relative(config.root, path);
|
||||
const filePath = relative(wxt.config.root, path);
|
||||
if (err instanceof ReferenceError) {
|
||||
// "XXX is not defined" - usually due to WXT removing imports
|
||||
const variableName = err.message.replace(' is not defined', '');
|
||||
@@ -98,7 +95,7 @@ export async function importEntrypointFile<T>(
|
||||
{ cause: err },
|
||||
);
|
||||
} else {
|
||||
config.logger.error(err);
|
||||
wxt.logger.error(err);
|
||||
throw Error(`Failed to load entrypoint: ${filePath}`, { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export * from './build-entrypoints';
|
||||
export * from './detect-dev-changes';
|
||||
export * from './find-entrypoints';
|
||||
export * from './generate-wxt-dir';
|
||||
export * from './get-internal-config';
|
||||
export * from './resolve-config';
|
||||
export * from './group-entrypoints';
|
||||
export * from './import-entrypoint';
|
||||
export * from './internal-build';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { findEntrypoints } from './find-entrypoints';
|
||||
import { InternalConfig, BuildOutput, Entrypoint } from '~/types';
|
||||
import { BuildOutput, Entrypoint } from '~/types';
|
||||
import pc from 'picocolors';
|
||||
import fs from 'fs-extra';
|
||||
import { groupEntrypoints } from './group-entrypoints';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '../validation';
|
||||
import consola from 'consola';
|
||||
import { exec } from '../exec';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Builds the extension based on an internal config. No more config discovery is performed, the
|
||||
@@ -28,28 +29,28 @@ import { exec } from '../exec';
|
||||
* `rebuild` for more details)
|
||||
* 3. Prints the summary
|
||||
*/
|
||||
export async function internalBuild(
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput> {
|
||||
const verb = config.command === 'serve' ? 'Pre-rendering' : 'Building';
|
||||
const target = `${config.browser}-mv${config.manifestVersion}`;
|
||||
config.logger.info(
|
||||
`${verb} ${pc.cyan(target)} for ${pc.cyan(config.mode)} with ${pc.green(
|
||||
`${config.builder.name} ${config.builder.version}`,
|
||||
export async function internalBuild(): Promise<BuildOutput> {
|
||||
await wxt.hooks.callHook('build:before', wxt);
|
||||
|
||||
const verb = wxt.config.command === 'serve' ? 'Pre-rendering' : 'Building';
|
||||
const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
|
||||
wxt.logger.info(
|
||||
`${verb} ${pc.cyan(target)} for ${pc.cyan(wxt.config.mode)} with ${pc.green(
|
||||
`${wxt.config.builder.name} ${wxt.config.builder.version}`,
|
||||
)}`,
|
||||
);
|
||||
const startTime = Date.now();
|
||||
|
||||
// Cleanup
|
||||
await fs.rm(config.outDir, { recursive: true, force: true });
|
||||
await fs.ensureDir(config.outDir);
|
||||
await fs.rm(wxt.config.outDir, { recursive: true, force: true });
|
||||
await fs.ensureDir(wxt.config.outDir);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
config.logger.debug('Detected entrypoints:', entrypoints);
|
||||
const entrypoints = await findEntrypoints();
|
||||
wxt.logger.debug('Detected entrypoints:', entrypoints);
|
||||
|
||||
const validationResults = validateEntrypoints(entrypoints);
|
||||
if (validationResults.errorCount + validationResults.warningCount > 0) {
|
||||
printValidationResults(config, validationResults);
|
||||
printValidationResults(validationResults);
|
||||
}
|
||||
if (validationResults.errorCount > 0) {
|
||||
throw new ValidationError(`Entrypoint validation failed`, {
|
||||
@@ -58,55 +59,63 @@ export async function internalBuild(
|
||||
}
|
||||
|
||||
const groups = groupEntrypoints(entrypoints);
|
||||
const { output, warnings } = await rebuild(
|
||||
config,
|
||||
entrypoints,
|
||||
groups,
|
||||
undefined,
|
||||
);
|
||||
await wxt.hooks.callHook('entrypoints:grouped', wxt, groups);
|
||||
|
||||
const { output, warnings } = await rebuild(entrypoints, groups, undefined);
|
||||
await wxt.hooks.callHook('build:done', wxt, output);
|
||||
|
||||
// Post-build
|
||||
await printBuildSummary(
|
||||
config.logger.success,
|
||||
wxt.logger.success,
|
||||
`Built extension in ${formatDuration(Date.now() - startTime)}`,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
for (const warning of warnings) {
|
||||
config.logger.warn(...warning);
|
||||
wxt.logger.warn(...warning);
|
||||
}
|
||||
|
||||
if (config.analysis.enabled) {
|
||||
await combineAnalysisStats(config);
|
||||
config.logger.info(
|
||||
`Analysis complete:\n ${pc.gray('└─')} ${pc.yellow('stats.html')}`,
|
||||
if (wxt.config.analysis.enabled) {
|
||||
await combineAnalysisStats();
|
||||
const statsPath = relative(wxt.config.root, wxt.config.analysis.outputFile);
|
||||
wxt.logger.info(
|
||||
`Analysis complete:\n ${pc.gray('└─')} ${pc.yellow(statsPath)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async function combineAnalysisStats(config: InternalConfig): Promise<void> {
|
||||
const unixFiles = await glob(`stats-*.json`, {
|
||||
cwd: config.outDir,
|
||||
async function combineAnalysisStats(): Promise<void> {
|
||||
const unixFiles = await glob(`${wxt.config.analysis.outputName}-*.json`, {
|
||||
cwd: wxt.config.analysis.outputDir,
|
||||
absolute: true,
|
||||
});
|
||||
const absolutePaths = unixFiles.map(unnormalizePath);
|
||||
|
||||
await exec(
|
||||
config,
|
||||
'rollup-plugin-visualizer',
|
||||
[...absolutePaths, '--template', config.analysis.template],
|
||||
{ cwd: config.root, stdio: 'inherit' },
|
||||
[
|
||||
...absolutePaths,
|
||||
'--template',
|
||||
wxt.config.analysis.template,
|
||||
'--filename',
|
||||
wxt.config.analysis.outputFile,
|
||||
],
|
||||
{ cwd: wxt.config.root, stdio: 'inherit' },
|
||||
);
|
||||
|
||||
if (!wxt.config.analysis.keepArtifacts) {
|
||||
await Promise.all(absolutePaths.map((statsFile) => fs.remove(statsFile)));
|
||||
}
|
||||
}
|
||||
|
||||
function printValidationResults(
|
||||
config: InternalConfig,
|
||||
{ errorCount, errors, warningCount }: ValidationResults,
|
||||
) {
|
||||
(errorCount > 0 ? config.logger.error : config.logger.warn)(
|
||||
function printValidationResults({
|
||||
errorCount,
|
||||
errors,
|
||||
warningCount,
|
||||
}: ValidationResults) {
|
||||
(errorCount > 0 ? wxt.logger.error : wxt.logger.warn)(
|
||||
`Entrypoint validation failed: ${errorCount} error${
|
||||
errorCount === 1 ? '' : 's'
|
||||
}, ${warningCount} warning${warningCount === 1 ? '' : 's'}`,
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import type { Manifest } from '~/browser';
|
||||
import {
|
||||
BuildOutput,
|
||||
Entrypoint,
|
||||
EntrypointGroup,
|
||||
InternalConfig,
|
||||
} from '~/types';
|
||||
import { BuildOutput, Entrypoint, EntrypointGroup } from '~/types';
|
||||
import { generateTypesDir } from './generate-wxt-dir';
|
||||
import { buildEntrypoints } from './build-entrypoints';
|
||||
import { generateManifest, writeManifest } from '~/core/utils/manifest';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Given a configuration, list of entrypoints, and an existing, partial output, build the
|
||||
@@ -26,7 +22,6 @@ import { generateManifest, writeManifest } from '~/core/utils/manifest';
|
||||
* this is the first build.
|
||||
*/
|
||||
export async function rebuild(
|
||||
config: InternalConfig,
|
||||
allEntrypoints: Entrypoint[],
|
||||
entrypointGroups: EntrypointGroup[],
|
||||
existingOutput: Omit<BuildOutput, 'manifest'> = {
|
||||
@@ -42,28 +37,28 @@ export async function rebuild(
|
||||
const spinner = ora(`Preparing...`).start();
|
||||
|
||||
// Update types directory with new files and types
|
||||
await generateTypesDir(allEntrypoints, config).catch((err) => {
|
||||
config.logger.warn('Failed to update .wxt directory:', err);
|
||||
await generateTypesDir(allEntrypoints).catch((err) => {
|
||||
wxt.logger.warn('Failed to update .wxt directory:', err);
|
||||
// Throw the error if doing a regular build, don't for dev mode.
|
||||
if (config.command === 'build') throw err;
|
||||
if (wxt.config.command === 'build') throw err;
|
||||
});
|
||||
|
||||
// Build and merge the outputs
|
||||
const newOutput = await buildEntrypoints(entrypointGroups, config, spinner);
|
||||
const newOutput = await buildEntrypoints(entrypointGroups, spinner);
|
||||
const mergedOutput: Omit<BuildOutput, 'manifest'> = {
|
||||
steps: [...existingOutput.steps, ...newOutput.steps],
|
||||
publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets],
|
||||
};
|
||||
|
||||
const { manifest: newManifest, warnings: manifestWarnings } =
|
||||
await generateManifest(allEntrypoints, mergedOutput, config);
|
||||
await generateManifest(allEntrypoints, mergedOutput);
|
||||
const finalOutput: BuildOutput = {
|
||||
manifest: newManifest,
|
||||
...newOutput,
|
||||
};
|
||||
|
||||
// Write manifest
|
||||
await writeManifest(newManifest, finalOutput, config);
|
||||
await writeManifest(newManifest, finalOutput);
|
||||
|
||||
// Stop the spinner and remove it from the CLI output
|
||||
spinner.clear().stop();
|
||||
|
||||
+25
-9
@@ -1,7 +1,7 @@
|
||||
import { loadConfig } from 'c12';
|
||||
import {
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
ResolvedConfig,
|
||||
UserConfig,
|
||||
ConfigEnv,
|
||||
UserManifestFn,
|
||||
@@ -23,15 +23,15 @@ import { NullablyRequired } from '../types';
|
||||
* Inline config always has priority over user config. Cli flags are passed as inline config if set.
|
||||
* If unset, undefined is passed in, letting this function decide default values.
|
||||
*/
|
||||
export async function getInternalConfig(
|
||||
export async function resolveConfig(
|
||||
inlineConfig: InlineConfig,
|
||||
command: 'build' | 'serve',
|
||||
server?: WxtDevServer,
|
||||
): Promise<InternalConfig> {
|
||||
): Promise<ResolvedConfig> {
|
||||
// Load user config
|
||||
|
||||
let userConfig: UserConfig = {};
|
||||
let userConfigMetadata: InternalConfig['userConfigMetadata'] | undefined;
|
||||
let userConfigMetadata: ResolvedConfig['userConfigMetadata'] | undefined;
|
||||
if (inlineConfig.configFile !== false) {
|
||||
const { config: loadedConfig, ...metadata } = await loadConfig<UserConfig>({
|
||||
name: 'wxt',
|
||||
@@ -100,7 +100,14 @@ export async function getInternalConfig(
|
||||
}).map(([key, value]) => [key, path.resolve(root, value)]),
|
||||
);
|
||||
|
||||
const finalConfig: Omit<InternalConfig, 'builder'> = {
|
||||
const analysisOutputFile = path.resolve(
|
||||
root,
|
||||
mergedConfig.analysis?.outputFile ?? 'stats.html',
|
||||
);
|
||||
const analysisOutputDir = path.dirname(analysisOutputFile);
|
||||
const analysisOutputName = path.parse(analysisOutputFile).name;
|
||||
|
||||
const finalConfig: Omit<ResolvedConfig, 'builder'> = {
|
||||
browser,
|
||||
command,
|
||||
debug,
|
||||
@@ -129,6 +136,10 @@ export async function getInternalConfig(
|
||||
analysis: {
|
||||
enabled: mergedConfig.analysis?.enabled ?? false,
|
||||
template: mergedConfig.analysis?.template ?? 'treemap',
|
||||
outputFile: analysisOutputFile,
|
||||
outputDir: analysisOutputDir,
|
||||
outputName: analysisOutputName,
|
||||
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
|
||||
},
|
||||
userConfigMetadata: userConfigMetadata ?? {},
|
||||
alias,
|
||||
@@ -140,6 +151,7 @@ export async function getInternalConfig(
|
||||
dev: {
|
||||
reloadCommand,
|
||||
},
|
||||
hooks: mergedConfig.hooks ?? {},
|
||||
};
|
||||
|
||||
const builder = await createViteBuilder(
|
||||
@@ -191,6 +203,10 @@ function mergeInlineConfig(
|
||||
inlineConfig.zip ?? {},
|
||||
userConfig.zip ?? {},
|
||||
);
|
||||
const hooks: InlineConfig['hooks'] = defu(
|
||||
inlineConfig.hooks ?? {},
|
||||
userConfig.hooks ?? {},
|
||||
);
|
||||
|
||||
return {
|
||||
root: inlineConfig.root ?? userConfig.root,
|
||||
@@ -211,9 +227,8 @@ function mergeInlineConfig(
|
||||
outDir: inlineConfig.outDir ?? userConfig.outDir,
|
||||
zip,
|
||||
analysis: {
|
||||
enabled: inlineConfig.analysis?.enabled ?? userConfig.analysis?.enabled,
|
||||
template:
|
||||
inlineConfig.analysis?.template ?? userConfig.analysis?.template,
|
||||
...userConfig.analysis,
|
||||
...inlineConfig.analysis,
|
||||
},
|
||||
alias: {
|
||||
...userConfig.alias,
|
||||
@@ -229,13 +244,14 @@ function mergeInlineConfig(
|
||||
...userConfig.dev,
|
||||
...inlineConfig.dev,
|
||||
},
|
||||
hooks,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveInternalZipConfig(
|
||||
root: string,
|
||||
mergedConfig: InlineConfig,
|
||||
): NullablyRequired<InternalConfig['zip']> {
|
||||
): NullablyRequired<ResolvedConfig['zip']> {
|
||||
return {
|
||||
name: undefined,
|
||||
sourcesTemplate: '{{name}}-{{version}}-sources.zip',
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Manifest } from '~/browser';
|
||||
import { ContentScriptEntrypoint, InternalConfig } from '~/types';
|
||||
import { ContentScriptEntrypoint } from '~/types';
|
||||
import { resolvePerBrowserOption } from './entrypoints';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Returns a unique and consistent string hash based on a content scripts options.
|
||||
@@ -9,9 +10,8 @@ import { resolvePerBrowserOption } from './entrypoints';
|
||||
*/
|
||||
export function hashContentScriptOptions(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
config: InternalConfig,
|
||||
): string {
|
||||
const simplifiedOptions = mapWxtOptionsToContentScript(options, config);
|
||||
const simplifiedOptions = mapWxtOptionsToContentScript(options);
|
||||
|
||||
// Remove undefined fields and use defaults to generate hash
|
||||
Object.keys(simplifiedOptions).forEach((key) => {
|
||||
@@ -45,33 +45,32 @@ export function hashContentScriptOptions(
|
||||
|
||||
export function mapWxtOptionsToContentScript(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
config: InternalConfig,
|
||||
): Omit<Manifest.ContentScript, 'js' | 'css'> {
|
||||
return {
|
||||
matches: resolvePerBrowserOption(options.matches, config.browser),
|
||||
all_frames: resolvePerBrowserOption(options.allFrames, config.browser),
|
||||
matches: resolvePerBrowserOption(options.matches, wxt.config.browser),
|
||||
all_frames: resolvePerBrowserOption(options.allFrames, wxt.config.browser),
|
||||
match_about_blank: resolvePerBrowserOption(
|
||||
options.matchAboutBlank,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
exclude_globs: resolvePerBrowserOption(
|
||||
options.excludeGlobs,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
exclude_matches: resolvePerBrowserOption(
|
||||
options.excludeMatches,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
include_globs: resolvePerBrowserOption(
|
||||
options.includeGlobs,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
run_at: resolvePerBrowserOption(options.runAt, config.browser),
|
||||
run_at: resolvePerBrowserOption(options.runAt, wxt.config.browser),
|
||||
|
||||
// @ts-expect-error: untyped chrome options
|
||||
match_origin_as_fallback: resolvePerBrowserOption(
|
||||
options.matchOriginAsFallback,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
),
|
||||
world: options.world,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Options } from 'execa';
|
||||
import managePath from 'manage-path';
|
||||
import { resolve } from 'node:path';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
const managedPath = managePath(process.env);
|
||||
|
||||
@@ -9,7 +9,6 @@ const managedPath = managePath(process.env);
|
||||
* Wrapper around `execa` with a modified `PATH` variable containing CLI tools from WXT's dependencies.
|
||||
*/
|
||||
export const exec = async (
|
||||
config: InternalConfig,
|
||||
file: string,
|
||||
args?: readonly string[],
|
||||
options?: Options,
|
||||
@@ -18,7 +17,9 @@ export const exec = async (
|
||||
managedPath.restore();
|
||||
|
||||
// Add subdependency path for PNPM shamefully-hoist=false
|
||||
managedPath.push(resolve(config.root, 'node_modules/wxt/node_modules/.bin'));
|
||||
managedPath.push(
|
||||
resolve(wxt.config.root, 'node_modules/wxt/node_modules/.bin'),
|
||||
);
|
||||
|
||||
const { execa } = await import('execa');
|
||||
return await execa(file, args, options);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { unnormalizePath } from './paths';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Only write the contents to a file if it results in a change. This prevents unnecessary file
|
||||
@@ -27,11 +27,9 @@ export async function writeFileIfDifferent(
|
||||
* Get all the files in the project's public directory. Returned paths are relative to the
|
||||
* `config.publicDir`.
|
||||
*/
|
||||
export async function getPublicFiles(
|
||||
config: InternalConfig,
|
||||
): Promise<string[]> {
|
||||
if (!(await fs.exists(config.publicDir))) return [];
|
||||
export async function getPublicFiles(): Promise<string[]> {
|
||||
if (!(await fs.exists(wxt.config.publicDir))) return [];
|
||||
|
||||
const files = await glob('**/*', { cwd: config.publicDir });
|
||||
const files = await glob('**/*', { cwd: wxt.config.publicDir });
|
||||
return files.map(unnormalizePath);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
|
||||
export function getGlobals(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): Array<{ name: string; value: any; type: string }> {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { resolve } from 'path';
|
||||
import { BuildOutput, InternalConfig } from '~/types';
|
||||
import { BuildOutput } from '~/types';
|
||||
import { printFileList } from './printFileList';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
export async function printBuildSummary(
|
||||
log: (...args: any[]) => void,
|
||||
header: string,
|
||||
output: BuildOutput,
|
||||
config: InternalConfig,
|
||||
) {
|
||||
const chunks = [
|
||||
...output.steps.flatMap((step) => step.chunks),
|
||||
@@ -19,8 +19,10 @@ export async function printBuildSummary(
|
||||
return l.fileName.localeCompare(r.fileName);
|
||||
});
|
||||
|
||||
const files = chunks.map((chunk) => resolve(config.outDir, chunk.fileName));
|
||||
await printFileList(log, header, config.outDir, files);
|
||||
const files = chunks.map((chunk) =>
|
||||
resolve(wxt.config.outDir, chunk.fileName),
|
||||
);
|
||||
await printFileList(log, header, wxt.config.outDir, files);
|
||||
}
|
||||
|
||||
const DEFAULT_SORT_WEIGHT = 100;
|
||||
|
||||
+122
-78
@@ -4,7 +4,6 @@ import {
|
||||
BackgroundEntrypoint,
|
||||
BuildOutput,
|
||||
ContentScriptEntrypoint,
|
||||
InternalConfig,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
} from '~/types';
|
||||
@@ -22,8 +21,8 @@ import {
|
||||
import { getPackageJson } from './package';
|
||||
import { normalizePath } from './paths';
|
||||
import { writeFileIfDifferent } from './fs';
|
||||
import { produce } from 'immer';
|
||||
import defu from 'defu';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Writes the manifest to the output directory and the build output.
|
||||
@@ -31,15 +30,14 @@ import defu from 'defu';
|
||||
export async function writeManifest(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
output: BuildOutput,
|
||||
config: InternalConfig,
|
||||
): Promise<void> {
|
||||
const str =
|
||||
config.mode === 'production'
|
||||
wxt.config.mode === 'production'
|
||||
? JSON.stringify(manifest)
|
||||
: JSON.stringify(manifest, null, 2);
|
||||
|
||||
await fs.ensureDir(config.outDir);
|
||||
await writeFileIfDifferent(resolve(config.outDir, 'manifest.json'), str);
|
||||
await fs.ensureDir(wxt.config.outDir);
|
||||
await writeFileIfDifferent(resolve(wxt.config.outDir, 'manifest.json'), str);
|
||||
|
||||
output.publicAssets.unshift({
|
||||
type: 'asset',
|
||||
@@ -53,38 +51,39 @@ export async function writeManifest(
|
||||
export async function generateManifest(
|
||||
entrypoints: Entrypoint[],
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
config: InternalConfig,
|
||||
): Promise<{ manifest: Manifest.WebExtensionManifest; warnings: any[][] }> {
|
||||
const warnings: any[][] = [];
|
||||
const pkg = await getPackageJson(config);
|
||||
const pkg = await getPackageJson();
|
||||
|
||||
let versionName =
|
||||
config.manifest.version_name ?? config.manifest.version ?? pkg?.version;
|
||||
wxt.config.manifest.version_name ??
|
||||
wxt.config.manifest.version ??
|
||||
pkg?.version;
|
||||
if (versionName == null) {
|
||||
versionName = '0.0.0';
|
||||
config.logger.warn(
|
||||
wxt.logger.warn(
|
||||
'Extension version not found, defaulting to "0.0.0". Add a version to your `package.json` or `wxt.config.ts` file. For more details, see: https://wxt.dev/guide/manifest.html#version-and-version-name',
|
||||
);
|
||||
}
|
||||
const version = config.manifest.version ?? simplifyVersion(versionName);
|
||||
const version = wxt.config.manifest.version ?? simplifyVersion(versionName);
|
||||
|
||||
const baseManifest: Manifest.WebExtensionManifest = {
|
||||
manifest_version: config.manifestVersion,
|
||||
manifest_version: wxt.config.manifestVersion,
|
||||
name: pkg?.name,
|
||||
description: pkg?.description,
|
||||
version,
|
||||
short_name: pkg?.shortName,
|
||||
icons: discoverIcons(buildOutput),
|
||||
};
|
||||
const userManifest = config.manifest;
|
||||
const userManifest = wxt.config.manifest;
|
||||
|
||||
const manifest = defu(
|
||||
let manifest = defu(
|
||||
userManifest,
|
||||
baseManifest,
|
||||
) as Manifest.WebExtensionManifest;
|
||||
|
||||
// Add reload command in dev mode
|
||||
if (config.command === 'serve' && config.dev.reloadCommand) {
|
||||
if (wxt.config.command === 'serve' && wxt.config.dev.reloadCommand) {
|
||||
if (manifest.commands && Object.keys(manifest.commands).length >= 4) {
|
||||
warnings.push([
|
||||
"Extension already has 4 registered commands, WXT's reload command is disabled",
|
||||
@@ -94,7 +93,7 @@ export async function generateManifest(
|
||||
manifest.commands['wxt:reload-extension'] = {
|
||||
description: 'Reload the extension during development',
|
||||
suggested_key: {
|
||||
default: config.dev.reloadCommand,
|
||||
default: wxt.config.dev.reloadCommand,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -104,29 +103,38 @@ export async function generateManifest(
|
||||
manifest.version = version;
|
||||
manifest.version_name =
|
||||
// Firefox doesn't support version_name
|
||||
config.browser === 'firefox' || versionName === version
|
||||
wxt.config.browser === 'firefox' || versionName === version
|
||||
? undefined
|
||||
: versionName;
|
||||
|
||||
addEntrypoints(manifest, entrypoints, buildOutput, config);
|
||||
addEntrypoints(manifest, entrypoints, buildOutput);
|
||||
|
||||
if (config.command === 'serve') addDevModeCsp(manifest, config);
|
||||
if (config.command === 'serve') addDevModePermissions(manifest, config);
|
||||
if (wxt.config.command === 'serve') addDevModeCsp(manifest);
|
||||
if (wxt.config.command === 'serve') addDevModePermissions(manifest);
|
||||
|
||||
const finalManifest = produce(manifest, config.transformManifest);
|
||||
// TODO: Remove in v1
|
||||
wxt.config.transformManifest(manifest);
|
||||
await wxt.hooks.callHook('build:manifestGenerated', wxt, manifest);
|
||||
|
||||
if (finalManifest.name == null)
|
||||
if (wxt.config.manifestVersion === 2)
|
||||
convertWebAccessibleResourcesToMv2(manifest);
|
||||
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
validateMv3WebAccessbileResources(manifest);
|
||||
}
|
||||
|
||||
if (manifest.name == null)
|
||||
throw Error(
|
||||
"Manifest 'name' is missing. Either:\n1. Set the name in your <rootDir>/package.json\n2. Set a name via the manifest option in your wxt.config.ts",
|
||||
);
|
||||
if (finalManifest.version == null) {
|
||||
if (manifest.version == null) {
|
||||
throw Error(
|
||||
"Manifest 'version' is missing. Either:\n1. Add a version in your <rootDir>/package.json\n2. Pass the version via the manifest option in your wxt.config.ts",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
manifest: finalManifest,
|
||||
manifest,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
@@ -153,7 +161,6 @@ function addEntrypoints(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
entrypoints: Entrypoint[],
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
config: InternalConfig,
|
||||
): void {
|
||||
const entriesByType = entrypoints.reduce<
|
||||
Partial<Record<Entrypoint['type'], Entrypoint[]>>
|
||||
@@ -181,13 +188,17 @@ function addEntrypoints(
|
||||
const sidepanels = entriesByType['sidepanel'];
|
||||
|
||||
if (background) {
|
||||
const script = getEntrypointBundlePath(background, config.outDir, '.js');
|
||||
if (config.browser === 'firefox' && config.manifestVersion === 3) {
|
||||
const script = getEntrypointBundlePath(
|
||||
background,
|
||||
wxt.config.outDir,
|
||||
'.js',
|
||||
);
|
||||
if (wxt.config.browser === 'firefox' && wxt.config.manifestVersion === 3) {
|
||||
manifest.background = {
|
||||
type: background.options.type,
|
||||
scripts: [script],
|
||||
};
|
||||
} else if (config.manifestVersion === 3) {
|
||||
} else if (wxt.config.manifestVersion === 3) {
|
||||
manifest.background = {
|
||||
type: background.options.type,
|
||||
service_worker: script,
|
||||
@@ -201,8 +212,8 @@ function addEntrypoints(
|
||||
}
|
||||
|
||||
if (bookmarks) {
|
||||
if (config.browser === 'firefox') {
|
||||
config.logger.warn(
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
wxt.logger.warn(
|
||||
'Bookmarks are not supported by Firefox. chrome_url_overrides.bookmarks was not added to the manifest',
|
||||
);
|
||||
} else {
|
||||
@@ -210,15 +221,15 @@ function addEntrypoints(
|
||||
// @ts-expect-error: bookmarks is untyped in webextension-polyfill, but supported by chrome
|
||||
manifest.chrome_url_overrides.bookmarks = getEntrypointBundlePath(
|
||||
bookmarks,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (history) {
|
||||
if (config.browser === 'firefox') {
|
||||
config.logger.warn(
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
wxt.logger.warn(
|
||||
'Bookmarks are not supported by Firefox. chrome_url_overrides.history was not added to the manifest',
|
||||
);
|
||||
} else {
|
||||
@@ -226,7 +237,7 @@ function addEntrypoints(
|
||||
// @ts-expect-error: history is untyped in webextension-polyfill, but supported by chrome
|
||||
manifest.chrome_url_overrides.history = getEntrypointBundlePath(
|
||||
history,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
}
|
||||
@@ -236,7 +247,7 @@ function addEntrypoints(
|
||||
manifest.chrome_url_overrides ??= {};
|
||||
manifest.chrome_url_overrides.newtab = getEntrypointBundlePath(
|
||||
newtab,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
}
|
||||
@@ -244,7 +255,7 @@ function addEntrypoints(
|
||||
if (popup) {
|
||||
const default_popup = getEntrypointBundlePath(
|
||||
popup,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
const options: Manifest.ActionManifest = {};
|
||||
@@ -273,33 +284,37 @@ function addEntrypoints(
|
||||
if (devtools) {
|
||||
manifest.devtools_page = getEntrypointBundlePath(
|
||||
devtools,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
}
|
||||
|
||||
if (options) {
|
||||
const page = getEntrypointBundlePath(options, config.outDir, '.html');
|
||||
const page = getEntrypointBundlePath(options, wxt.config.outDir, '.html');
|
||||
manifest.options_ui = {
|
||||
open_in_tab: options.options.openInTab,
|
||||
browser_style:
|
||||
config.browser === 'firefox' ? options.options.browserStyle : undefined,
|
||||
wxt.config.browser === 'firefox'
|
||||
? options.options.browserStyle
|
||||
: undefined,
|
||||
chrome_style:
|
||||
config.browser !== 'firefox' ? options.options.chromeStyle : undefined,
|
||||
wxt.config.browser !== 'firefox'
|
||||
? options.options.chromeStyle
|
||||
: undefined,
|
||||
page,
|
||||
};
|
||||
}
|
||||
|
||||
if (sandboxes?.length) {
|
||||
if (config.browser === 'firefox') {
|
||||
config.logger.warn(
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
wxt.logger.warn(
|
||||
'Sandboxed pages not supported by Firefox. sandbox.pages was not added to the manifest',
|
||||
);
|
||||
} else {
|
||||
// @ts-expect-error: sandbox not typed
|
||||
manifest.sandbox = {
|
||||
pages: sandboxes.map((entry) =>
|
||||
getEntrypointBundlePath(entry, config.outDir, '.html'),
|
||||
getEntrypointBundlePath(entry, wxt.config.outDir, '.html'),
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -310,23 +325,23 @@ function addEntrypoints(
|
||||
sidepanels.find((entry) => entry.name === 'sidepanel') ?? sidepanels[0];
|
||||
const page = getEntrypointBundlePath(
|
||||
defaultSidepanel,
|
||||
config.outDir,
|
||||
wxt.config.outDir,
|
||||
'.html',
|
||||
);
|
||||
|
||||
if (config.browser === 'firefox') {
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
manifest.sidebar_action = {
|
||||
// TODO: Add options to side panel
|
||||
// ...defaultSidepanel.options,
|
||||
default_panel: page,
|
||||
};
|
||||
} else if (config.manifestVersion === 3) {
|
||||
} else if (wxt.config.manifestVersion === 3) {
|
||||
// @ts-expect-error: Untyped
|
||||
manifest.side_panel = {
|
||||
default_path: page,
|
||||
};
|
||||
} else {
|
||||
config.logger.warn(
|
||||
wxt.logger.warn(
|
||||
'Side panel not supported by Chromium using MV2. side_panel.default_path was not added to the manifest',
|
||||
);
|
||||
}
|
||||
@@ -337,12 +352,12 @@ function addEntrypoints(
|
||||
|
||||
// Don't add content scripts to the manifest in dev mode for MV3 - they're managed and reloaded
|
||||
// at runtime
|
||||
if (config.command === 'serve' && config.manifestVersion === 3) {
|
||||
if (wxt.config.command === 'serve' && wxt.config.manifestVersion === 3) {
|
||||
const hostPermissions = new Set<string>(manifest.host_permissions ?? []);
|
||||
contentScripts.forEach((script) => {
|
||||
const matches = resolvePerBrowserOption(
|
||||
script.options.matches,
|
||||
config.browser,
|
||||
wxt.config.browser,
|
||||
);
|
||||
matches.forEach((matchPattern) => {
|
||||
hostPermissions.add(matchPattern);
|
||||
@@ -353,7 +368,7 @@ function addEntrypoints(
|
||||
);
|
||||
} else {
|
||||
const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
|
||||
const hash = hashContentScriptOptions(script.options, config);
|
||||
const hash = hashContentScriptOptions(script.options);
|
||||
if (map.has(hash)) map.get(hash)?.push(script);
|
||||
else map.set(hash, [script]);
|
||||
return map;
|
||||
@@ -361,10 +376,10 @@ function addEntrypoints(
|
||||
|
||||
const newContentScripts = Array.from(hashToEntrypointsMap.entries()).map(
|
||||
([, scripts]) => ({
|
||||
...mapWxtOptionsToContentScript(scripts[0].options, config),
|
||||
...mapWxtOptionsToContentScript(scripts[0].options),
|
||||
css: getContentScriptCssFiles(scripts, cssMap),
|
||||
js: scripts.map((entry) =>
|
||||
getEntrypointBundlePath(entry, config.outDir, '.js'),
|
||||
getEntrypointBundlePath(entry, wxt.config.outDir, '.js'),
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -375,7 +390,6 @@ function addEntrypoints(
|
||||
}
|
||||
|
||||
const contentScriptCssResources = getContentScriptCssWebAccessibleResources(
|
||||
config,
|
||||
contentScripts,
|
||||
cssMap,
|
||||
);
|
||||
@@ -420,12 +434,9 @@ function discoverIcons(
|
||||
return icons.length > 0 ? Object.fromEntries(icons) : undefined;
|
||||
}
|
||||
|
||||
function addDevModeCsp(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
config: InternalConfig,
|
||||
): void {
|
||||
const permission = `http://${config.server?.hostname ?? ''}/*`;
|
||||
const allowedCsp = config.server?.origin ?? 'http://localhost:*';
|
||||
function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
|
||||
const permission = `http://${wxt.config.server?.hostname ?? ''}/*`;
|
||||
const allowedCsp = wxt.config.server?.origin ?? 'http://localhost:*';
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
addHostPermission(manifest, permission);
|
||||
@@ -442,7 +453,7 @@ function addDevModeCsp(
|
||||
"script-src 'self'; object-src 'self';", // default CSP for MV2
|
||||
);
|
||||
|
||||
if (config.server) csp.add('script-src', allowedCsp);
|
||||
if (wxt.config.server) csp.add('script-src', allowedCsp);
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
manifest.content_security_policy ??= {};
|
||||
@@ -453,15 +464,12 @@ function addDevModeCsp(
|
||||
}
|
||||
}
|
||||
|
||||
function addDevModePermissions(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
config: InternalConfig,
|
||||
) {
|
||||
function addDevModePermissions(manifest: Manifest.WebExtensionManifest) {
|
||||
// For reloading the page
|
||||
addPermission(manifest, 'tabs');
|
||||
|
||||
// For registering content scripts
|
||||
if (config.manifestVersion === 3) addPermission(manifest, 'scripting');
|
||||
if (wxt.config.manifestVersion === 3) addPermission(manifest, 'scripting');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,11 +505,11 @@ export function getContentScriptCssFiles(
|
||||
* added to.
|
||||
*/
|
||||
export function getContentScriptCssWebAccessibleResources(
|
||||
config: InternalConfig,
|
||||
contentScripts: ContentScriptEntrypoint[],
|
||||
contentScriptCssMap: Record<string, string | undefined>,
|
||||
): any[] {
|
||||
const resources: any[] = [];
|
||||
const resources: Manifest.WebExtensionManifestWebAccessibleResourcesC2ItemType[] =
|
||||
[];
|
||||
|
||||
contentScripts.forEach((script) => {
|
||||
if (script.options.cssInjectionMode !== 'ui') return;
|
||||
@@ -509,17 +517,13 @@ export function getContentScriptCssWebAccessibleResources(
|
||||
const cssFile = contentScriptCssMap[script.name];
|
||||
if (cssFile == null) return;
|
||||
|
||||
if (config.manifestVersion === 2) {
|
||||
resources.push(cssFile);
|
||||
} else {
|
||||
resources.push({
|
||||
resources: [cssFile],
|
||||
matches: resolvePerBrowserOption(
|
||||
script.options.matches,
|
||||
config.browser,
|
||||
).map((matchPattern) => stripPathFromMatchPattern(matchPattern)),
|
||||
});
|
||||
}
|
||||
resources.push({
|
||||
resources: [cssFile],
|
||||
matches: resolvePerBrowserOption(
|
||||
script.options.matches,
|
||||
wxt.config.browser,
|
||||
).map((matchPattern) => stripPathFromMatchPattern(matchPattern)),
|
||||
});
|
||||
});
|
||||
|
||||
return resources;
|
||||
@@ -573,3 +577,43 @@ export function stripPathFromMatchPattern(pattern: string) {
|
||||
const startOfPath = pattern.indexOf('/', protocolSepIndex + 3);
|
||||
return pattern.substring(0, startOfPath) + '/*';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are
|
||||
* generated in this file, and may be defined by the user in their manifest. In both cases, when
|
||||
* targetting MV2, automatically convert their definitions down to the basic MV2 array.
|
||||
*/
|
||||
export function convertWebAccessibleResourcesToMv2(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
): void {
|
||||
if (manifest.web_accessible_resources == null) return;
|
||||
|
||||
manifest.web_accessible_resources = Array.from(
|
||||
new Set(
|
||||
manifest.web_accessible_resources.flatMap((item) => {
|
||||
if (typeof item === 'string') return item;
|
||||
return item.resources;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all resources are in MV3 format. If not, add a wanring
|
||||
*/
|
||||
export function validateMv3WebAccessbileResources(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
): void {
|
||||
if (manifest.web_accessible_resources == null) return;
|
||||
|
||||
const stringResources = manifest.web_accessible_resources.filter(
|
||||
(item) => typeof item === 'string',
|
||||
);
|
||||
if (stringResources.length > 0) {
|
||||
throw Error(
|
||||
`Non-MV3 web_accessible_resources detected: ${JSON.stringify(
|
||||
stringResources,
|
||||
)}. When manually defining web_accessible_resources, define them as MV3 objects ({ matches: [...], resources: [...] }), and WXT will automatically convert them to MV2 when necessary.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import dns from 'node:dns';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import { withTimeout } from './time';
|
||||
|
||||
function isOffline(): Promise<boolean> {
|
||||
@@ -26,7 +26,7 @@ export async function isOnline(): Promise<boolean> {
|
||||
*/
|
||||
export async function fetchCached(
|
||||
url: string,
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): Promise<string> {
|
||||
let content: string = '';
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { InternalConfig } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Read the project's package.json.
|
||||
*
|
||||
* TODO: look in root and up directories until it's found
|
||||
*/
|
||||
export async function getPackageJson(
|
||||
config: InternalConfig,
|
||||
): Promise<Partial<Record<string, any>> | undefined> {
|
||||
const file = resolve(config.root, 'package.json');
|
||||
export async function getPackageJson(): Promise<
|
||||
Partial<Record<string, any>> | undefined
|
||||
> {
|
||||
const file = resolve(wxt.config.root, 'package.json');
|
||||
try {
|
||||
return await fs.readJson(file);
|
||||
} catch (err) {
|
||||
config.logger.debug(
|
||||
wxt.logger.debug(
|
||||
`Failed to read package.json at: ${file}. Returning undefined.`,
|
||||
);
|
||||
return {};
|
||||
|
||||
@@ -7,7 +7,7 @@ import merge from 'lodash.merge';
|
||||
import { Commands, type Manifest } from '~/browser';
|
||||
import {
|
||||
FsCache,
|
||||
InternalConfig,
|
||||
ResolvedConfig,
|
||||
WxtDevServer,
|
||||
BackgroundEntrypoint,
|
||||
ContentScriptEntrypoint,
|
||||
@@ -20,13 +20,19 @@ import {
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
UserManifest,
|
||||
Wxt,
|
||||
} from '~/types';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { vi } from 'vitest';
|
||||
import { setWxtForTesting } from '~/core/wxt';
|
||||
|
||||
faker.seed(__TEST_SEED__);
|
||||
|
||||
type DeepPartial<T> = { [key in keyof T]+?: Partial<T[key]> };
|
||||
type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
function fakeObjectCreator<T>(base: () => T) {
|
||||
return (overrides?: DeepPartial<T>): T => merge(base(), overrides);
|
||||
}
|
||||
@@ -198,7 +204,7 @@ export function fakeArray<T>(createItem: () => T, count = 3): T[] {
|
||||
return array;
|
||||
}
|
||||
|
||||
export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => {
|
||||
export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
const browser = faker.helpers.arrayElement(['chrome', 'firefox']);
|
||||
const command = faker.helpers.arrayElement(['build', 'serve'] as const);
|
||||
const manifestVersion = faker.helpers.arrayElement([2, 3] as const);
|
||||
@@ -230,6 +236,10 @@ export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => {
|
||||
analysis: {
|
||||
enabled: false,
|
||||
template: 'treemap',
|
||||
outputFile: fakeFile(),
|
||||
outputDir: fakeDir(),
|
||||
outputName: 'stats',
|
||||
keepArtifacts: false,
|
||||
},
|
||||
zip: {
|
||||
artifactTemplate: '{{name}}-{{version}}.zip',
|
||||
@@ -249,9 +259,22 @@ export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => {
|
||||
dev: {
|
||||
reloadCommand: 'Alt+R',
|
||||
},
|
||||
hooks: {},
|
||||
};
|
||||
});
|
||||
|
||||
export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
|
||||
config: fakeResolvedConfig(),
|
||||
hooks: mock(),
|
||||
logger: mock(),
|
||||
reloadConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
export function setFakeWxt(overrides?: DeepPartial<Wxt>) {
|
||||
const wxt = fakeWxt(overrides);
|
||||
setWxtForTesting(wxt);
|
||||
}
|
||||
|
||||
export const fakeBuildOutput = fakeObjectCreator<BuildOutput>(() => ({
|
||||
manifest: fakeManifest(),
|
||||
publicAssets: fakeArray(fakeOutputAsset),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import { defu } from 'defu';
|
||||
|
||||
export function getUnimportOptions(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
config: Omit<ResolvedConfig, 'builder'>,
|
||||
): Partial<UnimportOptions | false> {
|
||||
if (config.imports === false) return false;
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { InlineConfig, Wxt, WxtDevServer, WxtHooks } from '~/types';
|
||||
import { resolveConfig } from './utils/building';
|
||||
import { createHooks } from 'hookable';
|
||||
|
||||
/**
|
||||
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
|
||||
* global can be used instead of passing the variable as a function parameter everywhere.
|
||||
*/
|
||||
export let wxt: Wxt;
|
||||
|
||||
/**
|
||||
* Create and register a global instance of the Wxt interface for use throughout the project.
|
||||
*/
|
||||
export async function registerWxt(
|
||||
command: 'build' | 'serve',
|
||||
inlineConfig: InlineConfig = {},
|
||||
server?: WxtDevServer,
|
||||
): Promise<void> {
|
||||
const config = await resolveConfig(inlineConfig, command, server);
|
||||
const hooks = createHooks<WxtHooks>();
|
||||
|
||||
wxt = {
|
||||
config,
|
||||
hooks,
|
||||
get logger() {
|
||||
return config.logger;
|
||||
},
|
||||
async reloadConfig() {
|
||||
wxt.config = await resolveConfig(inlineConfig, command, server);
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize hooks
|
||||
wxt.hooks.addHooks(config.hooks);
|
||||
await wxt.hooks.callHook('ready', wxt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal ONLY USE FOR TESTING.
|
||||
*
|
||||
* @example
|
||||
* setWxtForTesting(fakeWxt({ ... }));
|
||||
* // Or use the shorthand
|
||||
* setFakeWxt({ ... })
|
||||
*/
|
||||
export function setWxtForTesting(testInstance: Wxt) {
|
||||
wxt = testInstance;
|
||||
}
|
||||
+22
-26
@@ -7,7 +7,8 @@ import { getPackageJson } from '~/core/utils/package';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { formatDuration } from '~/core/utils/time';
|
||||
import { printFileList } from '~/core/utils/log/printFileList';
|
||||
import { getInternalConfig, internalBuild } from '~/core/utils/building';
|
||||
import { internalBuild } from '~/core/utils/building';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Build and zip the extension for distribution.
|
||||
@@ -15,59 +16,54 @@ import { getInternalConfig, internalBuild } from '~/core/utils/building';
|
||||
* @returns A list of all files included in the ZIP.
|
||||
*/
|
||||
export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
const internalConfig = await getInternalConfig(config ?? {}, 'build');
|
||||
const output = await internalBuild(internalConfig);
|
||||
await registerWxt('build', config);
|
||||
const output = await internalBuild();
|
||||
|
||||
const start = Date.now();
|
||||
internalConfig.logger.info('Zipping extension...');
|
||||
wxt.logger.info('Zipping extension...');
|
||||
const zipFiles: string[] = [];
|
||||
|
||||
const projectName =
|
||||
internalConfig.zip.name ??
|
||||
wxt.config.zip.name ??
|
||||
kebabCaseAlphanumeric(
|
||||
(await getPackageJson(internalConfig))?.name || dirname(process.cwd()),
|
||||
(await getPackageJson())?.name || dirname(process.cwd()),
|
||||
);
|
||||
const applyTemplate = (template: string): string =>
|
||||
template
|
||||
.replaceAll('{{name}}', projectName)
|
||||
.replaceAll('{{browser}}', internalConfig.browser)
|
||||
.replaceAll('{{browser}}', wxt.config.browser)
|
||||
.replaceAll(
|
||||
'{{version}}',
|
||||
output.manifest.version_name ?? output.manifest.version,
|
||||
)
|
||||
.replaceAll('{{manifestVersion}}', `mv${internalConfig.manifestVersion}`);
|
||||
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
|
||||
|
||||
await fs.ensureDir(internalConfig.outBaseDir);
|
||||
await fs.ensureDir(wxt.config.outBaseDir);
|
||||
|
||||
// ZIP output directory
|
||||
|
||||
const outZipFilename = applyTemplate(internalConfig.zip.artifactTemplate);
|
||||
const outZipPath = resolve(internalConfig.outBaseDir, outZipFilename);
|
||||
await zipdir(internalConfig.outDir, {
|
||||
const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate);
|
||||
const outZipPath = resolve(wxt.config.outBaseDir, outZipFilename);
|
||||
await zipdir(wxt.config.outDir, {
|
||||
saveTo: outZipPath,
|
||||
});
|
||||
zipFiles.push(outZipPath);
|
||||
|
||||
// ZIP sources for Firefox
|
||||
|
||||
if (internalConfig.browser === 'firefox') {
|
||||
const sourcesZipFilename = applyTemplate(
|
||||
internalConfig.zip.sourcesTemplate,
|
||||
);
|
||||
const sourcesZipPath = resolve(
|
||||
internalConfig.outBaseDir,
|
||||
sourcesZipFilename,
|
||||
);
|
||||
await zipdir(internalConfig.zip.sourcesRoot, {
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate);
|
||||
const sourcesZipPath = resolve(wxt.config.outBaseDir, sourcesZipFilename);
|
||||
await zipdir(wxt.config.zip.sourcesRoot, {
|
||||
saveTo: sourcesZipPath,
|
||||
filter(path) {
|
||||
const relativePath = relative(internalConfig.zip.sourcesRoot, path);
|
||||
const relativePath = relative(wxt.config.zip.sourcesRoot, path);
|
||||
|
||||
return (
|
||||
internalConfig.zip.includeSources.some((pattern) =>
|
||||
wxt.config.zip.includeSources.some((pattern) =>
|
||||
minimatch(relativePath, pattern),
|
||||
) ||
|
||||
!internalConfig.zip.excludeSources.some((pattern) =>
|
||||
!wxt.config.zip.excludeSources.some((pattern) =>
|
||||
minimatch(relativePath, pattern),
|
||||
)
|
||||
);
|
||||
@@ -77,9 +73,9 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
}
|
||||
|
||||
await printFileList(
|
||||
internalConfig.logger.success,
|
||||
wxt.logger.success,
|
||||
`Zipped extension in ${formatDuration(Date.now() - start)}`,
|
||||
internalConfig.outBaseDir,
|
||||
wxt.config.outBaseDir,
|
||||
zipFiles,
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
* @module wxt
|
||||
*/
|
||||
export * from '~/core';
|
||||
export * from '~/types/external';
|
||||
export * from '~/types';
|
||||
export * from '~/version';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
globals,
|
||||
webextensionPolyfillMock,
|
||||
} from '../core/builders/vite/plugins';
|
||||
import { getInternalConfig } from '~/core/utils/building';
|
||||
import { resolveConfig } from '~/core/utils/building';
|
||||
import { InlineConfig } from '../types';
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,7 @@ import { InlineConfig } from '../types';
|
||||
* @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file.
|
||||
*/
|
||||
export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption {
|
||||
return getInternalConfig(inlineConfig ?? {}, 'serve').then((config) => [
|
||||
return resolveConfig(inlineConfig ?? {}, 'serve').then((config) => [
|
||||
webextensionPolyfillMock(config),
|
||||
unimport(config),
|
||||
globals(config),
|
||||
|
||||
@@ -1,795 +0,0 @@
|
||||
import type * as vite from 'vite';
|
||||
import type { Manifest, Scripting } from '~/browser';
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { LogLevel } from 'consola';
|
||||
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
|
||||
export interface InlineConfig {
|
||||
/**
|
||||
* Your project's root directory containing the `package.json` used to fill out the
|
||||
* `manifest.json`.
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
root?: string;
|
||||
/**
|
||||
* Directory containing all source code. Set to `"src"` to move all source code to a `src/`
|
||||
* directory.
|
||||
*
|
||||
* @default config.root
|
||||
*/
|
||||
srcDir?: string;
|
||||
/**
|
||||
* Directory containing files that will be copied to the output directory as-is.
|
||||
*
|
||||
* @default "${config.root}/public"
|
||||
*/
|
||||
publicDir?: string;
|
||||
/**
|
||||
* @default "${config.srcDir}/entrypoints"
|
||||
*/
|
||||
entrypointsDir?: string;
|
||||
/**
|
||||
* A list of entrypoint names (`"popup"`, `"options"`, etc.) to build. Will speed up the build if
|
||||
* your extension has lots of entrypoints, and you don't need to build all of them to develop a
|
||||
* feature.
|
||||
*/
|
||||
filterEntrypoints?: string[];
|
||||
/**
|
||||
* Output directory that stored build folders and ZIPs.
|
||||
*
|
||||
* @default ".output"
|
||||
*/
|
||||
outDir?: string;
|
||||
/**
|
||||
* > Only available when using the JS API. Not available in `wxt.config.ts` files
|
||||
*
|
||||
* Path to `wxt.config.ts` file or `false` to disable config file discovery.
|
||||
*
|
||||
* @default "wxt.config.ts"
|
||||
*/
|
||||
configFile?: string | false;
|
||||
/**
|
||||
* Set to `true` to show debug logs. Overriden by the command line `--debug` option.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Explicitly set a mode to run in. This will override the default mode for each command, and can
|
||||
* be overridden by the command line `--mode` option.
|
||||
*/
|
||||
mode?: string;
|
||||
/**
|
||||
* Customize auto-import options. Set to `false` to disable auto-imports.
|
||||
*
|
||||
* For example, to add a directory to auto-import from, you can use:
|
||||
*
|
||||
* ```ts
|
||||
* export default defineConfig({
|
||||
* imports: {
|
||||
* dirs: ["some-directory"]
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
imports?: Partial<UnimportOptions> | false;
|
||||
/**
|
||||
* Explicitly set a browser to build for. This will override the default browser for each command,
|
||||
* and can be overridden by the command line `--browser` option.
|
||||
*
|
||||
* @default
|
||||
* "chrome"
|
||||
*/
|
||||
browser?: TargetBrowser;
|
||||
/**
|
||||
* Explicitly set a manifest version to target. This will override the default manifest version
|
||||
* for each command, and can be overridden by the command line `--mv2` or `--mv3` option.
|
||||
*/
|
||||
manifestVersion?: TargetManifestVersion;
|
||||
/**
|
||||
* Override the logger used.
|
||||
*
|
||||
* @default
|
||||
* consola
|
||||
*/
|
||||
logger?: Logger;
|
||||
/**
|
||||
* Customize the `manifest.json` output. Can be an object, promise, or function that returns an
|
||||
* object or promise.
|
||||
*/
|
||||
manifest?: UserManifest | Promise<UserManifest> | UserManifestFn;
|
||||
/**
|
||||
* Custom runner options. Options set here can be overridden in a `web-ext.config.ts` file.
|
||||
*/
|
||||
runner?: ExtensionRunnerConfig;
|
||||
zip?: {
|
||||
/**
|
||||
* Configure the filename output when zipping files.
|
||||
*
|
||||
* Available template variables:
|
||||
*
|
||||
* - `{{name}}` - The project's name converted to kebab-case
|
||||
* - `{{version}}` - The version_name or version from the manifest
|
||||
* - `{{browser}}` - The target browser from the `--browser` CLI flag
|
||||
* - `{{manifestVersion}}` - Either "2" or "3"
|
||||
*
|
||||
* @default "{{name}}-{{version}}-{{browser}}.zip"
|
||||
*/
|
||||
artifactTemplate?: string;
|
||||
/**
|
||||
* Configure the filename output when zipping files.
|
||||
*
|
||||
* Available template variables:
|
||||
*
|
||||
* - `{{name}}` - The project's name converted to kebab-case
|
||||
* - `{{version}}` - The version_name or version from the manifest
|
||||
* - `{{browser}}` - The target browser from the `--browser` CLI flag
|
||||
* - `{{manifestVersion}}` - Either "2" or "3"
|
||||
*
|
||||
* @default "{{name}}-{{version}}-sources.zip"
|
||||
*/
|
||||
sourcesTemplate?: string;
|
||||
/**
|
||||
* Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s
|
||||
* name, or if that doesn't exist, the current working directories name.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Root directory to ZIP when generating the sources ZIP.
|
||||
*
|
||||
* @default config.root
|
||||
*/
|
||||
sourcesRoot?: string;
|
||||
/**
|
||||
* [Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to include when
|
||||
* creating a ZIP of all your source code for Firefox. Patterns are relative to your
|
||||
* `config.zip.sourcesRoot`.
|
||||
*
|
||||
* This setting overrides `excludeSources`. So if a file matches both lists, it is included in the ZIP.
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
|
||||
* ]
|
||||
*/
|
||||
includeSources?: string[];
|
||||
/**
|
||||
* [Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to exclude when
|
||||
* creating a ZIP of all your source code for Firefox. Patterns are relative to your
|
||||
* `config.zip.sourcesRoot`.
|
||||
*
|
||||
* Hidden files, node_modules, and tests are ignored by default.
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
|
||||
* ]
|
||||
*/
|
||||
excludeSources?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform the final manifest before it's written to the file system. Edit the `manifest`
|
||||
* parameter directly, do not return a new object. Return values are ignored.
|
||||
*
|
||||
* @example
|
||||
* defineConfig({
|
||||
* // Add a CSS-only content script.
|
||||
* transformManifest(manifest) {
|
||||
* manifest.content_scripts.push({
|
||||
* matches: ["*://google.com/*"],
|
||||
* css: ["content-scripts/some-example.css"],
|
||||
* });
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
transformManifest?: (manifest: Manifest.WebExtensionManifest) => void;
|
||||
analysis?: {
|
||||
/**
|
||||
* Explicitly include bundle analysis when running `wxt build`. This can be overridden by the
|
||||
* command line `--analysis` option.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* When running `wxt build --analyze` or setting `analysis.enabled` to true, customize how the
|
||||
* bundle will be visualized. See
|
||||
* [`rollup-plugin-visualizer`](https://github.com/btd/rollup-plugin-visualizer#how-to-use-generated-files)
|
||||
* for more details.
|
||||
*
|
||||
* @default "treemap"
|
||||
*/
|
||||
template?: PluginVisualizerOptions['template'];
|
||||
};
|
||||
/**
|
||||
* Add additional paths to the `.wxt/tsconfig.json`. Use this instead of overwriting the `paths`
|
||||
* in the root `tsconfig.json` if you want to add new paths.
|
||||
*
|
||||
* The key is the import alias and the value is either a relative path to the root directory or an absolute path.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* "testing": "src/utils/testing.ts"
|
||||
* }
|
||||
*/
|
||||
alias?: Record<string, string>;
|
||||
/**
|
||||
* Experimental settings - use with caution.
|
||||
*/
|
||||
experimental?: {
|
||||
/**
|
||||
* Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill)
|
||||
* when importing `browser` from `wxt/browser`.
|
||||
*
|
||||
* When set to `false`, WXT will export the chrome global instead of the polyfill from
|
||||
* `wxt/browser`.
|
||||
*
|
||||
* You should use `browser` to access the web extension APIs.
|
||||
*
|
||||
* @experimental This option will remain experimental until Manifest V2 is dead.
|
||||
*
|
||||
* @default true
|
||||
* @example
|
||||
* export default defineConfig({
|
||||
* experimental: {
|
||||
* includeBrowserPolyfill: false
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
includeBrowserPolyfill?: boolean;
|
||||
};
|
||||
/**
|
||||
* Config effecting dev mode only.
|
||||
*/
|
||||
dev?: {
|
||||
/**
|
||||
* Controls whether a custom keyboard shortcut command, `Alt+R`, is added during dev mode to
|
||||
* quickly reload the extension.
|
||||
*
|
||||
* If false, the shortcut is not added during development.
|
||||
*
|
||||
* If set to a custom string, you can override the key combo used. See
|
||||
* [Chrome's command docs](https://developer.chrome.com/docs/extensions/reference/api/commands)
|
||||
* for available options.
|
||||
*
|
||||
* @default "Alt+R"
|
||||
*/
|
||||
reloadCommand?: string | false;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Extract to @wxt/vite-builder and use module augmentation to include the vite field
|
||||
export interface InlineConfig {
|
||||
/**
|
||||
* Return custom Vite options from a function. See
|
||||
* <https://vitejs.dev/config/shared-options.html>.
|
||||
*
|
||||
* [`root`](#root), [`configFile`](#configfile), and [`mode`](#mode) should be set in WXT's config
|
||||
* instead of Vite's.
|
||||
*
|
||||
* This is a function because any vite plugins added need to be recreated for each individual
|
||||
* build step, incase they have internal state causing them to fail when reused.
|
||||
*/
|
||||
vite?: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
|
||||
}
|
||||
|
||||
// TODO: Move into @wxt/vite-builder
|
||||
export type WxtViteConfig = Omit<
|
||||
vite.UserConfig,
|
||||
'root' | 'configFile' | 'mode'
|
||||
>;
|
||||
|
||||
export interface BuildOutput {
|
||||
manifest: Manifest.WebExtensionManifest;
|
||||
publicAssets: OutputAsset[];
|
||||
steps: BuildStepOutput[];
|
||||
}
|
||||
|
||||
export type OutputFile = OutputChunk | OutputAsset;
|
||||
|
||||
export interface OutputChunk {
|
||||
type: 'chunk';
|
||||
/**
|
||||
* Relative, normalized path relative to the output directory.
|
||||
*
|
||||
* Ex: "content-scripts/overlay.js"
|
||||
*/
|
||||
fileName: string;
|
||||
/**
|
||||
* Absolute, normalized paths to all dependencies this chunk relies on.
|
||||
*/
|
||||
moduleIds: string[];
|
||||
}
|
||||
|
||||
export interface OutputAsset {
|
||||
type: 'asset';
|
||||
/**
|
||||
* Relative, normalized path relative to the output directory.
|
||||
*
|
||||
* Ex: "icons/16.png"
|
||||
*/
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface BuildStepOutput {
|
||||
entrypoints: EntrypointGroup;
|
||||
chunks: OutputFile[];
|
||||
}
|
||||
|
||||
export interface WxtDevServer
|
||||
extends Omit<WxtBuilderServer, 'listen' | 'close'>,
|
||||
ServerInfo {
|
||||
/**
|
||||
* Stores the current build output of the server.
|
||||
*/
|
||||
currentOutput: BuildOutput | undefined;
|
||||
/**
|
||||
* Start the server.
|
||||
*/
|
||||
start(): Promise<void>;
|
||||
/**
|
||||
* Stop the server.
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
/**
|
||||
* Close the browser, stop the server, rebuild the entire extension, and start the server again.
|
||||
*/
|
||||
restart(): Promise<void>;
|
||||
/**
|
||||
* Transform the HTML for dev mode.
|
||||
*/
|
||||
transformHtml(
|
||||
url: string,
|
||||
html: string,
|
||||
originalUrl?: string | undefined,
|
||||
): Promise<string>;
|
||||
/**
|
||||
* Tell the extension to reload by running `browser.runtime.reload`.
|
||||
*/
|
||||
reloadExtension: () => void;
|
||||
/**
|
||||
* Tell an extension page to reload.
|
||||
*
|
||||
* The path is the bundle path, not the input paths, so if the input paths is
|
||||
* "src/options/index.html", you would pass "options.html" because that's where it is written to
|
||||
* in the dist directory, and where it's available at in the actual extension.
|
||||
*
|
||||
* @example
|
||||
* server.reloadPage("popup.html")
|
||||
* server.reloadPage("sandbox.html")
|
||||
*/
|
||||
reloadPage: (path: string) => void;
|
||||
/**
|
||||
* Tell the extension to restart a content script.
|
||||
*
|
||||
* @param contentScript The manifest definition for a content script
|
||||
*/
|
||||
reloadContentScript: (
|
||||
contentScript: Omit<Scripting.RegisteredContentScript, 'id'>,
|
||||
) => void;
|
||||
/**
|
||||
* Grab the latest runner config and restart the browser.
|
||||
*/
|
||||
restartBrowser: () => void;
|
||||
}
|
||||
|
||||
export type TargetBrowser = string;
|
||||
export type TargetManifestVersion = 2 | 3;
|
||||
|
||||
export type UserConfig = Omit<InlineConfig, 'configFile'>;
|
||||
|
||||
export interface Logger {
|
||||
debug(...args: any[]): void;
|
||||
log(...args: any[]): void;
|
||||
info(...args: any[]): void;
|
||||
warn(...args: any[]): void;
|
||||
error(...args: any[]): void;
|
||||
fatal(...args: any[]): void;
|
||||
success(...args: any[]): void;
|
||||
level: LogLevel;
|
||||
}
|
||||
|
||||
export interface BaseEntrypointOptions {
|
||||
include?: TargetBrowser[];
|
||||
exclude?: TargetBrowser[];
|
||||
}
|
||||
|
||||
export interface BaseEntrypoint {
|
||||
/**
|
||||
* The entrypoint's name. This is the filename or dirname without the type suffix.
|
||||
*
|
||||
* Examples:
|
||||
* - `popup.html` → `popup`
|
||||
* - `options/index.html` → `options`
|
||||
* - `named.sandbox.html` → `named`
|
||||
* - `named.sandbox/index.html` → `named`
|
||||
* - `sandbox.html` → `sandbox`
|
||||
* - `sandbox/index.html` → `sandbox`
|
||||
* - `overlay.content.ts` → `overlay`
|
||||
* - `overlay.content/index.ts` → `overlay`
|
||||
*
|
||||
* The name is used when generating an output file:
|
||||
* `<entrypoint.outputDir>/<entrypoint.name>.<ext>`
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Absolute path to the entrypoint's input file.
|
||||
*/
|
||||
inputPath: string;
|
||||
/**
|
||||
* Absolute path to the entrypoint's output directory. Can be the`InternalConfg.outDir` or a
|
||||
* subdirectory of it.
|
||||
*/
|
||||
outputDir: string;
|
||||
options: BaseEntrypointOptions;
|
||||
skipped: boolean;
|
||||
}
|
||||
|
||||
export interface GenericEntrypoint extends BaseEntrypoint {
|
||||
type:
|
||||
| 'sandbox'
|
||||
| 'bookmarks'
|
||||
| 'history'
|
||||
| 'newtab'
|
||||
| 'sidepanel'
|
||||
| 'devtools'
|
||||
| 'unlisted-page'
|
||||
| 'unlisted-script'
|
||||
| 'unlisted-style'
|
||||
| 'content-script-style';
|
||||
}
|
||||
|
||||
export interface BackgroundEntrypoint extends BaseEntrypoint {
|
||||
type: 'background';
|
||||
options: {
|
||||
persistent?: boolean;
|
||||
type?: 'module';
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface ContentScriptEntrypoint extends BaseEntrypoint {
|
||||
type: 'content-script';
|
||||
options: Omit<ContentScriptDefinition, 'main'> & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface PopupEntrypoint extends BaseEntrypoint {
|
||||
type: 'popup';
|
||||
options: {
|
||||
/**
|
||||
* Defaults to "browser_action" to be equivalent to MV3's "action" key
|
||||
*/
|
||||
mv2Key?: 'browser_action' | 'page_action';
|
||||
defaultIcon?: Record<string, string>;
|
||||
defaultTitle?: string;
|
||||
browserStyle?: boolean;
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface OptionsEntrypoint extends BaseEntrypoint {
|
||||
type: 'options';
|
||||
options: {
|
||||
openInTab?: boolean;
|
||||
browserStyle?: boolean;
|
||||
chromeStyle?: boolean;
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export type Entrypoint =
|
||||
| GenericEntrypoint
|
||||
| BackgroundEntrypoint
|
||||
| ContentScriptEntrypoint
|
||||
| PopupEntrypoint
|
||||
| OptionsEntrypoint;
|
||||
|
||||
export type EntrypointGroup = Entrypoint | Entrypoint[];
|
||||
|
||||
export type OnContentScriptStopped = (cb: () => void) => void;
|
||||
|
||||
export type ContentScriptDefinition =
|
||||
| ContentScriptIsolatedWorldDefinition
|
||||
| ContentScriptMainWorldDefinition;
|
||||
|
||||
export interface ContentScriptIsolatedWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
* @default "ISOLATED"
|
||||
*/
|
||||
world?: 'ISOLATED';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(ctx: ContentScriptContext): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptMainWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
*/
|
||||
world: 'MAIN';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptBaseDefinition extends ExcludableEntrypoint {
|
||||
matches: PerBrowserOption<Manifest.ContentScript['matches']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default "documentIdle"
|
||||
*/
|
||||
runAt?: PerBrowserOption<Manifest.ContentScript['run_at']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
matchAboutBlank?: PerBrowserOption<
|
||||
Manifest.ContentScript['match_about_blank']
|
||||
>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
excludeMatches?: PerBrowserOption<Manifest.ContentScript['exclude_matches']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
includeGlobs?: PerBrowserOption<Manifest.ContentScript['include_globs']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
excludeGlobs?: PerBrowserOption<Manifest.ContentScript['exclude_globs']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
allFrames?: PerBrowserOption<Manifest.ContentScript['all_frames']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
matchOriginAsFallback?: PerBrowserOption<boolean>;
|
||||
/**
|
||||
* Customize how imported/generated styles are injected with the content script. Regardless of the
|
||||
* mode selected, CSS will always be built and included in the output directory.
|
||||
*
|
||||
* - `"manifest"` - Include the CSS in the manifest, under the content script's `css` array.
|
||||
* - `"manual"` - Exclude the CSS from the manifest. You are responsible for manually loading it
|
||||
* onto the page. Use `browser.runtime.getURL("content-scripts/<name>.css")` to get the file's
|
||||
* URL
|
||||
* - `"ui"` - Exclude the CSS from the manifest. CSS will be automatically added to your UI when
|
||||
* calling `createContentScriptUi`
|
||||
*
|
||||
* @default "manifest"
|
||||
*/
|
||||
cssInjectionMode?: PerBrowserOption<'manifest' | 'manual' | 'ui'>;
|
||||
}
|
||||
|
||||
export interface BackgroundDefinition extends ExcludableEntrypoint {
|
||||
type?: PerBrowserOption<'module'>;
|
||||
persistent?: PerBrowserOption<boolean>;
|
||||
main(): void;
|
||||
}
|
||||
|
||||
export interface UnlistedScriptDefinition extends ExcludableEntrypoint {
|
||||
/**
|
||||
* Main function executed when the unlisted script is ran.
|
||||
*/
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export type PerBrowserOption<T> = T | { [browser: TargetBrowser]: T };
|
||||
|
||||
export interface ExcludableEntrypoint {
|
||||
/**
|
||||
* List of target browsers to include this entrypoint in. Defaults to being included in all
|
||||
* builds. Cannot be used with `exclude`. You must choose one of the two options.
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
include?: TargetBrowser[];
|
||||
/**
|
||||
* List of target browsers to exclude this entrypoint from. Cannot be used with `include`. You
|
||||
* must choose one of the two options.
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
exclude?: TargetBrowser[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Manifest customization available in the `wxt.config.ts` file. You cannot configure entrypoints
|
||||
* here, they are configured inline.
|
||||
*/
|
||||
export type UserManifest = Partial<
|
||||
Omit<
|
||||
Manifest.WebExtensionManifest,
|
||||
| 'background'
|
||||
| 'chrome_url_overrides'
|
||||
| 'devtools_page'
|
||||
| 'manifest_version'
|
||||
| 'options_page'
|
||||
| 'options_ui'
|
||||
| 'sandbox'
|
||||
| 'sidepanel'
|
||||
| 'sidebar_action'
|
||||
>
|
||||
>;
|
||||
|
||||
export type UserManifestFn = (
|
||||
env: ConfigEnv,
|
||||
) => UserManifest | Promise<UserManifest>;
|
||||
|
||||
export interface ConfigEnv {
|
||||
mode: string;
|
||||
command: 'build' | 'serve';
|
||||
/**
|
||||
* Browser passed in from the CLI
|
||||
*/
|
||||
browser: TargetBrowser;
|
||||
/**
|
||||
* Manifest version passed in from the CLI
|
||||
*/
|
||||
manifestVersion: 2 | 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how the browser starts up.
|
||||
*/
|
||||
export interface ExtensionRunnerConfig {
|
||||
/**
|
||||
* Whether or not to open the browser with the extension installed in dev mode.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#browser-console
|
||||
*/
|
||||
openConsole?: boolean;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#devtools
|
||||
*/
|
||||
openDevtools?: boolean;
|
||||
/**
|
||||
* List of browser names and the binary that should be used to open the browser.
|
||||
*
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#chromium-binary
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#firefox
|
||||
*/
|
||||
binaries?: Record<string, string>;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#firefox-profile
|
||||
*/
|
||||
firefoxProfile?: string;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#chromium-profile
|
||||
*/
|
||||
chromiumProfile?: string;
|
||||
/**
|
||||
* An map of chrome preferences from https://chromium.googlesource.com/chromium/src/+/main/chrome/common/pref_names.h
|
||||
*
|
||||
* @example
|
||||
* // change your downloads directory
|
||||
* {
|
||||
* download: {
|
||||
* default_directory: "/my/custom/dir",
|
||||
* },
|
||||
* }
|
||||
*
|
||||
* @default
|
||||
* // Enable dev mode and allow content script sourcemaps
|
||||
* {
|
||||
* devtools: {
|
||||
* synced_preferences_sync_disabled: {
|
||||
* skipContentScripts: false,
|
||||
* },
|
||||
* }
|
||||
* extensions: {
|
||||
* ui: {
|
||||
* developer_mode: true,
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
chromiumPref?: string;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#pref
|
||||
*/
|
||||
firefoxPrefs?: Record<string, string>;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#args
|
||||
*/
|
||||
firefoxArgs?: string[];
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#args
|
||||
*/
|
||||
chromiumArgs?: string[];
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#start-url
|
||||
*/
|
||||
startUrls?: string[];
|
||||
}
|
||||
|
||||
export interface WxtBuilder {
|
||||
/**
|
||||
* Name of tool used to build. Ex: "Vite" or "Webpack".
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Version of tool used to build. Ex: "5.0.2"
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Build a single entrypoint group. This is effectively one of the multiple "steps" during the
|
||||
* build process.
|
||||
*/
|
||||
build(group: EntrypointGroup): Promise<BuildStepOutput>;
|
||||
/**
|
||||
* Start a dev server at the provided port.
|
||||
*/
|
||||
createServer(info: ServerInfo): Promise<WxtBuilderServer>;
|
||||
}
|
||||
|
||||
export interface WxtBuilderServer {
|
||||
/**
|
||||
* Start the server.
|
||||
*/
|
||||
listen(): Promise<void>;
|
||||
/**
|
||||
* Stop the server.
|
||||
*/
|
||||
close(): Promise<void>;
|
||||
/**
|
||||
* Transform the HTML for dev mode.
|
||||
*/
|
||||
transformHtml(
|
||||
url: string,
|
||||
html: string,
|
||||
originalUrl?: string | undefined,
|
||||
): Promise<string>;
|
||||
/**
|
||||
* The web socket server used to communicate with the extension.
|
||||
*/
|
||||
ws: {
|
||||
/**
|
||||
* Send a message via the server's websocket, with an optional payload.
|
||||
*
|
||||
* @example
|
||||
* ws.send("wxt:reload-extension");
|
||||
* ws.send("wxt:reload-content-script", { ... });
|
||||
*/
|
||||
send(message: string, payload?: any): void;
|
||||
/**
|
||||
* Listen for messages over the server's websocket.
|
||||
*/
|
||||
on(message: string, cb: (payload: any) => void): void;
|
||||
};
|
||||
/**
|
||||
* Chokidar file watcher instance.
|
||||
*/
|
||||
watcher: FSWatcher;
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
/**
|
||||
* Ex: `3000`
|
||||
*/
|
||||
port: number;
|
||||
/**
|
||||
* Ex: `"localhost"`
|
||||
*/
|
||||
hostname: string;
|
||||
/**
|
||||
* Ex: `"http://localhost:3000"`
|
||||
*/
|
||||
origin: string;
|
||||
}
|
||||
+956
-2
@@ -1,2 +1,956 @@
|
||||
export * from './external';
|
||||
export * from './internal';
|
||||
import type * as vite from 'vite';
|
||||
import type { Manifest, Scripting } from '~/browser';
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { LogLevel } from 'consola';
|
||||
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
import { ResolvedConfig as C12ResolvedConfig } from 'c12';
|
||||
import { Hookable, NestedHooks } from 'hookable';
|
||||
|
||||
export interface InlineConfig {
|
||||
/**
|
||||
* Your project's root directory containing the `package.json` used to fill out the
|
||||
* `manifest.json`.
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
root?: string;
|
||||
/**
|
||||
* Directory containing all source code. Set to `"src"` to move all source code to a `src/`
|
||||
* directory.
|
||||
*
|
||||
* @default config.root
|
||||
*/
|
||||
srcDir?: string;
|
||||
/**
|
||||
* Directory containing files that will be copied to the output directory as-is.
|
||||
*
|
||||
* @default "${config.root}/public"
|
||||
*/
|
||||
publicDir?: string;
|
||||
/**
|
||||
* @default "${config.srcDir}/entrypoints"
|
||||
*/
|
||||
entrypointsDir?: string;
|
||||
/**
|
||||
* A list of entrypoint names (`"popup"`, `"options"`, etc.) to build. Will speed up the build if
|
||||
* your extension has lots of entrypoints, and you don't need to build all of them to develop a
|
||||
* feature.
|
||||
*/
|
||||
filterEntrypoints?: string[];
|
||||
/**
|
||||
* Output directory that stored build folders and ZIPs.
|
||||
*
|
||||
* @default ".output"
|
||||
*/
|
||||
outDir?: string;
|
||||
/**
|
||||
* > Only available when using the JS API. Not available in `wxt.config.ts` files
|
||||
*
|
||||
* Path to `wxt.config.ts` file or `false` to disable config file discovery.
|
||||
*
|
||||
* @default "wxt.config.ts"
|
||||
*/
|
||||
configFile?: string | false;
|
||||
/**
|
||||
* Set to `true` to show debug logs. Overriden by the command line `--debug` option.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Explicitly set a mode to run in. This will override the default mode for each command, and can
|
||||
* be overridden by the command line `--mode` option.
|
||||
*/
|
||||
mode?: string;
|
||||
/**
|
||||
* Customize auto-import options. Set to `false` to disable auto-imports.
|
||||
*
|
||||
* For example, to add a directory to auto-import from, you can use:
|
||||
*
|
||||
* ```ts
|
||||
* export default defineConfig({
|
||||
* imports: {
|
||||
* dirs: ["some-directory"]
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
imports?: Partial<UnimportOptions> | false;
|
||||
/**
|
||||
* Explicitly set a browser to build for. This will override the default browser for each command,
|
||||
* and can be overridden by the command line `--browser` option.
|
||||
*
|
||||
* @default
|
||||
* "chrome"
|
||||
*/
|
||||
browser?: TargetBrowser;
|
||||
/**
|
||||
* Explicitly set a manifest version to target. This will override the default manifest version
|
||||
* for each command, and can be overridden by the command line `--mv2` or `--mv3` option.
|
||||
*/
|
||||
manifestVersion?: TargetManifestVersion;
|
||||
/**
|
||||
* Override the logger used.
|
||||
*
|
||||
* @default
|
||||
* consola
|
||||
*/
|
||||
logger?: Logger;
|
||||
/**
|
||||
* Customize the `manifest.json` output. Can be an object, promise, or function that returns an
|
||||
* object or promise.
|
||||
*/
|
||||
manifest?: UserManifest | Promise<UserManifest> | UserManifestFn;
|
||||
/**
|
||||
* Custom runner options. Options set here can be overridden in a `web-ext.config.ts` file.
|
||||
*/
|
||||
runner?: ExtensionRunnerConfig;
|
||||
zip?: {
|
||||
/**
|
||||
* Configure the filename output when zipping files.
|
||||
*
|
||||
* Available template variables:
|
||||
*
|
||||
* - `{{name}}` - The project's name converted to kebab-case
|
||||
* - `{{version}}` - The version_name or version from the manifest
|
||||
* - `{{browser}}` - The target browser from the `--browser` CLI flag
|
||||
* - `{{manifestVersion}}` - Either "2" or "3"
|
||||
*
|
||||
* @default "{{name}}-{{version}}-{{browser}}.zip"
|
||||
*/
|
||||
artifactTemplate?: string;
|
||||
/**
|
||||
* Configure the filename output when zipping files.
|
||||
*
|
||||
* Available template variables:
|
||||
*
|
||||
* - `{{name}}` - The project's name converted to kebab-case
|
||||
* - `{{version}}` - The version_name or version from the manifest
|
||||
* - `{{browser}}` - The target browser from the `--browser` CLI flag
|
||||
* - `{{manifestVersion}}` - Either "2" or "3"
|
||||
*
|
||||
* @default "{{name}}-{{version}}-sources.zip"
|
||||
*/
|
||||
sourcesTemplate?: string;
|
||||
/**
|
||||
* Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s
|
||||
* name, or if that doesn't exist, the current working directories name.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Root directory to ZIP when generating the sources ZIP.
|
||||
*
|
||||
* @default config.root
|
||||
*/
|
||||
sourcesRoot?: string;
|
||||
/**
|
||||
* [Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to include when
|
||||
* creating a ZIP of all your source code for Firefox. Patterns are relative to your
|
||||
* `config.zip.sourcesRoot`.
|
||||
*
|
||||
* This setting overrides `excludeSources`. So if a file matches both lists, it is included in the ZIP.
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
|
||||
* ]
|
||||
*/
|
||||
includeSources?: string[];
|
||||
/**
|
||||
* [Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to exclude when
|
||||
* creating a ZIP of all your source code for Firefox. Patterns are relative to your
|
||||
* `config.zip.sourcesRoot`.
|
||||
*
|
||||
* Hidden files, node_modules, and tests are ignored by default.
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
|
||||
* ]
|
||||
*/
|
||||
excludeSources?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use `hooks.build.manifestGenerated` to modify your manifest instead. This option
|
||||
* will be removed in v1.0
|
||||
*
|
||||
* Transform the final manifest before it's written to the file system. Edit the `manifest`
|
||||
* parameter directly, do not return a new object. Return values are ignored.
|
||||
*
|
||||
* @example
|
||||
* defineConfig({
|
||||
* // Add a CSS-only content script.
|
||||
* transformManifest(manifest) {
|
||||
* manifest.content_scripts.push({
|
||||
* matches: ["*://google.com/*"],
|
||||
* css: ["content-scripts/some-example.css"],
|
||||
* });
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
transformManifest?: (manifest: Manifest.WebExtensionManifest) => void;
|
||||
analysis?: {
|
||||
/**
|
||||
* Explicitly include bundle analysis when running `wxt build`. This can be overridden by the
|
||||
* command line `--analysis` option.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* When running `wxt build --analyze` or setting `analysis.enabled` to true, customize how the
|
||||
* bundle will be visualized. See
|
||||
* [`rollup-plugin-visualizer`](https://github.com/btd/rollup-plugin-visualizer#how-to-use-generated-files)
|
||||
* for more details.
|
||||
*
|
||||
* @default "treemap"
|
||||
*/
|
||||
template?: PluginVisualizerOptions['template'];
|
||||
/**
|
||||
* Name of the output HTML file. Relative to the project's root directory.
|
||||
*
|
||||
* Changing the filename of the outputFile also effects the names of the artifacts generated
|
||||
* when setting `keepArtifacts` to true:
|
||||
* - "stats.html" => "stats-*.json"
|
||||
* - "stats/bundle.html" => "bundle-*.json"
|
||||
* - ".analysis/index.html" => "index-*.json"
|
||||
*
|
||||
* @default "stats.html"
|
||||
*/
|
||||
outputFile?: string;
|
||||
/**
|
||||
* By default, the `stats-*.json` artifacts generated during bundle analysis are deleted. Set to
|
||||
* `true` to keep them.
|
||||
*
|
||||
* One stats file is output per build step.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
keepArtifacts?: boolean;
|
||||
};
|
||||
/**
|
||||
* Add additional paths to the `.wxt/tsconfig.json`. Use this instead of overwriting the `paths`
|
||||
* in the root `tsconfig.json` if you want to add new paths.
|
||||
*
|
||||
* The key is the import alias and the value is either a relative path to the root directory or an absolute path.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* "testing": "src/utils/testing.ts"
|
||||
* }
|
||||
*/
|
||||
alias?: Record<string, string>;
|
||||
/**
|
||||
* Experimental settings - use with caution.
|
||||
*/
|
||||
experimental?: {
|
||||
/**
|
||||
* Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill)
|
||||
* when importing `browser` from `wxt/browser`.
|
||||
*
|
||||
* When set to `false`, WXT will export the chrome global instead of the polyfill from
|
||||
* `wxt/browser`.
|
||||
*
|
||||
* You should use `browser` to access the web extension APIs.
|
||||
*
|
||||
* @experimental This option will remain experimental until Manifest V2 is dead.
|
||||
*
|
||||
* @default true
|
||||
* @example
|
||||
* export default defineConfig({
|
||||
* experimental: {
|
||||
* includeBrowserPolyfill: false
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
includeBrowserPolyfill?: boolean;
|
||||
};
|
||||
/**
|
||||
* Config effecting dev mode only.
|
||||
*/
|
||||
dev?: {
|
||||
/**
|
||||
* Controls whether a custom keyboard shortcut command, `Alt+R`, is added during dev mode to
|
||||
* quickly reload the extension.
|
||||
*
|
||||
* If false, the shortcut is not added during development.
|
||||
*
|
||||
* If set to a custom string, you can override the key combo used. See
|
||||
* [Chrome's command docs](https://developer.chrome.com/docs/extensions/reference/api/commands)
|
||||
* for available options.
|
||||
*
|
||||
* @default "Alt+R"
|
||||
*/
|
||||
reloadCommand?: string | false;
|
||||
};
|
||||
/**
|
||||
* Project hooks for running logic during the build process.
|
||||
*/
|
||||
hooks?: NestedHooks<WxtHooks>;
|
||||
}
|
||||
|
||||
// TODO: Extract to @wxt/vite-builder and use module augmentation to include the vite field
|
||||
export interface InlineConfig {
|
||||
/**
|
||||
* Return custom Vite options from a function. See
|
||||
* <https://vitejs.dev/config/shared-options.html>.
|
||||
*
|
||||
* [`root`](#root), [`configFile`](#configfile), and [`mode`](#mode) should be set in WXT's config
|
||||
* instead of Vite's.
|
||||
*
|
||||
* This is a function because any vite plugins added need to be recreated for each individual
|
||||
* build step, incase they have internal state causing them to fail when reused.
|
||||
*/
|
||||
vite?: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
|
||||
}
|
||||
|
||||
// TODO: Move into @wxt/vite-builder
|
||||
export type WxtViteConfig = Omit<
|
||||
vite.UserConfig,
|
||||
'root' | 'configFile' | 'mode'
|
||||
>;
|
||||
|
||||
export interface BuildOutput {
|
||||
manifest: Manifest.WebExtensionManifest;
|
||||
publicAssets: OutputAsset[];
|
||||
steps: BuildStepOutput[];
|
||||
}
|
||||
|
||||
export type OutputFile = OutputChunk | OutputAsset;
|
||||
|
||||
export interface OutputChunk {
|
||||
type: 'chunk';
|
||||
/**
|
||||
* Relative, normalized path relative to the output directory.
|
||||
*
|
||||
* Ex: "content-scripts/overlay.js"
|
||||
*/
|
||||
fileName: string;
|
||||
/**
|
||||
* Absolute, normalized paths to all dependencies this chunk relies on.
|
||||
*/
|
||||
moduleIds: string[];
|
||||
}
|
||||
|
||||
export interface OutputAsset {
|
||||
type: 'asset';
|
||||
/**
|
||||
* Relative, normalized path relative to the output directory.
|
||||
*
|
||||
* Ex: "icons/16.png"
|
||||
*/
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface BuildStepOutput {
|
||||
entrypoints: EntrypointGroup;
|
||||
chunks: OutputFile[];
|
||||
}
|
||||
|
||||
export interface WxtDevServer
|
||||
extends Omit<WxtBuilderServer, 'listen' | 'close'>,
|
||||
ServerInfo {
|
||||
/**
|
||||
* Stores the current build output of the server.
|
||||
*/
|
||||
currentOutput: BuildOutput | undefined;
|
||||
/**
|
||||
* Start the server.
|
||||
*/
|
||||
start(): Promise<void>;
|
||||
/**
|
||||
* Stop the server.
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
/**
|
||||
* Close the browser, stop the server, rebuild the entire extension, and start the server again.
|
||||
*/
|
||||
restart(): Promise<void>;
|
||||
/**
|
||||
* Transform the HTML for dev mode.
|
||||
*/
|
||||
transformHtml(
|
||||
url: string,
|
||||
html: string,
|
||||
originalUrl?: string | undefined,
|
||||
): Promise<string>;
|
||||
/**
|
||||
* Tell the extension to reload by running `browser.runtime.reload`.
|
||||
*/
|
||||
reloadExtension: () => void;
|
||||
/**
|
||||
* Tell an extension page to reload.
|
||||
*
|
||||
* The path is the bundle path, not the input paths, so if the input paths is
|
||||
* "src/options/index.html", you would pass "options.html" because that's where it is written to
|
||||
* in the dist directory, and where it's available at in the actual extension.
|
||||
*
|
||||
* @example
|
||||
* server.reloadPage("popup.html")
|
||||
* server.reloadPage("sandbox.html")
|
||||
*/
|
||||
reloadPage: (path: string) => void;
|
||||
/**
|
||||
* Tell the extension to restart a content script.
|
||||
*
|
||||
* @param contentScript The manifest definition for a content script
|
||||
*/
|
||||
reloadContentScript: (
|
||||
contentScript: Omit<Scripting.RegisteredContentScript, 'id'>,
|
||||
) => void;
|
||||
/**
|
||||
* Grab the latest runner config and restart the browser.
|
||||
*/
|
||||
restartBrowser: () => void;
|
||||
}
|
||||
|
||||
export type TargetBrowser = string;
|
||||
export type TargetManifestVersion = 2 | 3;
|
||||
|
||||
export type UserConfig = Omit<InlineConfig, 'configFile'>;
|
||||
|
||||
export interface Logger {
|
||||
debug(...args: any[]): void;
|
||||
log(...args: any[]): void;
|
||||
info(...args: any[]): void;
|
||||
warn(...args: any[]): void;
|
||||
error(...args: any[]): void;
|
||||
fatal(...args: any[]): void;
|
||||
success(...args: any[]): void;
|
||||
level: LogLevel;
|
||||
}
|
||||
|
||||
export interface BaseEntrypointOptions {
|
||||
include?: TargetBrowser[];
|
||||
exclude?: TargetBrowser[];
|
||||
}
|
||||
|
||||
export interface BaseEntrypoint {
|
||||
/**
|
||||
* The entrypoint's name. This is the filename or dirname without the type suffix.
|
||||
*
|
||||
* Examples:
|
||||
* - `popup.html` → `popup`
|
||||
* - `options/index.html` → `options`
|
||||
* - `named.sandbox.html` → `named`
|
||||
* - `named.sandbox/index.html` → `named`
|
||||
* - `sandbox.html` → `sandbox`
|
||||
* - `sandbox/index.html` → `sandbox`
|
||||
* - `overlay.content.ts` → `overlay`
|
||||
* - `overlay.content/index.ts` → `overlay`
|
||||
*
|
||||
* The name is used when generating an output file:
|
||||
* `<entrypoint.outputDir>/<entrypoint.name>.<ext>`
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Absolute path to the entrypoint's input file.
|
||||
*/
|
||||
inputPath: string;
|
||||
/**
|
||||
* Absolute path to the entrypoint's output directory. Can be the`InternalConfg.outDir` or a
|
||||
* subdirectory of it.
|
||||
*/
|
||||
outputDir: string;
|
||||
options: BaseEntrypointOptions;
|
||||
skipped: boolean;
|
||||
}
|
||||
|
||||
export interface GenericEntrypoint extends BaseEntrypoint {
|
||||
type:
|
||||
| 'sandbox'
|
||||
| 'bookmarks'
|
||||
| 'history'
|
||||
| 'newtab'
|
||||
| 'sidepanel'
|
||||
| 'devtools'
|
||||
| 'unlisted-page'
|
||||
| 'unlisted-script'
|
||||
| 'unlisted-style'
|
||||
| 'content-script-style';
|
||||
}
|
||||
|
||||
export interface BackgroundEntrypoint extends BaseEntrypoint {
|
||||
type: 'background';
|
||||
options: {
|
||||
persistent?: boolean;
|
||||
type?: 'module';
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface ContentScriptEntrypoint extends BaseEntrypoint {
|
||||
type: 'content-script';
|
||||
options: Omit<ContentScriptDefinition, 'main'> & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface PopupEntrypoint extends BaseEntrypoint {
|
||||
type: 'popup';
|
||||
options: {
|
||||
/**
|
||||
* Defaults to "browser_action" to be equivalent to MV3's "action" key
|
||||
*/
|
||||
mv2Key?: 'browser_action' | 'page_action';
|
||||
defaultIcon?: Record<string, string>;
|
||||
defaultTitle?: string;
|
||||
browserStyle?: boolean;
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export interface OptionsEntrypoint extends BaseEntrypoint {
|
||||
type: 'options';
|
||||
options: {
|
||||
openInTab?: boolean;
|
||||
browserStyle?: boolean;
|
||||
chromeStyle?: boolean;
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
export type Entrypoint =
|
||||
| GenericEntrypoint
|
||||
| BackgroundEntrypoint
|
||||
| ContentScriptEntrypoint
|
||||
| PopupEntrypoint
|
||||
| OptionsEntrypoint;
|
||||
|
||||
export type EntrypointGroup = Entrypoint | Entrypoint[];
|
||||
|
||||
export type OnContentScriptStopped = (cb: () => void) => void;
|
||||
|
||||
export type ContentScriptDefinition =
|
||||
| ContentScriptIsolatedWorldDefinition
|
||||
| ContentScriptMainWorldDefinition;
|
||||
|
||||
export interface ContentScriptIsolatedWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
* @default "ISOLATED"
|
||||
*/
|
||||
world?: 'ISOLATED';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(ctx: ContentScriptContext): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptMainWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
*/
|
||||
world: 'MAIN';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptBaseDefinition extends ExcludableEntrypoint {
|
||||
matches: PerBrowserOption<Manifest.ContentScript['matches']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default "documentIdle"
|
||||
*/
|
||||
runAt?: PerBrowserOption<Manifest.ContentScript['run_at']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
matchAboutBlank?: PerBrowserOption<
|
||||
Manifest.ContentScript['match_about_blank']
|
||||
>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
excludeMatches?: PerBrowserOption<Manifest.ContentScript['exclude_matches']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
includeGlobs?: PerBrowserOption<Manifest.ContentScript['include_globs']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default []
|
||||
*/
|
||||
excludeGlobs?: PerBrowserOption<Manifest.ContentScript['exclude_globs']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
allFrames?: PerBrowserOption<Manifest.ContentScript['all_frames']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default false
|
||||
*/
|
||||
matchOriginAsFallback?: PerBrowserOption<boolean>;
|
||||
/**
|
||||
* Customize how imported/generated styles are injected with the content script. Regardless of the
|
||||
* mode selected, CSS will always be built and included in the output directory.
|
||||
*
|
||||
* - `"manifest"` - Include the CSS in the manifest, under the content script's `css` array.
|
||||
* - `"manual"` - Exclude the CSS from the manifest. You are responsible for manually loading it
|
||||
* onto the page. Use `browser.runtime.getURL("content-scripts/<name>.css")` to get the file's
|
||||
* URL
|
||||
* - `"ui"` - Exclude the CSS from the manifest. CSS will be automatically added to your UI when
|
||||
* calling `createContentScriptUi`
|
||||
*
|
||||
* @default "manifest"
|
||||
*/
|
||||
cssInjectionMode?: PerBrowserOption<'manifest' | 'manual' | 'ui'>;
|
||||
}
|
||||
|
||||
export interface BackgroundDefinition extends ExcludableEntrypoint {
|
||||
type?: PerBrowserOption<'module'>;
|
||||
persistent?: PerBrowserOption<boolean>;
|
||||
main(): void;
|
||||
}
|
||||
|
||||
export interface UnlistedScriptDefinition extends ExcludableEntrypoint {
|
||||
/**
|
||||
* Main function executed when the unlisted script is ran.
|
||||
*/
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export type PerBrowserOption<T> = T | { [browser: TargetBrowser]: T };
|
||||
|
||||
export interface ExcludableEntrypoint {
|
||||
/**
|
||||
* List of target browsers to include this entrypoint in. Defaults to being included in all
|
||||
* builds. Cannot be used with `exclude`. You must choose one of the two options.
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
include?: TargetBrowser[];
|
||||
/**
|
||||
* List of target browsers to exclude this entrypoint from. Cannot be used with `include`. You
|
||||
* must choose one of the two options.
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
exclude?: TargetBrowser[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Manifest customization available in the `wxt.config.ts` file. You cannot configure entrypoints
|
||||
* here, they are configured inline.
|
||||
*/
|
||||
export type UserManifest = Partial<
|
||||
Omit<
|
||||
Manifest.WebExtensionManifest,
|
||||
| 'background'
|
||||
| 'chrome_url_overrides'
|
||||
| 'devtools_page'
|
||||
| 'manifest_version'
|
||||
| 'options_page'
|
||||
| 'options_ui'
|
||||
| 'sandbox'
|
||||
| 'sidepanel'
|
||||
| 'sidebar_action'
|
||||
>
|
||||
>;
|
||||
|
||||
export type UserManifestFn = (
|
||||
env: ConfigEnv,
|
||||
) => UserManifest | Promise<UserManifest>;
|
||||
|
||||
export interface ConfigEnv {
|
||||
mode: string;
|
||||
command: 'build' | 'serve';
|
||||
/**
|
||||
* Browser passed in from the CLI
|
||||
*/
|
||||
browser: TargetBrowser;
|
||||
/**
|
||||
* Manifest version passed in from the CLI
|
||||
*/
|
||||
manifestVersion: 2 | 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how the browser starts up.
|
||||
*/
|
||||
export interface ExtensionRunnerConfig {
|
||||
/**
|
||||
* Whether or not to open the browser with the extension installed in dev mode.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#browser-console
|
||||
*/
|
||||
openConsole?: boolean;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#devtools
|
||||
*/
|
||||
openDevtools?: boolean;
|
||||
/**
|
||||
* List of browser names and the binary that should be used to open the browser.
|
||||
*
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#chromium-binary
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#firefox
|
||||
*/
|
||||
binaries?: Record<string, string>;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#firefox-profile
|
||||
*/
|
||||
firefoxProfile?: string;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#chromium-profile
|
||||
*/
|
||||
chromiumProfile?: string;
|
||||
/**
|
||||
* An map of chrome preferences from https://chromium.googlesource.com/chromium/src/+/main/chrome/common/pref_names.h
|
||||
*
|
||||
* @example
|
||||
* // change your downloads directory
|
||||
* {
|
||||
* download: {
|
||||
* default_directory: "/my/custom/dir",
|
||||
* },
|
||||
* }
|
||||
*
|
||||
* @default
|
||||
* // Enable dev mode and allow content script sourcemaps
|
||||
* {
|
||||
* devtools: {
|
||||
* synced_preferences_sync_disabled: {
|
||||
* skipContentScripts: false,
|
||||
* },
|
||||
* }
|
||||
* extensions: {
|
||||
* ui: {
|
||||
* developer_mode: true,
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
chromiumPref?: string;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#pref
|
||||
*/
|
||||
firefoxPrefs?: Record<string, string>;
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#args
|
||||
*/
|
||||
firefoxArgs?: string[];
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#args
|
||||
*/
|
||||
chromiumArgs?: string[];
|
||||
/**
|
||||
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#start-url
|
||||
*/
|
||||
startUrls?: string[];
|
||||
}
|
||||
|
||||
export interface WxtBuilder {
|
||||
/**
|
||||
* Name of tool used to build. Ex: "Vite" or "Webpack".
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Version of tool used to build. Ex: "5.0.2"
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Build a single entrypoint group. This is effectively one of the multiple "steps" during the
|
||||
* build process.
|
||||
*/
|
||||
build(group: EntrypointGroup): Promise<BuildStepOutput>;
|
||||
/**
|
||||
* Start a dev server at the provided port.
|
||||
*/
|
||||
createServer(info: ServerInfo): Promise<WxtBuilderServer>;
|
||||
}
|
||||
|
||||
export interface WxtBuilderServer {
|
||||
/**
|
||||
* Start the server.
|
||||
*/
|
||||
listen(): Promise<void>;
|
||||
/**
|
||||
* Stop the server.
|
||||
*/
|
||||
close(): Promise<void>;
|
||||
/**
|
||||
* Transform the HTML for dev mode.
|
||||
*/
|
||||
transformHtml(
|
||||
url: string,
|
||||
html: string,
|
||||
originalUrl?: string | undefined,
|
||||
): Promise<string>;
|
||||
/**
|
||||
* The web socket server used to communicate with the extension.
|
||||
*/
|
||||
ws: {
|
||||
/**
|
||||
* Send a message via the server's websocket, with an optional payload.
|
||||
*
|
||||
* @example
|
||||
* ws.send("wxt:reload-extension");
|
||||
* ws.send("wxt:reload-content-script", { ... });
|
||||
*/
|
||||
send(message: string, payload?: any): void;
|
||||
/**
|
||||
* Listen for messages over the server's websocket.
|
||||
*/
|
||||
on(message: string, cb: (payload: any) => void): void;
|
||||
};
|
||||
/**
|
||||
* Chokidar file watcher instance.
|
||||
*/
|
||||
watcher: FSWatcher;
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
/**
|
||||
* Ex: `3000`
|
||||
*/
|
||||
port: number;
|
||||
/**
|
||||
* Ex: `"localhost"`
|
||||
*/
|
||||
hostname: string;
|
||||
/**
|
||||
* Ex: `"http://localhost:3000"`
|
||||
*/
|
||||
origin: string;
|
||||
}
|
||||
|
||||
export type HookResult = Promise<void> | void;
|
||||
|
||||
export interface WxtHooks {
|
||||
/**
|
||||
* Called after WXT initialization, when the WXT instance is ready to work.
|
||||
* @param wxt The configured WXT object
|
||||
* @returns Promise
|
||||
*/
|
||||
ready: (wxt: Wxt) => HookResult;
|
||||
/**
|
||||
* Called before the build is started in both dev mode and build mode.
|
||||
*
|
||||
* @param wxt The configured WXT object
|
||||
*/
|
||||
'build:before': (wxt: Wxt) => HookResult;
|
||||
/**
|
||||
* Called once the build process has finished.
|
||||
* @param wxt The configured WXT object
|
||||
* @param output The results of the build
|
||||
*/
|
||||
'build:done': (wxt: Wxt, output: Readonly<BuildOutput>) => HookResult;
|
||||
/**
|
||||
* Called once the manifest has been generated. Used to transform the manifest by reference before
|
||||
* it is written to the output directory.
|
||||
* @param wxt The configured WXT object
|
||||
* @param manifest The manifest that was generated
|
||||
*/
|
||||
'build:manifestGenerated': (
|
||||
wxt: Wxt,
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
) => HookResult;
|
||||
/**
|
||||
* Called once all entrypoints have been loaded from the `entrypointsDir`.
|
||||
* @param wxt The configured WXT object
|
||||
* @param entrypoints The list of entrypoints to be built
|
||||
*/
|
||||
'entrypoints:resolved': (wxt: Wxt, entrypoints: Entrypoint[]) => HookResult;
|
||||
/**
|
||||
* Called once all entrypoints have been grouped into their build groups.
|
||||
* @param wxt The configured WXT object
|
||||
* @param entrypoints The list of groups to build in each build step
|
||||
*/
|
||||
'entrypoints:grouped': (wxt: Wxt, groups: EntrypointGroup[]) => HookResult;
|
||||
}
|
||||
|
||||
export interface Wxt {
|
||||
config: ResolvedConfig;
|
||||
hooks: Hookable<WxtHooks>;
|
||||
/**
|
||||
* Alias for config.logger
|
||||
*/
|
||||
logger: Logger;
|
||||
/**
|
||||
* Reload config file and update the `config` field with the result.
|
||||
*/
|
||||
reloadConfig: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ResolvedConfig {
|
||||
root: string;
|
||||
srcDir: string;
|
||||
publicDir: string;
|
||||
wxtDir: string;
|
||||
typesDir: string;
|
||||
entrypointsDir: string;
|
||||
filterEntrypoints?: Set<string>;
|
||||
outBaseDir: string;
|
||||
outDir: string;
|
||||
debug: boolean;
|
||||
mode: string;
|
||||
command: 'build' | 'serve';
|
||||
browser: TargetBrowser;
|
||||
manifestVersion: TargetManifestVersion;
|
||||
env: ConfigEnv;
|
||||
logger: Logger;
|
||||
imports: false | Partial<UnimportOptions>;
|
||||
manifest: UserManifest;
|
||||
fsCache: FsCache;
|
||||
server?: WxtDevServer;
|
||||
runnerConfig: C12ResolvedConfig<ExtensionRunnerConfig>;
|
||||
zip: {
|
||||
name?: string;
|
||||
artifactTemplate: string;
|
||||
sourcesTemplate: string;
|
||||
includeSources: string[];
|
||||
excludeSources: string[];
|
||||
sourcesRoot: string;
|
||||
};
|
||||
transformManifest: (manifest: Manifest.WebExtensionManifest) => void;
|
||||
analysis: {
|
||||
enabled: boolean;
|
||||
template: NonNullable<PluginVisualizerOptions['template']>;
|
||||
/** Absolute file path to the `stats.html` file */
|
||||
outputFile: string;
|
||||
/** The directory where the final `stats.html` file is located */
|
||||
outputDir: string;
|
||||
/** Name of the `stats.html` file, minus ".html" */
|
||||
outputName: string;
|
||||
keepArtifacts: boolean;
|
||||
};
|
||||
userConfigMetadata: Omit<C12ResolvedConfig<UserConfig>, 'config'>;
|
||||
/**
|
||||
* Import aliases to absolute paths.
|
||||
*/
|
||||
alias: Record<string, string>;
|
||||
experimental: {
|
||||
includeBrowserPolyfill: boolean;
|
||||
};
|
||||
builder: WxtBuilder;
|
||||
dev: {
|
||||
reloadCommand: string | false;
|
||||
};
|
||||
hooks: Partial<WxtHooks>;
|
||||
}
|
||||
|
||||
export interface FsCache {
|
||||
set(key: string, value: string): Promise<void>;
|
||||
get(key: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export interface ExtensionRunner {
|
||||
openBrowser(): Promise<void>;
|
||||
closeBrowser(): Promise<void>;
|
||||
}
|
||||
|
||||
export type VirtualEntrypointType =
|
||||
| 'content-script-main-world'
|
||||
| 'content-script-isolated-world'
|
||||
| 'background'
|
||||
| 'unlisted-script';
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import {
|
||||
WxtDevServer,
|
||||
Logger,
|
||||
TargetBrowser,
|
||||
TargetManifestVersion,
|
||||
UserManifest,
|
||||
ExtensionRunnerConfig,
|
||||
ConfigEnv,
|
||||
UserConfig,
|
||||
WxtBuilder,
|
||||
} from './external';
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { ResolvedConfig } from 'c12';
|
||||
import type { Manifest } from '~/browser';
|
||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||
|
||||
export interface InternalConfig {
|
||||
root: string;
|
||||
srcDir: string;
|
||||
publicDir: string;
|
||||
wxtDir: string;
|
||||
typesDir: string;
|
||||
entrypointsDir: string;
|
||||
filterEntrypoints?: Set<string>;
|
||||
outBaseDir: string;
|
||||
outDir: string;
|
||||
debug: boolean;
|
||||
mode: string;
|
||||
command: 'build' | 'serve';
|
||||
browser: TargetBrowser;
|
||||
manifestVersion: TargetManifestVersion;
|
||||
env: ConfigEnv;
|
||||
logger: Logger;
|
||||
imports: false | Partial<UnimportOptions>;
|
||||
manifest: UserManifest;
|
||||
fsCache: FsCache;
|
||||
server?: WxtDevServer;
|
||||
runnerConfig: ResolvedConfig<ExtensionRunnerConfig>;
|
||||
zip: {
|
||||
name?: string;
|
||||
artifactTemplate: string;
|
||||
sourcesTemplate: string;
|
||||
includeSources: string[];
|
||||
excludeSources: string[];
|
||||
sourcesRoot: string;
|
||||
};
|
||||
transformManifest: (manifest: Manifest.WebExtensionManifest) => void;
|
||||
analysis: {
|
||||
enabled: boolean;
|
||||
template: NonNullable<PluginVisualizerOptions['template']>;
|
||||
};
|
||||
userConfigMetadata: Omit<ResolvedConfig<UserConfig>, 'config'>;
|
||||
/**
|
||||
* Import aliases to absolute paths.
|
||||
*/
|
||||
alias: Record<string, string>;
|
||||
experimental: {
|
||||
includeBrowserPolyfill: boolean;
|
||||
};
|
||||
builder: WxtBuilder;
|
||||
dev: {
|
||||
reloadCommand: string | false;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FsCache {
|
||||
set(key: string, value: string): Promise<void>;
|
||||
get(key: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export interface ExtensionRunner {
|
||||
openBrowser(config: InternalConfig): Promise<void>;
|
||||
closeBrowser(): Promise<void>;
|
||||
}
|
||||
|
||||
export type VirtualEntrypointType =
|
||||
| 'content-script-main-world'
|
||||
| 'content-script-isolated-world'
|
||||
| 'background'
|
||||
| 'unlisted-script';
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"typescript": "^5.3.3",
|
||||
"wxt": "^0.15.0"
|
||||
"wxt": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.3",
|
||||
"vite-plugin-solid": "^2.8.0",
|
||||
"wxt": "^0.15.0"
|
||||
"wxt": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"svelte-check": "^3.6.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"wxt": "^0.15.0"
|
||||
"wxt": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.3",
|
||||
"wxt": "^0.15.0"
|
||||
"wxt": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
"@vitejs/plugin-vue": "^5.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vue-tsc": "^1.8.27",
|
||||
"wxt": "^0.15.0"
|
||||
"wxt": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user