使用 React 创建 Chrome 扩展程序
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
最近,我着手开发一个 Chrome 扩展程序,并利用 JavaScript 和 React 将一个组件注入到网站中。最终成果完美地结合了 Mutation Observer 和 JavaScript 的强大功能!
代码可以在Github上找到。
准备!
首先,我从Chrome 开发者网站下载了一个 Chrome 扩展程序的入门模板。如果你想学习扩展程序开发的基础知识,我强烈建议你访问这个网站。我立即删除了 `<filename>` options.js、options.html`<filename>` 和popup.js`<filename>` 文件。在 `<filename>`manifest.json文件中,我删除了数组中的options_page键和值。接下来,你需要向数组中添加内容。storagepermissionshttps://www.myweekinjs.com/permissions
我会在文中多次提到myweekinjs ,它可以是任何你想注入 React 组件的网站。
接下来,我创建了一个app.js简单的console.log测试用例来验证脚本是否有效,并更新了脚本background.js;
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
hostEquals: 'www.myweekinjs.com',
schemes: ['https', 'http'],
pathContains: 'inject-me'
},
css: ['div']
})],
actions: [
new chrome.declarativeContent.RequestContentScript({
js: ['app.js']
})
]
}]);
});
});
好了!信息量真大!background.js脚本将执行以下操作:
- 注意页面/标签页的变化
- 检查当前页面是否为 (http|https): //www.myweekinjs.com/inject-me
- 如果是,它将加载我们的
app.js文件
请按照以下步骤加载您的扩展程序以进行测试。
让我们开始编写脚本吧!
下一步是创建webpack.config.js用于编译 React 和 Javascript 的文件。此时,我建议创建一个名为 dist 的文件夹,其中包含当前文件(不包括 node_modules app.js),并将该文件夹解压为扩展名。这样,您就可以将代码编译到这个dist文件夹中,而不会将node_modules包含到扩展名中。
我们将使用这个很棒的资源来生成我们的 webpack 和 .babelrc 文件createapp.dev
- 打开资源 ^
- 勾选 React 和 Babel。取消勾选 React 热加载器
- 运行
npm init -y并安装资源中列出的软件包。 webpack.config.js将文件复制.babelrc到您的项目中scripts从……复制package.json
我们需要做一些小的调整,包括webpack.config.js更改输入和输出设置;
var config = {
entry: './app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.js'
},
...
}
将build-dev脚本更改为:
"dev": "webpack -d --mode development --watch"
你的语法可能存在一些问题.babelrc,不过应该很容易解决,主要是双引号的使用问题。
运行build-prod脚本后,文件将被编译app.js。解压并重新加载后,你应该会看到和console.log之前一样的内容。虽然过程漫长,但接下来才是真正有趣的部分!
让我们一起快乐起来!
我们希望我们的应用程序能够实现以下几个功能;
- 请等待页面完全加载。
- 观察目标容器上的突变
- 插入我们的 React 根目录
- 渲染 React 组件
我们将从以下结构开始。它为窗口添加了一个监听器load,并包含我们的主要回调函数,我将其命名为app。
window.addEventListener('load', function() {})
const app = () => {}
第一步完成!继续!
接下来,我们将添加一个Mutation Observer,它赋予我们监视 DOM 树变化的强大功能。这非常实用。在我们的项目中,我们将监视测试页面target-test上的 div 元素(测试页面位于我的个人博客上)。以下代码已添加到加载回调函数中。
// Specifies the element we want to watch
const watch = document.getElementById('target-test')
// Creates a new Mutation Observer
const observer = new MutationObserver((mutationList, observer) => {
})
// Starts observing the child list of the element
observer.observe(watch, {
childList: true
})
接下来,我们要遍历这些变更,如果能找到我们要查找的元素,就调用我们的应用程序方法。
const observer = new MutationObserver((mutationList, observer) => {
// Loops through the mutations
for (const mutation of mutationList) {
// Checks if it is a change to the child elements
if (mutation.type === 'childList') {
// Attempts to find the p tag
const target = watch.querySelector('p')
if (target) {
// Calls our app method
app(observer, target)
}
}
}
})
// Update the callback to accept those arguements
const app = (observer, target) => {}
快完成了!现在我们要为 React 组件创建一个根元素,并将其插入到目标元素之前。
const app = (observer, target) => {
// Disconnects from the observer to stop any additional watching
observer.disconnect()
// Checks if the element doesn't exist
if (!document.getElementById('react-root-test')) {
// Create and inserts the element before the target
const parent = target.parentNode
const root = document.createElement('div')
root.setAttribute('id', 'react-root-test')
parent.insertBefore(root, target)
}
}
让我们开始反应吧!
现在我们有了 React 根组件,终于可以创建并渲染组件了。我将在同一个文件中创建一个简单的 React 组件。当然,你可以创建任何你想要的组件,这完全取决于你!添加组件后,解压扩展程序并重新加载测试页面,你应该就能看到组件出现了!
import React from 'react'
import ReactDOM from 'react-dom'
const TestComponent = () => (
<h1>I am dynamically added!</h1>
)
const app = () => {
//...
parent.insertBefore(root, target)
ReactDOM.render(<TestComponent />, document.getElementById('react-root-test'))
}
繁荣!
我们成功了!这仅仅是使用 Chrome 扩展程序和 React 实现功能的冰山一角。运用同样的技术,你还可以为网站添加各种功能,类似于 Grammarly 或 LastPass 等扩展程序。可能性几乎是无限的!
总结
我觉得这个项目挺酷的。我之前完全没想到用 Chrome 扩展程序能做到这种程度。在这个例子里用 Mutation Observer 可能有点小题大做。不过,当你遇到一个动态渲染内容的网站时,能够等到需要的内容加载完毕再执行操作,真的非常棒!如果你对代码或流程有任何疑问,欢迎在Twitter上联系我,我很乐意继续讨论并改进我的代码。
感谢您阅读我的文章,这对我意义重大!❤️ 欢迎提供任何反馈或评论,我一直在努力改进,也期待与您进行有意义的讨论。这篇文章是我参加#myweekinjs挑战的一部分,如果您有兴趣了解更多,我还有其他有趣的文章。