Extro Browser Extension Starter Kit

repository·main·Indexed 19 days ago

https://github.com/turbostarter/extro

An opinionated, production-ready boilerplate for building modern web extensions. Built with the WXT framework, Extro integrates React, Supabase, Tailwind, and shadcn/ui to provide out-of-the-box authentication, storage, and messaging. It includes preconfigured entrypoints for background scripts, content scripts, devtools, new tabs, options, popups, and sidepanels, with support for both Chrome and Firefox development and publishing.

Tokens
8.2K
Snippets
39
Records
42
Agent score
64%

What's inside extro

  1. Preconfigured extension pages in Extro

    main

    Extro comes with several extension entrypoints preconfigured using the WXT framework:

    • background: The background service worker.
    • content: Content scripts that run in the context of web pages.
    • devtools: A devtools page with custom panels.
    • newtab: The new tab page.
    • options: The extension options page.
    • popup: The extension popup window.
    • sidepanel: The extension side panel.
    • tabs: Unlisted custom pages delivered with the extension.
  2. Publish your extension

    main

    Manual Publishing

    To build the extension files for manual upload to the Chrome Web Store or Firefox Add-ons:

    • Build for both browsers: bun run build
    • Build for Chrome only: bun build:chrome
    • Build for Firefox only: bun build:firefox

    After building, locate the .zip files in the build directory to upload.

    CI/CD Publishing

    You can automate publishing using GitHub Actions:

    1. Obtain the required API keys for your submission (refer to the WXT official token guide).
    2. Add these keys as GitHub Secrets in your repository.
    3. Trigger the CI / Publish workflow.
    bun run build
    # or
    bun build:chrome
    bun build:firefox
  3. Install Extro boilerplate

    main

    To set up a new project using the Extro boilerplate, ensure you have Bun installed, then follow these steps:

    1. Clone the repository:
      git clone git@github.com:turbostarter/extro.git
    2. Install dependencies using Bun:
      bun install
    3. Configure your environment variables by copying the example file:
      cp .env.example .env
      Note: You must update the variables in .env with your own credentials (e.g., Supabase, etc.) before running the project.
    git clone git@github.com:turbostarter/extro.git
    bun install
    cp .env.example .env
  4. Develop for Firefox

    main

    To run Extro in development mode for Firefox:

    1. Start the development server:
      bun dev:firefox
    2. Open Firefox and navigate to about:debugging#/runtime/this-firefox.
    3. Click Load Temporary Add-on....
    4. Select the build/firefox-mv2/manifest.json file located at the root of your project.
    NOTE

    In Firefox, the extension is loaded in temporary mode, meaning it will be removed once the browser is closed.

    bun dev:firefox
  5. Develop for Chrome

    main

    To run Extro in development mode for Google Chrome:

    1. Start the development server:
      bun dev:chrome
    2. Open Chrome and navigate to chrome://extensions.
    3. Enable Developer mode.
    4. Click Load unpacked.
    5. Select the build/chrome-mv3 directory located at the root of your project.
    bun dev:chrome
  6. Initialize the Tabs application entrypoint

    main

    The src/app/tabs/main.tsx file serves as the entrypoint for the Tabs application. It initializes a React application using ReactDOM.createRoot and configures client-side routing using react-router-dom's createHashRouter.

    The application is mounted to the DOM element with the ID root. The router defines the following top-level routes:

    • /ai: Renders the AI component.
    • /login: Renders the Login component.
    • /register: Renders the Register component.

    Note that because it uses createHashRouter, the application URLs will follow the hash pattern (e.g., /#/ai, /#/login).

    import ReactDOM from "react-dom/client";
    import { createHashRouter, RouterProvider } from "react-router-dom";
    import { AI } from "./ai";
    import { Login } from "./login";
    import { Register } from "./register";
    
    const router = createHashRouter([
      {
        children: [
          {
            path: "ai",
            element: <AI />,
          },
          {
            path: "login",
            element: <Login />,
          },
          {
            path: "register",
            element: <Register />,
          },
        ],
      },
    ]);
    
    ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
      <RouterProvider router={router} />,
    );
  7. Initialize the extension popup entrypoint

    main

    The extension popup is initialized by rendering a Popup component into a DOM element with the ID root. The popup structure uses a Layout wrapper around a Main component. The Main component requires a filename prop (set to app/popup in the default entrypoint) and can accept a className for styling the container width and padding.

    import React from "react";
    import ReactDOM from "react-dom/client";
    import { Main } from "~/components/common/main";
    import { Layout } from "~/components/layout/layout";
    
    const Popup = () => {
      return (
        <Layout>
          <Main className="w-[23rem] px-4" filename="app/popup" />
        </Layout>
      );
    };
    
    ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
      <React.StrictMode>
        <Popup />
      </React.StrictMode>,
    );
  8. Initialize the Sidepanel entrypoint

    main

    The sidepanel is initialized by rendering a SidePanel component into a DOM element with the ID root. The component structure uses a Layout wrapper around a Main component, which is configured with the filename prop set to app/sidepanel to load the appropriate application logic.

    import React from "react";
    import ReactDOM from "react-dom/client";
    import { Main } from "~/components/common/main";
    import { Layout } from "~/components/layout/layout";
    
    const SidePanel = () => {
      return (
        <Layout>
          <Main filename="app/sidepanel" />
        </Layout>
      );
    };
    
    ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
      <React.StrictMode>
        <SidePanel />
      </React.StrictMode>,
    );
  9. Initialize the DevTools panel and sidebar

    main

    The DevTools interface is initialized using the wxt/browser API. It creates a primary panel using the extension's localized name and an icon, loading the content from devtools.html. Additionally, it creates a sidebar pane within the elements context, which is initialized with a default object containing the name DevTools.

    // Creates the main DevTools panel
    browser.devtools.panels.create(
      browser.i18n.getMessage("extensionName"),
      "icons/128.png",
      "devtools.html",
    );
    
    // Creates a sidebar pane within the Elements panel
    browser.devtools.panels.elements.createSidebarPane(
      browser.i18n.getMessage("extensionName"),
      (sidebar) => {
        sidebar.setObject({
          name: "DevTools",
        });
      },
    );
  10. Configure environment variables for Extro

    main

    Extro uses envin and zod to define and validate environment variables. The configuration is split into shared (available to both client and server), client (prefixed with VITE_ and available to the browser), and env (the source mapping).

    Required Client Variables

    To run the client-side application, the following environment variables must be provided with the VITE_ prefix:

    • VITE_OPEN_PANEL_KEY: A string key for the open panel.
    • VITE_SUPABASE_URL: A valid URL for the Supabase instance.
    • VITE_SUPABASE_ANON_KEY: The Supabase anonymous API key.

    Shared Variables

    • NODE_ENV: Determines the runtime environment. It defaults to development if not specified.
    import { defineEnv } from "envin";
    import { z } from "zod";
    import { NodeEnv } from "./src/types";
    
    export default defineEnv({
      shared: {
        NODE_ENV: z.nativeEnum(NodeEnv).default(NodeEnv.DEVELOPMENT),
      },
      clientPrefix: "VITE_",
      client: {
        VITE_OPEN_PANEL_KEY: z.string(),
        VITE_SUPABASE_URL: z.string().url(),
        VITE_SUPABASE_ANON_KEY: z.string(),
      },
      // ...
    });
  11. Configure WXT modules and Vite plugins

    main

    WXT allows you to extend functionality using modules and custom Vite configurations:

    • modules: An array of WXT modules to include (e.g., @wxt-dev/module-react, @wxt-dev/auto-icons).
    • vite: A function that returns a WxtViteConfig object, allowing you to inject custom Vite plugins (e.g., tailwindcss, vite-plugin-svgr).
    import tailwindcss from "@tailwindcss/vite";
    import svgr from "vite-plugin-svgr";
    import { defineConfig, type WxtViteConfig } from "wxt";
    
    export default defineConfig({
      modules: ["@wxt-dev/module-react", "@wxt-dev/auto-icons"],
      vite: () => ({
        plugins: [svgr(), tailwindcss()],
      }) as WxtViteConfig,
    });
  12. Configure WXT directory structure and output

    main

    You can customize the project's file organization and build output using the following keys in defineConfig:

    • srcDir: Specifies the directory containing your source code (e.g., "src").
    • entrypointsDir: Specifies the directory where entrypoints are located (e.g., "app").
    • outDir: Specifies the directory where the build output will be placed (e.g., "build").
    export default defineConfig({
      srcDir: "src",
      entrypointsDir: "app",
      outDir: "build",
    });