This project is listed in the Awesome Vite
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.
- React 18
- TypeScript
- Jest
- React Testing Library
- Vite
- SASS
- Prettier
- ESLint
- Husky
- Commitlint
- Conventional Commits
- Chrome Extension Manifest Version 3
- HRR(Hot Rebuild & Refresh/Reload)
- Clone this repository.
- Change
name
anddescription
in package.json => Auto synchronize with manifest - Install pnpm globally:
npm install -g pnpm
(check your node version >= 16.6, recommended >= 18) - Run
pnpm install
- Run:
- Dev:
pnpm dev
ornpm run dev
- Prod:
pnpm build
ornpm run build
- Dev:
- Open in browser -
chrome://extensions
- Check -
Developer mode
- Find and Click -
Load unpacked extension
- Select -
dist
folder
- Run:
- Dev:
pnpm dev:firefox
ornpm run dev:firefox
- Prod:
pnpm build:firefox
ornpm run build:firefox
- Dev:
- Open in browser -
about:debugging#/runtime/this-firefox
- Find and Click -
Load Temporary Add-on...
- Select -
manifest.json
fromdist
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.
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 <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>,
);
Override Chrome pageschrome_url_overrides.newtab
in manifest.json
Browser actionsaction.default_pupup
in manifest.json
Devtoolsdevtools_page
in manifest.json
Backgroundbackground.service_worker
in manifest.json
Content Scriptcontent_scripts[0]
in manifest.json
Optionsoptions_page
in manifest.json
SidePanelside_panel.default_path
in manifest.json
Black | White |
---|---|
- https://github.com/Jonghakseo/drag-gpt-extension
- https://github.com/Jonghakseo/pr-commit-noti
- https://github.com/ariburaco/chatgpt-file-uploader-extended
Jetbrains | Jackson Hong |
---|---|