乐趣区

关于前端:数栈技术干货从0到1实现谷歌插件开发探索及应用

本文整顿自:技术干货丨谷歌插件开发摸索及其利用

数栈是云原生—站式数据中台 PaaS,咱们在 github 和 gitee 上有一个乏味的开源我的项目:FlinkX,记得给咱们点个 star!star!star!

gitee 开源我的项目:https://gitee.com/dtstack_dev…

github 开源我的项目:https://github.com/DTStack/fl…

FlinkX 是一个基于 Flink 的批流对立的数据同步工具,既能够采集动态的数据,比方 MySQL,HDFS 等,也能够采集实时变动的数据,比方 MySQL binlog,Kafka 等,是全域、异构、批流一体的数据同步引擎,大家如果有趣味,欢送来 github 社区找咱们玩~

一、前言

笔者之前始终想理解一些对于谷歌插件的相干常识,心愿通过谷歌插件能够更好的意识到谷歌的调试工具,同时也想着能够应用谷歌插件去写一些小工具,既学习了新的货色,又有肯定的趣味性。恰逢近段时间须要分享,因而花了两周工夫学习和理解谷歌插件相干常识,本篇文章就将笔者在学习过程中的一些思考分享给大家。当然,因为工夫的起因,如果笔者对于这一块的意识有不对的中央,欢送批评指正~
二、什么是谷歌插件

上面先介绍一下谷歌插件的次要组成部分,因为目前谷歌插件应用比拟广泛的版本为 2.0 版本,因而本文都是基于 2.0 版本进行应用阐明,3.0 版本相较于 2.0 版本更为简便,感兴趣的同学能够点击文章开端处的链接理解更多相干常识。

(一)配置文件

谷歌插件的外围文件就是配置文件 –manifest.json(清单)文件。
其中,manifest.json 文件最根本的 Api 如下:

{

"name": "chrome extension",
"version": "1.0.0",
"manifest_version": 2,
"description": "A litlle chrome extension demo"

}

[点击并拖拽以挪动]

次要是蕴含所写谷歌插件的名称、版本以及相干形容,其中 manifest_version 示意清单文件版本。
manifest.json 作为谷歌插件的外围局部,笔者认为该文件对插件来说就相当于一个入口配置文件,开发人员只须要在这个文件通过配置相应的 js,调用谷歌浏览器提供的 Api,就能实现达到欠缺这个插件的目标。

1、根本应用 Api

在清单文件中有 许多的 Api,笔者就不一一列举了,上面为大家介绍几个笔者认为比拟重要的 Api,通过以下几个 Api 的介绍,心愿能够使读者对于谷歌插件的开发过程有一个大略的意识。

1)browser_action

{

...

"browser_action": {
    "default_icon": {
        "16": "images/get_started16.png",
        "32": "images/get_started32.png"
    },
    "default_title": "谷歌划词翻译",
    "default_popup": "popup.html"
},

...

}

[点击并拖拽以挪动]

browser_action 可设置浏览器右上角的图标及名称。
default_popup 可配置点击图标后会呈现的一个小窗口,这里能够做一些临时性的操作

2)permissions

{

...

"permissions": ["activeTab", "storage", "tabs", "contextMenus"],

...

}

[点击并拖拽以挪动]

permissions 可配置谷歌插件权限申请,如 contextMenus(右键菜单)、tabs(标签)和 storage(插件本地存储)。

3)content_scripts

{

...

"content_scripts": {"matches": ["<all_urls>"],
    "css": ["content/content_script.css"],
    "js": ["content/content_script.js"]
},

...

}

content-scripts 其实就是谷歌插件中向页面注入脚本的一种模式(尽管名为 script,其实还能够包含 CSS 的),借助 content-scripts 能够实现通过配置的形式轻松向指定页面注入 JS 和 CSS。

4)background

{

···

"background": {"scripts": ["background.js"],
    "persistent": false
},

···

}

[点击并拖拽以挪动]

background 是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的关上而关上,随着浏览器的敞开而敞开,所以通常把须要始终运行的、启动就运行的及全局的代码放在 background 外面。

笔者也画了一个下面波及到的脚本在浏览器中的散布,如下图:

以上就是笔者认为比拟重要的一些 Api,在介绍完之后,感兴趣的同学就能够开始着手写几个简略的工具,用来实现本人远大的抱负以及现实,升华本人高贵的灵魂。

一些同学可能不太晓得开发谷歌插件的前置条件,在这里为大家简略介绍下。

首先须要关上治理扩大程序,关上开发者模式。

点击加载已解压的程序按钮即可加载本地谷歌插件,开发的时候代码如果有更新的话,须要刷新已加载插件,点击敞开后再开启,不用刷新开发页面。

[点击并拖拽以挪动]

在理解完前置条件后,笔者将在下文中为大家分享谷歌划词翻译插件从 0 - 1 的实现过程,通过开发这个工具也能够加深对于大家谷歌插件的意识。
三、谷歌划词翻译插件

谷歌翻译算是笔者应用比拟频繁的插件,对于在网页上看到的不懂的英文单词或者句子,间接应用鼠标选中,轻松快捷的翻译出相应的中文。因而在学习的过程中,笔者就在想谷歌浏览器插件的翻译工具是如何实现的呢?

(一)思考

如何去做一个划词翻译插件,首先要思考的有以下几点:

如何实现翻译成果
如何选中咱们须要的元素
选中元素之后如何展现划词翻译面板
所有的浏览器 Tab 都须要反对翻译成果

思考完下面的这些点后,带着这几个纳闷,笔者将在下文一一解答,同时也列举一下遇到的一些点。

(二)划词翻译面板

首先不去思考该插件的性能,先写下划词翻译的面板的款式,所达到的成果如下:

HTML 代码如下:

<div class=”translate-panel show”>

<header> 谷歌划词翻译插件 <span class="close">X</span></header>
<main>
    <div class="source">
        <div class="title"> 英文 </div>
        <div class="content">test</div>
    </div>
    <div class="result">
        <div class="title"> 简体中文 </div>
        <div class="content">...</div>
    </div>
</main>

</div>

[点击并拖拽以挪动]

将下面的款式简略写好之后,开始思考如何将划词翻译的面板展现在浏览器以后页面。对于谷歌浏览器来说,在网页上进行的交互是属于 content_scripts 的,须要引入划词翻译面板所须要的 JS 或者 CSS 来生成以后面板。

其次,在配置文件中配置 content_scripts 引入 JS 文件,动静的生成 DOM 元素。大抵的思路就是通过监听到鼠标松开后,去生成翻译面板,在生成的元素下面增加 opacity 款式管制显隐,应用谷歌收费翻译 Api 进行翻译。

其中代码如下所示:

// manifest.json
{

...

"content_scripts": {"matches": ["<all_urls>"],
    "css": ["content_script.css"],
    "js": ["content_script.js"]
},
"permissions": ["activeTab"],

...

}

// content_script.js
class TranslatePanel {

createPanel = () => {let wrapper = document.createElement('div')
    wrapper.innerHTML = `
        <header> 谷歌划词翻译插件 <span class="close">X</span></header>
        <main>
            <div class="source">
                <div class="title"> 英文 </div>
                <div class="content">test</div>
            </div>
            <div class="result">
                <div class="title"> 简体中文 </div>
                <div class="content">...</div>
            </div>
        </main>
    `
    wrapper.classList.add('translate-panel')
    wrapper.querySelector('.close').onclick = () => {this.wrapper.classList.remove('show')
    }
    document.body.appendChild(wrapper)
    this.wrapper = wrapper

}

showPanel = () => {this.wrapper.classList.add('show')
}

translateSelect = (content) => {const source = this.wrapper.querySelector('.source .content')
    const result = this.wrapper.querySelector('.result .content')
    source.innerHTML = content
    result.innerHTML = '翻译中...'

    fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${content}`)
        .then(res => res.json())
        .then(res => {result.innerHTML = res[0][0][0]
        })
}

locationPanel = (target) => {
    this.wrapper.style.top = target.y + 'px'
    this.wrapper.style.left = target.x + 'px'

}
}

let panel = new TranslatePanel()
panel.createPanel()

window.onmouseup = (target) => {

// 获取选中内容
const content = window.getSelection().toString().trim()

if (!content) return
panel.locationPanel({x: target.pageX, y: target.pageY})
panel.translateSelect(content)
panel.showPanel()

}

[点击并拖拽以挪动]

在下面过程的中,笔者应用了谷歌收费的翻译接口,然而这个接口依照目前的设置还是有一点问题,咱们临时不表。当初划词翻译的面板就曾经根本写好了。

(三)脚本通信

划词翻译插件开发到这里,仔细的同学应该发现了,每次选中单词时都会触发划词翻译性能,此时急需一个管制翻译性能的开关,这个开关就能够放在 popup 脚本下面。

具体的款式的实现就不去介绍了,次要看一下 HTML 构造:

<div class=”switch-wrapper”>

<div class="switch-desc"> 是否启用划词翻译 </div>
<input type="checkbox" class="switch" />

</div>

根本成果如下:

此时面板和划词翻译的面板都曾经有了,再考虑一下如何实现 popup 脚本与 content_script 脚本之间的通信。首先,在
popup 脚本,咱们在关上窗口的时候须要去查问是否有存储开启划词翻译的状态,同时,
同时当状态产生变更的时候须要将其存储时,再在以后的 Tab 上面发送申请。

// popup.js
let switchWrapp = document.querySelector(‘.switch’)

chrome.storage.sync.get([‘checked’], (target) => {

if (target) {switchWrapp.checked = target.checked}

})

switchWrapp.onclick = (e) => {

chrome.storage.sync.set({checked: e.target.checked})

chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {chrome.tabs.sendMessage(tabs[0].id, {checked: e.target.checked})
})

}

[点击并拖拽以挪动]

下面代码中的 chrome.storage 可用于存储数据,追踪数据。storage.sync 的作用是让谷歌浏览器的数据同步,这使得在不同 Tab 页下面切换的状态也是能够同步的,同时也不必将数据保留在 background 后盾页面中,storage 还有很多 Api 比方监听 storage 数据变动的 onChanged,这里就不一一介绍了。

将开启或敞开划词翻译的状态发送后,content_script.JS 须要增加监听事件,获取到该状态后,进行敞开或开启操作。

// content_script.js
let checked = false

window.onmouseup = (target) => {

···

if (!content || !checked) return

···

}

chrome.storage.sync.get([‘checked’], (target) => {

if (target) checked = target.checked

})

chrome.runtime.onMessage.addListener((target) => {

if (target) {checked = target.checked}

})

[点击并拖拽以挪动]

在开发过程中,发现在以后的 Tab 是能够去实现这个操作的,然而当开启了多个 Tab 的状况下就会呈现开启翻译却不能展现翻译面板的状况。

针对以上状况笔者思考了一下,此时应该将 checked 储存起来,不应该放在 content_script 脚本当中。

// content_script.js
let panel = new TranslatePanel()
panel.createPanel()

window.onmouseup = (target) => {

// 获取选中内容
const content = window.getSelection().toString().trim()

if (!content) return

window.chrome.storage.sync.get(['checked'], (result) => {if (result.checked) {panel.locationPanel({ x: target.pageX, y: target.pageY})
        panel.translateSelect(content)
        panel.showPanel()}
})

}

chrome.runtime.onMessage.addListener((target) => {

if (target.type == 'CHECKED') {chrome.storage.sync.set({ checked: target.checked})
}

})

[点击并拖拽以挪动]

以上,popup 脚本和 content_script 脚本之间就实现了通信,翻译插件也能够通过 popup 下面的按钮,进行开启或敞开翻译性能。

同理,也能够晓得其余模块也是能够通过这种形式去进行通信,不同的是其余脚本向 content_script 通信是须要应用 tabs,先查找到以后的 Tab 在发送申请。

[点击并拖拽以挪动]

(四)右键中转翻译页面

当敞开划词翻译的时候,间接无奈翻译选中内容也不是很敌对,这个时候能够设置为点击右键的时候呈现翻译菜单项。因为这部分内容须要始终存在就加在 background 中。

// backgrond.js
// 当扩大程序第一次装置、更新至新版本或 Chrome 浏览器更新至新版本时产生
chrome.runtime.onInstalled.addListener(() => {

chrome.contextMenus.create({
    "id": "SELECT_TRANSLATE",
    "title": "翻译 %s",
    "contexts": ["selection"]
})

})

chrome.contextMenus.onClicked.addListener((target) => {

if (target.menuItemId == 'SELECT_TRANSLATE') {chrome.tabs.create({url: `https://translate.google.cn/?sl=en&tl=zh-CN&text=${target.selectionText}&op=translate`})
}

})

[点击并拖拽以挪动]

(五)跨域问题

开发过程中,有的同学也看进去了一个问题,比如说谷歌的这个翻译的 Api 须要同源的状况下能力失常调用该接口,而后就只能在谷歌翻译的页面中应用划词翻译,局面一度非常难堪 …

那么,失常来说这个划词翻译应用起来也是非常不合理的,接下来就须要解决一下这个跨域的问题。

笔者过后想要尝试的是应用 JSONP,也就是去应用嵌入脚本去进行跨域,发现还是会有一些问题,次要是谷歌的翻译的接口不反对回调函数。

同时也去查阅了一些材料,发现是能够在 content_script 中告诉 background,background 后盾去调用谷歌翻译的 Api 是来防止这个状况的。

次要因为 background 的权限十分高,简直能够调用所有的 Chrome 扩大 Api,而且它能够无限度跨域,也就是能够跨域拜访任何网站而无需要求对方设置 CORS。

具体增加的代码如下:

// content_script.js
translateSelect = (content) => {

const source = this.wrapper.querySelector('.source .content')
const result = this.wrapper.querySelector('.result .content')
source.innerHTML = content
result.innerHTML = '翻译中...'

chrome.runtime.sendMessage({type: 'QUERY_TRANSLATE', queryContent: content}, (res) => {result.innerHTML = res[0][0][0]
})

}

// background.js
chrome.runtime.onMessage.addListener((request, sender, callBack) => {

if (request.type == 'QUERY_TRANSLATE') {fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${request.queryContent}`)
        .then(res => res.json())
        .then(res => {callBack(res)
        })
    return true
}

})

[点击并拖拽以挪动]

background 中的发送音讯的监听事件返回 true 是为了与 content_script 的音讯通道放弃关上,通过异步的形式发送申请。

当初想想,如果应用插件的 background 就能够去跨域去进行申请一些借口,应用不得当的话感觉还是很危险的,能够去获取其余网站的一些信息,由此可见,还是要谨慎的进行此操作。

(六)待欠缺的点

反对其余语言的翻译,谷歌翻译的接口有两个 Api,sl(文本翻译之前的语言)和 tl(文本须要翻译成的语言)可通过扭转对应的值反对其余语言的翻译;款式欠缺,实现先选中图标在进行翻译。多增加一步感觉对交互更敌对,不必去进行开关的操作。

代码构造

[点击并拖拽以挪动]

介绍完了划词翻译插件,笔者本来是打算再分享一个对于谷歌 devtool 开发工具,开发一个相似于 React Developer Tools 的本地开发工具,然而因为工夫也是不太够,波及到的点比拟多,同时查阅了很多的材料对于这一块的介绍也是比拟的浅,所以还是决定着重于介绍划词翻译。

置信通过划词翻译的开发能使得读者比拟疾速的意识到谷歌插件,除了谷歌插件外,如果对于 devtool 工具插件开发有趣味的同学也能够去理解一下。

另外,有的同学可能会认为目前开发的效率是有一点低的,当初的话谷歌插件的开发也是能够基于 react + antd 去进行开发的,也是能够达到高效疾速的去开发一个插件的成果。
四、结尾

在学习谷歌插件的时候,笔者遇到了一些问题,比如说文档比拟少,官网文档又是英文的还常常 404,不过呢,好在谷歌浏览器有提供了很多 Api,笔者这边在写插件的时候也感觉到了很多趣味性。本篇文章还是写的比拟通俗易懂,如果有什么点写的不对或着不清晰的中央,欢送大家留言进行探讨。

相干资源及参考:

官网文档:https://developer.chrome.com/docs/extensions/mv3/
react + antd 脚手架:https://github.com/jhen0409/react-chrome-extension-boilerplate
谷歌翻译插件残缺代码:https://github.com/ting0130/chrome-extensions-translate
退出移动版