Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0369316463 | |||
| 8b74291d18 | |||
| 739b738100 | |||
| 95442dd39b | |||
| 4f82645c08 | |||
| d200c376f8 | |||
| b58fb02016 | |||
| e1bab6c746 |
+4
-2
@@ -2,5 +2,7 @@ coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 80%
|
||||
threshold: 1%
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
@@ -13,3 +13,4 @@ CHANGELOG.md
|
||||
*.txt
|
||||
_gitignore
|
||||
_redirects
|
||||
*.svelte
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## v0.7.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.7.4...v0.7.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- More consistent `version_name` generation between browsers ([#163](https://github.com/wxt-dev/wxt/pull/163))
|
||||
- Ignore non-manifest fields when merging content script entries ([#164](https://github.com/wxt-dev/wxt/pull/164))
|
||||
- Add `browser_style` to popup options ([#165](https://github.com/wxt-dev/wxt/pull/165))
|
||||
|
||||
## v0.7.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.7.3...v0.7.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Support `react-refresh` when pre-rendering HTML pages in dev mode ([#158](https://github.com/wxt-dev/wxt/pull/158))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add migration guides ([b58fb02](https://github.com/wxt-dev/wxt/commit/b58fb02))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade templates to v0.7 ([#156](https://github.com/wxt-dev/wxt/pull/156))
|
||||
|
||||
## v0.7.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.7.2...v0.7.3)
|
||||
|
||||
@@ -80,6 +80,12 @@ export default defineConfig({
|
||||
{ text: 'Development', link: '/guide/development.md' },
|
||||
{ text: 'Testing', link: '/guide/testing.md' },
|
||||
{ text: 'Vite', link: '/guide/vite.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Other',
|
||||
items: [
|
||||
{ text: 'Migrate to WXT', link: '/guide/migrate-to-wxt.md' },
|
||||
{ text: 'Compare', link: '/guide/compare.md' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
}"
|
||||
/>
|
||||
<meta name="manifest.type" content="page_action|browser_action" />
|
||||
<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', ...]" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Installation
|
||||
|
||||
Bootstrap a new project, start from scratch, or migrate an existing project.
|
||||
Bootstrap a new project, start from scratch, or [migrate an existing project](/guide/migrate-to-wxt).
|
||||
|
||||
## Bootstrap Project
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Migrate to WXT
|
||||
|
||||
> If you have problems migrating to WXT, feel free to ask for help in GitHub by [starting a discussion](https://github.com/wxt-dev/wxt/discussions/new?category=q-a)!
|
||||
|
||||
## Overview
|
||||
|
||||
Always start by generating a new vanilla project and merging it into your project one file at a time.
|
||||
|
||||
```sh
|
||||
cd path/to/your/project
|
||||
pnpx wxt@latest init example-wxt --template vanilla
|
||||
```
|
||||
|
||||
In general, you'll need to:
|
||||
|
||||
<input type="checkbox" /> Install `wxt`<br />
|
||||
<input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
|
||||
<input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
|
||||
<input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
|
||||
<input type="checkbox" /> Move manifest.json content into `wxt.config.ts`<br />
|
||||
<input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
<input type="checkbox" /> Add a default export to JS entrypoints<br />
|
||||
<input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
|
||||
<input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br />
|
||||
<input type="checkbox" /> Extension output by `wxt build` works the same way as before the migration<br />
|
||||
|
||||
Every project is different, so there's no one-solution-fits-all to migrating your project. Just make sure `wxt dev` runs, `wxt build` results in a working extension, and the list of permissions in the `manifest.json` hasn't changed. If all that looks good, you've finished migrating your extension!
|
||||
|
||||
## Popular Tools/Frameworks
|
||||
|
||||
Here's specific steps for other popuplar frameworks/build tools.
|
||||
|
||||
### `vite-plugin-web-extension`
|
||||
|
||||
Since you're already using Vite, it's a simple refactor.
|
||||
|
||||
1. Install `wxt`
|
||||
2. Move and refactor your entrypoints to WXT's style (with a default export)
|
||||
3. Update package.json scripts to use `wxt`
|
||||
4. Add `"postinstall": "wxt prepare"` script
|
||||
5. Move the `manifest.json` into `wxt.config.ts`
|
||||
6. Move any custom settings from `vite.config.ts` into `wxt.config.ts`'s
|
||||
7. Compare `dist/manifest.json` to `.output/*/manifest.json`, they should have the same content as before. If not, tweak your entrypoints and config to get as close as possible.
|
||||
|
||||
### `plasmo`
|
||||
|
||||
1. Install `wxt`
|
||||
2. Move entrypoints into `entrypoints/` directory, merging the named exports used to configure your JS entrypoints into WXT's default export
|
||||
3. Move public `assets/*` into the `public/` directory
|
||||
4. If you use CSUI, migrate to WXT's `createContentScriptUi`
|
||||
5. Convert Plasmo's custom import resolutions to Vite's
|
||||
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
|
||||
7. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible.
|
||||
@@ -60,7 +60,7 @@ Eventually, WXT will provide utilities for setting up these auto-imports. For no
|
||||
|
||||
Not all testing frameworks can handle auto-imports. If your framework or setup is not listed below, it may be easiest to disable auto-imports.
|
||||
|
||||
To setup auto-imports manually, use [`unplugin-auto-import`](https://www.npmjs.com/package/unplugin-auto-import). It uses the same tool, `unimport`, as WXT and will result in compatiple auto-imports. `unplugin-auto-import` supports lots of different tools (vite, webpage, esbuild, rollup, etc). You can try and integrate it into your build process.
|
||||
To setup auto-imports manually, use [`unplugin-auto-import`](https://www.npmjs.com/package/unplugin-auto-import). It uses the same tool, `unimport`, as WXT and will result in compatiple auto-imports. `unplugin-auto-import` supports lots of different tools (vite, webpack, esbuild, rollup, etc). You can try and integrate it into your build process.
|
||||
|
||||
### Vitest (Recommended)
|
||||
|
||||
|
||||
@@ -194,15 +194,15 @@ describe('Manifest Content', () => {
|
||||
});
|
||||
|
||||
describe('content_scripts', () => {
|
||||
it('should group content scripts and styles together based on their matches and run_at', async () => {
|
||||
it('should group content scripts and styles together based on their manifest properties', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/one.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
main: () => {},
|
||||
})`,
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/one.content/style.css',
|
||||
@@ -211,11 +211,11 @@ describe('Manifest Content', () => {
|
||||
project.addFile(
|
||||
'entrypoints/two.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/two.content/style.css',
|
||||
@@ -224,11 +224,11 @@ describe('Manifest Content', () => {
|
||||
project.addFile(
|
||||
'entrypoints/three.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/three.content/style.css',
|
||||
@@ -237,11 +237,11 @@ describe('Manifest Content', () => {
|
||||
project.addFile(
|
||||
'entrypoints/four.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://duckduckgo.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
export default defineContentScript({
|
||||
matches: ["*://duckduckgo.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/four.content/style.css',
|
||||
@@ -276,15 +276,15 @@ describe('Manifest Content', () => {
|
||||
project.addFile(
|
||||
'entrypoints/one.content/index.ts',
|
||||
`export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
main: () => {},
|
||||
})`,
|
||||
matches: ["*://google.com/*"],
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/two.content/style.css',
|
||||
`body {
|
||||
background-color: red;
|
||||
}`,
|
||||
background-color: red;
|
||||
}`,
|
||||
);
|
||||
project.setConfigFileConfig({
|
||||
manifest: {
|
||||
@@ -333,7 +333,7 @@ describe('Manifest Content', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"css\\":[\\"content-scripts/content.css\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"css\\":[\\"content-scripts/content.css\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -360,7 +360,7 @@ describe('Manifest Content', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"css\\":[\\"content-scripts/content.css\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"css\\":[\\"content-scripts/content.css\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -385,10 +385,10 @@ describe('Manifest Content', () => {
|
||||
|
||||
expect(await project.serializeFile('.output/chrome-mv3/manifest.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
`);
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not add a content script entry for CSS when cssInjectionMode is "ui", but add a web_accessible_resources entry for MV2', async () => {
|
||||
@@ -416,7 +416,7 @@ describe('Manifest Content', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv2/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":2,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}],\\"web_accessible_resources\\":[\\"content-scripts/content.css\\"]}"
|
||||
{\\"manifest_version\\":2,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}],\\"web_accessible_resources\\":[\\"content-scripts/content.css\\"]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -445,7 +445,7 @@ describe('Manifest Content', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}],\\"web_accessible_resources\\":[{\\"resources\\":[\\"content-scripts/content.css\\"],\\"matches\\":[\\"https://*.google.com/*\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}],\\"web_accessible_resources\\":[{\\"resources\\":[\\"content-scripts/content.css\\"],\\"matches\\":[\\"https://*.google.com/*\\"]}]}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -480,7 +480,7 @@ describe('Manifest Content', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"web_accessible_resources\\":[{\\"resources\\":[\\"one.png\\"],\\"matches\\":[\\"https://one.com/*\\"]},{\\"resources\\":[\\"content-scripts/content.css\\"],\\"matches\\":[\\"https://*.google.com/*\\"]}],\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"web_accessible_resources\\":[{\\"resources\\":[\\"one.png\\"],\\"matches\\":[\\"https://one.com/*\\"]},{\\"resources\\":[\\"content-scripts/content.css\\"],\\"matches\\":[\\"https://*.google.com/*\\"]}],\\"content_scripts\\":[{\\"matches\\":[\\"https://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -505,7 +505,7 @@ describe('Manifest Content', () => {
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"author\\":\\"Custom Author\\"}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"author\\":\\"Custom Author\\"}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -537,4 +537,66 @@ describe('Manifest Content', () => {
|
||||
expect(safariManifest.background.persistent).toBe(expected);
|
||||
},
|
||||
);
|
||||
|
||||
describe('versions', () => {
|
||||
it.each([
|
||||
['chrome', 3] as const,
|
||||
['safari', 2] as const,
|
||||
['edge', 3] as const,
|
||||
])(
|
||||
'should include version_name on %s when it needs simplified',
|
||||
async (browser, manifestVersion) => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0-alpha1',
|
||||
});
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
`.output/${browser}-mv${manifestVersion}/manifest.json`,
|
||||
);
|
||||
|
||||
expect(manifest.version).toBe('1.0.0');
|
||||
expect(manifest.version_name).toBe('1.0.0-alpha1');
|
||||
},
|
||||
);
|
||||
|
||||
it.each([['firefox', 2] as const])(
|
||||
"should not include a version_name on %s because the browser doesn't support it",
|
||||
async (browser, manifestVersion) => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0-alpha1',
|
||||
});
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
`.output/${browser}-mv${manifestVersion}/manifest.json`,
|
||||
);
|
||||
|
||||
expect(manifest.version).toBe('1.0.0');
|
||||
expect(manifest.version_name).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
['chrome', 3] as const,
|
||||
['firefox', 2] as const,
|
||||
['safari', 3] as const,
|
||||
['edge', 3] as const,
|
||||
])(
|
||||
'should not include the version_name if it is equal to version',
|
||||
async (browser, manifestVersion) => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0.1',
|
||||
});
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
`.output/${browser}-mv${manifestVersion}/manifest.json`,
|
||||
);
|
||||
|
||||
expect(manifest.version).toBe('1.0.0.1');
|
||||
expect(manifest.version_name).toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('Output Directory Structure', () => {
|
||||
expect(await project.serializeOutput()).toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\"}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\"}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('Output Directory Structure', () => {
|
||||
================================================================================
|
||||
.output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"content-scripts/one.css\\",\\"content-scripts/two.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"content-scripts/one.css\\",\\"content-scripts/two.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('Output Directory Structure', () => {
|
||||
================================================================================
|
||||
.output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"js\\":[\\"content-scripts/overlay-one.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"js\\":[\\"content-scripts/overlay-one.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -192,10 +192,10 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
expect(await project.serializeFile('.output/chrome-mv3/manifest.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"},\\"content_scripts\\":[{\\"matches\\":[\\"*://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]},{\\"matches\\":[\\"*://*.duckduckgo.com/*\\"],\\"js\\":[\\"content-scripts/named.js\\"]}]}"
|
||||
`);
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"},\\"content_scripts\\":[{\\"matches\\":[\\"*://*.google.com/*\\"],\\"js\\":[\\"content-scripts/content.js\\"]},{\\"matches\\":[\\"*://*.duckduckgo.com/*\\"],\\"js\\":[\\"content-scripts/named.js\\"]}]}"
|
||||
`);
|
||||
expect(await project.fileExists('.output/chrome-mv3/background.js'));
|
||||
expect(
|
||||
await project.fileExists('.output/chrome-mv3/content-scripts/content.js'),
|
||||
|
||||
@@ -35,9 +35,9 @@ describe('React', () => {
|
||||
).toBe(true);
|
||||
expect(await project.serializeFile('.output/chrome-mv3/manifest.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":\\"<all_urls>\\",\\"js\\":[\\"content-scripts/demo.js\\"]}]}"
|
||||
`);
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"content_scripts\\":[{\\"matches\\":\\"<all_urls>\\",\\"js\\":[\\"content-scripts/demo.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('User Config', () => {
|
||||
================================================================================
|
||||
.output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('User Config', () => {
|
||||
================================================================================
|
||||
.output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('User Config', () => {
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export class TestProject {
|
||||
{
|
||||
name: 'E2E Extension',
|
||||
description: 'Example description',
|
||||
version: '0.0.0-test',
|
||||
version: '0.0.0',
|
||||
dependencies: {
|
||||
wxt: '../../..',
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.7.3",
|
||||
"version": "0.7.5",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"engines": {
|
||||
"node": ">=18.16.0",
|
||||
|
||||
@@ -201,12 +201,19 @@ async function getPopupEntrypoint(
|
||||
}
|
||||
}
|
||||
|
||||
const mv2KeyContent = document
|
||||
const mv2TypeContent = document
|
||||
.querySelector("meta[name='manifest.type']")
|
||||
?.getAttribute('content');
|
||||
if (mv2KeyContent) {
|
||||
if (mv2TypeContent) {
|
||||
options.mv2Key =
|
||||
mv2KeyContent === 'page_action' ? 'page_action' : 'browser_action';
|
||||
mv2TypeContent === 'page_action' ? 'page_action' : 'browser_action';
|
||||
}
|
||||
|
||||
const browserStyleContent = document
|
||||
.querySelector("meta[name='manifest.browser_style']")
|
||||
?.getAttribute('content');
|
||||
if (browserStyleContent) {
|
||||
options.browserStyle = browserStyleContent === 'true';
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -343,6 +343,7 @@ export interface PopupEntrypoint extends BaseEntrypoint {
|
||||
mv2Key?: 'browser_action' | 'page_action';
|
||||
defaultIcon?: Record<string, string>;
|
||||
defaultTitle?: string;
|
||||
browserStyle?: boolean;
|
||||
} & BaseEntrypointOptions;
|
||||
}
|
||||
|
||||
@@ -416,6 +417,8 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint {
|
||||
* - `"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"
|
||||
*/
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { hashContentScriptOptions } from '../content-scripts';
|
||||
import { fakeInternalConfig } from '../../../testing/fake-objects';
|
||||
|
||||
describe('Content Script Utils', () => {
|
||||
describe('hashContentScriptOptions', () => {
|
||||
it('should return a string containing all the options with defaults applied', () => {
|
||||
const hash = hashContentScriptOptions({ matches: [] });
|
||||
const hash = hashContentScriptOptions(
|
||||
{ matches: [] },
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
|
||||
expect(hash).toMatchInlineSnapshot(
|
||||
'"[[\\"allFrames\\",false],[\\"excludeGlobs\\",[]],[\\"excludeMatches\\",[]],[\\"includeGlobs\\",[]],[\\"matchAboutBlank\\",false],[\\"matches\\",[]],[\\"matchOriginAsFallback\\",false],[\\"runAt\\",\\"document_idle\\"],[\\"world\\",\\"ISOLATED\\"]]"',
|
||||
'"[[\\"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\\"]]"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should be consistent regardless of the object ordering and default values', () => {
|
||||
const hash1 = hashContentScriptOptions({
|
||||
allFrames: true,
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
matchAboutBlank: false,
|
||||
});
|
||||
const hash2 = hashContentScriptOptions({
|
||||
matches: ['*://duckduckgo.com/*', '*://google.com/*'],
|
||||
allFrames: true,
|
||||
});
|
||||
const hash1 = hashContentScriptOptions(
|
||||
{
|
||||
allFrames: true,
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
matchAboutBlank: false,
|
||||
},
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
const hash2 = hashContentScriptOptions(
|
||||
{
|
||||
matches: ['*://duckduckgo.com/*', '*://google.com/*'],
|
||||
allFrames: true,
|
||||
},
|
||||
fakeInternalConfig(),
|
||||
);
|
||||
|
||||
expect(hash1).toBe(hash2);
|
||||
});
|
||||
|
||||
@@ -9,18 +9,27 @@ import { resolvePerBrowserOption } from './entrypoints';
|
||||
*/
|
||||
export function hashContentScriptOptions(
|
||||
options: ContentScriptEntrypoint['options'],
|
||||
config: InternalConfig,
|
||||
): string {
|
||||
const withDefaults: ContentScriptEntrypoint['options'] = {
|
||||
excludeGlobs: [],
|
||||
excludeMatches: [],
|
||||
includeGlobs: [],
|
||||
matchAboutBlank: false,
|
||||
matchOriginAsFallback: false,
|
||||
runAt: 'document_idle',
|
||||
allFrames: false,
|
||||
const simplifiedOptions = mapWxtOptionsToContentScript(options, config);
|
||||
|
||||
// Remove undefined fields and use defaults to generate hash
|
||||
Object.keys(simplifiedOptions).forEach((key) => {
|
||||
// @ts-expect-error: key not typed as keyof ...
|
||||
if (simplifiedOptions[key] == null) delete simplifiedOptions[key];
|
||||
});
|
||||
|
||||
const withDefaults: Manifest.ContentScript = {
|
||||
exclude_globs: [],
|
||||
exclude_matches: [],
|
||||
include_globs: [],
|
||||
match_about_blank: false,
|
||||
run_at: 'document_idle',
|
||||
all_frames: false,
|
||||
// @ts-expect-error - not in type
|
||||
match_origin_as_fallback: false,
|
||||
world: 'ISOLATED',
|
||||
// TODO: strip undefined fields from options object to improve content script grouping.
|
||||
...options,
|
||||
...simplifiedOptions,
|
||||
};
|
||||
return JSON.stringify(
|
||||
Object.entries(withDefaults)
|
||||
|
||||
@@ -60,16 +60,19 @@ export async function generateMainfest(
|
||||
): Promise<Manifest.WebExtensionManifest> {
|
||||
const pkg = await getPackageJson(config);
|
||||
|
||||
const versionName = config.manifest.version_name ?? pkg?.version;
|
||||
const version = config.manifest.version ?? simplifyVersion(pkg?.version);
|
||||
|
||||
const baseManifest: Manifest.WebExtensionManifest = {
|
||||
manifest_version: config.manifestVersion,
|
||||
name: pkg?.name,
|
||||
description: pkg?.description,
|
||||
version: pkg?.version && simplifyVersion(pkg.version),
|
||||
// Only add the version name to chromium and if the user hasn't specified a custom version.
|
||||
version,
|
||||
version_name:
|
||||
config.browser !== 'firefox' && !config.manifest.version
|
||||
? pkg?.version
|
||||
: undefined,
|
||||
// Firefox doesn't support version_name
|
||||
config.browser === 'firefox' || versionName === version
|
||||
? undefined
|
||||
: versionName,
|
||||
short_name: pkg?.shortName,
|
||||
icons: discoverIcons(buildOutput),
|
||||
};
|
||||
@@ -311,7 +314,7 @@ function addEntrypoints(
|
||||
);
|
||||
} else {
|
||||
const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
|
||||
const hash = hashContentScriptOptions(script.options);
|
||||
const hash = hashContentScriptOptions(script.options, config);
|
||||
if (map.has(hash)) map.get(hash)?.push(script);
|
||||
else map.set(hash, [script]);
|
||||
return map;
|
||||
|
||||
@@ -4,68 +4,147 @@ import { getEntrypointName } from '../utils/entrypoints';
|
||||
import { parseHTML } from 'linkedom';
|
||||
import { dirname, isAbsolute, relative, resolve } from 'path';
|
||||
|
||||
// Cache the preamble script for all devHtmlPrerender plugins, not just one
|
||||
let reactRefreshPreamble = '';
|
||||
|
||||
/**
|
||||
* Pre-renders the HTML entrypoints when building the extension to connect to the dev server.
|
||||
*/
|
||||
export function devHtmlPrerender(config: InternalConfig): vite.Plugin {
|
||||
return {
|
||||
apply: 'build',
|
||||
name: 'wxt:dev-html-prerender',
|
||||
config() {
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@wxt/reload-html': resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual-modules/reload-html.js',
|
||||
),
|
||||
export function devHtmlPrerender(config: InternalConfig): vite.PluginOption {
|
||||
const htmlReloadId = '@wxt/reload-html';
|
||||
const resolvedHtmlReloadId = resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual-modules/reload-html.js',
|
||||
);
|
||||
const virtualReactRefreshId = '@wxt/virtual-react-refresh';
|
||||
const resolvedVirtualReactRefreshId = '\0' + virtualReactRefreshId;
|
||||
|
||||
return [
|
||||
{
|
||||
apply: 'build',
|
||||
name: 'wxt:dev-html-prerender',
|
||||
config() {
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
[htmlReloadId]: resolvedHtmlReloadId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
// Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
|
||||
// before the paths are replaced with their bundled path
|
||||
transform(code, id) {
|
||||
const server = config.server;
|
||||
if (
|
||||
config.command !== 'serve' ||
|
||||
server == null ||
|
||||
!id.endsWith('.html')
|
||||
)
|
||||
return;
|
||||
|
||||
const { document } = parseHTML(code);
|
||||
|
||||
const pointToDevServer = (
|
||||
querySelector: string,
|
||||
attr: string,
|
||||
): void => {
|
||||
document.querySelectorAll(querySelector).forEach((element) => {
|
||||
const src = element.getAttribute(attr);
|
||||
if (!src) return;
|
||||
|
||||
if (isAbsolute(src)) {
|
||||
element.setAttribute(attr, server.origin + src);
|
||||
} else if (src.startsWith('.')) {
|
||||
const abs = resolve(dirname(id), src);
|
||||
const pathname = relative(config.root, abs);
|
||||
element.setAttribute(attr, `${server.origin}/${pathname}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
pointToDevServer('script[type=module]', 'src');
|
||||
pointToDevServer('link[rel=stylesheet]', 'href');
|
||||
|
||||
// Add a script to add page reloading
|
||||
const reloader = document.createElement('script');
|
||||
reloader.src = htmlReloadId;
|
||||
reloader.type = 'module';
|
||||
document.head.appendChild(reloader);
|
||||
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('transform ' + id);
|
||||
config.logger.debug('Old HTML:\n' + code);
|
||||
config.logger.debug('New HTML:\n' + newHtml);
|
||||
return newHtml;
|
||||
},
|
||||
|
||||
// Pass the HTML through the dev server to add dev-mode specific code
|
||||
async transformIndexHtml(html, ctx) {
|
||||
const server = config.server;
|
||||
if (config.command !== 'serve' || server == null) return;
|
||||
|
||||
const originalUrl = `${server.origin}${ctx.path}`;
|
||||
const name = getEntrypointName(config.entrypointsDir, ctx.filename);
|
||||
const url = `${server.origin}/${name}.html`;
|
||||
const serverHtml = await server.transformIndexHtml(
|
||||
url,
|
||||
html,
|
||||
originalUrl,
|
||||
);
|
||||
const { document } = parseHTML(serverHtml);
|
||||
|
||||
// React pages include a preamble as an unsafe-inline type="module" script to enable fast refresh, as shown here:
|
||||
// https://github.com/wxt-dev/wxt/issues/157#issuecomment-1756497616
|
||||
// Since unsafe-inline scripts are blocked by MV3 CSPs, we need to virtualize it.
|
||||
const reactRefreshScript = Array.from(
|
||||
document.querySelectorAll('script[type=module]'),
|
||||
).find((script) => script.innerHTML.includes('@react-refresh'));
|
||||
if (reactRefreshScript) {
|
||||
// Save preamble to serve from server
|
||||
reactRefreshPreamble = reactRefreshScript.innerHTML;
|
||||
|
||||
// Replace unsafe inline script
|
||||
const virtualScript = document.createElement('script');
|
||||
virtualScript.type = 'module';
|
||||
virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
|
||||
reactRefreshScript.replaceWith(virtualScript);
|
||||
}
|
||||
|
||||
// Change /@vite/client -> http://localhost:3000/@vite/client
|
||||
const viteClientScript = document.querySelector<HTMLScriptElement>(
|
||||
"script[src='/@vite/client']",
|
||||
);
|
||||
if (viteClientScript) {
|
||||
viteClientScript.src = `${server.origin}${viteClientScript.src}`;
|
||||
}
|
||||
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('transformIndexHtml ' + ctx.filename);
|
||||
config.logger.debug('Old HTML:\n' + html);
|
||||
config.logger.debug('New HTML:\n' + newHtml);
|
||||
return newHtml;
|
||||
},
|
||||
},
|
||||
async transform(html, id) {
|
||||
const server = config.server;
|
||||
if (config.command !== 'serve' || server == null || !id.endsWith('.html'))
|
||||
return;
|
||||
|
||||
const originalUrl = `${server.origin}${id}`;
|
||||
const name = getEntrypointName(config.entrypointsDir, id);
|
||||
const url = `${server.origin}/${name}.html`;
|
||||
const serverHtml = await server.transformIndexHtml(
|
||||
url,
|
||||
html,
|
||||
originalUrl,
|
||||
);
|
||||
const { document } = parseHTML(serverHtml);
|
||||
|
||||
const pointToDevServer = (querySelector: string, attr: string): void => {
|
||||
document.querySelectorAll(querySelector).forEach((element) => {
|
||||
const src = element.getAttribute(attr);
|
||||
if (!src) return;
|
||||
|
||||
if (isAbsolute(src)) {
|
||||
element.setAttribute(attr, server.origin + src);
|
||||
} else if (src.startsWith('.')) {
|
||||
const abs = resolve(dirname(id), src);
|
||||
const pathname = relative(config.root, abs);
|
||||
element.setAttribute(attr, `${server.origin}/${pathname}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
pointToDevServer('script[type=module]', 'src');
|
||||
pointToDevServer('link[rel=stylesheet]', 'href');
|
||||
|
||||
// Add a script to add page reloading
|
||||
const reloader = document.createElement('script');
|
||||
reloader.src = '@wxt/reload-html';
|
||||
reloader.type = 'module';
|
||||
document.head.appendChild(reloader);
|
||||
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('Transformed ' + id);
|
||||
config.logger.debug('Old HTML:\n' + html);
|
||||
config.logger.debug('New HTML:\n' + newHtml);
|
||||
return newHtml;
|
||||
{
|
||||
name: 'wxt:virtualize-react-refresh',
|
||||
apply: 'serve',
|
||||
resolveId(id) {
|
||||
if (id === `/${virtualReactRefreshId}`) {
|
||||
return resolvedVirtualReactRefreshId;
|
||||
}
|
||||
// Ignore chunk contents when pre-rendering
|
||||
if (id.startsWith('/chunks/')) {
|
||||
return '\0noop';
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualReactRefreshId) {
|
||||
return reactRefreshPreamble;
|
||||
}
|
||||
if (id === '\0noop') {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"typescript": "^5.1.6",
|
||||
"wxt": "^0.5.0"
|
||||
"wxt": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"typescript": "^5.1.6",
|
||||
"vite-plugin-solid": "^2.7.0",
|
||||
"wxt": "^0.5.0"
|
||||
"wxt": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"svelte-check": "^3.4.6",
|
||||
"tslib": "^2.6.0",
|
||||
"typescript": "^5.1.6",
|
||||
"wxt": "^0.5.0"
|
||||
"wxt": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>WXT 2 + Svelte</h1>
|
||||
<h1>WXT + Svelte</h1>
|
||||
|
||||
<div class="card">
|
||||
<Counter />
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.1.6",
|
||||
"wxt": "^0.5.0"
|
||||
"wxt": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"typescript": "^5.1.6",
|
||||
"vue-tsc": "^1.8.4",
|
||||
"wxt": "^0.5.0"
|
||||
"wxt": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user