Skip to content

Latest commit

 

History

History
executable file
·
518 lines (395 loc) · 27.6 KB

File metadata and controls

executable file
·
518 lines (395 loc) · 27.6 KB
logo

Chrome Extension Boilerplate with
React + Vite + TypeScript

GitHub action badge hits

This project is listed in the Awesome Vite

Table of Contents

Intro

This boilerplate is made for creating chrome extensions using React and Typescript.

The focus was on improving the build speed and development experience with Vite.

Features

Installation

Procedures:

  1. Clone this repository.
  2. Change extensionDescription and extensionName in messages.json
  3. Install pnpm globally: npm install -g pnpm (check your node version >= 16.6, recommended >= 18)
  4. Run pnpm install

And next, depending on the needs:

For Chrome:

  1. Run:
    • Dev: pnpm dev or npm run dev
    • Prod: pnpm build or npm run build
  2. Open in browser - chrome://extensions
  3. Check - Developer mode
  4. Find and Click - Load unpacked extension
  5. Select - dist folder

For Firefox:

  1. Run:
    • Dev: pnpm dev:firefox or npm run dev:firefox
    • Prod: pnpm build:firefox or npm run build:firefox
  2. Open in browser - about:debugging#/runtime/this-firefox
  3. Find and Click - Load Temporary Add-on...
  4. Select - manifest.json from dist folder

Remember in firefox you add plugin in temporary mode, that's mean it's disappear after close browser, you must do it again, on next launch.

Add Style Library

IMPORTANT: If you DO NOT want to use css file in the content script, you need to delete the css file in your manifest.js

content_scripts: [
  {
    // YOU NEED TO DELETE THIS
    css: ["assets/css/contentStyle<KEY>.chunk.css"]
  }
];

Twind

The smallest, fastest, most feature complete Tailwind-in-JS solution in existence

1. Install the library:

$ pnpm install -D @twind/core @twind/preset-autoprefix @twind/preset-tailwind

2. Create twind.config.ts in the root folder

twind.config.ts
import { defineConfig } from '@twind/core';
import presetTailwind from '@twind/preset-tailwind';
import presetAutoprefix from '@twind/preset-autoprefix';

export default defineConfig({
  presets: [presetAutoprefix(), presetTailwind()],
});

3. Create src/shared/style/twind.ts for importing

src/shared/style/twind.ts
import { twind, cssom, observe } from '@twind/core';
import 'construct-style-sheets-polyfill';
import config from '@root/twind.config';

export function attachTwindStyle<T extends { adoptedStyleSheets: unknown }>(
  observedElement: Element,
  documentOrShadowRoot: T,
) {
  const sheet = cssom(new CSSStyleSheet());
  const tw = twind(config, sheet);
  observe(tw, observedElement);
  documentOrShadowRoot.adoptedStyleSheets = [sheet.target];
}

4. You can use Tailwind in your project:

src/pages/popup/index.tsx
import { attachTwindStyle } from '@src/shared/style/twind';

...
attachTwindStyle(appContainer, document);
const root = createRoot(appContainer);
root.render(<Popup />);

5. If you want to use Twind in the content script, you need to add the following code:

src/pages/content/ui/root.tsx
import { attachTwindStyle } from '@src/shared/style/twind';

...
attachTwindStyle(rootIntoShadow, shadowRoot);
createRoot(rootIntoShadow).render(<App />);

See more examples

Chakra UI

1. Install the library:

$ pnpm install @chakra-ui/react @emotion/cache @emotion/react

2. You should add the code to vite.config.ts for Ignore unnecessary warnings

vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      // Add below code ~~~~~
      onwarn(warning, warn) {
        if (
          warning.code === "MODULE_LEVEL_DIRECTIVE" &&
          warning.message.includes(`"use client"`)
        ) {
          return;
        }
        warn(warning);
      },
      // Add above code ~~~~
    },
  },
});

3. You can use Chakra UI in your project:

src/pages/popup/Popup.tsx
import { Button } from "@chakra-ui/react";

export default function Popup() {
  return (
    <ChakraProvider>
      <Button colorScheme="teal">Button</Button>;
    </ChakraProvider>
  ); 
}

if you don't want to use Chakra UI in the content script, you can skip 4,5 step.

4. If you want to use Chakra UI in the content script, you need to add the following code:

src/pages/content/ui/CustomChakraProvider.tsx
import { ReactNode, useCallback, useEffect, useState } from "react";
import {
  ColorMode,
  ColorModeContext,
  ColorModeScript,
  CSSReset,
  extendTheme,
  GlobalStyle,
  ThemeProvider
} from "@chakra-ui/react";

const theme = extendTheme();

const getCurrentTheme = () => {
  const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  return isDark ? "dark" : "light";
};

type CustomChakraProviderProps = {
  shadowRootId: string;
  children: ReactNode;
};
export default function CustomChakraProvider({ children, shadowRootId }: CustomChakraProviderProps) {
  const [colorMode, setColorMode] = useState<ColorMode>(getCurrentTheme());

  useEffect(() => {
    const darkThemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const onChangeColorSchema = (event: MediaQueryListEvent) => {
      const isDark = event.matches;
      setColorMode(isDark ? "dark" : "light");
    };

    darkThemeMediaQuery.addEventListener("change", onChangeColorSchema);

    return () => {
      darkThemeMediaQuery.removeEventListener("change", onChangeColorSchema);
    };
  }, []);

  const toggleColorMode = useCallback(() => {
    setColorMode(prev => (prev === "dark" ? "light" : "dark"));
  }, []);

  return (
    <ThemeProvider theme={{ ...theme, config: { ...theme.config, colorMode } }} cssVarsRoot={`#${shadowRootId}`}>
      <ColorModeScript initialColorMode="system" />
      <ColorModeContext.Provider value={{ colorMode, setColorMode, toggleColorMode }}>
        <CSSReset />
        <GlobalStyle />
        {children}
      </ColorModeContext.Provider>
    </ThemeProvider>
  );
}
src/pages/content/ui/EmotionCacheProvider.tsx
import createCache from '@emotion/cache';
import { CacheProvider, type EmotionCache } from '@emotion/react';
import { ReactNode, useEffect, useState } from 'react';

export default function EmotionCacheProvider({ children, rootId }: { rootId: string; children: ReactNode }) {
  const [emotionCache, setEmotionCache] = useState<EmotionCache | null>(null);

  useEffect(() => {
    function setEmotionStyles(shadowRoot: ShadowRoot) {
      setEmotionCache(
        createCache({
          key: rootId,
          container: shadowRoot,
        }),
      );
    }

    const root = document.getElementById(rootId);
    if (root && root.shadowRoot) {
      setEmotionStyles(root.shadowRoot);
    }
  }, []);

  return emotionCache ? <CacheProvider value={emotionCache}>{children}</CacheProvider> : null;
}

5. Fix the src/pages/content/ui/root.tsx file:

src/pages/content/ui/root.tsx
import CustomChakraProvider from '@pages/content/ui/CustomChakraProvider';
import EmotionCacheProvider from '@pages/content/ui/EmotionCacheProvider';

// ...

createRoot(rootIntoShadow).render(
  // Add Providers
  <EmotionCacheProvider rootId={root.id}>
    <CustomChakraProvider shadowRootId={rootIntoShadow.id}>
      <App />
    </CustomChakraProvider>
  </EmotionCacheProvider>,
);

Pages

New Tab

Override Chrome pages
chrome_url_overrides.newtab in manifest.json

Popup

Browser actions
action.default_popup in manifest.json

Devtools

Devtools
devtools_page in manifest.json

Background

Background
background.service_worker in manifest.json

ContentScript

Content Script (contentInjected/contentUI)
content_scripts in manifest.json

Options

Options
options_page in manifest.json

SidePanel (Chrome 114+)

SidePanel
side_panel.default_path in manifest.json

Screenshots

New Tab

newtab

Popup

Black White
black white

Devtools

devtools

Examples

Documents

Star History

Star History Chart

Contributors

JunyWuuuu91
JunyWuuuu91

💻
dim0147
dim0147

🐛
jon lepage
jon lepage

🐛
LironH
LironH

🤔
Spencer Chang
Spencer Chang

🐛
deld123
deld123

🐛
Michal Hantl
Michal Hantl

🤔 🐛
Jordan Burgess
Jordan Burgess

🤔
NAMEUN CHO
NAMEUN CHO

🐛
Andrew Mudrov
Andrew Mudrov

💬
Shubham Lad
Shubham Lad

🐛
hanrong
hanrong

🐛
Florian König
Florian König

💬
Tran Phong
Tran Phong

🐛
tonychandesign
tonychandesign

🐛
Ihor Makarchuk
Ihor Makarchuk

🐛
hugoobauer
hugoobauer

🐛
Karan Singh
Karan Singh

🤔
remusris
remusris

🤔
hegel_dark
hegel_dark

🤔
Jingsi
Jingsi

🐛 💻
Chris Ozgo
Chris Ozgo

🐛
Cong
Cong

🐛
PatrykKuniczak
PatrykKuniczak

🤔 💻 📖
Hector Parra
Hector Parra

🐛
JeongHyeon Kim
JeongHyeon Kim

🚇
Terminels
Terminels

💻
WonkyDD
WonkyDD

💻 🐛
wangxy
wangxy

🐛
Rasul
Rasul

📖
gavinhow
gavinhow

🐛
Anand D.
Anand D.

📖
Romain Dequidt
Romain Dequidt

📖
Jakob Guddas
Jakob Guddas

📖 🐛
Dino Scheidt
Dino Scheidt

💻
秋知
秋知

💻
Hiverse
Hiverse

🐛
rosendolu
rosendolu

💻
Egor Stronhin
Egor Stronhin

📖
webLiang
webLiang

💻
Adam Spiers
Adam Spiers

🐛
Ofir Zeitoun
Ofir Zeitoun

💻
Dmitri Yourchev
Dmitri Yourchev

💻 🐛
Gaishi Hirota
Gaishi Hirota

💻
pipizhu
pipizhu

💻

Thanks To

Jetbrains Jackson Hong
JetBrains Logo (Main) logo. Jackson Hong

Jonghakseo