The frameworks
| Dimension | Extension.js | WXT | CRXJS | Plasmo |
|---|---|---|---|---|
| Model | manifest.json as source of truth | File conventions + config | Vite plugin reading a manifest | File conventions |
| Bundler | Rspack (internal, zero config) | Vite | Vite (plugin layer) | Parcel |
| Cross-browser | Chrome, Edge, Firefox from one source | Chrome, Firefox, MV2 support | Chromium focus | Chrome, Firefox targets |
| Setup | None required | wxt.config.ts | vite.config.ts + plugin | package.json manifest field |
| Bundler coupling | Internal detail | Tracks Vite majors | Breaks with bundler changes | Tied to Parcel |
- Where the manifest lives. Extension.js reads your
manifest.jsonand compiles whatever it declares. WXT and Plasmo generate the manifest from file conventions and config. CRXJS reads a manifest but resolves it through Vite’s plugin API, which is why bundler upgrades can break it (see the Vite 8 fileName error). - Who owns the bundler. A framework that wraps a general-purpose bundler inherits that bundler’s breaking changes. Extension.js treats the bundler as an internal detail: you never configure it, and upgrades are the framework’s problem, not yours.
Detailed comparisons and migrations
- Extension.js vs WXT: the closest comparison, dimension by dimension.
- Migrate from CRXJS: step-by-step, about ten minutes for a typical project.
- Fix: Content script fileName is undefined: the CRXJS + Vite 8 build error, with workarounds.
- Migrate from Plasmo: convention-to-manifest mapping, env vars, and CSUI.
Try it without committing
Run any extension template, or any extension repository on GitHub, with one command:chrome.* calls, and styling carry over from any of these frameworks; the migration guides cover the wiring that changes.
