fervor [>]CODING & CURIOSITY
FERVOR LEARNING SYSTEMTUTORIALS
← React

React / 28 MIN READ

Chrome Extension 101

Building a Chrome Extension

From the original Fervor library. Examples may use older package versions.

Building a React-Based Browser Extension with Vite: A Comprehensive Tutorial

Using Vite instead of Create React App (CRA) for building a React-based browser extension offers faster development builds, easier configuration, and enhanced flexibility. This tutorial will guide you through creating a Google Chrome extension using React and Vite. By the end, you’ll have a functional extension ready for testing and deployment.

Table of Contents

  1. Prerequisites
  2. Step 1: Setting Up the Vite React Project
  3. Step 2: Configuring the Manifest File
  4. Step 3: Building the Popup with React
  5. Step 4: Adding Extension Functionality
  6. Step 5: Adjusting the Build Process
  7. Step 6: Testing Your Extension
  8. Step 7: Packaging and Publishing
  9. Conclusion
  10. Bonus: Tips, Tricks, and Advanced Features

Prerequisites

Before you begin, ensure you have the following:

  • Basic Knowledge of React: Familiarity with React components, state, and props.
  • Node.js and npm Installed: Download Node.js which includes npm.
  • A Text Editor: Such as Visual Studio Code.
  • Google Chrome Browser: For testing and loading your extension.

Step 1: Setting Up the Vite React Project

We’ll use Vite to scaffold our React project, providing a faster and more flexible development environment compared to CRA.

1.1. Create a New Vite React Project

Open your terminal and run the following command to create a new Vite project with React:

npm create vite@latest react-chrome-extension --template react

This command creates a new directory named react-chrome-extension with a React setup.

1.2. Navigate to the Project Directory

cd react-chrome-extension

1.3. Install Dependencies

Install the necessary dependencies by running:

npm install

1.4. Project Structure Overview

After setup, your project structure should look like this:

react-chrome-extension/
├── node_modules/
├── public/
│   ├── icons/
│   │   ├── icon16.png
│   │   ├── icon48.png
│   │   └── icon128.png
│   └── manifest.json
├── src/
│   ├── assets/
│   ├── components/
│   ├── App.jsx
│   ├── main.jsx
│   └── index.css
├── .gitignore
├── index.html
├── package.json
├── vite.config.js
└── README.md

We’ll be modifying and adding to this structure as we build the extension.

1.5. Clean Up Unnecessary Files

For simplicity, remove or adjust unnecessary default files:

rm src/App.css src/assets/logo.svg

Update src/index.css and src/App.jsx as needed in later steps.


Step 2: Configuring the Manifest File

The manifest.json file is essential for defining your extension’s metadata, permissions, and behavior.

2.1. Create manifest.json

Inside the public folder, create a new file named manifest.json with the following content:

{
  "manifest_version": 3,
  "name": "React Chrome Extension with Vite",
  "version": "1.0.0",
  "description": "A Chrome extension built with React and Vite.",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "activeTab",
    "scripting",
    "storage",
    "notifications"
  ]
}

2.2. Add Extension Icons

Create an icons folder inside the public directory and add icon images named icon16.png, icon48.png, and icon128.png. These icons represent your extension in various contexts.

Tip: You can create simple icons using tools like Canva or download free icons from IconFinder.


Step 3: Building the Popup with React

The popup is the UI that appears when users click on your extension’s icon in the browser toolbar. We’ll create a React component for this popup.

3.1. Modify src/App.jsx

Replace the content of src/App.jsx with the following:

// src/App.jsx
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [color, setColor] = useState('#ffffff');
  const [savedColor, setSavedColor] = useState('#ffffff');

  // Load saved color on component mount
  useEffect(() => {
    chrome.storage.sync.get(['color'], (result) => {
      if (result.color) {
        setColor(result.color);
        setSavedColor(result.color);
      }
    });
  }, []);

  const handleChangeColor = () => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      chrome.scripting.executeScript({
        target: { tabId: tabs[0].id },
        func: (selectedColor) => {
          document.body.style.backgroundColor = selectedColor;
        },
        args: [color],
      }, () => {
        // Save the selected color to storage
        chrome.storage.sync.set({ color }, () => {
          setSavedColor(color);
          chrome.notifications.create({
            type: 'basic',
            iconUrl: 'icons/icon48.png',
            title: 'Color Changed',
            message: `Background color changed to ${color}`,
          });
        });
      });
    });
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif', width: '250px' }}>
      <h3>Change Background</h3>
      <input
        type="color"
        value={color}
        onChange={(e) => setColor(e.target.value)}
        style={{ width: '100%', height: '40px', border: 'none', cursor: 'pointer' }}
      />
      <button
        onClick={handleChangeColor}
        style={{
          marginTop: '15px',
          width: '100%',
          padding: '10px',
          backgroundColor: '#4CAF50',
          color: 'white',
          border: 'none',
          borderRadius: '5px',
          cursor: 'pointer',
        }}
      >
        Apply Color
      </button>
      {savedColor !== color && (
        <p style={{ marginTop: '10px', color: 'gray' }}>Unsaved changes</p>
      )}
    </div>
  );
}

export default App;

3.2. Update src/App.css

Create or modify src/App.css to style the popup:

/* src/App.css */
body {
  margin: 0;
  padding: 0;
}

h3 {
  margin-bottom: 10px;
}

button:hover {
  background-color: #45a049;
}

3.3. Adjust src/main.jsx

Ensure that src/main.jsx renders the App component correctly:

// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

3.4. Create popup.html

Vite builds a single index.html by default, but for a browser extension, we’ll use popup.html as the entry point for the popup. To achieve this, we’ll need to duplicate and adjust the index.html file.

  1. Duplicate index.html:

    cp public/index.html public/popup.html
    
  2. Modify popup.html:

    Open public/popup.html and ensure it has the correct title and references:

    <!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.0" />
        <title>React Chrome Extension Popup</title>
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="/src/main.jsx"></script>
      </body>
    </html>
    

    Note: The src path may vary based on your Vite configuration. We’ll adjust the build process later to handle multiple entry points.


Step 4: Adding Extension Functionality

In this tutorial, our extension allows users to change the background color of the current webpage. We’ve set up the core functionality in the React component. Additionally, we’ll implement a background script to handle notifications and other background tasks.

4.1. Create background.js

Inside the public folder, create a new file named background.js with the following content:

// public/background.js

chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'fetchData') {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => sendResponse({ data }))
      .catch(error => sendResponse({ error }));
    return true; // Indicates asynchronous response
  }
});

Note: In this simple extension, the background script isn’t strictly necessary, but it’s included here for demonstration and future scalability.

4.2. Update manifest.json Permissions

Ensure that the manifest.json includes the necessary permissions for the background script and notifications:

"permissions": [
  "activeTab",
  "scripting",
  "storage",
  "notifications"
],

4.3. Implement Notifications

In the handleChangeColor function within App.jsx, we’ve already implemented a notification to inform users when the background color changes:

chrome.notifications.create({
  type: 'basic',
  iconUrl: 'icons/icon48.png',
  title: 'Color Changed',
  message: `Background color changed to ${color}`,
});

Ensure that the notifications permission is included in manifest.json.


Step 5: Adjusting the Build Process

Vite is highly configurable and can handle multiple entry points, which is essential for browser extensions that may have separate scripts like popups, backgrounds, and options pages.

5.1. Install Additional Plugins

To handle multiple entry points, we’ll use the vite-plugin-multi-page plugin or manually configure multiple entry points. For simplicity, we’ll manually configure Vite.

5.2. Modify vite.config.js

Update vite.config.js to handle multiple HTML files (index.html for options page, popup.html for the popup, etc.).

Here’s a sample configuration:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

// Plugin to generate multiple HTML files
const multiPage = () => ({
  name: 'multi-page',
  transformIndexHtml(html, { path }) {
    if (path === '/popup.html') {
      return html.replace(/%PUBLIC_URL%/g, '');
    }
    return html;
  },
});

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), multiPage()],
  build: {
    rollupOptions: {
      input: {
        popup: resolve(__dirname, 'public/popup.html'),
        background: resolve(__dirname, 'public/background.js'),
      },
      output: {
        entryFileNames: '[name].js',
        chunkFileNames: '[name].js',
        assetFileNames: '[name].[ext]',
      },
    },
  },
});

Explanation:

  • Plugins: Uses React plugin and a custom multi-page plugin to handle multiple HTML files.
  • Input: Specifies popup.html and background.js as entry points.
  • Output: Configures the naming conventions for built files.

5.3. Adjust File Paths in popup.html

Ensure that popup.html correctly references the built JavaScript file. Modify popup.html as follows:

<!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.0" />
    <title>React Chrome Extension Popup</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/popup.js"></script>
  </body>
</html>

5.4. Update background.js Import (If Necessary)

If your background script requires bundling (e.g., using imports), ensure it’s properly handled. For this simple example, the background script doesn’t require bundling.


Step 6: Testing Your Extension

Before publishing, it’s essential to test your extension to ensure it works as expected.

6.1. Build the Project

Run the build command to generate the production-ready files:

npm run build

This command creates a dist folder containing the built files:

dist/
├── background.js
├── icons/
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
├── manifest.json
├── popup.html
├── popup.js
└── ...

6.2. Load the Extension in Chrome

  1. Open Chrome Extensions Page:

    • Open Google Chrome.
    • Navigate to chrome://extensions/ by typing it into the address bar.
  2. Enable Developer Mode:

    • In the top right corner, toggle the Developer mode switch to the “on” position.
  3. Load Unpacked Extension:

    • Click the Load unpacked button.
    • In the dialog that appears, navigate to your project’s dist folder and select it.
  4. Verify the Extension:

    • Your extension should appear in the list with the specified icon.
    • The extension’s icon should also appear in the Chrome toolbar.
  5. Test the Functionality:

    • Click on the extension’s icon to open the React-based popup.
    • Select a color using the color picker and click “Apply Color.”
    • The background color of the current webpage should change to the selected color.
    • A notification should appear confirming the color change.
  6. Debugging:

    • If something isn’t working, right-click on the popup and select Inspect to open the Developer Tools.
    • Check the Console for any error messages and debug accordingly.
    • Ensure that all necessary files are correctly referenced and that permissions are properly set in manifest.json.

6.3. Make Adjustments as Needed

Based on your testing, you might need to make adjustments to your code, manifest, or build configuration. Repeat the build and load steps until the extension functions as intended.


Step 7: Packaging and Publishing

Once you’re satisfied with your extension, you can package it and publish it to the Chrome Web Store.

7.1. Packaging Your Extension

  1. Navigate to Extensions Page:

    • Go to chrome://extensions/ in Chrome.
  2. Pack Extension:

    • In Developer Mode, click the Pack extension button.
    • Extension root directory: Select your project’s dist folder.
    • Private key file: Leave blank if this is your first time packaging. Chrome will generate a .pem file for you. Keep this file secure as it’s required for future updates.
  3. Click “Pack Extension”:

    • Chrome will create a .crx file (the extension package) and a .pem file (the private key).

7.2. Publishing to Chrome Web Store

  1. Create a Developer Account:

  2. Upload Your Extension:

    • Click Add a new item.

    • Upload the .zip file of your extension by compressing your dist folder:

      cd dist
      zip -r ../react-chrome-extension.zip *
      cd ..
      
  3. Fill in Extension Details:

    • Provide a detailed description, screenshots, and other required information.
    • Ensure you comply with the Chrome Web Store policies.
  4. Submit for Review:

    • After filling in all necessary information, submit your extension for review.
    • The review process can take from a few hours to several days.
  5. Publish:

    • Once approved, your extension will be available on the Chrome Web Store for users to install.

Tip: Monitor your extension’s performance and user feedback through the Developer Dashboard to make informed updates and improvements.


Conclusion

Congratulations! You’ve successfully built a Chrome browser extension using React and Vite. This setup leverages Vite’s rapid development capabilities, providing a streamlined and efficient workflow for extension development.

Recap:

  • Project Setup: Initialized a React project with Vite for faster builds and enhanced flexibility.
  • Manifest Configuration: Defined essential metadata, permissions, and scripts in manifest.json.
  • Popup Development: Created a React-based popup UI to interact with users.
  • Functionality Implementation: Enabled background color changes on the active tab and integrated Chrome APIs for storage and notifications.
  • Build Configuration: Adjusted Vite’s configuration to handle multiple entry points required for browser extensions.
  • Testing: Built and loaded the extension locally to ensure proper functionality.
  • Packaging and Publishing: Prepared the extension for deployment and submitted it to the Chrome Web Store.

Next Steps

To further enhance your extension, consider exploring:

  • Advanced APIs: Interact with bookmarks, history, storage, and more.
  • Background Scripts: Handle tasks that run in the background, independent of the UI.
  • Content Scripts: Inject scripts into web pages to interact with their content.
  • Options Pages: Provide customizable settings for users.
  • Internationalization (i18n): Support multiple languages.
  • State Management: Utilize tools like Redux or Context API for complex state management.
  • Styling Enhancements: Improve the UI with CSS frameworks or libraries like Material-UI or Tailwind CSS.

For more detailed information, refer to the following resources:

Happy coding!


Bonus: Tips, Tricks, and Advanced Features

Enhancing your React-based Chrome extension with advanced strategies can significantly improve its functionality, performance, and user experience. Here are some tips and tricks, common pitfalls to watch out for, and cool features you can implement to elevate your extension.

Table of Contents

  1. Tips and Tricks
  2. What to Watch Out For
  3. Cool Things to Do

Tips and Tricks

1. Optimize Your Build for Performance

  • Code Splitting: Use React’s lazy loading and Suspense to split your code into manageable chunks. This reduces the initial load time of your extension’s popup.

    import React, { Suspense, lazy } from 'react';
    
    const LazyComponent = lazy(() => import('./LazyComponent'));
    
    function App() {
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </Suspense>
      );
    }
    
  • Minimize Bundle Size: Remove unnecessary dependencies and use tools like Rollup (Vite uses Rollup under the hood) to inspect and reduce your bundle size.

    npm install --save-dev rollup-plugin-visualizer
    
  • Use Production Builds: Ensure you’re using production builds for better performance and smaller bundle sizes.

    npm run build
    

2. Manage State Efficiently

  • Context API or Redux: For extensions with complex state management needs, consider using React’s Context API or state management libraries like Redux or Zustand.

    npm install redux react-redux
    
    // src/store.js
    import { createStore } from 'redux';
    
    const initialState = { color: '#ffffff' };
    
    function reducer(state = initialState, action) {
      switch (action.type) {
        case 'SET_COLOR':
          return { ...state, color: action.payload };
        default:
          return state;
      }
    }
    
    const store = createStore(reducer);
    
    export default store;
    

3. Reuse Components Across Different Parts of the Extension

  • Modular Components: Create reusable React components for parts of your extension like buttons, modals, or forms. This promotes code reusability and consistency.

    // src/components/Button.jsx
    import React from 'react';
    
    const Button = ({ onClick, children }) => (
      <button onClick={onClick} style={{ padding: '10px', cursor: 'pointer' }}>
        {children}
      </button>
    );
    
    export default Button;
    

4. Leverage Chrome’s Storage API

  • Persistent Storage: Use chrome.storage to save user preferences or extension state, ensuring data persists across browser sessions.

    // Save data
    chrome.storage.sync.set({ color: selectedColor }, () => {
      console.log('Color saved');
    });
    
    // Retrieve data
    chrome.storage.sync.get(['color'], (result) => {
      console.log('Color retrieved:', result.color);
    });
    

5. Use Environment Variables

  • Secure Configuration: Manage different configurations for development and production using environment variables.

    # .env
    VITE_API_URL=https://api.example.com
    
    // src/config.js
    export const API_URL = import.meta.env.VITE_API_URL;
    

What to Watch Out For

1. Manifest Version Changes

  • Stay Updated: Chrome frequently updates its extension manifest specifications. Ensure you’re using the correct manifest_version (preferably version 3) and keep an eye on Chrome’s Manifest Documentation for updates.

2. Overusing Permissions

  • Minimal Permissions: Only request the permissions your extension truly needs. Overreaching can lead to security vulnerabilities and may deter users from installing your extension.

    "permissions": [
      "activeTab",
      "scripting"
    ]
    

3. Security Concerns

  • Content Security Policy (CSP): Chrome enforces CSP to prevent XSS attacks. Avoid using eval() or inline JavaScript. Use external scripts and adhere to CSP guidelines.

  • Sanitize User Input: If your extension interacts with user input or external data, ensure it’s properly sanitized to prevent injection attacks.

4. Handling Asynchronous Operations

  • Promises and Async/Await: Chrome APIs are often asynchronous. Properly handle promises and use async/await for cleaner and more readable code.

    async function changeColor(color) {
      let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: (selectedColor) => {
          document.body.style.backgroundColor = selectedColor;
        },
        args: [color],
      });
    }
    

5. Debugging Extensions

  • Developer Tools: Use Chrome’s Developer Tools to debug your extension. Right-click on the popup and select Inspect to open the console and debug your React components.

  • Logging: Implement comprehensive logging to track the behavior of your extension and identify issues quickly.

6. Handling Build Outputs Correctly

  • Ensure All Assets Are Included: Make sure your manifest.json, icons, and other assets are correctly referenced and included in the build output.

  • File Paths: Pay attention to file paths in manifest.json and your React components to avoid broken links or missing resources.


Cool Things to Do

1. Add an Options Page

Provide users with customizable settings by adding an options page where they can configure various aspects of your extension.

  • Create an Options Component:

    // src/Options.jsx
    import React, { useState, useEffect } from 'react';
    
    function Options() {
      const [color, setColor] = useState('#ffffff');
    
      useEffect(() => {
        chrome.storage.sync.get(['color'], (result) => {
          if (result.color) {
            setColor(result.color);
          }
        });
      }, []);
    
      const saveColor = () => {
        chrome.storage.sync.set({ color }, () => {
          alert('Color saved!');
        });
      };
    
      return (
        <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
          <h3>Options</h3>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
          />
          <button onClick={saveColor} style={{ marginLeft: '10px' }}>
            Save
          </button>
        </div>
      );
    }
    
    export default Options;
    
  • Create options.html:

    Inside the public folder, create options.html:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Extension Options</title>
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="/src/options.jsx"></script>
      </body>
    </html>
    
  • Add Options Page to manifest.json:

    "options_page": "options.html",
    
  • Create src/options.jsx:

    // src/options.jsx
    import React from 'react';
    import ReactDOM from 'react-dom';
    import Options from './Options';
    import './index.css';
    
    ReactDOM.render(
      <React.StrictMode>
        <Options />
      </React.StrictMode>,
      document.getElementById('root')
    );
    
  • Update vite.config.js:

    Modify vite.config.js to include options.html as an entry point.

    // vite.config.js
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    import { resolve } from 'path';
    
    export default defineConfig({
      plugins: [react()],
      build: {
        rollupOptions: {
          input: {
            popup: resolve(__dirname, 'public/popup.html'),
            options: resolve(__dirname, 'public/options.html'),
            background: resolve(__dirname, 'public/background.js'),
          },
          output: {
            entryFileNames: '[name].js',
            chunkFileNames: '[name].js',
            assetFileNames: '[name].[ext]',
          },
        },
      },
    });
    

2. Implement Context Menus

Enhance user interaction by adding context menu items that perform specific actions when users right-click on a page.

  • Add Context Menu in background.js:

    // public/background.js
    
    chrome.runtime.onInstalled.addListener(() => {
      chrome.contextMenus.create({
        id: 'changeColor',
        title: 'Change Background Color to Blue',
        contexts: ['all'],
      });
    });
    
    chrome.contextMenus.onClicked.addListener((info, tab) => {
      if (info.menuItemId === 'changeColor') {
        chrome.scripting.executeScript({
          target: { tabId: tab.id },
          func: () => {
            document.body.style.backgroundColor = '#0000FF';
          },
        });
      }
    });
    
  • Update manifest.json Permissions:

    "permissions": [
      "activeTab",
      "scripting",
      "storage",
      "notifications",
      "contextMenus"
    ],
    

3. Integrate Third-Party APIs

Extend your extension’s capabilities by integrating with third-party APIs. For example, fetch data from a weather API and display it in your popup.

  • Fetch Data in React Component:

    // src/App.jsx
    import React, { useState, useEffect } from 'react';
    import './App.css';
    
    function App() {
      const [color, setColor] = useState('#ffffff');
      const [weather, setWeather] = useState(null);
    
      useEffect(() => {
        chrome.storage.sync.get(['color'], (result) => {
          if (result.color) {
            setColor(result.color);
          }
        });
    
        // Fetch weather data
        fetch('https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY')
          .then(response => response.json())
          .then(data => setWeather(data))
          .catch(error => console.error('Error fetching weather:', error));
      }, []);
    
      const handleChangeColor = () => {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          chrome.scripting.executeScript({
            target: { tabId: tabs[0].id },
            func: (selectedColor) => {
              document.body.style.backgroundColor = selectedColor;
            },
            args: [color],
          }, () => {
            chrome.storage.sync.set({ color }, () => {
              chrome.notifications.create({
                type: 'basic',
                iconUrl: 'icons/icon48.png',
                title: 'Color Changed',
                message: `Background color changed to ${color}`,
              });
            });
          });
        });
      };
    
      return (
        <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif', width: '250px' }}>
          <h3>Change Background</h3>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
            style={{ width: '100%', height: '40px', border: 'none', cursor: 'pointer' }}
          />
          <button
            onClick={handleChangeColor}
            style={{
              marginTop: '15px',
              width: '100%',
              padding: '10px',
              backgroundColor: '#4CAF50',
              color: 'white',
              border: 'none',
              borderRadius: '5px',
              cursor: 'pointer',
            }}
          >
            Apply Color
          </button>
          {weather && (
            <div style={{ marginTop: '20px' }}>
              <h4>Weather in {weather.name}</h4>
              <p>Temperature: {Math.round(weather.main.temp - 273.15)}°C</p>
              <p>Condition: {weather.weather[0].description}</p>
            </div>
          )}
        </div>
      );
    }
    
    export default App;
    

Note: Replace YOUR_API_KEY with your actual OpenWeatherMap API key. You can obtain one by signing up at OpenWeatherMap.

4. Use Advanced UI Libraries

Enhance your extension’s user interface with advanced UI libraries like Material-UI, Ant Design, or Tailwind CSS to create a more polished and responsive design.

  • Install Material-UI:

    npm install @mui/material @emotion/react @emotion/styled
    
  • Use Material-UI Components:

    // src/App.jsx
    import React, { useState, useEffect } from 'react';
    import { Button, Typography, Box } from '@mui/material';
    import './App.css';
    
    function App() {
      const [color, setColor] = useState('#ffffff');
      const [weather, setWeather] = useState(null);
    
      useEffect(() => {
        chrome.storage.sync.get(['color'], (result) => {
          if (result.color) {
            setColor(result.color);
          }
        });
    
        // Fetch weather data
        fetch('https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY')
          .then(response => response.json())
          .then(data => setWeather(data))
          .catch(error => console.error('Error fetching weather:', error));
      }, []);
    
      const handleChangeColor = () => {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          chrome.scripting.executeScript({
            target: { tabId: tabs[0].id },
            func: (selectedColor) => {
              document.body.style.backgroundColor = selectedColor;
            },
            args: [color],
          }, () => {
            chrome.storage.sync.set({ color }, () => {
              chrome.notifications.create({
                type: 'basic',
                iconUrl: 'icons/icon48.png',
                title: 'Color Changed',
                message: `Background color changed to ${color}`,
              });
            });
          });
        });
      };
    
      return (
        <Box sx={{ padding: '20px', fontFamily: 'Arial, sans-serif', width: '250px' }}>
          <Typography variant="h6">Change Background</Typography>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
            style={{ width: '100%', height: '40px', border: 'none', cursor: 'pointer' }}
          />
          <Button
            variant="contained"
            color="primary"
            onClick={handleChangeColor}
            sx={{ marginTop: '15px', width: '100%' }}
          >
            Apply Color
          </Button>
          {weather && (
            <Box sx={{ marginTop: '20px' }}>
              <Typography variant="subtitle1">Weather in {weather.name}</Typography>
              <Typography variant="body2">Temperature: {Math.round(weather.main.temp - 273.15)}°C</Typography>
              <Typography variant="body2">Condition: {weather.weather[0].description}</Typography>
            </Box>
          )}
        </Box>
      );
    }
    
    export default App;
    

5. Implement Notifications

Provide feedback to users through Chrome’s notifications API when certain actions are performed.

  • Trigger a Notification:

    // Inside handleChangeColor function in App.jsx
    chrome.notifications.create({
      type: 'basic',
      iconUrl: 'icons/icon48.png',
      title: 'Color Changed',
      message: `Background color changed to ${color}`,
    });
    
  • Update manifest.json to Include Notifications Permission:

    "permissions": [
      "activeTab",
      "scripting",
      "storage",
      "notifications",
      "contextMenus"
    ],
    

6. Add Keyboard Shortcuts

Allow users to trigger extension actions using keyboard shortcuts for a more seamless experience.

  • Define Shortcuts in manifest.json:

    "commands": {
      "toggle-color": {
        "suggested_key": {
          "default": "Ctrl+Shift+Y"
        },
        "description": "Toggle background color"
      }
    }
    
  • Listen for Commands in background.js:

    // public/background.js
    
    chrome.commands.onCommand.addListener((command) => {
      if (command === 'toggle-color') {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          chrome.scripting.executeScript({
            target: { tabId: tabs[0].id },
            func: () => {
              document.body.style.backgroundColor =
                document.body.style.backgroundColor === 'yellow' ? 'white' : 'yellow';
            },
          });
        });
      }
    });
    

Conclusion

By leveraging Vite for your React-based Chrome extension, you’ve benefited from a modern build tool that offers rapid development and easy configuration. This setup not only streamlines the development process but also provides the flexibility to scale and enhance your extension with advanced features.

Recap:

  • Project Setup with Vite: Initialized a React project using Vite for faster builds and better performance.
  • Manifest Configuration: Defined essential metadata, permissions, and scripts in manifest.json.
  • Popup and Options Development: Created React-based popup and options pages for user interaction and customization.
  • Functionality Implementation: Enabled background color changes, context menus, notifications, and keyboard shortcuts using Chrome APIs.
  • Build Configuration: Configured Vite to handle multiple entry points required for browser extensions.
  • Testing and Deployment: Built, loaded, and tested the extension locally before packaging and publishing to the Chrome Web Store.

Next Steps

To further enhance your extension, consider exploring:

  • Advanced APIs: Interact with bookmarks, history, storage, and more.
  • Background Scripts: Handle tasks that run in the background, independent of the UI.
  • Content Scripts: Inject scripts into web pages to interact with their content.
  • Internationalization (i18n): Support multiple languages.
  • State Management: Utilize tools like Redux or Context API for complex state management.
  • Styling Enhancements: Improve the UI with CSS frameworks or libraries like Material-UI or Tailwind CSS.

For more detailed information, refer to the following resources:

Happy coding!

Bonus tips

Bonus: Tips, Tricks, and Advanced Features

Congratulations on building your first React-based Chrome extension! To take your extension development skills to the next level, here are some tips and tricks, common pitfalls to watch out for, and cool features you can implement to enhance your extension’s functionality and user experience.

Table of Contents

  1. Tips and Tricks
  2. What to Watch Out For
  3. Cool Things to Do

Tips and Tricks

1. Optimize Your Build for Performance

  • Code Splitting: Use React’s lazy loading and Suspense to split your code into manageable chunks. This reduces the initial load time of your extension’s popup.

    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
    function App() {
      return (
        <React.Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </React.Suspense>
      );
    }
    
  • Minimize Bundle Size: Remove unnecessary dependencies and use tools like Bundle Analyzer to inspect and reduce your bundle size.

    npm install --save-dev webpack-bundle-analyzer
    
  • Use Production Builds: Ensure you’re using production builds for better performance and smaller bundle sizes.

    npm run build
    

2. Manage State Efficiently

  • Context API or Redux: For extensions with complex state management needs, consider using React’s Context API or state management libraries like Redux or Zustand.

    npm install redux react-redux
    
    // src/store.js
    import { createStore } from 'redux';
    
    const initialState = { color: '#ffffff' };
    
    function reducer(state = initialState, action) {
      switch (action.type) {
        case 'SET_COLOR':
          return { ...state, color: action.payload };
        default:
          return state;
      }
    }
    
    const store = createStore(reducer);
    
    export default store;
    

3. Reuse Components Across Different Parts of the Extension

  • Modular Components: Create reusable React components for parts of your extension like buttons, modals, or forms. This promotes code reusability and consistency.

    // src/components/Button.js
    import React from 'react';
    
    const Button = ({ onClick, children }) => (
      <button onClick={onClick} style={{ padding: '10px', cursor: 'pointer' }}>
        {children}
      </button>
    );
    
    export default Button;
    

4. Leverage Chrome’s Storage API

  • Persistent Storage: Use chrome.storage to save user preferences or extension state, ensuring data persists across browser sessions.

    // Save data
    chrome.storage.sync.set({ color: selectedColor }, () => {
      console.log('Color saved');
    });
    
    // Retrieve data
    chrome.storage.sync.get(['color'], (result) => {
      console.log('Color retrieved:', result.color);
    });
    

5. Use Environment Variables

  • Secure Configuration: Manage different configurations for development and production using environment variables.

    // .env
    REACT_APP_API_URL=https://api.example.com
    
    // src/config.js
    export const API_URL = process.env.REACT_APP_API_URL;
    

What to Watch Out For

1. Manifest Version Changes

  • Stay Updated: Chrome frequently updates its extension manifest specifications. Ensure you’re using the correct manifest_version (preferably version 3) and keep an eye on Chrome’s Manifest Documentation for updates.

2. Overusing Permissions

  • Minimal Permissions: Only request the permissions your extension truly needs. Overreaching can lead to security vulnerabilities and may deter users from installing your extension.

    "permissions": [
      "activeTab",
      "scripting"
    ]
    

3. Security Concerns

  • Content Security Policy (CSP): Chrome enforces CSP to prevent XSS attacks. Avoid using eval() or inline JavaScript. Use external scripts and adhere to CSP guidelines.

  • Sanitize User Input: If your extension interacts with user input or external data, ensure it’s properly sanitized to prevent injection attacks.

4. Handling Asynchronous Operations

  • Promises and Async/Await: Chrome APIs are often asynchronous. Properly handle promises and use async/await for cleaner and more readable code.

    async function changeColor(color) {
      let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: (selectedColor) => {
          document.body.style.backgroundColor = selectedColor;
        },
        args: [color],
      });
    }
    

5. Debugging Extensions

  • Developer Tools: Use Chrome’s Developer Tools to debug your extension. Right-click on the popup and select Inspect to open the console and debug your React components.

  • Logging: Implement comprehensive logging to track the behavior of your extension and identify issues quickly.

6. Handling Build Outputs Correctly

  • Ensure All Assets Are Included: Make sure your manifest.json, icons, and other assets are correctly referenced and included in the build output.

  • File Paths: Pay attention to file paths in manifest.json and your React components to avoid broken links or missing resources.


Cool Things to Do

1. Add an Options Page

Provide users with customizable settings by adding an options page where they can configure various aspects of your extension.

  • Create an Options Component:

    // src/Options.js
    import React, { useState, useEffect } from 'react';
    
    function Options() {
      const [color, setColor] = useState('#ffffff');
    
      useEffect(() => {
        chrome.storage.sync.get(['color'], (result) => {
          if (result.color) {
            setColor(result.color);
          }
        });
      }, []);
    
      const saveColor = () => {
        chrome.storage.sync.set({ color }, () => {
          alert('Color saved!');
        });
      };
    
      return (
        <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
          <h3>Options</h3>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
          />
          <button onClick={saveColor} style={{ marginLeft: '10px' }}>
            Save
          </button>
        </div>
      );
    }
    
    export default Options;
    
  • Update manifest.json to Include Options Page:

    "options_page": "options.html"
    
  • Create options.html:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Extension Options</title>
      </head>
      <body>
        <div id="root"></div>
        <script src="options.js"></script>
      </body>
    </html>
    
  • Adjust Build Configuration: Ensure that options.html is built correctly, similar to popup.html.

2. Implement Context Menus

Enhance user interaction by adding context menu items that perform specific actions when users right-click on a page.

  • Add Context Menu in Background Script:

    // src/background.js
    chrome.runtime.onInstalled.addListener(() => {
      chrome.contextMenus.create({
        id: 'changeColor',
        title: 'Change Background Color',
        contexts: ['all'],
      });
    });
    
    chrome.contextMenus.onClicked.addListener((info, tab) => {
      if (info.menuItemId === 'changeColor') {
        chrome.scripting.executeScript({
          target: { tabId: tab.id },
          func: () => {
            document.body.style.backgroundColor = '#ff0000';
          },
        });
      }
    });
    
  • Update manifest.json to Include Background Service Worker:

    "background": {
      "service_worker": "background.js"
    },
    "permissions": [
      "contextMenus",
      "scripting",
      "activeTab"
    ]
    

3. Integrate Third-Party APIs

Extend your extension’s capabilities by integrating with third-party APIs. For example, fetch data from a weather API and display it in your popup.

  • Fetch Data in React Component:

    // src/App.js
    import React, { useState, useEffect } from 'react';
    
    function App() {
      const [weather, setWeather] = useState(null);
    
      useEffect(() => {
        fetch('https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY')
          .then(response => response.json())
          .then(data => setWeather(data));
      }, []);
    
      return (
        <div>
          <h3>Weather in London</h3>
          {weather ? (
            <div>
              <p>Temperature: {Math.round(weather.main.temp - 273.15)}°C</p>
              <p>Condition: {weather.weather[0].description}</p>
            </div>
          ) : (
            <p>Loading...</p>
          )}
        </div>
      );
    }
    
    export default App;
    

4. Use Advanced UI Libraries

Enhance your extension’s user interface with advanced UI libraries like Material-UI, Ant Design, or Tailwind CSS to create a more polished and responsive design.

  • Install Material-UI:

    npm install @mui/material @emotion/react @emotion/styled
    
  • Use Material-UI Components:

    // src/App.js
    import React, { useState } from 'react';
    import { Button, Typography, Box } from '@mui/material';
    
    function App() {
      const [color, setColor] = useState('#ffffff');
    
      const handleChangeColor = () => {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          chrome.scripting.executeScript({
            target: { tabId: tabs[0].id },
            func: (selectedColor) => {
              document.body.style.backgroundColor = selectedColor;
            },
            args: [color],
          });
        });
      };
    
      return (
        <Box sx={{ padding: '20px', fontFamily: 'Arial, sans-serif', width: '250px' }}>
          <Typography variant="h6">Change Background</Typography>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
            style={{ width: '100%', height: '40px', border: 'none', cursor: 'pointer' }}
          />
          <Button
            variant="contained"
            color="primary"
            onClick={handleChangeColor}
            sx={{ marginTop: '15px', width: '100%' }}
          >
            Apply Color
          </Button>
        </Box>
      );
    }
    
    export default App;
    

5. Implement Notifications

Provide feedback to users through Chrome’s notifications API when certain actions are performed.

  • Trigger a Notification:

    // src/App.js
    const handleChangeColor = () => {
      chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        chrome.scripting.executeScript({
          target: { tabId: tabs[0].id },
          func: (selectedColor) => {
            document.body.style.backgroundColor = selectedColor;
          },
          args: [color],
        }, () => {
          chrome.notifications.create({
            type: 'basic',
            iconUrl: 'icons/icon48.png',
            title: 'Color Changed',
            message: `Background color changed to ${color}`,
          });
        });
      });
    };
    
  • Update manifest.json to Include Notifications Permission:

    "permissions": [
      "activeTab",
      "scripting",
      "notifications"
    ]
    

6. Add Keyboard Shortcuts

Allow users to trigger extension actions using keyboard shortcuts for a more seamless experience.

  • Define Shortcuts in manifest.json:

    "commands": {
      "toggle-color": {
        "suggested_key": {
          "default": "Ctrl+Shift+Y"
        },
        "description": "Toggle background color"
      }
    }
    
  • Listen for Commands in Background Script:

    // src/background.js
    chrome.commands.onCommand.addListener((command) => {
      if (command === 'toggle-color') {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          chrome.scripting.executeScript({
            target: { tabId: tabs[0].id },
            func: () => {
              document.body.style.backgroundColor =
                document.body.style.backgroundColor === 'yellow' ? 'white' : 'yellow';
            },
          });
        });
      }
    });
    

7. Implement Internationalization (i18n)

Make your extension accessible to a broader audience by supporting multiple languages.

  • Structure for i18n:

    public/
      _locales/
        en/
          messages.json
        es/
          messages.json
    
  • Example messages.json:

    // public/_locales/en/messages.json
    {
      "extensionName": {
        "message": "React Chrome Extension"
      },
      "extensionDescription": {
        "message": "A Chrome extension built with React."
      }
    }
    
  • Update manifest.json:

    "name": "__MSG_extensionName__",
    "description": "__MSG_extensionDescription__",
    
  • Access Messages in React:

    const extensionName = chrome.i18n.getMessage('extensionName');
    

8. Use Service Workers for Background Tasks

Leverage service workers to handle background tasks efficiently without impacting the popup’s performance.

  • Implement a Service Worker:

    // src/service-worker.js
    chrome.runtime.onInstalled.addListener(() => {
      console.log('Extension installed');
    });
    
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
      if (request.action === 'fetchData') {
        fetch('https://api.example.com/data')
          .then(response => response.json())
          .then(data => sendResponse({ data }))
          .catch(error => sendResponse({ error }));
        return true; // Indicates asynchronous response
      }
    });
    
  • Update manifest.json:

    "background": {
      "service_worker": "service-worker.js"
    }
    

9. Implement Dark Mode Support

Enhance user experience by supporting dark mode, respecting the user’s system preferences.

  • Detect Dark Mode:

    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    
  • Apply Styles Accordingly:

    const App = () => {
      const [color, setColor] = useState(prefersDark ? '#333333' : '#ffffff');
    
      // Rest of the component
    };
    
  • Use CSS Variables:

    /* src/App.css */
    :root {
      --background-color: #ffffff;
      --text-color: #000000;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --background-color: #333333;
        --text-color: #ffffff;
      }
    }
    
    body {
      background-color: var(--background-color);
      color: var(--text-color);
    }
    

10. Integrate with Other Browser APIs

Explore and integrate other powerful Chrome APIs to extend your extension’s capabilities, such as:

  • Bookmarks API: Manage and organize bookmarks.

  • History API: Access and manipulate the browser’s history.

  • Tabs API: Control browser tabs, including creation, modification, and removal.

    // Example: Creating a new tab
    chrome.tabs.create({ url: 'https://www.example.com' });
    

Conclusion

Enhancing your React-based Chrome extension with these tips, tricks, and advanced features can significantly improve its functionality, performance, and user experience. Here’s a quick recap of what you can achieve:

  • Performance Optimization: Efficiently manage your extension’s performance through code splitting and minimizing bundle sizes.
  • State Management: Utilize powerful state management tools to handle complex application states.
  • Enhanced User Interaction: Implement context menus, keyboard shortcuts, and notifications to make your extension more interactive and user-friendly.
  • Security and Best Practices: Adhere to Chrome’s security guidelines, manage permissions wisely, and handle asynchronous operations effectively.
  • Advanced Features: Integrate with third-party APIs, implement internationalization, support dark mode, and leverage other Chrome APIs to add rich features to your extension.

Additional Resources

Final Tips

  • Stay Updated: The browser extension ecosystem evolves rapidly. Regularly check for updates in Chrome’s extension APIs and best practices.
  • User Feedback: Encourage users to provide feedback and reviews. Use this feedback to improve and iterate on your extension.
  • Testing: Continuously test your extension across different scenarios and environments to ensure reliability and performance.

By incorporating these advanced strategies and features, you can develop robust, feature-rich, and user-friendly Chrome extensions that stand out in the Chrome Web Store.

Keep your curiosity going.Explore more React →
287 TUTORIALS · 22 TOPICSREADY