Skip to content

Build Web Extensions with Vite

Updated:

no cap fr fr, we’re about to build the most bussin’ browser extension using Vite, Tailwind CSS, and some zip magic ✨ this gonna hit different i promise πŸ’―

Table of contents

Open Table of contents

🌐 Download Chrome & Firefox (duh) πŸ¦ŠπŸ’™

bestie you literally NEED Chrome and Firefox installed for testing. like… how else are you gonna test it??? 😭 no browsers = no vibes = L πŸ“‰

πŸ“¦ Let’s Get This Bread with Vite 🍞

okay so run this command and watch the magic happen fr fr ✨

bun create vite-plugin-web-extension

(btw bun is goated, npm is giving 2022 energy but do you i guess πŸ’€)

Pick Your Main Character Energy ⚑

when it asks, enter your project name (be creative bestie). then choose your template:

pick your package manager (i’m team bun but that’s just me being based 😎)

πŸ›€οΈ CD Into That Folder Real Quick πŸ“

if it doesn’t auto-cd for you (ugh so annoying), just do it yourself king/queen πŸ‘‘

cd "Project-Name"

literally just navigate there manually if you have to, it’s not that deep πŸ™„

πŸ“¦ Install the Rest of the Squad 🀝

bun add tailwindcss @tailwindcss/vite cross-env zip-a-folder -d npm-run-all

🎨 Make It GORGEOUS with Tailwind CSS πŸ’…βœ¨

time to make this extension absolutely SLAY with some styling periodt πŸ’…

update your vite.config.js (copy-paste this bestie, you’re welcome):

import { defineConfig } from "vite";
import tailwindcss from '@tailwindcss/vite';
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [
    tailwindcss(),
  ],
});

πŸ–ŒοΈ Add Tailwind to Your Popup’s CSS πŸ’–

open pages/Popup.css and yeet this in there:

@import "tailwindcss";

that’s it. that’s the tweet. 🐦

🧩 Update Your Popup Component 🎯

let’s make this popup absolutely iconic fr fr. update pages/Popup.jsx:

import { useEffect } from "react";
import "./Popup.css";

export default function () {
  useEffect(() => {
    console.log("Hello from the popup! πŸ‘‹");
  }, []);

  return <h1 className="font-bold text-3xl underline">Hello world! Test πŸš€</h1>;
}

🦊 Firefox Support Because We’re Inclusive Like That 🫢

create src/manifest.chrome.json:

{
  "manifest_version": 3,
  "icons": {
    "16": "icon/16.png",
    "32": "icon/32.png",
    "48": "icon/48.png",
    "96": "icon/96.png",
    "128": "icon/128.png"
  },
  "action": {
    "default_popup": "src/popup.html"
  },
  "background": {
    "service_worker": "src/background.js"
  }
}

create src/manifest.firefox.json:

{
  "manifest_version": 2,
  "icons": {
    "16": "icon/16.png",
    "32": "icon/32.png",
    "48": "icon/48.png",
    "96": "icon/96.png",
    "128": "icon/128.png"
  },
  "browser_action": {
    "default_popup": "src/popup.html"
  },
  "background": {
    "scripts": ["src/background.js"]
  }
}

update vite.config.js (this is where the sauce is at πŸ”₯):

import { defineConfig } from "vite";
import tailwindcss from '@tailwindcss/vite'
import react from "@vitejs/plugin-react";
import webExtension, { readJsonFile } from "vite-plugin-web-extension";

const target = process.env.TARGET || "chrome";

function generateManifest() {
  const manifestFile = target === "firefox" 
    ? "src/manifest.firefox.json" 
    : "src/manifest.chrome.json";
  
  const manifest = readJsonFile(manifestFile);
  const pkg = readJsonFile("package.json");
  return {
    name: pkg.name,
    description: pkg.description,
    version: pkg.version,
    ...manifest,
  };
}

export default defineConfig({
  build: {
    outDir: `dist-${target}`,
  },
  plugins: [
    tailwindcss(),
    react(),
    webExtension({
      manifest: generateManifest,
      browser: target,
    }),
  ],
});

update package.json scripts:

  "scripts": {
    "dev": "vite",
    "dev:chrome": "cross-env TARGET=chrome vite",
    "dev:firefox": "cross-env TARGET=firefox vite --port 5174",
    "dev:both": "npm-run-all --parallel dev:chrome dev:firefox",
  },

πŸƒβ€β™€οΈ Run This Baby in Dev Mode πŸ’¨

okay moment of truth bestie, let’s see this thing pop off 🎬

bun dev:both

πŸ“¦ Zip It Up for Distribution πŸ—œοΈ

ready to share your creation with the world? let’s package this up ✨

create a zipScript.js file:

import { zip } from "zip-a-folder";
await zip("dist", "extension.zip");

πŸ› οΈ Update package.json One Last Time πŸ’ͺ

add these scripts so you can build and zip with one command (efficiency queen/king πŸ‘‘):

  "scripts": {
    "dev": "vite",
    "dev:chrome": "cross-env TARGET=chrome vite",
    "dev:firefox": "cross-env TARGET=firefox vite --port 5174",
    "dev:both": "npm-run-all --parallel dev:chrome dev:firefox",
    "build": "vite build && node zipScript.js",
    "zip": "node zipScript.js"
  },

πŸŽ‰ YEET! You Did That! πŸ’―πŸ”₯

bun build

you literally just built a whole browser extension that works on BOTH Chrome AND Firefox??? the serve is immaculate πŸ’…βœ¨

now go test it, add your own flavor, and make it absolutely iconic. you’re literally a coding icon now no cap πŸ¦Έβ€β™€οΈπŸ’»


Share this post on:

Next Post
Obsessed with "Bun" as my main Package Manager