Building a Web Extension in React
Tags: extensions, react, webdev, programming
Published: 2020-03-23 19:23:06

---
title: Building a Web Extension in React
published: true
description: Building an extension with React is pretty straightforward but has some wired quirks.
tags: extensions, react, webdev, programming
---


![Extension](https://dev-to-uploads.s3.amazonaws.com/i/82hh4m8faddj8yz7ozvu.png)

So I recently finished [building a webextension in React](https://github.com/fav-sh/extension) and I wanted to share my experience and hopefully help anyone down the road who is having trouble. I will be covering my setup in React though I do assume the reader has some prior knowledge of how to setup a React project.

## The Setup 

 My extension is currently using a file structure similar to this:

Packages:

```
parcel
parcel-plugin-web-ext

# Regular Packages
react
react-dom
redux
redux-persist
typescript
...

```


```
/entry/extension.html
/entry/extension.js
/js/App.js
manifest.json

```


`extension.html` is a basic html setup that creates a div with the class `root` attached and sets up the extension.js script which loads all the css, redux stores, this, that, other thing, etc as well as App.js which is where you might place the basis of the UI.

Now you can point parcel at extension.html using `yarn parcel entry/extension.html`. From here you can continue configuring the extension, adding pages, etc.

Please note this not everything you will need in order to have a react extension up and running, but the bare minimum + any React / Preact



## The Quirks


### Webpack not required

This is something I thought was required in my [previous attempt at making a web extension](https://github.com/fav-sh/legacy-extension-code/blob/master/webpack/chrome.dev.js) but I eventually figured out that using Webpack can be avoided.

Webpack isn't bad in itself, it's a hugely powerful bundler and can handle just about anything you throw at it. But that comes at the cost of simplicity and webpack can be very complex to setup and finicky to use if not setup properly.

The better setup in my opinion is [parcel](https://parceljs.org/) + [parcel-plugin-web-extension](https://github.com/kevincharm/parcel-plugin-web-extension). This combo of the bundler plus a plugin that allows reading of extension manifests and compiling from that. This is great as it allows for zero configuration, it almost baffled me the first time I tried it. Just point it at a manifest, bundle, done. [Check out how I implemented it here](https://github.com/fav-sh/extension/blob/master/package.json#L9)

### web-ext is essential

This cannot be understated, [web-ext](https://www.npmjs.com/package/web-ext) is a tool that will help launch your extension on Firefox and Chrome after it is built. It will spin up a copy of firefox or chrome with a fresh profile and install the extension, as well as open the browser for you. It can do much more but those are the basics. You can read more about it here, and see how I integrated it into my workflow [here](https://github.com/fav-sh/extension/blob/master/package.json#L14).


### Typescript + Babel

This is another thing I learned. Yes I get the great debate for typescript or no typescript. For small static sites I say no typescript required but for a complex web-extension, Typescript can be your best friend. One other thing is Babel. Parcel has Babel transpiling already in place but there are a few things I always add right off the bat. Namely adding in [module resolution](https://github.com/tleunen/babel-plugin-module-resolver) in both [babel](https://github.com/fav-sh/extension/blob/master/.babelrc) and [typescript](https://github.com/fav-sh/extension/blob/master/tsconfig.json) will let you auto import modules correctly every time.

### Redux Quirks!

Redux works as expected but ofcourse it has some quirks when it comes to web-extensions.

1. Redux Devtools: I haven't been able to get remote redux devtools to work in webextensions so my solution thus far has been to simply add the monitors into the page itself. Activated using CTRL + P when the extension is open. Check out how I implemented it [here](https://github.com/fav-sh/extension/blob/master/src/js/store/DevTools.tsx)

2. Redux persist: The problem that redux persist runs into is that it doesn't use the same storage as web pages. So you have to use an extension for Redux Persist or roll your own storage adapter to make it work. I decided to [roll my own adapter](https://github.com/fav-sh/extension/blob/master/src/js/browser/storageAdapter.ts) but that is totally up to you and your use case.

### Opening Browser Windows

You know how some extensions to open a webpage for settings? It took me forever to figure out how to do this properly. My current solution is to [add anther entry point](https://github.com/fav-sh/extension/blob/master/src/entry/settings.html) that [parcel will compile](https://github.com/fav-sh/extension/blob/master/package.json#L10).  Create that entry point + the web page, and then bundle the whole thing. Then in the extension add [this bit of code](https://github.com/fav-sh/extension/blob/master/src/js/browser/openSettings.ts) and it will open the page in the browser.

### Callback URL's

Callback URL's for extensions are wired. Your callback URL depends on the ID of your extension and extension ID's have a few limitations:

* They are not constant in development by default
* They differ for dev and prod versions and per browser
* You can create a consistent callback URL for dev but this is different for each browser.

The problem with creating a constant callback URL for an extension designed for Chrome + Firefox is that it requires that you actually have 4 callback URL's.

* Firefox Development
* Chromium Development
* Firefox Production
* Chromium Production

If you are trying to authenticate with an application that only accepts a single callback URL, you end up having to create 4 applications, one for each browser/environment, and managing that is a pain in the a***.

[How this is done in Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings)

[How this is done in Chrome](https://developer.chrome.com/apps/manifest/key)

While I do not use this feature any more, I have some code from when I did, check out [how my manifest looked](https://github.com/fav-sh/extension/blob/81f7e5c5645ebeb3d742a850401220d9ad5cc542/src/manifest.json) when I had a `key` and `browser_specific_settings` included.


### Differences between Chrome and Firefox

If you are planning on building an extension for Chrome + Firefox, then you will have to plan on Firefox having it's browser functions under `browser.*` and Chromium having it under `chrome.*`. Though the API under the hood is mostly the same, it's important to note that Firefox uses promises and Chrome uses callbacks. While a minor difference, this will prove to be a challenging problem if your extension has deep browser integration.

I solved this problem by [passing a TARGET environment variable](https://github.com/fav-sh/extension/blob/master/package.json#L9) to my application, then [routing within the application](https://github.com/fav-sh/extension/blob/master/src/js/browser/getTabInfo.ts#L14) depending on the value of that environment variable.

[More on this at MDN](https://extensionworkshop.com/documentation/develop/porting-a-google-chrome-extension/)


[Generally more info on web extensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)