Chrome API 在 React 功能组件中不起作用
Chrome API in React functional component not working
我正在尝试做一个 chrome 扩展并且很想使用 React,但是在 运行 整整一周陷入死胡同之后,这似乎不是理想的途径。因此,我决定在这里向你们优秀的 SO 用户征求一些建议,作为在使用 Vanilla JS 之前的最后努力。
这是我的 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 文件夹:
// 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
只是在 chrome.browserAction.onClicked.addListener((tab)=>{...})
单击扩展按钮时打开一个新选项卡 http://localhost:3000
。 这很好用。
将 build
文件夹添加到 Chrome Extensions 中的 Load Unpacked
会生成扩展名,但是当我单击按钮并创建新选项卡时,我得到
TypeError: Cannot read property 'addListener' of undefined
我知道 npm run eject
可以用于 automate/set /static/js/...
中的文件名,但现在我可以在每次构建更改时简单地更改它。
如有任何线索或想法,我们将不胜感激!
问题是您的扩展使用 两个 份相同的 React 应用 JS 文件 ("static/js/main.*.chunk.js"
) 运行 在不同的上下文中。一份 运行 作为内容脚本注入到活动页面中,这一份应该 运行 没有错误。但还有另一个副本 运行 在 CRA 的开发服务器 (localhost:3000
) 中,您可以在新选项卡中打开。而这个 运行s 作为 普通 JS 文件与你的扩展没有任何关系,所以它 不能 调用 Chrome API。这就是您收到该错误的原因。
一个可能的解决方案是将该 JS 文件分成两个单独的部分:一个(与 index.html
相关的普通 JS)- 仅进行 React 渲染,另一个(内容脚本)- 监听来自扩展的消息. Communication between such two parts can be done using 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
(plug). It facilitates usage of Create React App with browser extensions. Also see this article 用于特定用法示例(另一个插件)。
我正在尝试做一个 chrome 扩展并且很想使用 React,但是在 运行 整整一周陷入死胡同之后,这似乎不是理想的途径。因此,我决定在这里向你们优秀的 SO 用户征求一些建议,作为在使用 Vanilla JS 之前的最后努力。
这是我的 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 文件夹:
// 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
只是在 chrome.browserAction.onClicked.addListener((tab)=>{...})
单击扩展按钮时打开一个新选项卡 http://localhost:3000
。 这很好用。
将 build
文件夹添加到 Chrome Extensions 中的 Load Unpacked
会生成扩展名,但是当我单击按钮并创建新选项卡时,我得到
TypeError: Cannot read property 'addListener' of undefined
我知道 npm run eject
可以用于 automate/set /static/js/...
中的文件名,但现在我可以在每次构建更改时简单地更改它。
如有任何线索或想法,我们将不胜感激!
问题是您的扩展使用 两个 份相同的 React 应用 JS 文件 ("static/js/main.*.chunk.js"
) 运行 在不同的上下文中。一份 运行 作为内容脚本注入到活动页面中,这一份应该 运行 没有错误。但还有另一个副本 运行 在 CRA 的开发服务器 (localhost:3000
) 中,您可以在新选项卡中打开。而这个 运行s 作为 普通 JS 文件与你的扩展没有任何关系,所以它 不能 调用 Chrome API。这就是您收到该错误的原因。
一个可能的解决方案是将该 JS 文件分成两个单独的部分:一个(与 index.html
相关的普通 JS)- 仅进行 React 渲染,另一个(内容脚本)- 监听来自扩展的消息. Communication between such two parts can be done using 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
(plug). It facilitates usage of Create React App with browser extensions. Also see this article 用于特定用法示例(另一个插件)。