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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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,
|
||||
"__firefox__manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "Sample WebExtension",
|
||||
"version": "0.0.0",
|
||||
|
||||
@ -14,12 +13,12 @@
|
||||
"homepage_url": "https://github.com/abhijithvijayan/web-extension-starter",
|
||||
"short_name": "Sample Name",
|
||||
|
||||
"__chrome__permissions": [
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"storage"
|
||||
],
|
||||
|
||||
"__chrome__optional_permissions": [],
|
||||
"optional_permissions": [],
|
||||
|
||||
"__chrome__host_permissions": [],
|
||||
|
||||
@ -28,29 +27,27 @@
|
||||
"https://*/*"
|
||||
],
|
||||
|
||||
"__firefox__permissions": [
|
||||
"activeTab",
|
||||
"storage",
|
||||
"__firefox__optional_host_permissions": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
|
||||
"__chrome__content_security_policy": {
|
||||
"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": {
|
||||
"__firefox__browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{754FB1AD-CC3B-4856-B6A0-7786F8CA9D17}"
|
||||
"id": "{754FB1AD-CC3B-4856-B6A0-7786F8CA9D17}",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
},
|
||||
|
||||
"__chrome__minimum_chrome_version": "88",
|
||||
|
||||
"__chrome__action": {
|
||||
"action": {
|
||||
"default_popup": "Popup/popup.html",
|
||||
"default_icon": {
|
||||
"16": "assets/icons/favicon-16.png",
|
||||
@ -61,18 +58,6 @@
|
||||
"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",
|
||||
@ -82,9 +67,8 @@
|
||||
"background": {
|
||||
"__chrome__service_worker": "assets/js/background.bundle.js",
|
||||
"__chrome__type": "module",
|
||||
"__firefox__scripts": [
|
||||
"assets/js/background.bundle.js"
|
||||
]
|
||||
"__firefox__scripts": ["assets/js/background.bundle.js"],
|
||||
"__firefox__type": "module"
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
@ -101,14 +85,10 @@
|
||||
}
|
||||
],
|
||||
|
||||
"__firefox__web_accessible_resources": [
|
||||
"assets/*"
|
||||
],
|
||||
|
||||
"__chrome__web_accessible_resources": [
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [ "assets/*" ],
|
||||
"matches": [ "http://*/*", "https://*/*" ]
|
||||
"resources": ["assets/*"],
|
||||
"matches": ["http://*/*", "https://*/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -9,14 +9,15 @@ import WextManifest from 'vite-plugin-wext-manifest';
|
||||
|
||||
import type {Plugin} from 'vite';
|
||||
|
||||
// Custom plugin to build content scripts as IIFE (self-contained, no ES module imports)
|
||||
function buildContentScripts(options: {
|
||||
// Custom plugin to build scripts as IIFE (self-contained, no ES module imports)
|
||||
// Used for scripts that can't use ES modules (e.g., content scripts injected via manifest)
|
||||
function buildIIFEScripts(options: {
|
||||
scripts: {name: string; entry: string}[];
|
||||
outDir: string;
|
||||
isDevelopment: boolean;
|
||||
}): Plugin {
|
||||
return {
|
||||
name: 'build-content-scripts',
|
||||
name: 'build-iife-scripts',
|
||||
async writeBundle() {
|
||||
for (const script of options.scripts) {
|
||||
await build({
|
||||
@ -94,7 +95,7 @@ export default defineConfig(({ mode }) => {
|
||||
// delete previous built compressed file
|
||||
clean({
|
||||
targetFiles: [path.resolve(destPath, getExtensionZipFileName())],
|
||||
}),
|
||||
}) as Plugin,
|
||||
|
||||
// Run typescript checker in worker thread
|
||||
checker({
|
||||
@ -109,9 +110,9 @@ export default defineConfig(({ mode }) => {
|
||||
usePackageJSONVersion: true,
|
||||
}),
|
||||
|
||||
// Build content scripts as IIFE (no ES module imports)
|
||||
// Content scripts can't use ES modules in manifest-injected scripts
|
||||
buildContentScripts({
|
||||
// Build scripts as IIFE (no ES module imports)
|
||||
// Content scripts can't use ES modules when injected via manifest
|
||||
buildIIFEScripts({
|
||||
scripts: [
|
||||
{
|
||||
name: 'contentScript',
|
||||
@ -146,9 +147,10 @@ export default defineConfig(({ mode }) => {
|
||||
// Vite will find the <script> tag inside and bundle it.
|
||||
popup: path.resolve(sourcePath, 'Popup/popup.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'),
|
||||
// Note: contentScript is built separately as IIFE via buildContentScripts plugin
|
||||
// Note: contentScript is built separately as IIFE via buildIIFEScripts plugin
|
||||
},
|
||||
|
||||
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