Building my first browser extension: should I use a framework?
A few months ago, the Product team I work with came up with this new project in mind: let’s create a Browser extension to help our users.
All the tech team got excited about it as it was new to all of us.
But then, during our first technical refinement, one of the developers raised a very good question: should we use a framework or not?
So here is this article, to help you with this question if you face the same situation.
PS: This article will focus solely on Chrome, as it is currently the only browser we have experience with.
⚠️ Spoiler ⚠️
We decided to use a framework!
We chose Plasmo because the other options available were not suitable for us:
— CRXJS: Chrome specific, we had the ambition to deploy to other browsers in the future;
— Bedframe: early access only.
In this article, I will compare building an extension with Plasmo and without it.
Please feel free to compare the same bullet points on Bedframe and CRXJS, and share them in the comments.
Here is why we chose to use a framework:
Better developer experience: live reload
With Plasmo, you have got live reloading.
Whereas without it, you need to close and reopen the extension to see your changes.
Easier QA validation
Chrome extensions are subject to a review to be published. It can take a few days to be reviewed (depending on the content of your extension).
But then how do you do QA?
Here are the 2 ways:
Without Plasmo framework:
— the developer bundles the extension => got a zip file
—the developer sends it to QA
— QA manually uploads the new file in Chrome extension developer mode
✅ Free
🚨 Error-prone and time-consuming for both dev and QA teams
With Plasmo framework:
Plasmo proposes a service (Plasmo Itero) to have a staging environment for your extension.
Process: at merge, a CD job is automatically run, it builds and deploys the browser extension on a private store. QA testers install the extension once, and then just need to update the extension on chrome://extensions/ to get the last update.
✅ Fully automated process
🟠 Paid service: $65/month
Automatic permissions management
One of our learning on Chrome publication is “The more permissions your browser extension asks, the more time the Google review will take”
(and from our experience it can be quite long, for us it was often 1 week )
As a consequence, you do not want to mess up by asking for permissions that are not required.
It is yet another Plasmo framework benefit: as the manifest.json file (the file containing all your browser extension configuration, permissions included) is generated by Plasmo, permissions asked by your extension reflect the permissions actually needed by the codebase, nothing more.
One codebase for all browsers
One promise of Plasmo is: “you can build an extension once and easily target it to multiple browsers”.
We did not experience it, so I can not give feedback on that point. But that was an additional point that made us choose to use Plasmo.
Easier communication with external services
Finally, you will probably need your extension to communicate with external services (your API, external APIs, etc).
If you call the external API naively from the current page, chances are you will run into a CORS issue: your request origin is the current page and your API is expecting another origin. So to bypass this, you can use a background service worker to proxy the communication with your API.
- Without framework:
// manifest.json
{
...
"background": {
"service_worker": "service-worker.js"
},
...
}
// service-worker.js
function uploadAnswerHandler(req, sender, sendResponse) {
if (req.name === "uploadAnswer") {
await fetch('https://myApi.com', { body: req.body });
sendResponse({ ok: "true"});
}
}
chrome.runtime.onMessage.addListener(uploadAnswerHandler);
// ... later for cleanup
chrome.runtime.onMessage.removeListener(uploadAnswerHandler)
// popup.js
await chrome.runtime.sendMessage({
name: 'uploadAnswer',
body: {
answer: 'yes',
},
})
- With Plasmo:
// src/background/messages/uploadAnswer.ts
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
await fetch('https://myApi.com', { body: req.body });
return res.send({ ok: true });
};
export default handler;
// src/contents/components/PopUp.tsx
await sendToBackground({
name: 'uploadAnswer', // equals to message file name
body: {
answer: 'yes',
},
});
Advantages here:
- Plasmo defines listeners and does the cleanup for you.
- Again, Plasmo‘s code is not Chrome specific
- Code organisation: your messages are split into different files, and manifest.json background key is generated automatically for you
- Message name is type-safe, which can save you not pleasant debugging time:
await sendToBackground({
name: 'uplodAnswer', // typescript issue would be raised here
body: {
answer: 'yes',
},
});
Before I leave you, one advice:
I advise you to try a first publication to Chrome Web Store as soon as possible (you will be able to publish it as Private as a first step).
It will allow you to:
- See the list of elements required by the store (description, category, images with precise sizes, etc) and ask the right team in your company to give you those elements on time for your expected release in production.
- Get a sense of the time a publication review can take.
If you have any questions, please comment on this article, I hope we can have an answer to help you with our experience of building a browser extension.