mcpField
C
crxjs

crxjs

github.com/samber开发2414.5k 次安装4 周前更新
代码仓库

关于 crxjs

CRXJS 是一个依据扩展清单构建 Chrome 扩展的 Vite 插件。它为多种扩展界面提供 HMR,也支持内容脚本的真正 HMR,同时保持对浏览器 API 的轻量封装。

主要功能

  1. 01基于清单构建扩展
  2. 02弹出窗口、选项页和侧边栏 HMR
  3. 03内容脚本 HMR
  4. 04动态内容脚本导入
  5. 05类型安全的动态清单

适用场景

  1. 01使用 Vite 框架开发扩展
  2. 02开发时迭代内容脚本
  3. 03构建待发布的扩展包

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills --skill crxjs

安装后在 Claude Code 中输入/crxjs确认已加载。

详细介绍

CRXJS

CRXJS 是一款 Chrome 扩展开发工具,可为弹出窗口、选项页、内容脚本和侧边栏提供真正的 HMR。它会读取 manifest,自动生成扩展输出,处理内容脚本注入,并管理 service worker 构建。其底层是一个 Vite 插件(@crxjs/vite-plugin)。

当前状态

  • 软件包:@crxjs/vite-plugin(v2.x 稳定版,截至 2026 年 3 月的最新版本为 v2.4.0)
  • 脚手架:npm create crxjs@latest(始终使用 @latest)
  • 维护者:@Toumash 和 @FliPPeDround(自 2025 年年中起)
  • GitHub:github.com/crxjs/chrome-extension-tools(约 4k 星标)
  • Vite 兼容性:v3 至 v8-beta

快速开始

# Scaffold new project (picks framework interactively)
npm create crxjs@latest

# Or add to existing Vite project
npm install @crxjs/vite-plugin -D

各框架的 Vite 配置

CRXJS 作为 Vite 插件添加。不同框架的配置略有差异。

React
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

export default defineConfig({
  plugins: [react(), crx({ manifest })],
});

为获得最佳 HMR 兼容性,请使用 @vitejs/plugin-react(而不是 plugin-react-swc)。如果必须使用 SWC,请为 manifest 进行类型转换:

import { ManifestV3Export } from "@crxjs/vite-plugin";
const manifest = manifestJson as ManifestV3Export;
Vue
import vue from "@vitejs/plugin-vue";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

export default defineConfig({
  plugins: [vue(), crx({ manifest })],
});
Svelte
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

export default defineConfig({
  plugins: [svelte(), crx({ manifest })],
});
Vanilla TypeScript
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

export default defineConfig({
  plugins: [crx({ manifest })],
});

defineManifest — 类型安全的动态 manifest

你可以使用 CRXJS 的 defineManifest 代替静态 JSON 文件,以支持动态值和完整的 TypeScript 自动补全:

// manifest.ts
import { defineManifest } from "@crxjs/vite-plugin";
import pkg from "./package.json";

export default defineManifest((config) => ({
  manifest_version: 3,
  name: config.command === "serve" ? `[DEV] ${pkg.name}` : pkg.name,
  version: pkg.version,
  description: pkg.description,
  permissions: ["storage", "activeTab", "scripting"],
  action: {
    default_popup: "src/popup/index.html",
    default_icon: {
      "16": "public/icons/icon16.png",
      "48": "public/icons/icon48.png",
    },
  },
  background: {
    service_worker: "src/background/index.ts",
    type: "module",
  },
  content_scripts: [
    {
      matches: ["https://*/*"],
      js: ["src/content/index.ts"],
      css: ["src/content/styles.css"],
    },
  ],
  options_page: "src/options/index.html",
  side_panel: { default_path: "src/sidepanel/index.html" },
  icons: {
    "16": "public/icons/icon16.png",
    "48": "public/icons/icon48.png",
    "128": "public/icons/icon128.png",
  },
}));

在 vite.config.ts 中导入:

import manifest from "./manifest";
// ... crx({ manifest })

类型声明

添加到 src/vite-env.d.ts 或 src/crxjs.d.ts:

/// <reference types="@crxjs/vite-plugin/client" />

这会为 ?script 和 ?script&module 导入启用类型支持。

不同上下文中的 HMR 行为

上下文HMR工作方式
弹出窗口完整 HMR基于 WebSocket,保留状态
选项页完整 HMR与弹出窗口相同
侧边栏完整 HMR与弹出窗口相同
内容脚本(manifest)真正的 HMRCRXJS 注入加载器和 HMR 客户端
内容脚本(动态)真正的 HMR通过 ?script 导入
Service worker自动重新加载更改会触发整个扩展重新加载
Main world 脚本无 HMRCRXJS 加载器会跳过

内容脚本 HMR 能够正常工作,是因为 CRXJS 会生成一个加载器脚本,用于导入 HMR 前置代码、HMR 客户端和实际脚本,从而支持真正的模块级 HMR,而无需完整重新加载页面。这是 CRXJS 的主要差异化优势。

动态导入内容脚本

对于以编程方式注入(而非在 manifest 中声明)的内容脚本,CRXJS 提供了特殊的导入后缀:

// background.ts — ?script gives you a resolved path for executeScript
import contentScript from "./content?script";

chrome.action.onClicked.addListener(async (tab) => {
  await chrome.scripting.executeScript({
    target: { tabId: tab.id! },
    files: [contentScript],
  });
});

对于 Main world 注入(无 HMR):

import mainWorldScript from "./inject?script&module";

await chrome.scripting.executeScript({
  target: { tabId },
  world: "MAIN",
  files: [mainWorldScript],
});

CRXJS 插件选项

crx({
  manifest,
  browser: "chrome", // 'chrome' | 'firefox'
  contentScripts: {
    injectCss: true, // auto-inject CSS for content scripts
    hmrTimeout: 5000, // HMR connection timeout (ms)
  },
});

开发工作流

# Start dev server (outputs to dist/ with HMR)
npm run dev

# 1. Open chrome://extensions
# 2. Enable "Developer mode"
# 3. Click "Load unpacked"
# 4. Select the dist/ directory
# 5. Edit code — popup/content scripts update instantly via HMR
# 6. Service worker changes trigger automatic extension reload

加载一次后,后续运行 npm run dev 时会自动重新连接。除非 manifest.json 发生更改,否则无需重新加载扩展。

生产构建

npm run build    # outputs to dist/

dist/ 目录已准备就绪,可以压缩并上传到 Chrome Web Store:

cd dist && zip -r ../extension.zip .

禁用 Vite 的模块预加载,以避免 Chrome Web Store 因内联脚本而拒绝扩展:

build: {
  modulePreload: false;
}

已知问题及解决方法

内容脚本中的 Tailwind CSS HMR

新增 Tailwind 类可能不会触发内容脚本中的 CSS 更新。解决方法:添加新的实用类后重启开发服务器。v2.4.0 已有所改进,但问题尚未完全解决。请确保在配置中设置 injectCss: true。

WebSocket 连接错误(ws://localhost:undefined/)

原因:开发服务器端口与 HMR 配置中的端口不一致。修复方法:显式将两者设置为相同的值:

server: {
  port: 5173,
  strictPort: true,
  hmr: { port: 5173 },
}
“Manifest version 2 is deprecated” 警告

如果看到此警告,说明你的 manifest 被识别为 MV2。修复方法:确保设置了 "manifest_version": 3。

内容脚本未在 file:// URL 上注入

Chrome 要求用户在扩展设置中启用“允许访问文件网址”(chrome://extensions)。CRXJS 无法更改此设置。

Chrome 更新后 HMR 停止工作

CRXJS 的 HMR 依赖于注入一个连接到开发服务器 WebSocket 的内容脚本。Chrome 安全更新偶尔会导致此功能失效。修复方法:更新到最新版本的 CRXJS,该版本会跟进 Chrome 的变更。

CRXJS 与其他方案对比

特性CRXJSWXTPlasmo
内容脚本 HMR真正的 HMR基于文件的重新加载部分支持
框架支持任意 Vite 框架任意专注于 React
抽象层级轻量(Vite 插件)完整框架完整框架
消息传递辅助工具无(直接使用 chrome.*)内置内置
存储封装无内置内置
跨浏览器支持Chrome + FirefoxChrome + Firefox + SafariChrome + Firefox
基于文件的路由否是是
学习曲线低(了解 Vite 和 CRXJS 即可)中中

选择 CRXJS 的情况:你希望尽量减少对原始 Chrome API 的封装,并且最看重内容脚本 HMR。CRXJS 不会碍事——没有魔法路由,没有封装 API,只有带 HMR 的代码。

选择 WXT 的情况:你希望使用约定、内置实用工具和跨浏览器支持。

选择 Plasmo 的情况:你专注于 React,并希望获得最高层级的抽象。

项目结构(推荐)

my-extension/
├── src/
│   ├── background/
│   │   └── index.ts
│   ├── content/
│   │   ├── index.ts
│   │   └── styles.css
│   ├── popup/
│   │   ├── index.html        <- CRXJS resolves HTML entry points
│   │   ├── App.tsx
│   │   └── main.tsx
│   ├── options/
│   │   ├── index.html
│   │   └── main.tsx
│   ├── sidepanel/
│   │   ├── index.html
│   │   └── main.tsx
│   └── shared/
│       ├── messages.ts
│       └── storage.ts
├── public/
│   └── icons/
├── manifest.ts               <- or manifest.json
├── vite.config.ts
├── tsconfig.json
└── package.json

CRXJS 会自动解析清单中引用的 HTML 文件。你的 popup.html 可以使用标准的 <script type="module" src="./main.tsx">,并且能够正常运行。

如果你在 CRXJS 中遇到错误或意外行为,请在 github.com/crxjs/chrome-extension-tools/issues 提交问题。

评论

支持 Markdown