mirror of
https://github.com/abhijithvijayan/web-extension-starter.git
synced 2026-01-30 09:48:12 +01:00
feat: migrate firefox to manifest v3
This commit is contained in:
parent
9ef7f6ce9a
commit
e14efb7c4b
@ -47,9 +47,15 @@
|
|||||||
|
|
||||||
## Browser Support
|
## Browser Support
|
||||||
|
|
||||||
|
This starter uses **Manifest V3** for all browsers.
|
||||||
|
|
||||||
| [](/) | [](/) | [](/) | [](/) | [](/) |
|
| [](/) | [](/) | [](/) | [](/) | [](/) |
|
||||||
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||||
| 88 & later | 109 & later | 36 & later | 88 & later | Latest |
|
| 88+ (Jan 2021) | 109+ (Jan 2023) | 74+ (Chromium-based) | 88+ (Chromium-based) | Latest (Chromium-based) |
|
||||||
|
|
||||||
|
> **Note**: Firefox 109+ is required for Manifest V3 support with ES modules in background scripts.
|
||||||
|
>
|
||||||
|
> Need to support older Firefox versions? See [Firefox MV2 Guide](docs/FIREFOX_MV2.md) for using Manifest V2 with Firefox.
|
||||||
|
|
||||||
## Used by extensions in production that has over 100,000+ users.
|
## Used by extensions in production that has over 100,000+ users.
|
||||||
|
|
||||||
|
|||||||
205
docs/FIREFOX_MV2.md
Normal file
205
docs/FIREFOX_MV2.md
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# Using Manifest V2 for Firefox
|
||||||
|
|
||||||
|
By default, this starter uses **Manifest V3** for all browsers. However, if you need to support older Firefox versions (< 109) or prefer MV2 for Firefox, follow this guide.
|
||||||
|
|
||||||
|
## Why Use MV2 for Firefox?
|
||||||
|
|
||||||
|
- **Older Firefox support**: Firefox 109+ is required for MV3
|
||||||
|
- **Extended support**: Mozilla has not announced a deprecation date for MV2
|
||||||
|
- **API differences**: Some APIs work differently between MV2 and MV3
|
||||||
|
|
||||||
|
## Required Changes
|
||||||
|
|
||||||
|
### 1. Update `source/manifest.json`
|
||||||
|
|
||||||
|
Replace the unified manifest with browser-specific versions:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"__chrome__manifest_version": 3,
|
||||||
|
"__firefox__manifest_version": 2,
|
||||||
|
"name": "Sample WebExtension",
|
||||||
|
"version": "0.0.0",
|
||||||
|
|
||||||
|
"icons": {
|
||||||
|
"16": "assets/icons/favicon-16.png",
|
||||||
|
"32": "assets/icons/favicon-32.png",
|
||||||
|
"48": "assets/icons/favicon-48.png",
|
||||||
|
"128": "assets/icons/favicon-128.png"
|
||||||
|
},
|
||||||
|
"description": "Sample description",
|
||||||
|
"homepage_url": "https://github.com/abhijithvijayan/web-extension-starter",
|
||||||
|
"short_name": "Sample Name",
|
||||||
|
|
||||||
|
"__chrome__permissions": [
|
||||||
|
"activeTab",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
|
||||||
|
"__chrome__optional_permissions": [],
|
||||||
|
|
||||||
|
"__chrome__host_permissions": [],
|
||||||
|
|
||||||
|
"__chrome__optional_host_permissions": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
|
||||||
|
"__firefox__permissions": [
|
||||||
|
"activeTab",
|
||||||
|
"storage",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
|
||||||
|
"__chrome__content_security_policy": {
|
||||||
|
"extension_pages": "script-src 'self'; object-src 'self';"
|
||||||
|
},
|
||||||
|
"__firefox__content_security_policy": "script-src 'self'; object-src 'self'",
|
||||||
|
|
||||||
|
"__chrome|firefox__author": "abhijithvijayan",
|
||||||
|
|
||||||
|
"__firefox__applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "{754FB1AD-CC3B-4856-B6A0-7786F8CA9D17}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"__chrome__minimum_chrome_version": "88",
|
||||||
|
|
||||||
|
"__chrome__action": {
|
||||||
|
"default_popup": "Popup/popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "assets/icons/favicon-16.png",
|
||||||
|
"32": "assets/icons/favicon-32.png",
|
||||||
|
"48": "assets/icons/favicon-48.png",
|
||||||
|
"128": "assets/icons/favicon-128.png"
|
||||||
|
},
|
||||||
|
"default_title": "tiny title"
|
||||||
|
},
|
||||||
|
|
||||||
|
"__firefox__browser_action": {
|
||||||
|
"default_popup": "Popup/popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "assets/icons/favicon-16.png",
|
||||||
|
"32": "assets/icons/favicon-32.png",
|
||||||
|
"48": "assets/icons/favicon-48.png",
|
||||||
|
"128": "assets/icons/favicon-128.png"
|
||||||
|
},
|
||||||
|
"default_title": "tiny title",
|
||||||
|
"browser_style": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"__chrome__options_page": "Options/options.html",
|
||||||
|
"options_ui": {
|
||||||
|
"page": "Options/options.html",
|
||||||
|
"open_in_tab": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"background": {
|
||||||
|
"__chrome__service_worker": "assets/js/background.bundle.js",
|
||||||
|
"__chrome__type": "module",
|
||||||
|
"__firefox__scripts": [
|
||||||
|
"assets/js/background.bundle.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"run_at": "document_start",
|
||||||
|
"matches": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"css": [],
|
||||||
|
"js": [
|
||||||
|
"assets/js/contentScript.bundle.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"__firefox__web_accessible_resources": [
|
||||||
|
"assets/*"
|
||||||
|
],
|
||||||
|
|
||||||
|
"__chrome__web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["assets/*"],
|
||||||
|
"matches": ["http://*/*", "https://*/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Update `vite.config.ts`
|
||||||
|
|
||||||
|
Firefox MV2 background scripts don't support ES modules. Update the `buildIIFEScripts` plugin to also build the background script as IIFE for Firefox:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Build scripts as IIFE (no ES module imports)
|
||||||
|
// Content scripts can't use ES modules when injected via manifest
|
||||||
|
buildIIFEScripts({
|
||||||
|
scripts: [
|
||||||
|
{
|
||||||
|
name: 'contentScript',
|
||||||
|
entry: path.resolve(sourcePath, 'ContentScript/index.ts'),
|
||||||
|
},
|
||||||
|
// Firefox MV2 background scripts don't support ES modules
|
||||||
|
...(targetBrowser === 'firefox'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'background',
|
||||||
|
entry: path.resolve(sourcePath, 'Background/index.ts'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
outDir: getOutDir(),
|
||||||
|
isDevelopment,
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update the rollup input to exclude background for Firefox (since it's built as IIFE):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
popup: path.resolve(sourcePath, 'Popup/popup.html'),
|
||||||
|
options: path.resolve(sourcePath, 'Options/options.html'),
|
||||||
|
// Background script: Chrome MV3 uses ES modules (service worker)
|
||||||
|
// Firefox MV2 is built separately as IIFE via buildIIFEScripts plugin
|
||||||
|
...(targetBrowser !== 'firefox'
|
||||||
|
? { background: path.resolve(sourcePath, 'Background/index.ts') }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
// ... rest of config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences: MV2 vs MV3
|
||||||
|
|
||||||
|
| Feature | MV2 (Firefox) | MV3 (Chrome/Firefox) |
|
||||||
|
|---------|---------------|----------------------|
|
||||||
|
| Background | `background.scripts` | `background.service_worker` (Chrome) / `background.scripts` with `type: module` (Firefox) |
|
||||||
|
| Action | `browser_action` | `action` |
|
||||||
|
| Host permissions | In `permissions` array | Separate `host_permissions` array |
|
||||||
|
| Web accessible resources | String array | Object array with `resources` and `matches` |
|
||||||
|
| CSP | String | Object with `extension_pages` |
|
||||||
|
| ES modules in background | Not supported | Supported |
|
||||||
|
|
||||||
|
## Browser Support with MV2
|
||||||
|
|
||||||
|
When using MV2 for Firefox:
|
||||||
|
|
||||||
|
| Browser | Version | Manifest |
|
||||||
|
|---------|---------|----------|
|
||||||
|
| Chrome | 88+ | MV3 |
|
||||||
|
| Firefox | 48+ | MV2 |
|
||||||
|
| Edge | 88+ | MV3 |
|
||||||
|
| Opera | 74+ | MV3 |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `webextension-polyfill` library handles API differences between browsers
|
||||||
|
- Content scripts work the same way in both MV2 and MV3 (IIFE format)
|
||||||
|
- Test thoroughly on both browsers when using mixed manifest versions
|
||||||
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"__chrome__manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"__firefox__manifest_version": 2,
|
|
||||||
"name": "Sample WebExtension",
|
"name": "Sample WebExtension",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
|
||||||
@ -14,12 +13,12 @@
|
|||||||
"homepage_url": "https://github.com/abhijithvijayan/web-extension-starter",
|
"homepage_url": "https://github.com/abhijithvijayan/web-extension-starter",
|
||||||
"short_name": "Sample Name",
|
"short_name": "Sample Name",
|
||||||
|
|
||||||
"__chrome__permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"__chrome__optional_permissions": [],
|
"optional_permissions": [],
|
||||||
|
|
||||||
"__chrome__host_permissions": [],
|
"__chrome__host_permissions": [],
|
||||||
|
|
||||||
@ -28,29 +27,27 @@
|
|||||||
"https://*/*"
|
"https://*/*"
|
||||||
],
|
],
|
||||||
|
|
||||||
"__firefox__permissions": [
|
"__firefox__optional_host_permissions": [
|
||||||
"activeTab",
|
|
||||||
"storage",
|
|
||||||
"http://*/*",
|
"http://*/*",
|
||||||
"https://*/*"
|
"https://*/*"
|
||||||
],
|
],
|
||||||
|
|
||||||
"__chrome__content_security_policy": {
|
"content_security_policy": {
|
||||||
"extension_pages": "script-src 'self'; object-src 'self';"
|
"extension_pages": "script-src 'self'; object-src 'self';"
|
||||||
},
|
},
|
||||||
"__firefox__content_security_policy": "script-src 'self'; object-src 'self'",
|
|
||||||
|
|
||||||
"__chrome|firefox__author": "abhijithvijayan",
|
"__chrome|firefox__author": "abhijithvijayan",
|
||||||
|
|
||||||
"__firefox__applications": {
|
"__firefox__browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "{754FB1AD-CC3B-4856-B6A0-7786F8CA9D17}"
|
"id": "{754FB1AD-CC3B-4856-B6A0-7786F8CA9D17}",
|
||||||
|
"strict_min_version": "109.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"__chrome__minimum_chrome_version": "88",
|
"__chrome__minimum_chrome_version": "88",
|
||||||
|
|
||||||
"__chrome__action": {
|
"action": {
|
||||||
"default_popup": "Popup/popup.html",
|
"default_popup": "Popup/popup.html",
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"16": "assets/icons/favicon-16.png",
|
"16": "assets/icons/favicon-16.png",
|
||||||
@ -61,18 +58,6 @@
|
|||||||
"default_title": "tiny title"
|
"default_title": "tiny title"
|
||||||
},
|
},
|
||||||
|
|
||||||
"__firefox__browser_action": {
|
|
||||||
"default_popup": "Popup/popup.html",
|
|
||||||
"default_icon": {
|
|
||||||
"16": "assets/icons/favicon-16.png",
|
|
||||||
"32": "assets/icons/favicon-32.png",
|
|
||||||
"48": "assets/icons/favicon-48.png",
|
|
||||||
"128": "assets/icons/favicon-128.png"
|
|
||||||
},
|
|
||||||
"default_title": "tiny title",
|
|
||||||
"browser_style": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"__chrome__options_page": "Options/options.html",
|
"__chrome__options_page": "Options/options.html",
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "Options/options.html",
|
"page": "Options/options.html",
|
||||||
@ -82,9 +67,8 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"__chrome__service_worker": "assets/js/background.bundle.js",
|
"__chrome__service_worker": "assets/js/background.bundle.js",
|
||||||
"__chrome__type": "module",
|
"__chrome__type": "module",
|
||||||
"__firefox__scripts": [
|
"__firefox__scripts": ["assets/js/background.bundle.js"],
|
||||||
"assets/js/background.bundle.js"
|
"__firefox__type": "module"
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
@ -101,14 +85,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"__firefox__web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
"assets/*"
|
|
||||||
],
|
|
||||||
|
|
||||||
"__chrome__web_accessible_resources": [
|
|
||||||
{
|
{
|
||||||
"resources": [ "assets/*" ],
|
"resources": ["assets/*"],
|
||||||
"matches": [ "http://*/*", "https://*/*" ]
|
"matches": ["http://*/*", "https://*/*"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -9,14 +9,15 @@ import WextManifest from 'vite-plugin-wext-manifest';
|
|||||||
|
|
||||||
import type {Plugin} from 'vite';
|
import type {Plugin} from 'vite';
|
||||||
|
|
||||||
// Custom plugin to build content scripts as IIFE (self-contained, no ES module imports)
|
// Custom plugin to build scripts as IIFE (self-contained, no ES module imports)
|
||||||
function buildContentScripts(options: {
|
// Used for scripts that can't use ES modules (e.g., content scripts injected via manifest)
|
||||||
|
function buildIIFEScripts(options: {
|
||||||
scripts: {name: string; entry: string}[];
|
scripts: {name: string; entry: string}[];
|
||||||
outDir: string;
|
outDir: string;
|
||||||
isDevelopment: boolean;
|
isDevelopment: boolean;
|
||||||
}): Plugin {
|
}): Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'build-content-scripts',
|
name: 'build-iife-scripts',
|
||||||
async writeBundle() {
|
async writeBundle() {
|
||||||
for (const script of options.scripts) {
|
for (const script of options.scripts) {
|
||||||
await build({
|
await build({
|
||||||
@ -94,7 +95,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
// delete previous built compressed file
|
// delete previous built compressed file
|
||||||
clean({
|
clean({
|
||||||
targetFiles: [path.resolve(destPath, getExtensionZipFileName())],
|
targetFiles: [path.resolve(destPath, getExtensionZipFileName())],
|
||||||
}),
|
}) as Plugin,
|
||||||
|
|
||||||
// Run typescript checker in worker thread
|
// Run typescript checker in worker thread
|
||||||
checker({
|
checker({
|
||||||
@ -109,9 +110,9 @@ export default defineConfig(({ mode }) => {
|
|||||||
usePackageJSONVersion: true,
|
usePackageJSONVersion: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Build content scripts as IIFE (no ES module imports)
|
// Build scripts as IIFE (no ES module imports)
|
||||||
// Content scripts can't use ES modules in manifest-injected scripts
|
// Content scripts can't use ES modules when injected via manifest
|
||||||
buildContentScripts({
|
buildIIFEScripts({
|
||||||
scripts: [
|
scripts: [
|
||||||
{
|
{
|
||||||
name: 'contentScript',
|
name: 'contentScript',
|
||||||
@ -146,9 +147,10 @@ export default defineConfig(({ mode }) => {
|
|||||||
// Vite will find the <script> tag inside and bundle it.
|
// Vite will find the <script> tag inside and bundle it.
|
||||||
popup: path.resolve(sourcePath, 'Popup/popup.html'),
|
popup: path.resolve(sourcePath, 'Popup/popup.html'),
|
||||||
options: path.resolve(sourcePath, 'Options/options.html'),
|
options: path.resolve(sourcePath, 'Options/options.html'),
|
||||||
// Background script (service worker in MV3)
|
// Background script (service worker in Chrome, background script in Firefox)
|
||||||
|
// Both MV3 implementations support ES modules
|
||||||
background: path.resolve(sourcePath, 'Background/index.ts'),
|
background: path.resolve(sourcePath, 'Background/index.ts'),
|
||||||
// Note: contentScript is built separately as IIFE via buildContentScripts plugin
|
// Note: contentScript is built separately as IIFE via buildIIFEScripts plugin
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
@ -163,16 +165,11 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
terserOptions: {
|
|
||||||
mangle: true,
|
|
||||||
compress: {
|
|
||||||
drop_console: true,
|
|
||||||
drop_debugger: true,
|
|
||||||
},
|
|
||||||
format: {
|
|
||||||
comments: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// esbuild options - drop console/debugger in production
|
||||||
|
esbuild: mode === 'production' ? {
|
||||||
|
drop: ['console', 'debugger'],
|
||||||
|
} : {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user