DEV Community

Cover image for Building Web Extensions with Reactjs - from 0 to publish!
Yash Soni
Yash Soni

Posted on

Building Web Extensions with Reactjs - from 0 to publish!

Web Extensions - you might have heard of it, most probably you're already using it - example? AdBlock, Grammarly, Save to Pocket, etc. In this article, we will build a Web Extension from scratch using Reactjs.

We are going to bake a New Tab Greeting Extension and publish it on Chrome Web Store and Firefox Addon Dashboard.


Step 1 - Creating the App:

Let's create a React app with CRA:

npx create-react-app greetings-web-extension
cd greetings-web-extension

Let's do some quick cleanup:
Under the src folder, remove everything else other than App.js, index.js, and index.css.

From the public folder - remove everything else other than index.html and favicon.ico.

In the end, we should have the following directory structure:

  • greetings-web-extension
    • node_modules
    • public
      • favicon.ico
      • index.html
    • src
      • App.js
      • index.css
      • index.js
    • .gitignore
    • package.json
    • README.md
    • others

Step 2 - Preparing the App:

Quickly modify a few files as follows:

  • /src/index.js
import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") ); 
Enter fullscreen mode Exit fullscreen mode
  • /src/index.css
html, body, #root { height: 100%; } body { margin: 0; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .App { height: 100%; background: linear-gradient(to bottom right, #7b4397, #dc2430); color: white; text-align: center; display: flex; justify-content: center; align-items: center; } 
Enter fullscreen mode Exit fullscreen mode
  • /src/App.js
import React from "react"; const greetings = { morning: "Good morning", noon: "Good afternoon", evening: "Good evening", night: "Good night", }; class App extends React.Component { constructor() { super(); this.state = { greeting: greetings.morning, }; this.startTime = this.startTime.bind(this); } componentDidMount() { this.startTime(); } startTime() { let today = new Date(); let h = today.getHours(); let greeting; if (h > 6 && h < 12) { greeting = greetings.morning; } else if (h >= 12 && h < 17) { greeting = greetings.noon; } else if (h >= 17 && h < 20) { greeting = greetings.evening; } else { greeting = greetings.night; } this.setState({ greeting }); setTimeout(this.startTime, 1000); } render() { return ( <div className="App"> <h1>{this.state.greeting}</h1> </div> ); } } export default App; 
Enter fullscreen mode Exit fullscreen mode
  • /public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <title>Greetings</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html> 
Enter fullscreen mode Exit fullscreen mode

Step 3 - Testing React App:

Let's do a quick test of what we have at hand.
Fire it up with npm start.

You should see something like this in your browser:

Greetings Screenshot

Step 4 - preparing the Web Extension:

The first thing we need is a manifest.json file - which is a descriptor file for the web extension we are building.

Create a directory extras, create a new file manifest.json under it and Copy/Paste the following content:

{ "name": "greetings", "offline_enabled": true, "short_name": "greetings", "description": "Everyday greetings", "version": "1.0.0", "chrome_url_overrides": { "newtab": "index.html" }, "manifest_version": 2 } 
Enter fullscreen mode Exit fullscreen mode
  • offline_enabled: duh!
  • chrome_url_overrides: we want this extension to override our newtab with the index.html. Hence...

Now, let's create a build for our react-app:
INLINE_RUNTIME_CHUNK=false react-scripts build

Why INLINE_RUNTIME_CHUNK=false?
Because if we don't do this, the final index.html will contain an inline script as a result of the build process. Extension recommends using external scripts and discourages using the inline script. With INLINE_RUNTIME_CHUNK env variable, we will bypass the above-mentioned issue.

The above command will result in a build folder generated at the root of our project.

Now, Copy/Paste the manifest.json file we created into this new build folder. (Why didn't we create the file inside the build folder directly? Because the build folder is purged and re-created every time you run the build commands. Hence keeping it under extras.)

Step 5 - testing the Extension:

Now fire up Chrome Browser, go to chrome://extensions/ and enable Developer mode.

Now click on the Load unpacked Button and point to the build folder of our project.

Test by opening a new tab in Chrome.

Step 6 - packaging for Chrome and Firefox:

Chrome packaging is simply a zip command.
Firefox packaging can be done using a recommended tool called web-ext. Install this globally using npm i -g web-ext.

To help you eliminate the manual steps of copying/pasting manifest.json file, running commands for Firefox and Chrome packaging, etc. I have created a small node.js script that does the job.

Create a new file at the root of the project, let's call it buildPackage.js. Put the following content into it.

const { execSync } = require("child_process"); // FILENAMES const manifestFileName = "manifest.json"; // DIRECTORIES const buildDir = "./build/"; const extensionDir = "./extras/"; const outputs = "./"; // OUTPUTS const chromeOutput = "greetings-chrome.zip"; console.log("Building Extension Packages"); console.log("***COPYING MANIFEST FILE***\n\n"); execSync( `cp ${extensionDir}${manifestFileName} ${buildDir}${manifestFileName}` ); execSync(`zip -r ${outputs}${chromeOutput} ${buildDir}`); console.log("***CHROME BUILT SUCCESSFULLY***\n\n"); execSync(`web-ext build -s ${buildDir} -a ${outputs} --overwrite-dest`); console.log("***FIREFOX BUILT SUCCESSFULLY***"); 
Enter fullscreen mode Exit fullscreen mode

Modify the package.json to run this script as a part of build process. In package.json under scripts tag, modify as shown:

"build": "INLINE_RUNTIME_CHUNK=false react-scripts build && node buildPackage.js",

Now trigger the build script using npm run build and you should see 2 new files created at the root of the project:

  1. greetings-chrome.zip // for Chrome
  2. greetings-1.0.0.zip // for Firefox

Step 7 - Publishing:

Chrome:

  1. Go to Google Chrome Developer Dashboard
  2. Sign in with your Google account or create a new one
  3. Click on + New Item Button
  4. Add the greetings-chrome.zip file created in the previous step
  5. Fill up the required details and submit for review

(Usually takes around 12 hours to publish)

Firefox:

  1. Go to Mozilla Add-ons Dashboard
  2. Sign in with your Mozilla account or create a new one
  3. Click on Submit a New Add-on Button
  4. For distribution, select On this site and continue
  5. Add the greetings-1.0.0.zip file created in the previous step
  6. Fill up the required details and submit for review

(Usually takes around 6-10 mins to publish)


Final notes:

  • You need an icon for your extension, create one icon of size 128x128 (name it icon_128.png), and put it in the build directory. Add the following to manifest.json:
 "icons": { "128": "icon_128.png" }, 
Enter fullscreen mode Exit fullscreen mode
  • In some cases, your Web Extension might need to store and retrieve data. Though you can use browser localStorage, it is highly discouraged for Web Extension. Instead, you should use a designated DB storage area for extensions which is more reliable. Read more here.
  • This is a very basic extension that we created, generally, it can be a bit more complicated - with API calls, Storage, Background scripts, etc. All of this is possible!
  • Try and keep the Web Extension light - meaning an Extension should load fast, should serve a single purpose, should be accessible, etc.

I've created a Web Extension for Chrome and Firefox - minimylist. Do check it out and share it if you liked.


Until next time, Ciao! πŸ‘‹πŸ»

Top comments (1)

Collapse
 
yuuuuuuuri profile image
Yuri

how to use chrome permissions at localhost:3000?
like chrome.bookmarks is undefined (just at localhost)