by Timur Dautov

How to build a Chrome Extension using React, Tailwind and Vite

In this article, we will learn how to build a Chrome Extension using React, Tailwind CSS and Vite.

Prerequisites#

Before we start building the Chrome Extension, make sure you have the following tools installed on your machine:

  • Node.js
  • npm or yarn

Create and Initialize a new React app#

npm create vite@latest my-chrome-extension -- --template react-ts
cd my-chrome-extension
npm install

Install & Configure Tailwind CSS#

  • Install Tailwind and its dependencies:
npm install -D tailwindcss postcss autoprefixer
  • Generate Tailwind Config Files:
npx tailwindcss init -p
  • Update tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  • Add Tailwind directives to src/index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;

Configure Vite for Chrome extension#

Create a vite.config.ts file in the root directory:

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, 'index.html'),
        background: resolve(__dirname, 'src/background.ts'),
        content: resolve(__dirname, 'src/content.ts')
      },
      output: {
        entryFileNames: 'assets/[name].js',
        chunkFileNames: 'assets/[name].js',
        assetFileNames: 'assets/[name].[ext]'
      }
    }
  }
})

Create manifest.json#

Create a manifest.json file in the root directory:

{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "1.0.0",
"description": "A Chrome extension built with React, Vite, TypeScript, and Tailwind",
"action": {
  "default_popup": "index.html",
  "default_icon": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
},
"permissions": [],
"background": {
  "service_worker": "assets/background.js",
  "type": "module"
},
"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["assets/content.js"]
  }
],
"icons": {
  "16": "icons/icon16.png",
  "48": "icons/icon48.png",
  "128": "icons/icon128.png"
}
}

Create a background.js file#

Create a new file src/background.ts:

console.log('Background script running');

// Listen for messages from the popup or content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Received message:', message);
  sendResponse({ status: 'Message received' });
  return true;
});

Create a content.js file#

Create a new file src/content.ts:

console.log('Content script loaded');

// Example: Send a message to the background script
chrome.runtime.sendMessage({ action: 'contentScriptLoaded' }, (response) => {
  console.log('Response:', response);
});

Create a Popup Component#

Create a new file src/Popup.tsx:

import React from 'react'

export default function Popup() {

  const sendMessageToBackground = () => {
    chrome.runtime.sendMessage({ action: 'popupAction', data: 'Hello from popup!' }, 
      (response) => {
        console.log('Response from background:', response);
      }
    );
  };

  return (
    <div className="p-4 bg-white shadow rounded-lg">
      <h1 className="text-xl font-bold">Hello, Chrome Extension!</h1>
      <button 
        className="mt-2 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
        onClick={sendMessageToBackground}
      >
        Send Message
      </button>
    </div>
  )
}

Update index.tsx#

Update src/index.tsx to render the Popup component:


import React from 'react'
import ReactDOM from 'react-dom'
import Popup from './Popup'

ReactDOM.render(<Popup />, document.getElementById('root'))

Use chrome.storage#

Create a new file src/storage.ts:

export const Storage = {
  async set<T>(key: string, value: T): Promise<void> {
    const data = JSON.stringify(value);
    if (chrome.storage) {
      await chrome.storage.local.set({ [key]: data });
    } else {
      localStorage.setItem(key, data);
    }
  },

  async get<T>(key: string, defaultValue: T): Promise<T> {
    if (chrome.storage) {
      return new Promise<T>((resolve) => {
        chrome.storage.local.get([key], (result) => {
          resolve(result[key] ? JSON.parse(result[key]) : defaultValue);
        });
      });
    } else {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    }
  },

  async remove(key: string): Promise<void> {
    if (chrome.storage) {
      await chrome.storage.local.remove(key);
    } else {
      localStorage.removeItem(key);
    }
  },

  async clear(): Promise<void> {
    if (chrome.storage) {
      await chrome.storage.local.clear();
    } else {
      localStorage.clear();
    }
  },
};

Example of usage:

import { Storage } from './storage';

Storage.set('myKey', { name: 'John' });
const data = await Storage.get('myKey', { name: 'John' });

Install crx-js Vite Plugin (Optional)#

CRX is a Vite plugin that allows you to build Chrome extensions. Install it using npm:

npm install --save-dev @crxjs/vite-plugin@beta

Build the Chrome Extension#

Run the following command to build the Chrome Extension:

npm run build

Load the Chrome Extension#

Load the extension in Chrome:

  1. Open Chrome and go to chrome://extensions/
  2. Enable "Developer mode"
  3. Click "Load unpacked" and select the dist folder in your project directory

Publish the Chrome Extension#

  1. Go to the Chrome Web Store Developer Dashboard
  2. Click "Create new item" and select "Chrome extension"
  3. Fill in the required information
  4. Upload the extension files
  5. Click "Publish"
  • https://github.com/guocaoyi/create-chrome-ext