import NewsletterSignup from '../../components/NewsletterSignup.astro';

## Introduction

Creating web extensions that work seamlessly across multiple browsers can be challenging, especially when you want to use popular front-end frameworks like React, Astro, Svelte, and Angular. In this guide, you'll learn how to create a simple web extension using each framework. The extension will display a popup with a customizable message.

PLEASE NOTE: I do not consider myself an expert in all of these frameworks! I have shipped Firefox and Chrome extensions using
both Svelte and React, so I'm not a total newb, but just so you know. If I'm doing anything silly, please do contact
me and let me know. My contact info is on the [front page of my site](https://www.rhelmer.org/).

I've created a GitHub Repository to test these examples, both this blog post and the repository are a work-in-progress
but I've manually tested each. I will continue to flesh out the examples and add automated tests over time:

https://github.com/rhelmer/webext-framework-examples

<NewsletterSignup ctaName="blog-post-bottom" ctaName="blog-post-bottom" title="Master Cross-Browser Extension Development" description="Get practical guides for building browser extensions with modern frameworks. Learn from real implementations, testing insights, and cross-platform compatibility strategies that actually work." />

## Table of contents:

1. [React](#1-react)
2. [Astro](#2-astro)
3. [Svelte](#3-svelte)
4. [Angular](#4-angular)
5. [Testing](#testing)
6. [Packaging and Publishing](#packaging-and-publishing)

## Shared Setup

### Manifest File (`manifest.json`)
```json
{
  "manifest_version": 3,
  "name": "Framework Demo Extension",
  "version": "1.0",
  "description": "A simple web extension built using popular frameworks.",
  "action": {
    "default_popup": "index.html",
    "default_icon": {
      "16": "icon.png",
      "48": "icon.png",
      "128": "icon.png"
    }
  },
  "host_permissions": ["<all_urls>"]
}
```

### Folder Structure
```plaintext
project-root/
  |-- react-extension/
  |-- astro-extension/
  |-- svelte-extension/
  |-- angular-extension/
  |-- shared/
       |-- manifest.json
       |-- icon.png
```

## [1. React](#1-react)

### Commands
```bash
# NOTE: this installs React 19. If you want 18, try: `npx create-react-app react-extension`
npm create vite@latest react-extension --template react
cd react-extension
npm install --save-dev webextension-polyfill @types/webextension-polyfill
```

### Key File Adjustments
- Add `manifest.json` to `public/`.
```bash
cp ../shared/manifest.json public
```

- Create a `src/Popup.tsx` for the popup component:
```tsx
import browser from "webextension-polyfill";

class PopUp extends React.Component {
  handleClick() {
    // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create
    browser.tabs.create("about:blank");
  }

  render() {
    return (
      <>
        <p>Hello world!</p>
        <button type="button" onClick={handleClick}>Create Tab</button>
      </>
    );
  }
}

export default PopUp;
```

- Modify `src/main.tsx`:

```tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import PopUp from './PopUp'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <PopUp />
  </StrictMode>,
)
```

### Build
```bash
npm run build
cp ../shared/{manifest.json,icon.png} ./dist
```

### Test
```
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
```

## [2. Astro](#2-astro)

### Commands
```bash
npm create astro@latest astro-extension
cd astro-extension
npm install --save-dev webextension-polyfill @types/webextension-polyfill
```

### Key File Adjustments
- Create a `./src/components/Popup.astro` component:
```astro
<script>
    import browser from "webextension-polyfill";

    const button = document.getElementById("newtab");
    button?.addEventListener("click", () => {
        browser.tabs.create({ url: "about:blank" });
    });
</script>

<p>Hello world!</p>
<button id="newtab">Open New Tab</button>
```

- Modify `./src/pages/index.astro`:
```astro
---
import PopUp from "../components/Popup.astro";
---

<html>
	<head>
		<title>Popup</title>
	</head>
	<body>
		<PopUp />
	</body>
</html>

```

### Build
```bash
npm run build
cp ./shared/{manifest.json,icon.png} ./dist
```

### Test
```
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
```

## [3. Svelte](#3-svelte)

### Commands
```bash
npx degit sveltejs/template svelte-extension
cd svelte-extension
npm install --save-dev webextension-polyfill
```

### Key File Adjustments
- Create a `./src/Popup.svelte`:
```svelte
<script>
  import browser from "webextension-polyfill";
  let message = "Hello from Svelte!";
</script> 

<h1>{message}</h1>
```

- Modify `./src/main.js`:
```svelte
import App from './Popup.svelte';
// TODO: call the `browser.*` API

const app = new App({
	target: document.body,
});

export default app;
```

### Build
```bash
npm run build
cp ../shared/{manifest.json,icon.png} ./dist
```

### Test
```
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
```

## [4. Angular](#4-angular)

### Commands
```bash
npm install -g @angular/cli
ng new angular-extension --defaults --style=scss
cd angular-extension
npm i --save-dev @types/webextension-polyfill webextension-polyfill
```

### Key File Adjustments
- Create a `./src/app/popup.component.ts` with:
```typescript
import { Component } from '@angular/core';
import browser from 'webextension-polyfill';

@Component({
  selector: 'app-popup',
  template: `<h1>{{message}}</h1>`
})
export class PopupComponent {
  message = 'Hello from Angular!';
  // TODO: call the `browser.*` API
}
```

### Build
```bash
npm run build
cp ../shared/{manifest.json,icon.png} dist/angular-extension
```

### Test
```
cd dist/angular-extension
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
```

## Testing

1. **Load Extensions in Browsers**
   - Chrome: `chrome://extensions`
   - Firefox: `about:debugging#/runtime/this-firefox`
2. **Test Popup Functionality**

Click on the puzzle-piece icon in the toolbar and click the "Framework Demo Extension"

## Packaging and Publishing

### Produce unsigned .zip file for upload to extension stores
```
npx web-ext build
```

### **Chrome Web Store**

Chrome extensions are installed from the [Chrome Web Store](https://chromewebstore.google.com/), and must be hosted
there as well.

### **Firefox Add-ons**

Firefox extensions are installed from [Addons.Mozilla.Org](https://addons.mozilla.org) aka AMO. They may be self-hosted,
but must be uploaded to and signed by AMO first. Hosting on AMO is easiest (and the default).

### **Safari Extensions**

Safari extensions use the normal Apple App Store, and are packaged as macOS/iOS/iPadOS/etc. apps.

[Distributing your Safari web extension](https://developer.apple.com/documentation/safariservices/distributing-your-safari-web-extension)

### **Edge**

Extensions must use [Edge Add-ons](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home).

### **Vivaldi Extensions**

Vivaldi supports extensions from the Chrome Web Store ([source](https://help.vivaldi.com/desktop/appearance-customization/extensions/)).

### **Brave**

Brave supports extensions from the Chrome Web Store ([source](https://support.brave.com/hc/en-us/articles/360017909112-How-can-I-add-extensions-to-Brave)).

### **Opera**

Extensions must use the [Opera addons store](https://addons.opera.com/en/).

## Conclusion

This guide demonstrates how to build a basic web extension using popular frameworks, ensuring compatibility across major browsers. With these setups, you can extend your favorite framework into the realm of web extensions.