JoBins Engineering

Follow

JoBins Engineering

Follow
Unleashing the Power of ReactJS for Chrome Extension Development

Unleashing the Power of ReactJS for Chrome Extension Development

Nirajan Basnet's photo
Nirajan Basnet
ยทJan 27, 2023ยท

9 min read

Play this article

Table of contents

Building a chrome extension may seem like a daunting task, but it is quite easy to implement. By following a guide, you will be able to create your extension in no time.

This tutorial will provide you with information on how to create a popup, use a content script, and work with chrome local storage, among other things.

Why Chrome extension?

Google Chrome extension is like a special tool that you can add to your Google Chrome web browser to make it do extra things! Just like how you can add different tools to a toolbox, you can add different extensions to your web browser.

For example, you might have an extension that blocks ads or an extension that helps you save pictures you find on the internet.

It's just like having a magic wand, you can add different spells to it and use it whenever you want.


Chrome Extension Setup with ReactJs

1. Create Manifest Json

First, we need to create manifest.json with version 3 which is the latest

{
    "name": "Chrome Extension With ReactJs",
    "description": "This is the boilerplate for the chrome extension with React",
    "version": "1.0.0",
    "manifest_version": 3,
    "icons": {
        "16": "logo.png",
        "48": "logo.png",
        "128": "logo.png"
    },
    "action": {
        "default_title": "Chrome Extension With React",
        "default_popup": "popup.html"
    },
    "options_page": "options.html",
    "content_scripts": [
        {
            "matches": ["https://www.google.com/"],
            "js": ["contentScript.js"]
        }
    ],
    "permissions": ["storage"]
}

Main metadata fields that need to understand

manifest_version: This field specifies which version of the manifest file format is being used.

permissions: This field lists the specific permissions that the extension requires to function, such as access to a user's browsing history or the ability to modify web pages.

content_scripts: This field lists the JavaScript files that should be injected into web pages to add functionality to the extension.

background: This field describes the background script that runs in the background when the extension is active.

browser_action or page_action: This field describes the icon that appears in the browser toolbar when the extension is active, and what happens when the user clicks on it.

options_page: This field describes the URL of the options page that appears when the user clicks on the extension's settings.

icons: This field contains the path to the icons that are used to represent the extension in the browser's UI.

web_accessible_resources: This field lists the resources that should be made available on web pages.


2. Install Webpack, Webpack CLI

yarn add webpack webpack-cli -D

Here is an example of a webpack configuration file, webpack.config.js, that can be used to bundle your extension's code:

const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlPlugin = require("html-webpack-plugin");
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const Dotenv = require('dotenv-webpack');

module.exports = {
    mode: "development",
    devtool: "cheap-module-source-map",
    entry: {
        popup: path.resolve("src/popup/index.tsx"),
        options: path.resolve("src/options/index.tsx"),
        contentScript: path.resolve("src/contentScript/index.tsx"),
    },
    module: {
        rules: [
            {
                use: "ts-loader",
                test: /\.(tsx|ts)$/,
                exclude: /node_modules/,
            },
            {
                test: /\.css$/i,
                use: [
                    "style-loader",
                    {
                        loader: "css-loader",
                        options: {
                            importLoaders: 1,
                        },
                    },
                    {
                        loader: "postcss-loader", // postcss loader needed for tailwindcss
                        options: {
                            postcssOptions: {
                                ident: "postcss",
                                plugins: [tailwindcss, autoprefixer],
                            },
                        },
                    },
                ],
            },
            {
                type: "assets/resource",
                test: /\.(png|jpg|jpeg|gif|woff|woff2|tff|eot|svg)$/,
            },
        ],
    },
    plugins: [
        new Dotenv(),
        new CleanWebpackPlugin({
            cleanStaleWebpackAssets: false,
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve("src/static"),
                    to: path.resolve("dist"),
                },
            ],
        }),
        ...getHtmlPlugins(["popup", "options", "contentScript"]),
    ],
    resolve: {
        extensions: [".tsx", ".ts", ".js"],
    },
    output: {
        filename: "[name].js",
        path: path.join(__dirname, "dist"),
    },
    optimization: {
        splitChunks: {
            chunks(chunk) {
                return chunk.name !== "contentScript";
            },
        },
    },
};

function getHtmlPlugins(chunks) {
    return chunks.map(
        (chunk) =>
            new HtmlPlugin({
                title: "Chrome Extension with ReactJs",
                filename: `${chunk}.html`,
                chunks: [chunk],
            })
    );
}

Core functionality about webpack

entry: The entry point is the starting point for webpack to begin building the dependency graph for the application. It tells webpack where to start looking for code to bundle. It can be a single file or an object of multiple files.

rules: The "rules" field is used to specify loaders that should be used to process certain types of files. These rules are used to transform different types of files, such as TypeScript, JSX, and CSS, into a format that the browser can understand. Each rule has a "test" property that is used to match the files that should be processed by the loader and a "use" property that defines the loader that should be used.

plugins: Plugins are used to perform a wider range of tasks than loaders, such as bundle optimization, asset management, and injection of environment variables. They are added to the config using the "plugins" field and they can be used to perform a wide range of tasks, like bundle optimization, asset management, and injection of environment variables. Some examples of plugins are HtmlWebpackPlugin, CleanWebpackPlugin, and UglifyJsPlugin.

Reference: Visit Webpack


3. Install ReactJs and Typescript

For ReactJs

Run yarn add react react-dom

For Typescript

Run yarn add typescript ts-loader -D

ts-loader is the TypeScript loader for webpack.

Create a simple tsconfig.json file in the root directory

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "jsx": "react",
        "module": "es6",
        "target": "es6",
        "moduleResolution": "node",
        "esModuleInterop": true,
        "incremental": true,
        "downlevelIteration": true
    },
    "include": ["src/**/*.ts", "src/**/*.tsx"],
    "exclude": ["node_modules"]
}

4. Setup the project folder

React Project folder structure


5. Create Popup Component

First, let's create an index.tsx file in the popup folder

import React from "react";
import { createRoot } from "react-dom/client";
import "../assets/css/tailwind.css";
import Popup from "./Popup";

function init() {
    const appContainer = document.createElement("div");
    document.body.appendChild(appContainer);

    if (!appContainer) {
        throw new Error("Cannot find appContainer");
    }

    const root = createRoot(appContainer);
    root.render(<Popup />);
}

init()

Popup.tsx

import React from 'react'

function Popup() {
    return (
        <div className='w-52 h-20'>
            <div>
                <h1 className='text-center p-5 text-xl'>This is a popup section</h1>
            </div>
        </div>
    )
}

export default Popup

To include the filename "popup" in the webpack configuration, we need to make the following changes in the webpack.config.js file:

  • entry

  • getHtmlPlugins()


6. Content Script

You can create a contentscript component that works similarly to a popup component by adding the corresponding file in the webpack.config.js file.

This will allow webpack to bundle and process the content script in the same way as it would the popup component, making it ready for use in the extension.

But here is the twist. You need to add the contentscript.js file in the manifest.json

"content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["contentScript.js"] //Path to contentScript.js
    }
  ],

In the above code, the field is defined as an array with a single object, which has the following properties:

matches: This property specifies the URLs that the content script should be injected into. The value of "<all_urls>" means that the script will be injected into all URLs, regardless of the domain.

js: This property specifies the JavaScript files that should be injected into the web pages. The value ["contentScript.js"] means that the file located at "contentScript.js" will be injected.

This configuration tells the browser to inject the content script into all URLs and the script will be the one located at "contentScript.js".

This allows the extension to run the JavaScript code on any webpage the user visits and make modifications to the web page and interact with the web page.

Reference: Content Script with Manifest v3


7. Chrome Storage

Chrome storage is a way to store data in the browser, which can be used to persist data across browser sessions. Chrome provides two types of storage options: "local" and "sync".

"local" storage: Data stored in local storage will only be available on the device where it was stored, and will not be synced across devices.

"sync" storage: Data stored in sync storage will be synced across all devices where the user is signed in to Chrome with the same account.

First, we need to add certain information in manifest.json

{
  "name": "Chrome Extension With ReactJs",
  ...
  "permissions": [
    "storage"
  ],
  ...
}

Below is the code example using chrome.storage API to store and retrieve data in local storage in the Popup React Component

function Popup() {
    const [value, setValue] = useState<string>('');

    const handleSave = () => {
        chrome.storage.local.set({ key: value }, () => {
            console.log('Value is set to ' + value);
        });
    }

    const handleLoad = () => {
        chrome.storage.local.get(['key'], (result) => {
            console.log('Value currently is ' + result.key);
            setValue(result.key);
        });
    }

    return (
        <div className='w-72'>
            <div className='p-4'>
                <h1 className='text-base mb-4 font-medium'>Chrome Extension with ReactJs</h1>

                <div className='mb-10'>
                    <input
                        value={value}
                        onChange={e => setValue(e.target.value)}
                        className="bg-gray-50 border border-gray-200 w-full rounded-lg px-4 py-2 text-black focus:outline-none mb-5" />

                    <div className='flex items-center justify-end'>
                        <button onClick={handleSave} className="bg-green-500 hover:bg-green-400 transition duration-300 px-6 py-2 rounded-md text-white text-center">
                            <span>Save</span>
                        </button>
                    </div>
                </div>

                <div className=''>
                    <p className='text-sm mb-2'>Local Storage value</p>
                    <div className='flex items-center space-x-2'>
                        <button onClick={handleLoad} className="bg-orange-500 hover:bg-orange-400 transition duration-300 px-6 py-2 rounded-md text-white text-center">Load</button>
                        <span className='text-lg font-semibold'>{value}</span>
                    </div>
                </div>
            </div>
        </div>
    )
}

Reference: Chrome Storage


8. Tailwind CSS

Run yarn add tailwindcss postcss autoprefixer -D

For webpack, we also need to install

yarn add postcss-loader style-loader css-loader -D

Create postcss.config.js file in the root directory

module.exports = {
    plugins: {
        tailwindcss: {},
        autoprefixer: {},
    },
};

Also, create tailwind.css file inside the src/assets/css folder and add

@tailwind base;
@tailwind components;
@tailwind utilities;

9. Build the file and Load it into chrome://extensions/ in the Chrome browser

Run yarn build to bundle your code. It creates a "dist" folder, which will contain all the final files needed for your extension.

  • Go to "chrome://extensions" page from the URL.

  • Load your build file from the "dist" folder

  • Enable the developer mode.

  • Test your extension and make any necessary changes. Once you are satisfied, submit it to the Chrome Web Store for others to use.


10. Get the final code

You can find the final code for this project in a GitHub repository


Conclusion

Coming this far, creating a chrome extension with ReactJS can be a great way to add functionality to the browser and enhance the user experience. With the use of React, you can build a robust, maintainable, and scalable extension. It is important to understand the chrome extension API and how it works, as well as be familiar with webpack and manifest.json file

It is also important to note that Google has announced the Manifest V3 for chrome extension which will impact the way extensions work and change the way they interact with web pages. So, it's important to keep yourself updated with the latest changes and best practices.

๐Ÿ‘๐Ÿ‘ ๐Ÿ‘๐Ÿ‘

I encourage you to try building your chrome extension using ReactJS and see how it goes.

If you have any issues or questions, please don't hesitate to reach out and leave a comment. Additionally, I'd love to hear your thoughts and feedback on the project.

Till then, Keep on Hacking, Cheers

ย 
Share this