So I recently finished building a webextension in React 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.
My extension is currently using a file structure similar to this:
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
This is something I thought was required in my previous attempt at making a web extension 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 + 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
This cannot be understated, 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.
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 in both babel and typescript will let you auto import modules correctly every time.
Redux works as expected but ofcourse it has some quirks when it comes to web-extensions.
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
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 but that is totally up to you and your use case.
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 that parcel will compile. Create that entry point + the web page, and then bundle the whole thing. Then in the extension add this bit of code and it will open the page in the browser.
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***.
While I do not use this feature any more, I have some code from when I did, check out how my manifest looked when I had a
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.