crxjs

CRXJS Chrome extension development — true HMR for popup, options, content scripts, side panels, manifest-driven builds, dynamic content script imports (`?script`, `?script&module`), and `defineManifest` for type-safe manifests. Uses Vite as its build tool. Use when the user mentions CRXJS, crxjs, @c

By samber · 2,005 installs

npx skills add samber/cc-skills --skill crxjs

Source repository · Upstream listing

CRXJS CRXJS is a Chrome extension development tool that provides true HMR for popup, options, content scripts, and side panels. It reads your manifest to auto generate the extension output, handles content script injection, and manages the service worker build. Under the hood it is a Vite plugin ( @crxjs/vite plugin ). Current status Package : @crxjs/vite plugin (v2.x stable, latest v2.4.0 as of March 2026) Scaffolding : npm create crxjs@latest (always use @latest ) Maintained by : @Toumash and @FliPPeDround (since mid 2025) GitHub : github.com/crxjs/chrome extension tools (~4k stars) Vite compatibility : v3 through v8 beta Quick start Vite config by framework CRXJS is added as a Vite plugin. The setup varies slightly per framework. React Use @vitejs/plugin react (not plugin react swc ) for best HMR compatibility. If you must use SWC, cast the manifest: Vue Svelte Vanilla TypeScript defineManifest — type safe dynamic manifest Instead of a static JSON file, use CRXJS's defineManifest for dynamic values and full TypeScript autocompletion: Import in vite.config.ts: Type declarations Add to a src/vite env.d.ts or src/crxjs.d.ts : This enables types for ?script and ?script&module imports. HMR behavior by context Context HMR How it works Popup Full HMR WebSocket based, state preserved Options page Full HMR Same as popup Side panel Full HMR Same as popup Content script (manifest) True HMR CRXJS injects loader + HMR client Content script (dynamic) True HMR Via ?script import Service worker Auto reload Changes trigger full extension reload Main world scripts No HMR Skipped by CRXJS loader Content script HMR works because CRXJS generates a loader script that imports an HMR preamble, the HMR client, and your actual script — enabling real module level HMR without full page reload. This is CRXJS's main differentiator. Dynamic content script imports For content scripts injected programmatically (not in manifest), CRXJS provides special import suffixes: For main world injection (no HMR): CRXJS plugin options Development workflow After loading once, subsequent npm run dev sessions reconnect automatically. No need to re load the extension unless manifest.json changes. Production build The dist/ directory is ready to zip and upload to Chrome Web Store: Disable Vite's module preload to avoid CWS rejection of inline scripts: Known issues and workarounds Tailwind CSS HMR in content scripts New Tailwind classes may not trigger CSS updates in content scripts. Workaround : restart dev server after adding new utility classes. Improved in v2.4.0 but not fully resolved. Ensure injectCss: true in config. WebSocket connection errors ( ws://localhost:undefined/ ) Cause : port mismatch between dev server and HMR config. Fix : explicitly set both to the same value: "Manifest version 2 is deprecated" warning If you see this, your manifest is being interpreted as MV2. Fix : ensure "manifest version": 3 is set. Content scripts not injecting on file:// URLs Chrome requires the user to enable "Allow access to file URLs" in the extension settings at chrome://extensions. CRXJS cannot change this. HMR stops working after Chrome update CRXJS's HMR relies on injecting a content script that connects to the dev server's WebSocket. Chrome security updates occasionally break this. Fix : update to the latest CRXJS version, which tracks Chrome changes. CRXJS vs alternatives Feature CRXJS WXT Plasmo Content script HMR True HMR File based reload Partial Framework support Any Vite framework Any React focused Abstraction level Thin (Vite plugin) Full framework Full framework Messaging helpers None (use chrome.\ directly) Built in Built in Storage wrappers None Built in Built in Cross browser Chrome + Firefox Chrome + Firefox + Safari Chrome + Firefox File based routing No Yes Yes Learning curve Low (know Vite, know CRXJS) Medium Medium Choose CRXJS when : you want minimal abstraction over raw Chrome APIs and value content script HMR above all. CRXJS stays out of the way — no magic routing, no wrapper APIs, just your code with HMR. Choose WXT when : you want conventions, built in utilities, and cross browser support. Choose Plasmo when : you're React focused and want the highest level abstraction. Project structure (recommended) CRXJS resolves HTML files referenced in the manifest automatically. Your popup.html can use standard <script type="module" src="./main.tsx" and it works. If you encounter a bug or unexpected behavior in CRXJS, open an issue at github.com/crxjs/chrome extension tools/issues.