Chrome Extension Boilerplate with
React + Vite + TypeScript
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
- React 18
- TypeScript
- Vitest
- React Testing Library
- Vite
- SASS
- Prettier
- ESLint
- Husky
- Commitlint
- Conventional Commits
- Chrome Extension Manifest Version 3
- HRR(Hot Rebuild & Refresh/Reload)
Installation
Procedures:
- Clone this repository.
- Change
nameanddescriptionin package.json => Auto synchronize with manifest - Install pnpm globally:
npm install -g pnpm(check your node version >= 16.6, recommended >= 18) - Run
pnpm install
And next, depending on the needs:
For Chrome:
- Run:
- Dev:
pnpm devornpm run dev - Prod:
pnpm buildornpm run build
- Dev:
- Open in browser -
chrome://extensions - Check -
Developer mode - Find and Click -
Load unpacked extension - Select -
distfolder
For Firefox:
- Run:
- Dev:
pnpm dev:firefoxornpm run dev:firefox - Prod:
pnpm build:firefoxornpm run build:firefox
- Dev:
- Open in browser -
about:debugging#/runtime/this-firefox - Find and Click -
Load Temporary Add-on... - Select -
manifest.jsonfromdistfolder
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-tailwind2. 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/Popup.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/index.tsx
import { attachTwindStyle } from '@src/shared/style/twind';
...
attachTwindStyle(rootIntoShadow, shadowRoot);
createRoot(rootIntoShadow).render(<App />);Chakra UI
1. Install the library:
$ pnpm install @chakra-ui/react @emotion/cache @emotion/react2. 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 <Button colorScheme="teal">Button</Button>;
}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} 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/index.tsx file:
src/pages/content/index.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 pageschrome_url_overrides.newtab in
manifest.json
Popup
Browser actionsaction.default_popup in
manifest.json
Devtools
Devtoolsdevtools_page in manifest.json
Background
Backgroundbackground.service_worker in
manifest.json
ContentScript
Content Scriptcontent_scripts[0] in
manifest.json
Options
Optionsoptions_page in manifest.json
SidePanel (Chrome 144+)
SidePanelside_panel.default_path in
manifest.json
Screenshots
New Tab
Popup
| Black | White |
|---|---|
![]() |
![]() |
Devtools
Examples
- https://github.com/Jonghakseo/drag-gpt-extension
- https://github.com/Jonghakseo/pr-commit-noti
- https://github.com/ariburaco/chatgpt-file-uploader-extended
Documents
Star History
Contributors
Thanks To
| Jetbrains | Jackson Hong |
|---|---|
![]() |


