Compare commits

..

8 Commits

Author SHA1 Message Date
GitHub Actions 0369316463 chore(release): v0.7.5 2023-10-13 23:13:01 +00:00
Aaron 8b74291d18 fix: Add browser_style to popup options (#165) 2023-10-13 18:06:40 -05:00
Aaron 739b738100 fix: Ignore non-manifest fields when merging content script entries (#164) 2023-10-13 18:01:13 -05:00
Aaron 95442dd39b fix: More consistent version_name generation between browsers (#163) 2023-10-13 17:37:08 -05:00
GitHub Actions 4f82645c08 chore(release): v0.7.4 2023-10-11 15:59:17 +00:00
Aaron d200c376f8 fix: Support react-refresh when pre-rendering HTML pages in dev mode (#158) 2023-10-11 10:52:48 -05:00
Aaron Klinker b58fb02016 docs: Add migration guides 2023-10-07 10:49:31 -05:00
Aaron e1bab6c746 chore: Upgrade templates to v0.7 (#156) 2023-10-07 10:00:36 -05:00
26 changed files with 415 additions and 149 deletions
+4 -2
View File
@@ -2,5 +2,7 @@ coverage:
status:
project:
default:
target: 80%
threshold: 1%
informational: true
patch:
default:
informational: true
+1
View File
@@ -13,3 +13,4 @@ CHANGELOG.md
*.txt
_gitignore
_redirects
*.svelte
+26
View File
@@ -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)
+6
View File
@@ -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' },
],
},
+1
View File
@@ -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 -1
View File
@@ -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
+57
View File
@@ -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.
+1 -1
View File
@@ -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)
+97 -35
View File
@@ -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();
},
);
});
});
+7 -7
View File
@@ -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'),
+4 -4
View File
@@ -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\\"]}]}"
`);
});
});
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+10 -3
View File
@@ -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 {
+3
View File
@@ -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);
});
+19 -10
View File
@@ -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)
+9 -6
View File
@@ -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;
+137 -58
View File
@@ -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 '';
}
},
},
};
];
}
+1 -1
View File
@@ -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"
}
}
+1 -1
View File
@@ -20,6 +20,6 @@
"devDependencies": {
"typescript": "^5.1.6",
"vite-plugin-solid": "^2.7.0",
"wxt": "^0.5.0"
"wxt": "^0.7.0"
}
}
+1 -1
View File
@@ -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 />
+1 -1
View File
@@ -16,6 +16,6 @@
},
"devDependencies": {
"typescript": "^5.1.6",
"wxt": "^0.5.0"
"wxt": "^0.7.0"
}
}
+1 -1
View File
@@ -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"
}
}