手把手教你实现一个支持插件化的 uTools 工具箱(一)
前言
对于前端同学来说,我们会经常用到各种小工具,比如:图床、颜色拾取、二维码生成器、url 管理、文本比对、json 格式化。当然我们可以 chrome 收藏夹来管理各种在线的小工具,但作为一个有追求的前端,我们不仅仅要自己用的爽,也可以将一些好用的工具给团队用,提高团队的研发效率。
所以基于以上诉求,市面上或者很多公司内部都会做一些满足自己团队需要的客户端工具,大多是基于 electron
来实现。但如果本篇文章只介绍如何通过 electron
来做一个工具集,那就小了,格局小了! 要做就做大的,我们不仅仅为前端赋能,我们更需要为后端、测试、UI、产品甚至老板提效赋能。所以一旦做大了,工具箱将会变得非常臃肿,体积也会越来越大。每次更新所有人都必须升级才能体验新功能。已于以上问题,我们需要设计一款可插拔式的设计方式,用到时才安装,用不到不需要进行安装使用。插件独立于工具箱之外单独发布。
如果你对这样一款工具也感兴趣,可以继续阅读实现过程。不过可以直接上源码先:
取名
开篇第一步,按照我之前的套路都是先取好名字先占个坑,之前写了一本《从0开始可视化搭建》的小册,里面基于 dota 取了个 coco
的名字。这次我取名的是 rubick
即 拉比克
。知道 rubick
技能的就能领会啥意思了,主要是可以随心所欲想用啥技能就用啥技能,可插拔。和我们的理念也非常一致:
实现
初始化项目
这里我采用的是 electron-vue 来做的项目脚手架,直接按照官网介绍,开始 create 好一个 electron
项目:
# 安装 vue-cli 和 脚手架样板代码
npm install -g vue-cli
vue init simulatedgreg/electron-vue rubick
# 安装依赖并运行你的程序
cd rubick
yarn # 或者 npm install
yarn run dev # 或者 npm run dev
这里需要注意的是,由于 electron-vue
这玩意使用的 electron
版本太低了,而官方的 electron
文档已经更新到最新的了,所以如果仅仅按照 electron-vue
的版本来开发,再参考官方文档,会发现有很多文档中有但是无法使用的情况。所以尽量升级到最新的版本。
如果你是windows用户,在安装期间遇到了关于node-gyp、C++库等方面的问题的话,请参考官方文档给出的解决办法。
main 进程 和 renderer 进程
开发之前,有必要先了解一下 electron
的 main
进程 和 renderer
进程之间的关系,什么是 main
进程:electron
项目启动的时候会运行 main.js
的进程就是主进程,且一个项目有且只有一个主进程,创建窗口等所有系统事件都要在主进程中进行。简单的说就是我们的 electron 项目的主进程只有一个, 主进程的执行代码需要写到 main.js 中, 所有跟系统事件相关的代码统统都要写在这里。
什么是 renderer
进程呢?最粗浅的理解就是 Renderer
进程负责的就是我们熟悉的页面UI渲染。这里其实有篇博客总结的很好,想继续了解的可以查看 这里我先引用一下这里面的图。这张图包含了main
进程和 renderer
进程所具备的能力。
有了这些基础知识,假设你对 electron
相关的了解已经达到一个基本熟悉的层次。我们开始来进行开发工作。接下来的开发,我将会参考 utools 的设计交互,来一步步设计出一个类似于 utools 的 electron
工具箱。
窗口初始化
electron
可以通过 BrowserWindow
来新建一个窗口对象,这个时候需要构建一个 800 * 60 的主搜索窗口,打开 main.js
调整窗口尺寸和大小:
mainWindow = new BrowserWindow({
height: 60,
useContentSize: true,
width: 800,
frame: false,
title: '拉比克',
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
webviewTag: true,
nodeIntegration: true
}
})
这里有几个 webPreferences
参数需要说明一下:
- webSecurity 如果
BrowserWindow
发起了一些跨域请求,webSecurity 可以设置成 false - enableRemoteModule 可以让
renderer
进程使用remote
模块 - webviewTag 允许使用
webview
标签 - nodeIntegration 允许在网页中使用
node
到这里,我们就可以看到一个最基础的交互窗口了!
开发者模式
插件开发需要和 rubick
进行联调,所以 rubick
需要支持开发者模式,帮助开发者更好的开发插件。首先我们先建一个 plugin.json
用于描述插件的基础信息:
{
"pluginName": "测试插件",
"author": "muwoo",
"description": "我的第一个 rubick 插件",
"main": "index.html",
"version": "0.0.2",
"logo": "logo.png",
"name": "rubick-plugin-demo",
"gitUrl": "",
"features": [
{
"code": "hello",
"explain": "这是一个测试的插件",
"cmds":["hello222", "你好"]
}
],
"preload": "preload.js"
}
核心字段说明
name
插件仓库名称,用于 github dowload 标致pluginName
: 插件名称。description
: 插件描述,简洁的说明这个插件的作用main
: 入口文件,如果没有定义入口文件,此插件将变成一个模版插件version
: 插件的版本,需要符合 Semver (语义化版本) 规范。用于版本更新提示features
: 插件核心功能列表features.code
: 插件某个功能的识别码,在进入插件时会传递给你的代码,可用于区分不同的功能,显示不同的 UIfeatures.cmds
: 通过哪些方式可以进入这个功能,中文会自动支持 拼音及拼音首字母,无须重复添加
开发插件的方式是复制 plugin.json
进入到 rubick
的搜索框,所以需要监听搜索框的change
事件,用于读取当前剪切板复制的内容:
onSearch ({ commit }, paylpad) {
// 获取剪切板复制的文件路径
const fileUrl = clipboard.read('public.file-url').replace('file://', '');
// 如果是复制 plugin.json 文件
if (fileUrl && value === 'plugin.json') {
// 读取 json 文件
const config = JSON.parse(fs.readFileSync(fileUrl, 'utf-8'));
// 生成插件配置
const pluginConfig = {
...config,
// 记录 index.html 存方的路径
sourceFile: path.join(fileUrl, `../${config.main || 'index.html'}`),
id: uuidv4(),
// 标记为开发者
type: 'dev',
// 读取 icon
icon: 'image://' + path.join(fileUrl, `../${config.logo}`),
// 标记是否是模板
subType: (() => {
if (config.main) {
return ''
}
return 'template';
})()
};
}
}
到这里我们已经可以根据复制的 plugin.json
能获取到插件的最基础的信息,接下来就是需要展示搜索框:
commit('commonUpdate', {
options: [
{
name: '新建rubick开发插件',
value: 'new-plugin',
icon: 'https://static.91jkys.com/activity/img/b37ff555c748489f88f3adac15b76f18.png',
desc: '新建rubick开发插件',
click: (router) => {
commit('commonUpdate', {
showMain: true,
selected: {
key: 'plugin',
name: '新建rubick开发插件'
},
current: ['dev'],
});
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight(),
});
router.push('/home/dev')
}
},
{
name: '复制路径',
desc: '复制路径',
value: 'copy-path',
icon: 'https://static.91jkys.com/activity/img/ac0d4df0247345b9a84c8cd7ea3dd696.png',
click: () => {
clipboard.writeText(fileUrl);
commit('commonUpdate', {
showMain: false,
selected: null,
options: [],
});
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight([]),
});
remote.Notification('Rubick 通知', { body: '复制成功' });
}
}
]
});
到这里,当复制 plugin.json
进入搜索框时,变可直接出现 2个选项,一个新建插件,一个复制路径的功能:
当点击新建 rubick 插件
功能时,则需要跳转到 home
页,加载插件的基础类容,唯一需要注意的是 home
页加载的内容高度应该是rubick最大窗口的高度。所以需要调整窗口大小:
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight(),
});
关于 renderer
里面的 vue 代码这里就不再详细介绍了,因为大多是 css 画一下就好了,直接来看展示界面:
到这里,就完成了开发者模式,接下来再介绍如何让插件在 rubick
中跑起来。
运行插件
运行插件需要容器,electron 提供了一个 webview
的容器来加载外部网页。所以我们可以借助 webview
的能力实现动态网页渲染,这里所谓的网页就是插件。但是网页无法使用node
的能力,而且我们做插件的目的就是为了开放与约束,需要对插件开放一些内置的 API
能力。好在 webview
提供了一个 preload
的能力,可以在页面加载的时候去预置一个脚本来执行。
也就是说我们可以给自己的插件写一个 preload.js
来加载。但这里需要注意我们既要保持插件的个性又得向插件内注入全局 API
供插件使用,所以可以直接加载 rubick
内置 preload.js
,preload.js
内再加载个性化的 preload.js
:
// webview plugin.vue
<webview id="webview" :src="path" :preload="preload"></webview>
<script>
export default {
name: "index.vue",
data() {
return {
path: `File://${this.$route.query.sourceFile}`,
// 加载当前 static 目录中的 preload.js
preload: `File://${path.join(__static, './preload.js')}`,
webview: null,
query: this.$route.query,
config: {},
}
}
}
</script>
对于 preload.js
我们就可以这么用啦:
if (location.href.indexOf('targetFile') > -1) {
filePath = decodeURIComponent(getQueryVariable('targetFile'));
} else {
filePath = location.pathname.replace('file://', '');
}
window.utools = {
// utools 所有的 api 实现
}
// 加载插件 preload.js
require(path.join(filePath, '../preload.js'));
到这里就已经实现了插件的加载,我们来看看效果:
结语
本篇主要介绍如何实现一个类似于 utools 的插件加载过程,当然这远远不是 utools 的全部,下期我们再介绍如何实现 utools 的全局插件,比如屏幕取色、截图、搜索
等能力。欢迎大家前往体验 Rubick 有问题可以随时提 issue 我们会及时反馈。
另外,如果觉得设计实现思路对你有用,也欢迎给个 Star:https://github.com/clouDr-f2e/rubick
手把手教你实现一个支持插件化的 uTools 工具箱(一)的更多相关文章
- PWA入门:手把手教你制作一个PWA应用
摘要: PWA图文教程 原文:PWA入门:手把手教你制作一个PWA应用 作者:MudOnTire Fundebug经授权转载,版权归原作者所有. 简介 Web前端的同学是否想过学习app开发,以弥补自 ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- iOS回顾笔记(05) -- 手把手教你封装一个广告轮播图框架
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- R数据分析:跟随top期刊手把手教你做一个临床预测模型
临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...
- 教你打造一个Android组件化开发框架
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 CC:Component Caller,一个android组件化开发框架, 已开源,github地址:https://github ...
- Vue+ElementUI: 手把手教你做一个audio组件
目的 本项目的目的是教你如何实现一个简单的音乐播放器(这并不难) 本项目并不是一个可以用于生产环境的element播放器,所以并没有考虑太多的兼容性问题 本项目不是ElementUI的一个音频插件,只 ...
- 如何写一个c++插件化系统
1.为什么需要插件化系统 “编程就是构建一个一个自己的小积木, 然后用自己的小积木搭建大系统”. 但是程序还是会比积木要复杂, 我们的系统必须要保证小积木能搭建出大的系统(必须能被组合),有必须能使各 ...
- 用Python手把手教你搭一个Transformer!
来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...
- 手把手教你画一个 逼格满满圆形水波纹loadingview Android
才没有完结呢o( ̄︶ ̄)n .大家好,这里是番外篇. 拜读了爱哥的博客,又学到不少东西.爱哥曾经说过: 要站在巨人的丁丁上. 那么今天,我们就站在爱哥的丁丁上来学习制作一款自定义view(开个玩笑,爱 ...
随机推荐
- SprintBoot使用Validation
1.为什么要使用Validation 在开发过程中有没有使用一堆的if来判断字段是否为空.电话号码是否正确.某个输入是否符合长度等对字段的判断.这样的代码可读性差,而且还不美观,那么使用Validat ...
- sed -i '14s/yes/no/' tftp
修改tftp 内容 # cd /etc/xinetd.d/[root@localhost xinetd.d]# cp tftp tftp.bak[root@localhost xinetd.d]# c ...
- spec2006与spec2000的对比简要说明
ec2006使用说明 2014-10-10 五 性能测试 benchmark 一.工具介绍 SPEC CPU 2006 benchmark是SPEC新一代的行业标准化的CPU测试基准套件.重点测试 ...
- Linux_搭建Samba服务(认证访问)
[RHEL8]-SMBserver:[RHEL7]-SMBclient !!!测试环境我们首关闭防火墙和selinux(SMBserver和SMBclient都需要) [root@localhost ...
- Linux服务之批量部署篇
批量部署步骤: 1.检查环境 getenforce #检查内核防火墙是否关闭 systemctl status firewalld #检查firewalld是否 ...
- python基础之包、模块、命名空间和作用域
一.模块介绍 模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能. 模块的作用: (1)从文件级别组织程序,更方便管理:随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一 ...
- SpringMVC Jackson 库解析 json 串属性名大小写自动转换问题
问题描述 在项目开发中,当实体类和表中定义的某个字段为 RMBPrice,首字母是大写的,sql 查询出来的列名也是大写的 RMBPrice,但是使用 jquery 的 ajax 返回请求响应时却出错 ...
- xxl-job使用遇到的问题(二)
xxl-job使用遇到的问题(二) 关联阅读 xxl-job使用遇到的问题(一) 1.问题现象 最近有个老定时任务迁移到xxl-job的时候,遇到一个小问题.虽然很快解决,但是还是有必要记录一下~ j ...
- 大对象数据LOB的应用
概述 由于无结构的数据往往都是大型的,存储量特别大,而LOB(large object)类型主要用来支持无结构的大型数据. 用户可以利用LOB数据类型来存储大型的无结构数据,特别是文本,图形,视频和音 ...
- docker-ce 安装
配置源 确认版本 添加镜像加速器 https://docs.docker.com/engine/release-notes/19.03/ for centos wget -O /etc/yum.rep ...