Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3abb32cb4 | |||
| 223ede9ff9 | |||
| 9d31a4eba1 | |||
| ea0e40c269 | |||
| bc24ea45a4 | |||
| 86d0c9b397 | |||
| c275ccf0da | |||
| 447a48f306 | |||
| 70b5cbc8db | |||
| 3459de0375 | |||
| c6817479bd | |||
| f02aae21f6 | |||
| 87a62a1791 | |||
| a0d1643021 | |||
| 856a946fa7 | |||
| 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 |
@@ -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
|
||||
|
||||
+153
@@ -1,5 +1,158 @@
|
||||
# Changelog
|
||||
|
||||
## v0.16.9
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.8...v0.16.9)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Support setting side panel options in HTML file ([#468](https://github.com/wxt-dev/wxt/pull/468))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fix order of ShadowRootUI hooks calling ([#459](https://github.com/wxt-dev/wxt/pull/459))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add wrapper div to react's `createShadowRootUi` example ([bc24ea4](https://github.com/wxt-dev/wxt/commit/bc24ea4))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Simplify entrypoint types ([#464](https://github.com/wxt-dev/wxt/pull/464))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Okou
|
||||
|
||||
## v0.16.8
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.7...v0.16.8)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Watch files outside project root during development ([#454](https://github.com/wxt-dev/wxt/pull/454))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add loading and error states for "Who's using WXT" section ([447a48f](https://github.com/wxt-dev/wxt/commit/447a48f))
|
||||
|
||||
## v0.16.7
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.6...v0.16.7)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Generate ESLint globals file for auto-imports ([#450](https://github.com/wxt-dev/wxt/pull/450))
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- Upgrade Vite to 5.1 ([#452](https://github.com/wxt-dev/wxt/pull/452))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add section about dev mode differences ([a0d1643](https://github.com/wxt-dev/wxt/commit/a0d1643))
|
||||
- Remove anchor from content script ui examples ([87a62a1](https://github.com/wxt-dev/wxt/commit/87a62a1))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **e2e:** Use `wxt prepare` instead of `wxt build` when possible to speed up E2E tests ([#451](https://github.com/wxt-dev/wxt/pull/451))
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -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
-1
@@ -19,7 +19,7 @@
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.34",
|
||||
|
||||
@@ -7,6 +7,7 @@ export default defineBackground({
|
||||
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,
|
||||
|
||||
@@ -20,11 +20,12 @@ const chromeExtensionIds = [
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
];
|
||||
|
||||
const { data } = useListExtensionDetails(chromeExtensionIds);
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
const sortedExtensions = computed(() => {
|
||||
if (!data.value?.length) return [];
|
||||
|
||||
return [...data.value]
|
||||
.filter((item) => item != null)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
// Sort based on the user count weighted by the rating
|
||||
@@ -49,7 +50,16 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
Battle tested and ready for production. Explore chrome extensions made
|
||||
with WXT.
|
||||
</p>
|
||||
<ul>
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">
|
||||
Loading...
|
||||
</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
|
||||
@@ -26,6 +26,7 @@ const query = `query ${operationName}($ids:[String!]!) {
|
||||
export default function (ids: string[]) {
|
||||
const data = ref<ChromeExtension[]>();
|
||||
const err = ref<unknown>();
|
||||
const isLoading = ref(true);
|
||||
|
||||
fetch('https://queue.wxt.dev/api', {
|
||||
method: 'POST',
|
||||
@@ -36,6 +37,7 @@ export default function (ids: string[]) {
|
||||
}),
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
const {
|
||||
data: { chromeExtensions },
|
||||
} = await res.json();
|
||||
@@ -43,6 +45,7 @@ export default function (ids: string[]) {
|
||||
err.value = undefined;
|
||||
})
|
||||
.catch((error) => {
|
||||
isLoading.value = false;
|
||||
console.error(error);
|
||||
data.value = undefined;
|
||||
err.value = error;
|
||||
@@ -51,5 +54,6 @@ export default function (ids: string[]) {
|
||||
return {
|
||||
data,
|
||||
err,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars)
|
||||
|
||||
In Chrome, side panels use the "side_panel" API, while Firefox uses the "sidebar_action" API.
|
||||
|
||||
:::warning
|
||||
Chrome added support for sidepanels in Manifest V3, they are not available in Manfiest V2.
|
||||
:::
|
||||
@@ -25,7 +27,17 @@ Chrome added support for sidepanels in Manifest V3, they are not available in Ma
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<title>Default Side Panel Title</title>
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
<meta name="manifest.open_at_install" content="true|false" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
|
||||
@@ -26,7 +26,6 @@ export default defineContentScript({
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Append children to the container
|
||||
const app = document.createElement('p');
|
||||
@@ -52,7 +51,6 @@ export default defineContentScript({
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Create the app and mount it to the UI container
|
||||
const app = createApp(App);
|
||||
@@ -82,7 +80,6 @@ export default defineContentScript({
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Create a root on the UI container and render a component
|
||||
const root = ReactDOM.createRoot(container);
|
||||
@@ -111,7 +108,6 @@ export default defineContentScript({
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
@@ -141,7 +137,6 @@ export default defineContentScript({
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Render your app to the UI container
|
||||
const unmount = render(() => <div>...</div>, container);
|
||||
@@ -162,7 +157,7 @@ export default defineContentScript({
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/wxt/interfaces/ContentScriptBaseDefinition#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS.
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS.
|
||||
|
||||
## Shadow Root
|
||||
|
||||
@@ -173,7 +168,7 @@ WXT's [`createShadowRootUi`](/api/wxt/client/functions/createShadowRootUi) abstr
|
||||
To use `createShadowRootUi`, follow these steps:
|
||||
|
||||
1. Import your CSS file at the top of your content script
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/wxt/interfaces/ContentScriptBaseDefinition#cssinjectionmode) inside `defineContentScript`
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) inside `defineContentScript`
|
||||
3. Define your UI with `createShadowRootUi()`
|
||||
4. Mount the UI so it is visible to users
|
||||
|
||||
@@ -192,7 +187,6 @@ export default defineContentScript({
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
anchor: '#anchor',
|
||||
position: 'inline',
|
||||
onMount(container) {
|
||||
// Define how your UI will be mounted inside the container
|
||||
@@ -223,7 +217,6 @@ export default defineContentScript({
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
anchor: '#anchor',
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Define how your UI will be mounted inside the container
|
||||
@@ -233,7 +226,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Unmount the app when the UI is removed
|
||||
app.unmount();
|
||||
app?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -259,16 +252,19 @@ export default defineContentScript({
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Container is a body, and React warns when creating a root on the body, so create a wrapper div
|
||||
const app = document.createElement('div');
|
||||
container.append(app);
|
||||
|
||||
// Create a root on the UI container and render a component
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.createRoot(app);
|
||||
root.render(<App />);
|
||||
return root;
|
||||
},
|
||||
onRemove: (root) => {
|
||||
// Unmount the root when the UI is removed
|
||||
root.unmount();
|
||||
root?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -293,7 +289,6 @@ export default defineContentScript({
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
@@ -303,7 +298,7 @@ export default defineContentScript({
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app.$destroy();
|
||||
app?.$destroy();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -328,14 +323,13 @@ export default defineContentScript({
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
anchor: '#anchor',
|
||||
onMount: (container) => {
|
||||
// Render your app to the UI container
|
||||
const unmount = render(() => <div>...</div>, container);
|
||||
},
|
||||
onRemove: (unmount) => {
|
||||
// Unmount the app when the UI is removed
|
||||
unmount();
|
||||
unmount?.();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -398,7 +392,6 @@ WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/cre
|
||||
// Define the UI
|
||||
const ui = await createIframeUi(ctx, {
|
||||
page: '/example-iframe.html',
|
||||
anchor: '#anchor',
|
||||
position: 'inline',
|
||||
onMount: (wrapper, iframe) => {
|
||||
// Add styles to the iframe like width
|
||||
|
||||
@@ -13,6 +13,24 @@ WXT's main goal is providing the best DX it possibly can. When running your exte
|
||||
| `wxt.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
|
||||
| `web-ext.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
|
||||
|
||||
## Dev Mode vs Production Builds
|
||||
|
||||
There are some notible differences between the development and production versions of an extension. During development:
|
||||
|
||||
1. **Content scripts are not listed in the `manifest.json`** when targetting MV3. Instead, the [`scripting`](https://developer.chrome.com/docs/extensions/reference/api/scripting) permission is used to register content scripts at runtime so they can be reloaded individually.
|
||||
|
||||
To get the list of content scripts during development, run the following in the background's console:
|
||||
|
||||
```ts
|
||||
await chrome.scripting.getRegisteredContentScripts();
|
||||
```
|
||||
|
||||
2. **The CSP is modified to allow loading scripts from the dev server**. Make sure you're using Chrome v110 or above for HMR to work.
|
||||
|
||||
3. If you don't include a background script/service worker, one will be created to perform various tasks in dev mode, mostly related to reloading different parts of the extension on change.
|
||||
|
||||
For production builds, none of the above modifications will be applied, and you're extension/manifest will only include what you have defined.
|
||||
|
||||
## Configure Browser Startup
|
||||
|
||||
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
|
||||
@@ -83,6 +101,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`.
|
||||
:::
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-9
@@ -6,7 +6,7 @@ title: Next-gen Web Extension Framework
|
||||
hero:
|
||||
name: WXT
|
||||
text: Next-gen Web Extension Framework
|
||||
tagline: An open source tool that makes Chrome Extension devlopment faster than ever before.
|
||||
tagline: An open source tool that makes Chrome Extension development faster than ever before.
|
||||
image:
|
||||
src: /hero-logo.svg
|
||||
alt: WXT
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe('Auto Imports', () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
expect(await project.serializeFile('.wxt/types/imports.d.ts'))
|
||||
.toMatchInlineSnapshot(`
|
||||
@@ -38,7 +38,7 @@ describe('Auto Imports', () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
expect(await project.serializeFile('.wxt/wxt.d.ts'))
|
||||
.toMatchInlineSnapshot(`
|
||||
@@ -63,7 +63,7 @@ describe('Auto Imports', () => {
|
||||
});
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
expect(await project.fileExists('.wxt/types/imports.d.ts')).toBe(false);
|
||||
});
|
||||
@@ -75,7 +75,7 @@ describe('Auto Imports', () => {
|
||||
});
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
expect(
|
||||
await project.serializeFile('.wxt/wxt.d.ts'),
|
||||
@@ -93,4 +93,82 @@ describe('Auto Imports', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('eslintrc', () => {
|
||||
it('should output the globals list for ESLint to consume', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.prepare({
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.serializeFile('.wxt/eslintrc-auto-import.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".wxt/eslintrc-auto-import.json
|
||||
----------------------------------------
|
||||
{
|
||||
"globals": {
|
||||
"ContentScriptContext": true,
|
||||
"InvalidMatchPattern": true,
|
||||
"MatchPattern": true,
|
||||
"browser": true,
|
||||
"createIframeUi": true,
|
||||
"createIntegratedUi": true,
|
||||
"createShadowRootUi": true,
|
||||
"defineBackground": true,
|
||||
"defineConfig": true,
|
||||
"defineContentScript": true,
|
||||
"defineUnlistedScript": true,
|
||||
"fakeBrowser": true,
|
||||
"storage": true
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should allow customizing the output', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.prepare({
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: true,
|
||||
filePath: project.resolvePath('example.json'),
|
||||
globalsPropValue: 'readonly',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.serializeFile('example.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"example.json
|
||||
----------------------------------------
|
||||
{
|
||||
"globals": {
|
||||
"ContentScriptContext": "readonly",
|
||||
"InvalidMatchPattern": "readonly",
|
||||
"MatchPattern": "readonly",
|
||||
"browser": "readonly",
|
||||
"createIframeUi": "readonly",
|
||||
"createIntegratedUi": "readonly",
|
||||
"createShadowRootUi": "readonly",
|
||||
"defineBackground": "readonly",
|
||||
"defineConfig": "readonly",
|
||||
"defineContentScript": "readonly",
|
||||
"defineUnlistedScript": "readonly",
|
||||
"fakeBrowser": "readonly",
|
||||
"storage": "readonly"
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ describe('TypeScript Project', () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/types/globals.d.ts');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -38,7 +38,7 @@ describe('TypeScript Project', () => {
|
||||
project.addFile('entrypoints/options.html');
|
||||
project.addFile('entrypoints/sandbox.html');
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/types/paths.d.ts');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -103,7 +103,7 @@ describe('TypeScript Project', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/types/i18n.d.ts');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -225,7 +225,7 @@ describe('TypeScript Project', () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/wxt.d.ts');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -245,7 +245,7 @@ describe('TypeScript Project', () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/tsconfig.json');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -289,7 +289,7 @@ describe('TypeScript Project', () => {
|
||||
srcDir: 'src',
|
||||
});
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/tsconfig.json');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
@@ -337,7 +337,7 @@ describe('TypeScript Project', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await project.build();
|
||||
await project.prepare();
|
||||
|
||||
const output = await project.serializeFile('.wxt/tsconfig.json');
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
|
||||
+43
-11
@@ -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,15 +70,41 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `path.resolve` relative to the project's root directory.
|
||||
*/
|
||||
resolvePath(...path: string[]): string {
|
||||
return resolve(this.root, ...path);
|
||||
}
|
||||
|
||||
private async writeProjectToDisk() {
|
||||
if (this.config == null) this.setConfigFileConfig();
|
||||
|
||||
for (const file of this.files) {
|
||||
const [name, content] = file;
|
||||
const filePath = resolve(this.root, name);
|
||||
const filePath = this.resolvePath(name);
|
||||
const fileDir = dirname(filePath);
|
||||
await fs.ensureDir(fileDir);
|
||||
await fs.writeFile(filePath, content ?? '', 'utf-8');
|
||||
@@ -80,7 +113,6 @@ export class TestProject {
|
||||
await execaCommand('pnpm --ignore-workspace i --ignore-scripts', {
|
||||
cwd: this.root,
|
||||
});
|
||||
await build({ ...config, root: this.root });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,13 +147,13 @@ export class TestProject {
|
||||
ignoreContentsOfFilenames?: string[],
|
||||
): Promise<string> {
|
||||
const outputFiles = await glob('**/*', {
|
||||
cwd: resolve(this.root, dir),
|
||||
cwd: this.resolvePath(dir),
|
||||
ignore: ['**/node_modules', '**/.output'],
|
||||
});
|
||||
outputFiles.sort();
|
||||
const fileContents = [];
|
||||
for (const file of outputFiles) {
|
||||
const path = resolve(this.root, dir, file);
|
||||
const path = this.resolvePath(dir, file);
|
||||
const isContentIgnored = !!ignoreContentsOfFilenames?.find(
|
||||
(ignoredFile) => normalizePath(path).endsWith(ignoredFile),
|
||||
);
|
||||
@@ -136,7 +168,7 @@ export class TestProject {
|
||||
* the file contents.
|
||||
*/
|
||||
async serializeFile(path: string, ignoreContents?: boolean): Promise<string> {
|
||||
const absolutePath = resolve(this.root, path);
|
||||
const absolutePath = this.resolvePath(path);
|
||||
return [
|
||||
normalizePath(relative(this.root, absolutePath)),
|
||||
ignoreContents ? '<contents-ignored>' : await fs.readFile(absolutePath),
|
||||
@@ -144,12 +176,12 @@ export class TestProject {
|
||||
}
|
||||
|
||||
fileExists(path: string): Promise<boolean> {
|
||||
return fs.exists(resolve(this.root, path));
|
||||
return fs.exists(this.resolvePath(path));
|
||||
}
|
||||
|
||||
async getOutputManifest(
|
||||
path: string = '.output/chrome-mv3/manifest.json',
|
||||
): Promise<any> {
|
||||
return await fs.readJson(resolve(this.root, path));
|
||||
return await fs.readJson(this.resolvePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.16.1",
|
||||
"version": "0.16.9",
|
||||
"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,10 +132,10 @@
|
||||
"ora": "^7.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.1",
|
||||
"publish-browser-extension": "^2.1.2",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"unimport": "^3.4.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.1.3",
|
||||
"web-ext-run": "^0.2.0",
|
||||
"webextension-polyfill": "^0.10.0",
|
||||
"zip-dir": "^2.0.0"
|
||||
@@ -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",
|
||||
@@ -165,7 +165,7 @@
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.3.2",
|
||||
"vitepress": "1.0.0-rc.34",
|
||||
"vitest": "^1.1.0",
|
||||
"vitest": "^1.2.2",
|
||||
"vitest-mock-extended": "^1.3.1",
|
||||
"vue": "^3.3.10"
|
||||
},
|
||||
|
||||
Generated
+122
-128
@@ -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
|
||||
@@ -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,17 +96,17 @@ importers:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
publish-browser-extension:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
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
|
||||
vite:
|
||||
specifier: ^5.0.12
|
||||
version: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
|
||||
specifier: ^5.1.3
|
||||
version: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
|
||||
web-ext-run:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
@@ -140,13 +140,13 @@ importers:
|
||||
version: 2.4.9
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^1.0.1
|
||||
version: 1.2.2(vitest@1.1.0)
|
||||
version: 1.2.2(vitest@1.2.2)
|
||||
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.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)
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(@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.2.2)
|
||||
vue:
|
||||
specifier: ^3.3.10
|
||||
version: 3.3.10(typescript@5.3.2)
|
||||
version: 3.3.10(typescript@5.3.3)
|
||||
|
||||
demo:
|
||||
dependencies:
|
||||
@@ -208,8 +208,8 @@ importers:
|
||||
specifier: ^18.2.0
|
||||
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)
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.2.34
|
||||
@@ -1058,7 +1058,6 @@ packages:
|
||||
|
||||
/@types/estree@1.0.4:
|
||||
resolution: {integrity: sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==}
|
||||
dev: false
|
||||
|
||||
/@types/fs-extra@11.0.4:
|
||||
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
|
||||
@@ -1156,8 +1155,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:
|
||||
@@ -1168,18 +1167,18 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@vitejs/plugin-vue@5.0.2(vite@5.0.12)(vue@3.4.3):
|
||||
/@vitejs/plugin-vue@5.0.2(vite@5.1.3)(vue@3.4.3):
|
||||
resolution: {integrity: sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0
|
||||
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)
|
||||
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
/@vitest/coverage-v8@1.2.2(vitest@1.1.0):
|
||||
/@vitest/coverage-v8@1.2.2(vitest@1.2.2):
|
||||
resolution: {integrity: sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==}
|
||||
peerDependencies:
|
||||
vitest: ^1.0.0
|
||||
@@ -1197,41 +1196,42 @@ packages:
|
||||
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.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@vitest/expect@1.1.0:
|
||||
resolution: {integrity: sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w==}
|
||||
/@vitest/expect@1.2.2:
|
||||
resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==}
|
||||
dependencies:
|
||||
'@vitest/spy': 1.1.0
|
||||
'@vitest/utils': 1.1.0
|
||||
'@vitest/spy': 1.2.2
|
||||
'@vitest/utils': 1.2.2
|
||||
chai: 4.3.10
|
||||
|
||||
/@vitest/runner@1.1.0:
|
||||
resolution: {integrity: sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw==}
|
||||
/@vitest/runner@1.2.2:
|
||||
resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==}
|
||||
dependencies:
|
||||
'@vitest/utils': 1.1.0
|
||||
'@vitest/utils': 1.2.2
|
||||
p-limit: 5.0.0
|
||||
pathe: 1.1.1
|
||||
|
||||
/@vitest/snapshot@1.1.0:
|
||||
resolution: {integrity: sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ==}
|
||||
/@vitest/snapshot@1.2.2:
|
||||
resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==}
|
||||
dependencies:
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
pretty-format: 29.7.0
|
||||
|
||||
/@vitest/spy@1.1.0:
|
||||
resolution: {integrity: sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg==}
|
||||
/@vitest/spy@1.2.2:
|
||||
resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==}
|
||||
dependencies:
|
||||
tinyspy: 2.2.0
|
||||
|
||||
/@vitest/utils@1.1.0:
|
||||
resolution: {integrity: sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ==}
|
||||
/@vitest/utils@1.2.2:
|
||||
resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==}
|
||||
dependencies:
|
||||
diff-sequences: 29.6.3
|
||||
estree-walker: 3.0.3
|
||||
loupe: 2.3.7
|
||||
pretty-format: 29.7.0
|
||||
|
||||
@@ -1293,7 +1293,7 @@ packages:
|
||||
'@vue/shared': 3.4.3
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.5
|
||||
postcss: 8.4.32
|
||||
postcss: 8.4.35
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
@@ -1374,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):
|
||||
@@ -1384,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:
|
||||
@@ -1484,8 +1484,8 @@ packages:
|
||||
resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==}
|
||||
dev: false
|
||||
|
||||
/acorn-walk@8.3.0:
|
||||
resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==}
|
||||
/acorn-walk@8.3.2:
|
||||
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
/acorn@8.11.2:
|
||||
@@ -1744,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
|
||||
@@ -2053,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
|
||||
@@ -2159,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:
|
||||
@@ -2409,6 +2406,11 @@ packages:
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
/estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.4
|
||||
|
||||
/eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
@@ -2674,7 +2676,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
|
||||
@@ -2791,14 +2793,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:
|
||||
@@ -2852,6 +2852,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
|
||||
@@ -2908,12 +2912,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
|
||||
@@ -2934,10 +2932,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==}
|
||||
|
||||
@@ -4120,6 +4114,15 @@ packages:
|
||||
nanoid: 3.3.7
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/postcss@8.4.35:
|
||||
resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/preact@10.18.1:
|
||||
resolution: {integrity: sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==}
|
||||
@@ -4172,9 +4175,9 @@ packages:
|
||||
sade: 1.8.1
|
||||
dev: true
|
||||
|
||||
/publish-browser-extension@2.1.1:
|
||||
resolution: {integrity: sha512-WqCnhHXbmiYn4vIbgcyKWVHiOvCoPjjpt3X+hfnU8rlOozU43TURpHUWZlQ0W49LhvJvBRsBfGRQxtRMGhqsxw==}
|
||||
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
|
||||
@@ -4226,7 +4229,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
|
||||
@@ -4388,8 +4391,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:
|
||||
@@ -4460,9 +4463,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'}
|
||||
@@ -4899,8 +4899,8 @@ packages:
|
||||
/tinybench@2.5.1:
|
||||
resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
|
||||
|
||||
/tinypool@0.8.1:
|
||||
resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==}
|
||||
/tinypool@0.8.2:
|
||||
resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
/tinyspy@2.2.0:
|
||||
@@ -4941,12 +4941,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:
|
||||
@@ -4957,7 +4957,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
|
||||
@@ -4990,7 +4990,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
|
||||
@@ -5048,7 +5048,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):
|
||||
@@ -5059,7 +5059,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
|
||||
@@ -5070,11 +5070,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
|
||||
@@ -5192,8 +5192,8 @@ packages:
|
||||
spdx-expression-parse: 3.0.1
|
||||
dev: true
|
||||
|
||||
/vite-node@1.1.0(@types/node@20.10.3)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==}
|
||||
/vite-node@1.2.2(@types/node@20.10.3)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -5201,7 +5201,7 @@ packages:
|
||||
debug: 4.3.4
|
||||
pathe: 1.1.1
|
||||
picocolors: 1.0.0
|
||||
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
|
||||
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@@ -5212,8 +5212,8 @@ packages:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
/vite@5.0.12(@types/node@20.10.3)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
|
||||
/vite@5.1.3(@types/node@20.10.3)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -5242,13 +5242,13 @@ packages:
|
||||
dependencies:
|
||||
'@types/node': 20.10.3
|
||||
esbuild: 0.19.11
|
||||
postcss: 8.4.32
|
||||
postcss: 8.4.35
|
||||
rollup: 4.6.1
|
||||
sass: 1.69.5
|
||||
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:
|
||||
@@ -5263,7 +5263,7 @@ packages:
|
||||
'@docsearch/css': 3.5.2
|
||||
'@docsearch/js': 3.5.2
|
||||
'@types/markdown-it': 13.0.7
|
||||
'@vitejs/plugin-vue': 5.0.2(vite@5.0.12)(vue@3.4.3)
|
||||
'@vitejs/plugin-vue': 5.0.2(vite@5.1.3)(vue@3.4.3)
|
||||
'@vue/devtools-api': 6.5.1
|
||||
'@vueuse/core': 10.7.1(vue@3.4.3)
|
||||
'@vueuse/integrations': 10.7.1(focus-trap@7.5.4)(vue@3.4.3)
|
||||
@@ -5274,8 +5274,8 @@ packages:
|
||||
shikiji: 0.9.16
|
||||
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)
|
||||
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
|
||||
vue: 3.4.3(typescript@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- '@algolia/client-search'
|
||||
- '@types/node'
|
||||
@@ -5304,19 +5304,19 @@ 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.2.2):
|
||||
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.2.2(@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):
|
||||
resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==}
|
||||
/vitest@1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5):
|
||||
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -5341,17 +5341,17 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.10.3
|
||||
'@vitest/expect': 1.1.0
|
||||
'@vitest/runner': 1.1.0
|
||||
'@vitest/snapshot': 1.1.0
|
||||
'@vitest/spy': 1.1.0
|
||||
'@vitest/utils': 1.1.0
|
||||
acorn-walk: 8.3.0
|
||||
'@vitest/expect': 1.2.2
|
||||
'@vitest/runner': 1.2.2
|
||||
'@vitest/snapshot': 1.2.2
|
||||
'@vitest/spy': 1.2.2
|
||||
'@vitest/utils': 1.2.2
|
||||
acorn-walk: 8.3.2
|
||||
cac: 6.7.14
|
||||
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
|
||||
@@ -5359,9 +5359,9 @@ packages:
|
||||
std-env: 3.6.0
|
||||
strip-literal: 1.3.0
|
||||
tinybench: 2.5.1
|
||||
tinypool: 0.8.1
|
||||
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
|
||||
vite-node: 1.1.0(@types/node@20.10.3)(sass@1.69.5)
|
||||
tinypool: 0.8.2
|
||||
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
|
||||
vite-node: 1.2.2(@types/node@20.10.3)(sass@1.69.5)
|
||||
why-is-node-running: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
@@ -5392,10 +5392,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: '*'
|
||||
@@ -5408,10 +5408,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: '*'
|
||||
@@ -5424,7 +5424,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:
|
||||
@@ -5496,12 +5496,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',
|
||||
|
||||
@@ -119,19 +119,18 @@ export async function createShadowRootUi<TMounted>(
|
||||
let mounted: TMounted;
|
||||
|
||||
const mount = () => {
|
||||
// Mount UI inside shadow root
|
||||
mounted = options.onMount(uiContainer, shadow, shadowHost);
|
||||
|
||||
// Add shadow root element to DOM
|
||||
mountUi(shadowHost, options);
|
||||
applyPosition(shadowHost, shadow.querySelector('html'), options);
|
||||
// Mount UI inside shadow root
|
||||
mounted = options.onMount(uiContainer, shadow, shadowHost);
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
// Detatch shadow root from DOM
|
||||
shadowHost.remove();
|
||||
// Cleanup mounted state
|
||||
options.onRemove?.(mounted);
|
||||
// Detatch shadow root from DOM
|
||||
shadowHost.remove();
|
||||
// Remove children from uiContainer
|
||||
while (uiContainer.lastChild)
|
||||
uiContainer.removeChild(uiContainer.lastChild);
|
||||
@@ -211,20 +210,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;
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
import { createUnimport } from 'unimport';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
import { ResolvedConfig } from '~/types';
|
||||
import type * as vite from 'vite';
|
||||
import { extname } from 'path';
|
||||
|
||||
@@ -17,9 +16,9 @@ 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);
|
||||
const options = config.imports;
|
||||
if (options === false) return [];
|
||||
|
||||
const unimport = createUnimport(options);
|
||||
|
||||
@@ -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',
|
||||
|
||||
+67
-72
@@ -2,21 +2,16 @@ import {
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
ServerInfo,
|
||||
WxtDevServer,
|
||||
} from '~/types';
|
||||
import {
|
||||
getEntrypointBundlePath,
|
||||
resolvePerBrowserOption,
|
||||
} from '~/core/utils/entrypoints';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
import {
|
||||
getContentScriptCssFiles,
|
||||
getContentScriptsCssMap,
|
||||
} from '~/core/utils/manifest';
|
||||
import {
|
||||
internalBuild,
|
||||
getInternalConfig,
|
||||
detectDevChanges,
|
||||
rebuild,
|
||||
findEntrypoints,
|
||||
@@ -26,6 +21,9 @@ import { consola } from 'consola';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import pc from 'picocolors';
|
||||
import { relative } from 'node:path';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
import { unnormalizePath } from './utils/paths';
|
||||
import { mapWxtOptionsToRegisteredContentScript } from './utils/content-scripts';
|
||||
|
||||
/**
|
||||
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
|
||||
@@ -50,10 +48,18 @@ 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();
|
||||
|
||||
// Add file watchers for files not loaded by the dev server. See
|
||||
// https://github.com/wxt-dev/wxt/issues/428#issuecomment-1944731870
|
||||
try {
|
||||
server.watcher.add(getExternalOutputDependencies(server));
|
||||
} catch (err) {
|
||||
wxt.config.logger.warn('Failed to register additional file paths:', err);
|
||||
}
|
||||
|
||||
// Open browser after everything is ready to go.
|
||||
await runner.openBrowser(config);
|
||||
await runner.openBrowser();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,8 +67,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
|
||||
@@ -78,7 +84,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() {
|
||||
@@ -103,34 +109,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;
|
||||
@@ -145,21 +143,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 () => {
|
||||
@@ -170,36 +162,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(', ')}`,
|
||||
);
|
||||
|
||||
// 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,
|
||||
@@ -217,12 +204,11 @@ function createFileReloader(options: {
|
||||
const { reloadedNames } = reloadHtmlPages(
|
||||
changes.rebuildGroups,
|
||||
server,
|
||||
config,
|
||||
);
|
||||
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);
|
||||
@@ -236,38 +222,21 @@ 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,
|
||||
),
|
||||
excludeMatches: resolvePerBrowserOption(
|
||||
entry.options.excludeMatches,
|
||||
config.browser,
|
||||
),
|
||||
matches: resolvePerBrowserOption(entry.options.matches, config.browser),
|
||||
runAt: resolvePerBrowserOption(entry.options.runAt, 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),
|
||||
js,
|
||||
css,
|
||||
});
|
||||
server.reloadContentScript(
|
||||
mapWxtOptionsToRegisteredContentScript(entry.options, js, css),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
server.reloadExtension();
|
||||
@@ -277,7 +246,6 @@ function reloadContentScripts(
|
||||
function reloadHtmlPages(
|
||||
groups: EntrypointGroup[],
|
||||
server: WxtDevServer,
|
||||
config: InternalConfig,
|
||||
): { reloadedNames: string[] } {
|
||||
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
|
||||
const htmlEntries = groups
|
||||
@@ -285,7 +253,7 @@ function reloadHtmlPages(
|
||||
.filter((entry) => entry.inputPath.endsWith('.html'));
|
||||
|
||||
htmlEntries.forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(entry, config.outDir, '.html');
|
||||
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
|
||||
server.reloadPage(path);
|
||||
});
|
||||
|
||||
@@ -301,3 +269,30 @@ function getFilenameList(names: string[]): string {
|
||||
})
|
||||
.join(pc.dim(', '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the current build output, return a list of files that are:
|
||||
* 1. Not in node_modules
|
||||
* 2. Not inside project root
|
||||
*/
|
||||
function getExternalOutputDependencies(server: WxtDevServer) {
|
||||
return (
|
||||
server.currentOutput?.steps
|
||||
.flatMap((step, i) => {
|
||||
if (Array.isArray(step.entrypoints) && i === 0) {
|
||||
// Dev server is already watching all HTML/esm files
|
||||
return [];
|
||||
}
|
||||
|
||||
return step.chunks.flatMap((chunk) => {
|
||||
if (chunk.type === 'asset') return [];
|
||||
return chunk.moduleIds;
|
||||
});
|
||||
})
|
||||
.filter(
|
||||
(file) => !file.includes('node_modules') && !file.startsWith('\x00'),
|
||||
)
|
||||
.map(unnormalizePath)
|
||||
.filter((file) => !file.startsWith(wxt.config.root)) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
+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,15 @@
|
||||
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,
|
||||
fakeSidepanelEntrypoint,
|
||||
setFakeWxt,
|
||||
} from '../testing/fake-objects';
|
||||
import { Manifest } from 'webextension-polyfill';
|
||||
import {
|
||||
@@ -17,11 +18,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 +47,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 +65,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
@@ -74,9 +82,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 +97,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual[expectedType]).toEqual(expected);
|
||||
@@ -98,24 +107,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 +139,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 +156,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 +179,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[options],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.options_ui).toEqual(expected);
|
||||
@@ -189,10 +198,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 +214,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -211,10 +221,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 +237,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -236,10 +247,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 +263,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -258,10 +270,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 +286,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[background],
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.background).toEqual(expected);
|
||||
@@ -293,12 +306,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 +330,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 +355,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 +450,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 +471,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.content_scripts).toContainEqual({
|
||||
@@ -509,18 +520,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 +564,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 +610,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 +653,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 +696,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 +736,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([
|
||||
@@ -742,6 +759,72 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sidepanel', () => {
|
||||
it.each(['chrome', 'safari', 'edge'])(
|
||||
'should include a side_panel ignoring all options for %s',
|
||||
async (browser) => {
|
||||
const sidepanel = fakeSidepanelEntrypoint({
|
||||
outputDir: outDir,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
browser,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
side_panel: {
|
||||
default_path: 'sidepanel.html',
|
||||
},
|
||||
};
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[sidepanel],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(['firefox'])(
|
||||
'should include a sidebar_action for %s',
|
||||
async (browser) => {
|
||||
const sidepanel = fakeSidepanelEntrypoint({
|
||||
outputDir: outDir,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
browser,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
sidebar_action: {
|
||||
default_panel: 'sidepanel.html',
|
||||
open_at_install: sidepanel.options.openAtInstall,
|
||||
default_title: sidepanel.options.defaultTitle,
|
||||
default_icon: sidepanel.options.defaultIcon,
|
||||
browser_style: sidepanel.options.browserStyle,
|
||||
},
|
||||
};
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[sidepanel],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('web_accessible_resources', () => {
|
||||
it('should combine user defined resources and generated resources for MV3', async () => {
|
||||
const cs: ContentScriptEntrypoint = {
|
||||
@@ -765,21 +848,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 +897,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 +918,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 +974,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 +988,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
buildOutput,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
@@ -867,18 +1002,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 +1029,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 +1055,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 +1078,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 +1111,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 +1128,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 +1142,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual({
|
||||
@@ -1014,10 +1155,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 +1169,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toBeUndefined();
|
||||
@@ -1035,11 +1177,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 +1193,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual({
|
||||
@@ -1065,9 +1208,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 +1220,6 @@ describe('Manifest Utils', () => {
|
||||
const { manifest: actual, warnings } = await generateManifest(
|
||||
entrypoints,
|
||||
output,
|
||||
config,
|
||||
);
|
||||
|
||||
expect(actual.commands).toEqual(commands);
|
||||
@@ -1083,14 +1227,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,17 +1,23 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
BackgroundEntrypoint,
|
||||
BackgroundEntrypointOptions,
|
||||
BaseEntrypointOptions,
|
||||
ContentScriptEntrypoint,
|
||||
GenericEntrypoint,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
SidepanelEntrypoint,
|
||||
} from '~/types';
|
||||
import { resolve } from 'path';
|
||||
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,7 +32,7 @@ const readFileMock = vi.mocked(
|
||||
);
|
||||
|
||||
describe('findEntrypoints', () => {
|
||||
const config = fakeInternalConfig({
|
||||
const config = fakeResolvedConfig({
|
||||
manifestVersion: 3,
|
||||
root: '/',
|
||||
entrypointsDir: resolve('/src/entrypoints'),
|
||||
@@ -34,6 +40,10 @@ describe('findEntrypoints', () => {
|
||||
command: 'build',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setFakeWxt({ config });
|
||||
});
|
||||
|
||||
it.each<[string, string, PopupEntrypoint]>([
|
||||
[
|
||||
'popup.html',
|
||||
@@ -83,7 +93,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);
|
||||
@@ -136,7 +146,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);
|
||||
@@ -203,14 +213,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);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -238,56 +245,138 @@ describe('findEntrypoints', () => {
|
||||
])(
|
||||
'should find and load background entrypoint config from %s',
|
||||
async (path, expected) => {
|
||||
const options: BackgroundEntrypoint['options'] = {
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
type: 'module',
|
||||
};
|
||||
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.each<[string, string, SidepanelEntrypoint]>([
|
||||
[
|
||||
'sidepanel.html',
|
||||
`
|
||||
<html>
|
||||
<head>
|
||||
<title>Default Title</title>
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
|
||||
<meta name="manifest.open_at_install" content="true" />
|
||||
</head>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'sidepanel',
|
||||
inputPath: resolve(config.entrypointsDir, 'sidepanel.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {
|
||||
defaultTitle: 'Default Title',
|
||||
defaultIcon: { '16': '/icon/16.png' },
|
||||
openAtInstall: true,
|
||||
},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'sidepanel/index.html',
|
||||
`<html></html>`,
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'sidepanel',
|
||||
inputPath: resolve(config.entrypointsDir, 'sidepanel/index.html'),
|
||||
options: {},
|
||||
outputDir: config.outDir,
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'named.sidepanel.html',
|
||||
`<html></html>`,
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'named',
|
||||
inputPath: resolve(config.entrypointsDir, 'named.sidepanel.html'),
|
||||
options: {},
|
||||
outputDir: config.outDir,
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'named.sidepanel/index.html',
|
||||
`<html></html>`,
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'named',
|
||||
inputPath: resolve(config.entrypointsDir, 'named.sidepanel/index.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should find and load sidepanel entrypoint config from %s',
|
||||
async (path, content, expected) => {
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
readFileMock.mockResolvedValueOnce(content);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should remove type=module from MV2 background scripts', async () => {
|
||||
const config = fakeInternalConfig({ manifestVersion: 2 });
|
||||
const options: BackgroundEntrypoint['options'] = {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 2,
|
||||
},
|
||||
});
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
type: 'module',
|
||||
};
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints[0].options).toEqual({});
|
||||
});
|
||||
|
||||
it('should allow type=module for MV3 background service workers', async () => {
|
||||
const config = fakeInternalConfig({ manifestVersion: 3 });
|
||||
const options: BackgroundEntrypoint['options'] = {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
type: 'module',
|
||||
};
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointFileMock.mockResolvedValue(options);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
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({
|
||||
@@ -319,18 +408,15 @@ describe('findEntrypoints', () => {
|
||||
outputDir: config.outDir,
|
||||
skipped: false,
|
||||
};
|
||||
const options: GenericEntrypoint['options'] = {};
|
||||
const options: BaseEntrypointOptions = {};
|
||||
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);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -453,52 +539,6 @@ describe('findEntrypoints', () => {
|
||||
},
|
||||
],
|
||||
|
||||
// sidepanel
|
||||
[
|
||||
'sidepanel.html',
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'sidepanel',
|
||||
inputPath: resolve(config.entrypointsDir, 'sidepanel.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'sidepanel/index.html',
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'sidepanel',
|
||||
inputPath: resolve(config.entrypointsDir, 'sidepanel/index.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'named.sidepanel.html',
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'named',
|
||||
inputPath: resolve(config.entrypointsDir, 'named.sidepanel.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'named.sidepanel/index.html',
|
||||
{
|
||||
type: 'sidepanel',
|
||||
name: 'named',
|
||||
inputPath: resolve(config.entrypointsDir, 'named.sidepanel/index.html'),
|
||||
outputDir: config.outDir,
|
||||
options: {},
|
||||
skipped: false,
|
||||
},
|
||||
],
|
||||
|
||||
// devtools
|
||||
[
|
||||
'devtools.html',
|
||||
@@ -619,7 +659,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);
|
||||
@@ -635,7 +675,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.',
|
||||
'',
|
||||
@@ -653,7 +693,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)}`,
|
||||
);
|
||||
});
|
||||
@@ -665,7 +705,7 @@ describe('findEntrypoints', () => {
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -676,7 +716,7 @@ describe('findEntrypoints', () => {
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -693,7 +733,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -710,7 +750,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -727,7 +767,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -740,7 +780,7 @@ describe('findEntrypoints', () => {
|
||||
exclude: [config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -751,7 +791,7 @@ describe('findEntrypoints', () => {
|
||||
exclude: [config.browser],
|
||||
});
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -766,7 +806,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -781,7 +821,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -796,7 +836,7 @@ describe('findEntrypoints', () => {
|
||||
</html>`,
|
||||
);
|
||||
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toEqual([]);
|
||||
});
|
||||
@@ -812,15 +852,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,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,10 +7,13 @@ import {
|
||||
ContentScriptEntrypoint,
|
||||
Entrypoint,
|
||||
GenericEntrypoint,
|
||||
InternalConfig,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
UnlistedScriptDefinition,
|
||||
PopupEntrypointOptions,
|
||||
OptionsEntrypointOptions,
|
||||
SidepanelEntrypoint,
|
||||
SidepanelEntrypointOptions,
|
||||
} from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { minimatch } from 'minimatch';
|
||||
@@ -20,20 +23,19 @@ import { importEntrypointFile } from '~/core/utils/building';
|
||||
import glob from 'fast-glob';
|
||||
import {
|
||||
getEntrypointName,
|
||||
resolvePerBrowserOption,
|
||||
resolvePerBrowserOptions,
|
||||
} from '~/core/utils/entrypoints';
|
||||
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 +44,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 +56,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 +74,25 @@ export async function findEntrypoints(
|
||||
const { type } = info;
|
||||
switch (type) {
|
||||
case 'popup':
|
||||
return await getPopupEntrypoint(config, info);
|
||||
return await getPopupEntrypoint(info);
|
||||
case 'sidepanel':
|
||||
return await getSidepanelEntrypoint(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 +102,7 @@ export async function findEntrypoints(
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: config.outDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
@@ -108,9 +112,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 +123,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 +137,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 +154,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 +170,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 +184,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,194 +199,119 @@ 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getHtmlBaseOptions(document: Document): BaseEntrypointOptions {
|
||||
const options: BaseEntrypointOptions = {};
|
||||
|
||||
const includeContent = document
|
||||
.querySelector("meta[name='manifest.include']")
|
||||
?.getAttribute('content');
|
||||
if (includeContent) {
|
||||
options.include = JSON5.parse(includeContent);
|
||||
}
|
||||
|
||||
const excludeContent = document
|
||||
.querySelector("meta[name='manifest.exclude']")
|
||||
?.getAttribute('content');
|
||||
if (excludeContent) {
|
||||
options.exclude = JSON5.parse(excludeContent);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
info: EntrypointInfo,
|
||||
): Promise<PopupEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const options: PopupEntrypoint['options'] = getHtmlBaseOptions(document);
|
||||
|
||||
const title = document.querySelector('title');
|
||||
if (title != null) options.defaultTitle = title.textContent ?? undefined;
|
||||
|
||||
const defaultIconContent = document
|
||||
.querySelector("meta[name='manifest.default_icon']")
|
||||
?.getAttribute('content');
|
||||
if (defaultIconContent) {
|
||||
try {
|
||||
options.defaultIcon = JSON5.parse(defaultIconContent);
|
||||
} catch (err) {
|
||||
config.logger.fatal(
|
||||
`Failed to parse default_icon meta tag content as JSON5. content=${defaultIconContent}`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mv2TypeContent = document
|
||||
.querySelector("meta[name='manifest.type']")
|
||||
?.getAttribute('content');
|
||||
if (mv2TypeContent) {
|
||||
options.mv2Key =
|
||||
mv2TypeContent === 'page_action' ? 'page_action' : 'browser_action';
|
||||
}
|
||||
|
||||
const browserStyleContent = document
|
||||
.querySelector("meta[name='manifest.browser_style']")
|
||||
?.getAttribute('content');
|
||||
if (browserStyleContent) {
|
||||
options.browserStyle = browserStyleContent === 'true';
|
||||
}
|
||||
const options = await getHtmlEntrypointOptions<PopupEntrypointOptions>(
|
||||
info,
|
||||
{
|
||||
browserStyle: 'browse_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
defaultIcon: 'default_icon',
|
||||
defaultTitle: 'default_title',
|
||||
mv2Key: 'type',
|
||||
},
|
||||
{
|
||||
defaultTitle: (document) =>
|
||||
document.querySelector('title')?.textContent || undefined,
|
||||
},
|
||||
{
|
||||
defaultTitle: (content) => content,
|
||||
mv2Key: (content) =>
|
||||
content === 'page_action' ? 'page_action' : 'browser_action',
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'popup',
|
||||
name: 'popup',
|
||||
options,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
skipped,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped: info.skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
info: EntrypointInfo,
|
||||
): Promise<OptionsEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const options: OptionsEntrypoint['options'] = getHtmlBaseOptions(document);
|
||||
|
||||
const openInTabContent = document
|
||||
.querySelector("meta[name='manifest.open_in_tab']")
|
||||
?.getAttribute('content');
|
||||
if (openInTabContent) {
|
||||
options.openInTab = openInTabContent === 'true';
|
||||
}
|
||||
|
||||
const chromeStyleContent = document
|
||||
.querySelector("meta[name='manifest.chrome_style']")
|
||||
?.getAttribute('content');
|
||||
if (chromeStyleContent) {
|
||||
options.chromeStyle = chromeStyleContent === 'true';
|
||||
}
|
||||
|
||||
const browserStyleContent = document
|
||||
.querySelector("meta[name='manifest.browser_style']")
|
||||
?.getAttribute('content');
|
||||
if (browserStyleContent) {
|
||||
options.browserStyle = browserStyleContent === 'true';
|
||||
}
|
||||
|
||||
const options = await getHtmlEntrypointOptions<OptionsEntrypointOptions>(
|
||||
info,
|
||||
{
|
||||
browserStyle: 'browse_style',
|
||||
chromeStyle: 'chrome_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
openInTab: 'open_in_tab',
|
||||
},
|
||||
);
|
||||
return {
|
||||
type: 'options',
|
||||
name: 'options',
|
||||
options,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
skipped,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped: info.skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
info: EntrypointInfo,
|
||||
): Promise<GenericEntrypoint> {
|
||||
const content = await fs.readFile(inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
const options = await getHtmlEntrypointOptions<BaseEntrypointOptions>(info, {
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'unlisted-page',
|
||||
name: getEntrypointName(config.entrypointsDir, inputPath),
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
options: getHtmlBaseOptions(document),
|
||||
skipped,
|
||||
name: info.name,
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
options,
|
||||
skipped: info.skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(...)"?`,
|
||||
);
|
||||
}
|
||||
const { main: _, ...moduleOptions } = defaultExport;
|
||||
const options: Omit<UnlistedScriptDefinition, 'main'> = moduleOptions;
|
||||
const { main: _, ...options } = defaultExport;
|
||||
return {
|
||||
type: 'unlisted-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
options,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(...)"?`,
|
||||
@@ -393,7 +321,7 @@ async function getBackgroundEntrypoint(
|
||||
options = moduleOptions;
|
||||
}
|
||||
|
||||
if (config.manifestVersion !== 3) {
|
||||
if (wxt.config.manifestVersion !== 3) {
|
||||
delete options.type;
|
||||
}
|
||||
|
||||
@@ -401,25 +329,19 @@ async function getBackgroundEntrypoint(
|
||||
type: 'background',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: config.outDir,
|
||||
options: {
|
||||
...options,
|
||||
type: resolvePerBrowserOption(options.type, config.browser),
|
||||
persistent: resolvePerBrowserOption(options.persistent, config.browser),
|
||||
},
|
||||
outputDir: wxt.config.outDir,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(...)"?`,
|
||||
@@ -429,12 +351,88 @@ async function getContentScriptEntrypoint(
|
||||
type: 'content-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: resolve(config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options,
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
async function getSidepanelEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
): Promise<SidepanelEntrypoint> {
|
||||
const options = await getHtmlEntrypointOptions<SidepanelEntrypointOptions>(
|
||||
info,
|
||||
{
|
||||
browserStyle: 'browse_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
defaultIcon: 'default_icon',
|
||||
defaultTitle: 'default_title',
|
||||
openAtInstall: 'open_at_install',
|
||||
},
|
||||
{
|
||||
defaultTitle: (document) =>
|
||||
document.querySelector('title')?.textContent || undefined,
|
||||
},
|
||||
{
|
||||
defaultTitle: (content) => content,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'sidepanel',
|
||||
name: info.name,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped: info.skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the HTML tags to extract options from them.
|
||||
*/
|
||||
async function getHtmlEntrypointOptions<T extends BaseEntrypointOptions>(
|
||||
info: EntrypointInfo,
|
||||
keyMap: Record<keyof T, string>,
|
||||
queries?: Partial<{
|
||||
[key in keyof T]: (
|
||||
document: Document,
|
||||
manifestKey: string,
|
||||
) => string | undefined;
|
||||
}>,
|
||||
parsers?: Partial<{ [key in keyof T]: (content: string) => T[key] }>,
|
||||
): Promise<T> {
|
||||
const content = await fs.readFile(info.inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const options = {} as T;
|
||||
|
||||
const defaultQuery = (manifestKey: string) =>
|
||||
document
|
||||
.querySelector(`meta[name='manifest.${manifestKey}']`)
|
||||
?.getAttribute('content');
|
||||
|
||||
Object.entries(keyMap).forEach(([_key, manifestKey]) => {
|
||||
const key = _key as keyof T;
|
||||
const content = queries?.[key]
|
||||
? queries[key]!(document, manifestKey)
|
||||
: defaultQuery(manifestKey);
|
||||
if (content) {
|
||||
try {
|
||||
options[key] = (parsers?.[key] ?? JSON5.parse)(content);
|
||||
} catch (err) {
|
||||
wxt.logger.fatal(
|
||||
`Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content})`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
const PATH_GLOB_TO_TYPE_MAP: Record<string, Entrypoint['type']> = {
|
||||
'sandbox.html': 'sandbox',
|
||||
'sandbox/index.html': 'sandbox',
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
import { UnimportOptions, createUnimport } from 'unimport';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { Unimport, createUnimport } from 'unimport';
|
||||
import {
|
||||
EslintGlobalsPropValue,
|
||||
Entrypoint,
|
||||
WxtResolvedUnimportOptions,
|
||||
} from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { relative, resolve } from 'path';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
import { getEntrypointGlobals, getGlobals } from '~/core/utils/globals';
|
||||
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);
|
||||
if (imports !== false) {
|
||||
references.push(await writeImportsDeclarationFile(config, imports));
|
||||
if (wxt.config.imports !== false) {
|
||||
const unimport = createUnimport(wxt.config.imports);
|
||||
references.push(await writeImportsDeclarationFile(unimport));
|
||||
if (wxt.config.imports.eslintrc.enabled) {
|
||||
await writeImportsEslintFile(unimport, wxt.config.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 unimport = createUnimport(unimportOptions);
|
||||
async function writeImportsDeclarationFile(unimport: Unimport) {
|
||||
const filePath = resolve(wxt.config.typesDir, 'imports.d.ts');
|
||||
|
||||
// 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,
|
||||
@@ -54,20 +56,36 @@ async function writeImportsDeclarationFile(
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function writeImportsEslintFile(
|
||||
unimport: Unimport,
|
||||
options: WxtResolvedUnimportOptions,
|
||||
) {
|
||||
const globals: Record<string, EslintGlobalsPropValue> = {};
|
||||
const eslintrc = { globals };
|
||||
|
||||
(await unimport.getImports())
|
||||
.map((i) => i.as ?? i.name)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.forEach((name) => {
|
||||
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
|
||||
});
|
||||
await fs.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
|
||||
}
|
||||
|
||||
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 +113,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 +139,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 +170,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 +189,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 +206,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 +237,10 @@ ${paths}
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"${getTsconfigPath(config.root)}/**/*",
|
||||
"${getTsconfigPath(wxt.config.root)}/**/*",
|
||||
"./${getTsconfigPath(mainReference)}"
|
||||
],
|
||||
"exclude": ["${getTsconfigPath(config.outBaseDir)}"]
|
||||
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
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';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
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 +23,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),
|
||||
...wxt.config.imports,
|
||||
// Only allow specific imports, not all from the project
|
||||
dirs: [],
|
||||
});
|
||||
@@ -42,7 +38,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 +48,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 +85,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 +94,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();
|
||||
|
||||
+74
-10
@@ -1,13 +1,15 @@
|
||||
import { loadConfig } from 'c12';
|
||||
import {
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
ResolvedConfig,
|
||||
UserConfig,
|
||||
ConfigEnv,
|
||||
UserManifestFn,
|
||||
UserManifest,
|
||||
ExtensionRunnerConfig,
|
||||
WxtDevServer,
|
||||
WxtResolvedUnimportOptions,
|
||||
Logger,
|
||||
} from '~/types';
|
||||
import path from 'node:path';
|
||||
import { createFsCache } from '~/core/utils/cache';
|
||||
@@ -15,6 +17,7 @@ import consola, { LogLevels } from 'consola';
|
||||
import { createViteBuilder } from '~/core/builders/vite';
|
||||
import defu from 'defu';
|
||||
import { NullablyRequired } from '../types';
|
||||
import { isModuleInstalled } from '../package';
|
||||
|
||||
/**
|
||||
* Given an inline config, discover the config file if necessary, merge the results, resolve any
|
||||
@@ -23,15 +26,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 +103,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,
|
||||
@@ -108,7 +118,7 @@ export async function getInternalConfig(
|
||||
filterEntrypoints,
|
||||
env,
|
||||
fsCache: createFsCache(wxtDir),
|
||||
imports: mergedConfig.imports ?? {},
|
||||
imports: await getUnimportOptions(wxtDir, logger, mergedConfig),
|
||||
logger,
|
||||
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
|
||||
manifestVersion,
|
||||
@@ -129,6 +139,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 +154,7 @@ export async function getInternalConfig(
|
||||
dev: {
|
||||
reloadCommand,
|
||||
},
|
||||
hooks: mergedConfig.hooks ?? {},
|
||||
};
|
||||
|
||||
const builder = await createViteBuilder(
|
||||
@@ -191,6 +206,10 @@ function mergeInlineConfig(
|
||||
inlineConfig.zip ?? {},
|
||||
userConfig.zip ?? {},
|
||||
);
|
||||
const hooks: InlineConfig['hooks'] = defu(
|
||||
inlineConfig.hooks ?? {},
|
||||
userConfig.hooks ?? {},
|
||||
);
|
||||
|
||||
return {
|
||||
root: inlineConfig.root ?? userConfig.root,
|
||||
@@ -211,9 +230,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 +247,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',
|
||||
@@ -257,3 +276,48 @@ function resolveInternalZipConfig(
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async function getUnimportOptions(
|
||||
wxtDir: string,
|
||||
logger: Logger,
|
||||
config: InlineConfig,
|
||||
): Promise<WxtResolvedUnimportOptions | false> {
|
||||
if (config.imports === false) return false;
|
||||
|
||||
const enabledConfig = config.imports?.eslintrc?.enabled;
|
||||
let enabled: boolean;
|
||||
switch (enabledConfig) {
|
||||
case undefined:
|
||||
case 'auto':
|
||||
enabled = await isModuleInstalled('eslint');
|
||||
break;
|
||||
default:
|
||||
enabled = enabledConfig;
|
||||
}
|
||||
|
||||
const defaultOptions: WxtResolvedUnimportOptions = {
|
||||
debugLog: logger.debug,
|
||||
imports: [
|
||||
{ name: 'defineConfig', from: 'wxt' },
|
||||
{ name: 'fakeBrowser', from: 'wxt/testing' },
|
||||
],
|
||||
presets: [
|
||||
{ package: 'wxt/client' },
|
||||
{ package: 'wxt/browser' },
|
||||
{ package: 'wxt/sandbox' },
|
||||
{ package: 'wxt/storage' },
|
||||
],
|
||||
warn: logger.warn,
|
||||
dirs: ['components', 'composables', 'hooks', 'utils'],
|
||||
eslintrc: {
|
||||
enabled,
|
||||
filePath: path.resolve(wxtDir, 'eslintrc-auto-import.json'),
|
||||
globalsPropValue: true,
|
||||
},
|
||||
};
|
||||
|
||||
return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
|
||||
config.imports ?? {},
|
||||
defaultOptions,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Manifest } from '~/browser';
|
||||
import { ContentScriptEntrypoint, InternalConfig } from '~/types';
|
||||
import { resolvePerBrowserOption } from './entrypoints';
|
||||
import type { Manifest, Scripting } from '~/browser';
|
||||
import { ContentScriptEntrypoint } from '~/types';
|
||||
|
||||
/**
|
||||
* Returns a unique and consistent string hash based on a content scripts options.
|
||||
@@ -9,9 +8,12 @@ import { resolvePerBrowserOption } from './entrypoints';
|
||||
*/
|
||||
export function hashContentScriptOptions(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
config: InternalConfig,
|
||||
): string {
|
||||
const simplifiedOptions = mapWxtOptionsToContentScript(options, config);
|
||||
const simplifiedOptions = mapWxtOptionsToContentScript(
|
||||
options,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Remove undefined fields and use defaults to generate hash
|
||||
Object.keys(simplifiedOptions).forEach((key) => {
|
||||
@@ -45,34 +47,39 @@ export function hashContentScriptOptions(
|
||||
|
||||
export function mapWxtOptionsToContentScript(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
config: InternalConfig,
|
||||
): Omit<Manifest.ContentScript, 'js' | 'css'> {
|
||||
js: string[] | undefined,
|
||||
css: string[] | undefined,
|
||||
): Manifest.ContentScript {
|
||||
return {
|
||||
matches: resolvePerBrowserOption(options.matches, config.browser),
|
||||
all_frames: resolvePerBrowserOption(options.allFrames, config.browser),
|
||||
match_about_blank: resolvePerBrowserOption(
|
||||
options.matchAboutBlank,
|
||||
config.browser,
|
||||
),
|
||||
exclude_globs: resolvePerBrowserOption(
|
||||
options.excludeGlobs,
|
||||
config.browser,
|
||||
),
|
||||
exclude_matches: resolvePerBrowserOption(
|
||||
options.excludeMatches,
|
||||
config.browser,
|
||||
),
|
||||
include_globs: resolvePerBrowserOption(
|
||||
options.includeGlobs,
|
||||
config.browser,
|
||||
),
|
||||
run_at: resolvePerBrowserOption(options.runAt, config.browser),
|
||||
matches: options.matches,
|
||||
all_frames: options.allFrames,
|
||||
match_about_blank: options.matchAboutBlank,
|
||||
exclude_globs: options.excludeGlobs,
|
||||
exclude_matches: options.excludeMatches,
|
||||
include_globs: options.includeGlobs,
|
||||
run_at: options.runAt,
|
||||
css,
|
||||
js,
|
||||
|
||||
// @ts-expect-error: untyped chrome options
|
||||
match_origin_as_fallback: resolvePerBrowserOption(
|
||||
options.matchOriginAsFallback,
|
||||
config.browser,
|
||||
),
|
||||
match_origin_as_fallback: options.matchOriginAsFallback,
|
||||
world: options.world,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapWxtOptionsToRegisteredContentScript(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
js: string[] | undefined,
|
||||
css: string[] | undefined,
|
||||
): Omit<Scripting.RegisteredContentScript, 'id'> {
|
||||
return {
|
||||
allFrames: options.allFrames,
|
||||
excludeMatches: options.excludeMatches,
|
||||
matches: options.matches,
|
||||
runAt: options.runAt,
|
||||
js,
|
||||
css,
|
||||
// @ts-expect-error: Chrome accepts this, not typed in webextension-polyfill (https://developer.chrome.com/docs/extensions/reference/scripting/#type-RegisteredContentScript)
|
||||
world: options.world,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Entrypoint, PerBrowserOption, TargetBrowser } from '~/types';
|
||||
import {
|
||||
Entrypoint,
|
||||
PerBrowserOption,
|
||||
ResolvedPerBrowserOptions,
|
||||
TargetBrowser,
|
||||
} from '~/types';
|
||||
import path, { relative, resolve } from 'node:path';
|
||||
import { normalizePath } from './paths';
|
||||
|
||||
@@ -46,3 +51,22 @@ export function resolvePerBrowserOption<T>(
|
||||
return (option as any)[browser];
|
||||
return option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an entrypoint option, resolve it's value based on a target browser.
|
||||
*
|
||||
* defaultIcon is special, it's the only key that's a record, which can confuse this function. So
|
||||
* it's been manually excluded from resolution.
|
||||
*/
|
||||
export function resolvePerBrowserOptions<
|
||||
T extends Record<string, any>,
|
||||
TKeys extends keyof T,
|
||||
>(options: T, browser: TargetBrowser): ResolvedPerBrowserOptions<T, TKeys> {
|
||||
// @ts-expect-error: Object.entries is untyped.
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).map(([key, value]) => [
|
||||
key,
|
||||
key === 'defaultIcon' ? value : resolvePerBrowserOption(value, browser),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+135
-93
@@ -4,16 +4,13 @@ import {
|
||||
BackgroundEntrypoint,
|
||||
BuildOutput,
|
||||
ContentScriptEntrypoint,
|
||||
InternalConfig,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
SidepanelEntrypoint,
|
||||
} from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
import {
|
||||
getEntrypointBundlePath,
|
||||
resolvePerBrowserOption,
|
||||
} from './entrypoints';
|
||||
import { getEntrypointBundlePath } from './entrypoints';
|
||||
import { ContentSecurityPolicy } from './content-security-policy';
|
||||
import {
|
||||
hashContentScriptOptions,
|
||||
@@ -22,8 +19,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 +28,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 +49,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 +91,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 +101,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 +159,6 @@ function addEntrypoints(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
entrypoints: Entrypoint[],
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
config: InternalConfig,
|
||||
): void {
|
||||
const entriesByType = entrypoints.reduce<
|
||||
Partial<Record<Entrypoint['type'], Entrypoint[]>>
|
||||
@@ -178,16 +183,22 @@ function addEntrypoints(
|
||||
| undefined;
|
||||
const popup = entriesByType['popup']?.[0] as PopupEntrypoint | undefined;
|
||||
const sandboxes = entriesByType['sandbox'];
|
||||
const sidepanels = entriesByType['sidepanel'];
|
||||
const sidepanels = entriesByType['sidepanel'] as
|
||||
| SidepanelEntrypoint[]
|
||||
| undefined;
|
||||
|
||||
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,25 @@ 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,
|
||||
browser_style: defaultSidepanel.options.browserStyle,
|
||||
default_icon: defaultSidepanel.options.defaultIcon,
|
||||
default_title: defaultSidepanel.options.defaultTitle,
|
||||
open_at_install: defaultSidepanel.options.openAtInstall,
|
||||
};
|
||||
} 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,14 +354,10 @@ 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,
|
||||
);
|
||||
matches.forEach((matchPattern) => {
|
||||
script.options.matches.forEach((matchPattern) => {
|
||||
hostPermissions.add(matchPattern);
|
||||
});
|
||||
});
|
||||
@@ -353,20 +366,21 @@ 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;
|
||||
}, new Map<string, ContentScriptEntrypoint[]>());
|
||||
|
||||
const newContentScripts = Array.from(hashToEntrypointsMap.entries()).map(
|
||||
([, scripts]) => ({
|
||||
...mapWxtOptionsToContentScript(scripts[0].options, config),
|
||||
css: getContentScriptCssFiles(scripts, cssMap),
|
||||
js: scripts.map((entry) =>
|
||||
getEntrypointBundlePath(entry, config.outDir, '.js'),
|
||||
([, scripts]) =>
|
||||
mapWxtOptionsToContentScript(
|
||||
scripts[0].options,
|
||||
scripts.map((entry) =>
|
||||
getEntrypointBundlePath(entry, wxt.config.outDir, '.js'),
|
||||
),
|
||||
getContentScriptCssFiles(scripts, cssMap),
|
||||
),
|
||||
}),
|
||||
);
|
||||
if (newContentScripts.length >= 0) {
|
||||
manifest.content_scripts ??= [];
|
||||
@@ -375,7 +389,6 @@ function addEntrypoints(
|
||||
}
|
||||
|
||||
const contentScriptCssResources = getContentScriptCssWebAccessibleResources(
|
||||
config,
|
||||
contentScripts,
|
||||
cssMap,
|
||||
);
|
||||
@@ -420,12 +433,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 +452,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 +463,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 +504,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 +516,12 @@ 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: script.options.matches.map((matchPattern) =>
|
||||
stripPathFromMatchPattern(matchPattern),
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
return resources;
|
||||
@@ -573,3 +575,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,22 +1,26 @@
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
||||
export function isModuleInstalled(name: string) {
|
||||
return import(name).then(() => true).catch(() => false);
|
||||
}
|
||||
|
||||
@@ -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,20 @@ import {
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
UserManifest,
|
||||
Wxt,
|
||||
SidepanelEntrypoint,
|
||||
} 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);
|
||||
}
|
||||
@@ -142,6 +149,30 @@ export const fakePopupEntrypoint = fakeObjectCreator<PopupEntrypoint>(() => ({
|
||||
skipped: faker.datatype.boolean(),
|
||||
}));
|
||||
|
||||
export const fakeSidepanelEntrypoint = fakeObjectCreator<SidepanelEntrypoint>(
|
||||
() => ({
|
||||
type: 'sidepanel',
|
||||
inputPath: 'entrypoints/sidepanel.html',
|
||||
name: 'sidepanel',
|
||||
outputDir: fakeDir('.output'),
|
||||
options: {
|
||||
defaultTitle: faker.helpers.arrayElement([
|
||||
faker.person.fullName(),
|
||||
undefined,
|
||||
]),
|
||||
defaultIcon: faker.helpers.arrayElement([
|
||||
{
|
||||
'16': 'icon/16.png',
|
||||
'24': 'icon/24.png',
|
||||
'64': 'icon/64.png',
|
||||
},
|
||||
]),
|
||||
openAtInstall: faker.helpers.arrayElement([true, false, undefined]),
|
||||
},
|
||||
skipped: faker.datatype.boolean(),
|
||||
}),
|
||||
);
|
||||
|
||||
export const fakeGenericEntrypoint = fakeObjectCreator<GenericEntrypoint>(
|
||||
() => ({
|
||||
type: faker.helpers.arrayElement([
|
||||
@@ -149,7 +180,6 @@ export const fakeGenericEntrypoint = fakeObjectCreator<GenericEntrypoint>(
|
||||
'bookmarks',
|
||||
'history',
|
||||
'newtab',
|
||||
'sidepanel',
|
||||
'devtools',
|
||||
'unlisted-page',
|
||||
'unlisted-script',
|
||||
@@ -198,7 +228,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);
|
||||
@@ -210,7 +240,20 @@ export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => {
|
||||
entrypointsDir: fakeDir(),
|
||||
env: { browser, command, manifestVersion, mode },
|
||||
fsCache: mock<FsCache>(),
|
||||
imports: {},
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: faker.datatype.boolean(),
|
||||
filePath: fakeFile(),
|
||||
globalsPropValue: faker.helpers.arrayElement([
|
||||
true,
|
||||
false,
|
||||
'readable',
|
||||
'readonly',
|
||||
'writable',
|
||||
'writeable',
|
||||
] as const),
|
||||
},
|
||||
},
|
||||
logger: mock(),
|
||||
manifest: fakeUserManifest(),
|
||||
manifestVersion,
|
||||
@@ -230,6 +273,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 +296,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,27 +0,0 @@
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { defu } from 'defu';
|
||||
|
||||
export function getUnimportOptions(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
): Partial<UnimportOptions | false> {
|
||||
if (config.imports === false) return false;
|
||||
|
||||
const defaultOptions: Partial<UnimportOptions> = {
|
||||
debugLog: config.logger.debug,
|
||||
imports: [
|
||||
{ name: 'defineConfig', from: 'wxt' },
|
||||
{ name: 'fakeBrowser', from: 'wxt/testing' },
|
||||
],
|
||||
presets: [
|
||||
{ package: 'wxt/client' },
|
||||
{ package: 'wxt/browser' },
|
||||
{ package: 'wxt/sandbox' },
|
||||
{ package: 'wxt/storage' },
|
||||
],
|
||||
warn: config.logger.warn,
|
||||
dirs: ['components', 'composables', 'hooks', 'utils'],
|
||||
};
|
||||
|
||||
return defu(config.imports, defaultOptions);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+1059
-2
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
|
||||
Reference in New Issue
Block a user