How To Build a Chrome Extension Using React

Rate this content
Bookmark
Slides

Last year, as part of my work at Bloomberg, I was tasked with building a Chrome Extension, and persuaded my team to let me build the extension in React instead of vanilla JavaScript. This had several advantages, such as making the code more maintainable and easier to understand, as well as enabling us to use a pre-existing component library for consistent styling across the company, reducing the amount of CSS code we had to write. In my presentation, I will show you a sample Chrome Extension for personal use that I built using React and Material UI that auto-responds to emails sent in Gmail and allows you to take notes regarding the conversation. I will also share steps to build your own Chrome Extension using React, and lessons learned along the way!

18 min
06 Jun, 2023

Video Summary and Transcription

This Talk is about building a Chrome extension using React. It covers the setup, permissions, and behaviors of the extension, as well as calling external services and running the extension locally. The Talk also discusses the process of publishing the extension and includes a demo of an email management system. Overall, it provides a comprehensive overview of building and deploying a Chrome extension using React.

1. Introduction and Overview

Short description:

This part introduces the speaker and the topic of the talk, which is building a Chrome extension using React. The speaker shares their background and experience, as well as the purpose of the extension they built. They mention the features of the extension and their plan to discuss the steps to build a Chrome extension using React. They also mention briefly touching upon Firefox add-ons and compatibility.

Hi everyone, and thank you for tuning in. Also, a big thank you to the conference committee for accepting my talk here. My talk is about how to build a Chrome extension using React.

A little bit about me. My name is Adina. I am a full-stack software engineer at Bloomberg in the HR engineering team, where I focus primarily on front-end items. I am originally from Romania, but I have been in the United States for almost half of my life. I currently live in New York City with my husband and my dog, and my dog's name is Stacks and it's important because we'll be responding to a few emails that Stacks often receives in his inbox and which he might wish to postpone.

Last year, as part of my work at Bloomberg, I was tasked with building a Chrome extension. We decided to build the extension in React instead of vanilla JavaScript, as that would make the code more understandable and maintainable, as well as easier to style by using Bloomberg-specific React components. In this presentation, I will show you a sample extension that I dubbed Email Postponer, that I built specifically to demo here. This extension auto-responds to emails sent in Gmail based on user input, as well as allows users to add private notes to their emails. I will also share steps to build your own Chrome extension using React and lessons learned along the way. I will also briefly touch upon Firefox add-ons and the compatibility between the two.

2. Extension Overview and Setup

Short description:

This part provides an overview of the Email Postponer extension, including its features and how it interacts with Chrome. It explains the steps to set up the extension using Create React app and TypeScript, as well as the use of the Chrome API. The extension uses a manifest.json file and follows manifest version 3. It also discusses the role of the service worker and content scripts in the extension's functionality. The content scripts enable communication between the React code and the background script, and the extension is designed to open as a side panel on Gmail. The section concludes by mentioning the need to add web accessible resources to the extension.

For the agenda, I will start by giving an overview of the extension that I built to demo here, then explain the steps to set up an extension in React, highlight some interesting capabilities that I gave the Email Postponer extension, talk about running the extension locally, as well as publishing it to the store.

Then I will briefly touch upon cross-browser compatibility and how to convert a Chrome extension into a Firefox add-on. And I will end by showing a demo video of the Email Postponer extension in action.

So, for an overview, the Email Postponer extension looks like a web page, as you can see on the right, only opens on Gmail, opens as a side panel instead of a pop-up, because we want to be able to select information in the website without the pop-up closing, leverages Material UI for easy out-of-the-box component styling that is consistent with Google's Material design, allows users to interact with the contents of the web page both manually and automatically, and communicates with the back-end service using the Fetch API.

To set up the extension, I first created a new project using the Create React app in TypeScript. Then I had to change the build command to get React to generate separate JavaScript and HTML files in order to prevent content security policy errors. I also set it so that it doesn't generate source maps to get rid of some extra warnings. Because we are using TypeScript, I had to install some extra types to use the Chrome API. The project uses the Chrome API to interact with Chrome by using an injected object which is also called Chrome.

Create React app creates a manifest.json file for us, but extensions require a specific structure to that. Every extension needs to have a manifest.json file and it needs to be named manifest.json in its root directory. Google is in the process of phasing out manifest version 2 though they just announced some delays on this at Google I/O. Nevertheless, we're using manifest version 3 in our email postponer extension.

Because we want to open the extension as a side panel, we want to make sure action is empty here. Our extension will take its context from an index.html file that is compiled from React. The service worker which is also colloquially referred to as a background script, though there are some differences between the two. It runs on all tabs in the browser and gets executed on browser launch. It listens to specific events in the background, such as changing tabs or updating URLs. It takes browser-level actions accordingly, and it communicates with extension code and content scripts through message passing. This script is one of two scripts written in vanilla JavaScript, and we use it to open and close the site panel, as well as to call the res service using the fetch API, since calling it from React would give us cross-origin resource sharing errors.

Content scripts are scripts that run in the context of web pages, and they have access to DOM elements, objects, and methods. Separate instances run in separate tabs, one instance per tab, and they execute on pages that match the match's regular expression, and they communicate with the service worker and React code through message passing. Content scripts, which we only have one in this extension, are also written in vanilla JavaScript, and we use ours to create a side panel, iframe, pass messages between the React code and the background script, as well as copy and paste selected text in the extension. Also, the extension opens only in Gmail, since Gmail is the only expression that matches the regular expression in the matches property.

These are a few of the ways that messages are passed in extensions. Content scripts sit in the middle between the React code and the background script. React code communicates with content scripts through message passing. Content scripts either take action on the webpage the extension is running on or pass messages to the background script, which then can take browser lever actions accordingly. The background script can communicate with React code directly as well as with content scripts, also through message passing. Since we are running the extension in a side panel instead of a pop-up, we need to add web accessible resources to the extension.

3. Extension Permissions and Behaviors

Short description:

The extension code is in the index.html file, and the resources are available on all web pages. We are only using the active tab permission to open and close a side panel. The host permissions grant the extension the ability to send cross-origin resource sharing requests. We create an iframe in the content script that toggles between showing and hiding when receiving a toggle extension message. Extensions will soon be able to use the side panel in Chrome, which greatly improves workflows. We can copy selected text from the browser by querying the active tab and using the dom document get selection method. We can also auto-click buttons on the website by querying the active tab and using the DOMDocumentQuerySelector method.

And there are none by default. The extension code is in the index.html file, and the resources are available on all web pages. The permissions option is similar to permissions on your phone when you are installing an app. We are only using the active tab permission here, which gives an extension temporary access to the currently active tab. We also need this permission to open and close a side panel.

Lastly, the host permissions grant the extension the ability to send cross-origin resource sharing requests to the hosts mentioned in the array. This allows us to make the service calls that allow us to save our notes. Normally, extensions open in a popup, and that closes when it loses focus. Since our extension is a form which we want to complete using text from the website, we need it to stay focused and on. The first step was to make sure that there is no default popup property on the action field in manifest.json. Remember, that was empty in the previous slide. Then, we need an event listener in the service worker for when the extension icon is clicked. We can call this toggle extension. This sends a message to the content script.

In the content script, we need to create an iframe that is not displayed, but whose source is indexed.html. As you can see here, we have an iframe and the source is index.html. And then, we need to add an event listener that flips between showing and hiding the iframe when receiving a toggle extension message. It is worth mentioning that at the Google I/O conference a few days ago, the speaker mentioned extensions will soon be able to use the side panel in Chrome. The side panel API is an upcoming Manifest version three feature that isn't yet widely available. And it will behave a bit differently than what I described here. But it will greatly improve workflows, at least in Chrome.

One of the behaviors I want to highlight is copying selected text in the browser. To copy selected text from the browser, we need to query the current active tab in the current window, and send a message to the content script. When the content script receives the message, it just uses the dom document get selection method to get the text, and then it sends it back to the caller. Be sure to remove ranges to ensure predictable behavior as not using it works fine for me on Windows, but not on Mac.

The other behavior that I want to highlight is auto-clicking buttons in the website that the extension is running on. To auto-click buttons in the browser, we need to query the current active tab in the current window, and then send a message to the content script. When the content script receives the message, it just uses the DOMDocumentQuerySelector method with a selector for the button you want to automatically click in order to get a button element. And then, it just calls the click method on the button element. Our response is sent back to the caller based on whether or not the operations were successful.

4. Calling External Services

Short description:

We call external services using the Fetch API. Only the service worker can call the Fetch API without course errors. We pass messages from the extension code to the content script and then relay those messages to the background script. We use a helper function called selfSendRequest to send and handle the messages. In the background script, we use the Fetch API to make requests and send the response or error back to the caller.

The last behavior I want to highlight is calling external services. We call external services using the Fetch API. An important caveat is that only the service worker can call the Fetch api without course errors. So, we need to pass messages from extension code to the content script, which is the only one that the extension code can pass messages to. And then, relay those messages to the background script. I wrote a helper function called selfSendRequest that sends the message to the content script and then either rejects the response if it's an error or resolves it to the response. We can then call this method asynchronously with our request details. In the background script, we just call a standard Fetch API with a given request and then send the response or an error back to the caller depending on whether the fetch request was successful.

5. Running the Extension Locally

Short description:

To run the extension locally, you need to build it, turn on developer mode, and load it from the build folder in Manage Extensions. Refresh the pages or close them before loading the extension. For Firefox, you need a different version of the extension due to the use of background scripts. Firefox uses the browser namespace for JavaScript APIs. To add the extension in Firefox, go to About Debugging and load a temporary add-on. Chrome extensions stay installed when loaded unpacked, while Firefox add-ons are temporary. To publish to the Chrome Web Store, you need a developer account and a zip file of the build folder.

So the next thing I want to talk about is running the extension locally. To run the extension locally, you first need to build the extension, which will create the build folder for you. Then you can go to Manage Extensions, turn on developer mode, and load Unpacked. You need to select the build folder to load the extension. And you need to make sure to refresh the pages that the extension should be running on after the update. Or preferably not have them open when loading the extension.

I have a video to show you how to do this. So you go to Manage Extensions, which takes you to that URL. And then you can turn on developer mode, load Unpacked. Make sure to select the build folder. And then your extension's right there. So now you can navigate to Gmail in our case. And you can pin the extension on the top right. Pin the extension on the top right. And then you can click to open and close it.

If you have an extension in Chrome, you can convert it to a Firefox add-on relatively easily. Both Chrome and Firefox are currently running Manifest Version 3. However, Firefox hasn't adopted Service Workers and still uses background scripts. This means that your Manifest.json file will differ, at least in the background line. And since neither Chrome nor Firefox allow a different name for the manifest file, you must have a different version of the extension for each browser, even if everything else is compatible.

In Firefox, JavaScript APIs run under the browser namespace, as opposed to Chrome. But for cross-compatibility, Firefox accepts the use of the Chrome keyword, too. Adding the extension in Firefox is also a bit different. You need to go to About Debugging, click this Firefox on the left, and then press Load Temporary Add-on button. You also can't select the whole build folder, but any file within it will load the add-on. It's also worth noting that while Chrome extensions stay installed when you load them unpacked, Firefox add-ons are temporary. So this is an example, this is my current extension that I loaded temporarily. You can see it there, press it, it opens in a tab, and you can work with it.

To publish to the Chrome Web Store, you need a developer account and a zip file of your build folder. You can upload the extension to the Chrome developer dashboard.

6. Publishing, Demo, and Conclusion

Short description:

Before publishing the Chrome extension, it undergoes a review process. The same applies to Firefox add-ons, which also require a review. The demo showcases an email management system with features like email postponement and note-taking. Tex responds to emails, adds notes, and generates new emails. The presentation concludes with available resources and an invitation to connect on LinkedIn or Twitter.

Before it becomes available, the Chrome extension will undergo a review process. More details at that link. To publish a Firefox add-on, you also need a developer account for Firefox in this case. You will also need a zip file of your build folder or any of the other accepted package types. When you upload the extension, it undergoes automatic validation. After it passes validation, you can submit the extension for publishing. Firefox add-ons are available immediately after upload. However, they will also undergo a review process and they might be blocked later.

And this is my demo. For the demo, Tex has received a bunch of emails. Let's check this email. This is an email that Tex has responded to. He requested a postponement. These are the notes that he added. The first one is generated automatically, with the text of the email that he responded. He can add new notes. Now, let's see a new email. Random company wants to talk to Tex. He might respond to Adena, who works at Random Company. And I missed the Y, so Random Company. And give a timeline, it's the beginning of August. Figure out what time of the day it is, and then you can generate an email. Check the email content, and then it can be automatically sent. It appears in the notes. And more notes can be added here. Very slowly. Yes, and this is my presentation, this is my demo. If you want to read more about the tech used in this talk, this is a list of helpful resources. The slides will be made available to you. And thank you so much for listening to my talk. You can connect with me on LinkedIn or Twitter at adinotech21.