top of page
  • Writer's pictureAn Object Is A

Build a Chrome Extension Using ReactJS

Updated: Nov 9, 2021



 

This tutorial is based on Chrome Extension Manifest Version 2.

Let's Begin.


We’ll start by initializing npm.

>> npm init -y

Then we'll install all of the packages we'll need.


First the dev dependencies.

>> npm install --save-dev @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader copy-webpack-plugin clean-webpack-plugin html-loader html-webpack-plugin webpack webpack-cli webpack-dev-server

Then the non-dev dependencies.

>> npm install react react-dom react-router-dom

In the package.json we'll write scripts for our development and production. Under scripts, we'll add,

// package.json

"build": "webpack-dev-server",
"build:prod": "webpack -p"

Let's create the ReactJS files.


Create a 'src' folder for these files.

Create a 'components' folder inside the 'src' folder for the ReactJS components we'll be writing.


It's important that we cover all of our Google Chrome Extension bases.

This means we'll need a foreground or content page, popup page, and options page minimum.

The other files, background script, manifest, and icons will come later.


The architecture of our ReactJS files is this:

  1. An entry point — this is an HTML file with a "div" we can inject into

  2. An initial render file — this is a JavaScript file that injects one ReactJS component into the entry point

  3. An initial ReactJS component file — this is a JavaScript file that we'll use as the HTML to initially render


Let's create the entry points for the foreground, popup, and options.


The code is all the same except for the 'id' we give the "div".

// foreground.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Document</title>
    </head>
    <body>
        <div id="foreground"></div>
    </body>
</html>
// popup.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Document</title>
    </head>
    <body>
        <div id="popup"></div>
    </body>
</html>
// options.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Document</title>
    </head>
    <body>
        <div id="options"></div>
    </body>
</html>

Let's create the initial render file for the foreground, popup, and options.


The code is all the same except for the name of the import.

// index-foreground.js

import React from 'react';
import { render } from 'react-dom';

import Foreground from './components/Foreground.js';

render(<Foreground />, document.querySelector('#foreground'));
// index-popup.js

import React from 'react';
import { render } from 'react-dom';

import Popup from './components/Popup.js';

render(<Popup />, document.querySelector('#popup'));
// index-options.js

import React from 'react';
import { render } from 'react-dom';

import Options from './components/Options.js';

render(<Options />, document.querySelector('#options'));

Let's create the initial ReactJS component for the foreground, popup, and options.


Here, you're free to create your ReactJS app.

// components/Foreground.js

import React from 'react';

function Foreground() {
    return (
        <div style={styles.main}>
            <h1>Chrome Ext - Foreground</h1>
        </div>
    )
}

const styles = {
        main: {
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        zIndex: '1000',
        fontSize: '80px',
        pointerEvents: 'none'
    }
}

export default Foreground;
// components/Popup.js

import React from 'react';

function Popup() {
    return (
        <div style={styles.main}>
            <h1>Chrome Ext - Popup</h1>
        </div>
    )
}

const styles = {
    main: {
        width: '300px',
        height: '600px'
    }
}

export default Popup;
// components/Options.js

import React from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    Redirect
} from "react-router-dom";

import Popup from './Popup.js';
import Foreground from './Foreground.js';

function Options() {
    return (
        <Router>
            <div style={styles.container}>
                <div style={styles.nav_bar}>
                    <h1>Chrome Ext - Options</h1>
                    <nav>
                        <ul>
                            <li>
                                <Link to="/">Options</Link>
                            </li>
                            <li>
                                <Link to="/popup">Popup</Link>
                            </li>
                            <li>
                                <Link to="/foreground">Foreground</Link>
                            </li>
                        </ul>
                    </nav>
                </div>
                <Switch>
                    <Route exact path="/popup">
                        <Popup />
                    </Route>
                    <Route exact path="/foreground">
                        <Foreground />
                    </Route>
                    <Route exact path="/">
                        <Redirect to="/options.html" />
                    </Route>
                </Switch>
            </div>
        </Router>
    )
}

const styles = {
    container: {
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center'
    }
}

export default Options;

Note: Notice how the CSS of this project is in the ReactJS files. We won't be doing separate CSS files for this tutorial.


Let’s create the Chrome Extension-specific files.


We'll need:

  • a manifest.json file

  • a background.js file

  • a script file to inject our foreground.html "div" (this is VERY important)

  • icons x 4

Our manifest.json doesn't need anything special.


Just a normal manifest with background, options, and popup pages specified.

// manifest.json

{
    "name": "ReactJS Chrome Extension",
    "description": "Using ReactJS to build a Chrome Extension",
    "version": "0.1.0",
    "manifest_version": 2,
    "icons": {
        "16": "./obj-16x16.png",
        "32": "./obj-32x32.png",
        "48": "./obj-48x48.png",
        "128": "./obj-128x128.png"
    },
    "background": {
        "scripts": ["./background.js"]
    },
    "options_page": "./options.html",
    "browser_action": {
    "  default_popup": "popup.html"
    },
    "permissions": [
        "tabs",
        "<all_urls>"
    ] 
}

Our background.js is unique in its work-flow.


Here's the problem we need to solve:

When we're developing our ReactJS app, the index-foreground.js JSX file injects into the foreground.html's "div".


When we move to a Chrome Extension, we want to inject a foreground or content script into the page the user is viewing.


Problem is, they don't have the architecture for this.

There is no foreground.html "div".

We need to create this element on their page BEFORE we inject our index-foreground.js into their page.

// background.js

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    if (changeInfo.status === 'complete' &&   tab.url.includes('http')) {
        chrome.tabs.executeScript(tabId, { file: './inject_script.js' }, function () {
            chrome.tabs.executeScript(tabId, { file: './foreground.bundle.js' }, function () {
                console.log('INJECTED AND EXECUTED');
            });
        });
    }
});
// inject-script.js

const foreground_entry_point = document.createElement('div');
let reactJS_script = document.createElement('script');

foreground_entry_point.id = 'foreground';

foreground_entry_point.appendChild(reactJS_script);

document.querySelector("body").appendChild(foreground_entry_point);

Note: I've created an inject-script.js file that creates that special foreground.html "div".


The icons are self-explanatory.


THE MOST IMPORTANT STEP IN THIS TUTORIAL.

DO NOT SKIP THIS INFORMATION.

THIS IS HOW WE GET A REACTJS PROJECT TO “CONVERT” INTO A CHROME EXTENSION.


Let's deal with the 'webpack.config.js'.


I'm not going to go into too much detail about how exactly webpack works. I will, however, explain what the file is saying.


What are we doing with webpack??


We have a lot of files. Some of those files are html. Some are pngs or jpgs. Some are JavaScript exclusively. Some are a mix of JavaScript and JSX (ReactJS).


We need to get webpack to transpile some of these items and simply copy the others. Specifically, we want to transpile the JSX into Javascript(this is what those babel dependencies are for) and we want to copy our html, icon, and JavaScript-only files.


Note: So what's happening here?


HtmlWebpackPlugin allows us to copy html files into a target destination.


CopyWebpackPlugin allows us to copy any file, without transformations, into a target destination.


CleanWebpackPlugin is used to make sure all files in a target destination are deleted before building.


devServer is needed for developing ReactJS in real-time.


entry defines three points. These are the JSX initial render files that inject our JSX into the html entry-point files.


output tells webpack to name them as bundles and where to save them.


module is where we tell webpack how to deal with different types of files.

For all JavaScript/JSX files included in the webpack chain, transpile the code.

For all HTML files, transform them so they're ready for webpack to merge them with our bundles.


plugins run after individual files get transformed with modules.

We are merging our HTML files with the appropriate bundles(chunks) and exporting them (either to server or hard-drive), copying files we don't want transpiled, and running our cleaner.


We have our developer environment setup and the files all filled out.


How do we run this thing?


Two ways.


For development, run the build command.

>> npm run build

View your ReactJS app in the browser--localhost:8080


For production, we need to tweak our webpack.config.js then run the build:prod command.

comment out the 'HtmlWebpackPlugin' for the foreground

We don't need to export the foreground.html file into production because we're creating our own foreground.html "div" using the background.js and inject-script.js scripts.

Now run the production script.

>> npm run build:prod

Load your production extension in your Chrome Browser.

Choose the 'dist' directory.


Keep in mind, this is an experimental and a bit "hacky" solution to using ReactJS to create Google Chrome Extensions.


I've tried to make the development and production pipeline as streamlined as is possible.

I haven't done extensive testing on a lot of npm packages.

From what I have tried, react-router-dom works.

You're mileage may vary.


You can get the source files here.



Build a Chrome Extension with React (2020 Web Development)


1,011 views1 comment

Recent Posts

See All
bottom of page