我正在尝试进行chrome扩展,并希望使用React,但是经过一整周的死胡同之后,这似乎并不是理想的选择。因此,在只使用Vanilla JS之前,我决定向您寻求一些SO用户的建议,这是最后的努力。
这是我的public/manifest.json
文件(我现在手动更改“ js”:[]字段以匹配构建文件夹的名称):
{
"manifest_version": 2,
"name": "Name",
"version": "0.1.0",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["/static/js/main.356978b8.chunk.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"permissions": ["tabs", "http://*/"],
"browser_action": {
"default_title": "Title"
}
}
我的想法是,内容脚本将是所有React函数组件文件以及使用以上内容的全部index.js
。
这里是package.json
:
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"env": {
"webextensions": true
}
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
这是我的文件夹结构:
src文件夹:
// src/App.js
function App() {
return <div>Hello</div>;
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log(request.tabs);
});
export default App;
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
公用文件夹:
// public/background.js
chrome.browserAction.onClicked.addListener((tab) => {
chrome.tabs.create(
{
windowId: null,
url: "http://localhost:3000",
active: true,
openerTabId: tab.id,
},
(newTab) => {
// wait for tab to load, then send message with tabs
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status == "complete" && tabId == newTab.id) {
chrome.tabs.getAllInWindow(null, (tabs) => {
chrome.tabs.sendMessage(newTab.id, { tabs });
});
}
});
}
);
});
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Title</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
当我用background.js
单击扩展按钮时,我的http://localhost:3000
只是向chrome.browserAction.onClicked.addListener((tab)=>{...})
打开一个新标签。 这很好。
在Chrome扩展程序中将build
文件夹添加到Load Unpacked
会生成扩展程序,但是当我单击按钮并创建新标签时,我得到了
TypeError: Cannot read property 'addListener' of undefined
我知道npm run eject
可用于自动化/设置/static/js/...
中的文件名,但是现在我可以在每次构建更改时只需对其进行更改即可。
任何线索或想法将不胜感激!
答案 0 :(得分:1)
问题是您的扩展使用了在不同上下文中运行的同一React app JS文件("static/js/main.*.chunk.js"
的 两个 副本。一个副本作为内容脚本运行到活动页面中,并且该副本应无错误运行。但是,您在新标签页中打开的CRA开发服务器(localhost:3000
)中还有另一个副本。而且该文件以普通 JS文件的形式运行,与您的扩展程序没有任何关系,因此它 不能 调用Chrome API。这就是为什么您会收到该错误。
一种可能的解决方案是将该JS文件分成两个单独的部分:一个(与index.html
相关的普通JS)-仅执行React渲染,另一个(内容脚本)-侦听扩展名中的消息。这两个部分之间的Communication可以使用window.postMessage
完成。在您的情况下,如下所示:
public / manifest.json:
{
...
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentScript.js"]
}
],
...
}
public / contentScript.js:
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
window.postMessage({ type: "FROM_EXT", tabs: request.tabs }, "http://localhost:3000");
});
src / index.js:
...
window.addEventListener("message", event => {
if (event.source != window)
return;
const {type, tabs} = event.data;
if (type !== "FROM_EXT")
return;
console.log(tabs);
});
此外,我建议您使用自定义CRA模板-complex-browserext
(插件)。它有助于使用带有浏览器扩展的Create React App。有关特定用法示例(另一个插件),另请参见this article。