Skip to content

graybearo/chrome-extension-webpack-react

Repository files navigation

chrome-extension-webpack-react

A Chrome extension boilerplate for Manifest V3 (MV3) — webpack 5 + React + TypeScript, with live reload powered by webpack-ext-reloader-next, typed message passing, and a service-worker keepalive baked in.

MV3 webpack 5 React 18 TS 5

A modern, opinionated webpack-based starter for building Chrome extensions (and Edge / Firefox / browser extensions in general) on Manifest V3. If you're more comfortable with webpack than Vite, or your existing toolchain is webpack-based, this is the starter you want.

What's in the box

  • 🔧 Webpack 5 + ts-loader (transpileOnly: true) — fast cold builds, filesystem cache for warm rebuilds.
  • ⚛️ React 18 + TypeScript (strict) — popup and options are real React apps with full TS support.
  • 🔁 Live reload via webpack-ext-reloader-next — rebuild triggers an automatic extension reload. No manual Reload clicks.
  • 📡 Typed messaging via mv3-message-router — one Messages interface, end-to-end typing in SW + popup + content.
  • Durable scheduling via mv3-keepalivechrome.alarms that survive SW termination.
  • 📦 pnpm zip — packs dist/ into a ready-to-upload .zip.

Quick start

git clone https://github.com/graybearo/chrome-extension-webpack-react my-extension
cd my-extension
pnpm install
pnpm dev

Then in Chrome:

  1. Open chrome://extensions/
  2. Toggle Developer mode on
  3. Click Load unpacked
  4. Select the dist/ folder

Edit any file in src/; the extension reloads automatically.

Layout

src/
├── background/index.ts     # service worker — router + alarms live here
├── content/index.ts        # injected into every https:// page
├── popup/                  # React popup (action.default_popup)
├── options/                # React options page
├── shared/
│   ├── messages.ts         # typed message contract
│   └── storage.ts          # thin chrome.storage wrapper
└── manifest.json           # MV3 manifest (Chrome / Edge)

How messaging works

Declare your messages once:

// src/shared/messages.ts
export interface Messages {
  GET_STATE: { input: void; output: { count: number } };
  INCREMENT: { input: { by: number }; output: { count: number } };
}

Register handlers in the SW:

// src/background/index.ts
const router = createRouter<Messages>();
router.on("INCREMENT", async ({ by }) => { /* ... */ });
router.listen();

Call from popup / content / options:

const client = createClient<Messages>();
const { count } = await client.send("INCREMENT", { by: 1 });
//      ^? number

No untyped chrome.runtime.sendMessage, no return true foot-guns, no manual error serialization.

Building

pnpm dev             # watch mode + live reload
pnpm build           # production bundle into dist/
pnpm zip             # zip dist/ for web-store upload

Adding an icon set

Drop icon-16.png, icon-32.png, icon-48.png, icon-128.png into public/icons/ and reference them in src/manifest.json. Everything in public/ is copied verbatim to dist/.

Why webpack and not Vite?

Choose based on your team's stack:

  • Webpack — mature, huge plugin ecosystem, integrates with existing webpack-based build infrastructure. This starter.
  • Vite — faster HMR, smaller config. See chrome-extension-vite-react.

Both starters share the same src/ layout and use the same mv3-keepalive + mv3-message-router packages, so you can switch between them without rewriting your app code.

Related packages

Part of a small MV3 toolkit for Chrome / Edge / Firefox extensions by @graybearo:

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors