DEV Community

Boryamba
Boryamba

Posted on

Building a Chrome Extension Using React and Vite: Part 1 - Setting things up

Hello everyone 👋

For a while, I've been toying with the idea of building an extension for the ChatGPT website. I wanted to enhance its functionality by allowing multiple chat deletions and adding other useful features. But every time I started the development process, I found myself hitting a wall, particularly around Vite configuration for extension bundling or communication between different extension contexts.

After many attempts, I finally succeeded! And I thought I'd share my experience with you all ❤️. This is the first part in a series of blog posts where I will guide you through my process.

In this initial part, we'll cover the setup process: initializing the project, creating the popup, content script and background worker, and incorporating them into our manifest.json file.

The second part which shows how to connect different parts of the extension together is available here

Setup

Let's start by setting up a Vite project. Use your favorite package manager to establish a fresh Vite project. Here, I'll be using yarn:

yarn create vite
Enter fullscreen mode Exit fullscreen mode

During the installation, I select React -> Typescript with SWC.

Once done, I add a few Vite plugins in the root directory of the newly created project:

yarn add -D vite-tsconfig-paths @crxjs/vite-plugin@beta
Enter fullscreen mode Exit fullscreen mode

The vite-tsconfig-paths plugin supports path aliases and @crxjs/vite-plugin@beta is a library for bundling the extension.

Next, we need to register these plugins in the vite.config.ts file:

import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

import react from "@vitejs/plugin-react-swc";
import { ManifestV3Export, crx } from "@crxjs/vite-plugin";

import manifestJson from "./manifest.json";

const manifest = manifestJson as ManifestV3Export;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [tsconfigPaths(), react(), crx({ manifest })],
});
Enter fullscreen mode Exit fullscreen mode

As you can see, I've imported the manifest file and asserted it to the ManifestV3Export type. The crx plugin's typings require this to function correctly 😅. A quick note - I needed to add the manifest.json file to my tsconfig.node.json's include field to avoid a resolution error.

Now let's discuss the manifest.json file. This file provides an overview of my extension: its name, description, permissions, as well as the locations of entry files for the popup, content script, and background worker.

{
  "manifest_version": 3,
  "name": "ChatGPT Extension",
  "version": "0.0.1",
  "description": "Improve your ChatGPT experience with this extension! Bulk exports, bulk deletes, save prompts and more!",
  "permissions": [
    "activeTab",
    "cookies",
    "declarativeContent",
    "scripting",
    "storage",
    "tabs"
  ],
  "host_permissions": ["https://chat.openai.com/*"],
  "action": {
    "default_popup": "index.html"
  },
  "background": {
    "service_worker": "src/background/background.ts",
    "type": "module",
    "persistent": false
  },
  "content_scripts": [
    {
      "matches": ["https://chat.openai.com/*"],
      "js": ["src/contentScript/main.tsx"],
      "run_at": "document_end"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Here, permissions like

activeTab, cookies, and tabs grant the extension access to various aspects of the browser's functionalities. For example, activeTab allows the extension to interact with the current tab while cookies and tabs allow access to cookie data and tab details respectively.

Next, we organize our application structure. The setup process has provided us with several files. Here's how we arrange them:

1) I create a new popup directory inside my src folder, move there main.tsx and App.tsx files, and update the index.html file to point to the new entry file location:

...
    <script type="module" src="/src/popup/main.tsx"></script>
...
Enter fullscreen mode Exit fullscreen mode

The popup runs in its own context, independently from the background workers or content scripts. It doesn't have access to the DOM, which is why we'll be using a content script for that purpose.

2) I duplicate the popup directory, rename it to contentScript, and within the main.tsx script, create a new div element to append it to the document's body. Then, I render my application into the created div:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "../index.css";

const root = document.createElement("div");
root.id = "chatgpt-content-script-root";

document.body.appendChild(root);

ReactDOM.createRoot(root).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

This approach ensures: a) there is a root element to render our application into; b) we will not have conflicts with the website's existing DOM elements, ensuring our extension doesn't interfere with the website's functionality or vice versa.

3) Lastly, we create a background script inside the background folder. For now, let's insert a dummy console.log just to ensure everything is working correctly.

Now comes the exciting part: testing our setup:

1) yarn build - this command builds my extension into a dist directory.
2) Enable developer mode in Google Chrome extensions.
3) Load the extension by clicking the Load unpacked button and selecting the dist directory.
4) Open https://chat.openai.com/ - at this point, you should notice the website's user interface has been modified by a Vite counter. When you click the extension button, guess what pops up? 😏 The last thing we need to test is the background script. Right-click anywhere inside the extension popup and select Inspect popup. Here, you should see your dummy console.log.

Yay! Congratulations to us! 🎉 We've just created our first Google Chrome extension. It currently doesn't do much more than adjusting the website's layout, but it's just the beginning.

In case you face any issues during setup, ensure to double-check your file structure and the content of your manifest.json file. Most errors at this stage come from misconfigurations in these areas.

Stay tuned for the next part where I'll set up communication between different contexts of our extension.

In the meantime, if you've tried these steps and have feedback, or if you'd like me to explain anything in more detail, please leave a comment below. I'd love to hear from you!

Top comments (4)

Collapse
 
clumsycoder profile image
Kaushal Joshi

I have a question... how to use environment variables in web extension built with React + Vite?

Collapse
 
pagarevijayy profile image
Vijay Pagare

Awesome! thanks for sharing this.

Didn't know where to put the background script and how to configure vite in order to make the extension work.

kudos! 🎉

Collapse
 
softwareengineerpalace profile image
SoftwareEngineerPalace

is there a github repo ?

Collapse
 
abubakarimam profile image
Abubakar Imam👨🏿‍💻

I inspect my popup but i cant see my log message I type in background.js