Chrome扩展注入内容脚本的React渲染及Import依赖问题求助
解决方案:Chrome扩展注入React组件 + Vite打包配置
问题根源
你当前通过chrome.scripting.executeScript的func参数注入ui函数时,函数会被序列化为纯字符串传递到主世界执行,但外部依赖(如createRoot、App组件)不会被一同序列化。主世界环境中没有这些依赖的定义,因此会出现"渲染函数未定义"的错误。另外,Vite默认的打包逻辑不适配Chrome扩展的多入口(后台Service Worker、内容脚本、注入脚本)需求,需要针对性配置。
解决步骤
1. 重构注入逻辑:从注入函数改为注入打包后的脚本
放弃func参数传递带依赖的函数,改为将React相关代码打包成独立脚本,通过files参数注入。
后台脚本修改(background.ts)
(() => { async function inject(tabId: number, scriptPath: string, ...args: Array<any>) { // 先将参数挂载到主世界的全局变量 await chrome.scripting.executeScript({ target: { tabId }, world: "MAIN", injectImmediately: true, func: (args) => { (window as any).__INJECT_ARGS__ = args; }, args: [args], }); // 注入打包后的UI脚本 await chrome.scripting.executeScript({ target: { tabId }, world: "MAIN", injectImmediately: true, files: [scriptPath], }); return !chrome.runtime.lastError; } chrome.runtime.onConnect.addListener(async (connection) => { if (connection.name === "connect") { await inject(connection.sender.tab.id!, "/dist/ui-inject.js", chrome.runtime.id); } }); })();
独立UI注入脚本(ui-inject.tsx)
将React组件渲染逻辑和依赖全部包含在这个文件中,通过全局变量获取注入参数:
import { createRoot } from "react-dom/client"; import { App } from "./ui/App.tsx"; // 获取后台注入的参数 const [runtimeId] = (window as any).__INJECT_ARGS__; const connection = chrome.runtime.connect(runtimeId, { name: "ui" }); // 处理连接逻辑... function render() { if (!document.head || !document.body) { setTimeout(render, 100); return; } const element = document.createElement("div"); // 给容器加样式避免和页面样式冲突 element.style.position = "fixed"; element.style.zIndex = "9999"; element.style.top = "0"; element.style.left = "0"; document.body.appendChild(element); const root = createRoot(element); root.render(<App />); } render();
2. Vite打包配置适配Chrome扩展
修改vite.config.ts,配置多入口打包,适配后台Service Worker、内容脚本、UI注入脚本的需求:
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], build: { rollupOptions: { // 配置多个入口文件 input: { background: "./src/background.ts", content: "./src/content-script.ts", // 你的WebSocket连接内容脚本 "ui-inject": "./src/ui-inject.tsx", }, output: { // 输出到dist目录,保持文件名和入口名一致 entryFileNames: "[name].js", chunkFileNames: "[name].js", assetFileNames: "[name].[ext]", }, }, // 禁用sourcemap,避免Chrome扩展加载报错 sourcemap: false, outDir: "dist", }, });
3. Manifest配置(manifest.json)
确保Manifest v3配置正确关联打包后的文件:
{ "manifest_version": 3, "name": "你的扩展名称", "version": "1.0", "background": { "service_worker": "dist/background.js" }, "content_scripts": [ { "matches": ["<all_urls>"], "js": ["dist/content.js"], "run_at": "document_start" } ], "permissions": ["scripting", "activeTab"], "host_permissions": ["<all_urls>"] }
关键注意事项
- 主世界与隔离世界:注入的UI脚本运行在页面的主世界(MAIN),可以直接操作DOM,但无法访问其他内容脚本的变量,必须通过Chrome扩展API(如
chrome.runtime.connect)与后台通信。 - 依赖打包:Vite会自动将React、ReactDOM以及你的
App组件打包进ui-inject.js,无需额外引入CDN资源。 - 开发调试:使用
vite build --watch命令开启监听模式,修改代码后自动重新打包,刷新Chrome扩展即可看到效果。
内容的提问来源于stack exchange,提问作者xaller
相关产品推荐
相关产品推荐

