前言

对于前端同学来说,咱们会常常用到各种小工具,比方:图床、色彩拾取、二维码生成器、url 治理、文本比对、json 格式化。当然咱们能够 chrome 收藏夹来治理各种在线的小工具,但作为一个有谋求的前端,咱们不仅仅要本人用的爽,也能够将一些好用的工具给团队用,进步团队的研发效率。

所以基于以上诉求,市面上或者很多公司外部都会做一些满足本人团队须要的客户端工具,大多是基于 electron 来实现。但如果本篇文章只介绍如何通过 electron 来做一个工具集,那就小了,格局小了! 要做就做大的,咱们不仅仅为前端赋能,咱们更须要为后端、测试、UI、产品甚至老板提效赋能。所以一旦做大了,工具箱将会变得十分臃肿,体积也会越来越大。每次更新所有人都必须降级能力体验新性能。已于以上问题,咱们须要设计一款可插拔式的设计形式,用到时才装置,用不到不须要进行装置应用。插件独立于工具箱之外独自公布。

如果你对这样一款工具也感兴趣,能够持续浏览实现过程。不过能够间接上源码先:

Rubick 源码

取名

开篇第一步,依照我之前的套路都是先取好名字先占个坑,之前写了一本《从0开始可视化搭建》的小册,外面基于 dota 取了个 coco 的名字。这次我取名的是 rubick拉比克。晓得 rubick 技能的就能体会啥意思了,次要是能够得心应手想用啥技能就用啥技能,可插拔。和咱们的理念也十分统一:

实现

初始化我的项目

这里我采纳的是 electron-vue 来做的我的项目脚手架,间接依照官网介绍,开始 create 好一个 electron 我的项目:

# 装置 vue-cli 和 脚手架样板代码npm install -g vue-clivue init simulatedgreg/electron-vue rubick# 装置依赖并运行你的程序cd rubickyarn # 或者 npm installyarn run dev # 或者 npm run dev

这里须要留神的是,因为 electron-vue 这玩意应用的 electron 版本太低了,而官网的 electron 文档曾经更新到最新的了,所以如果仅仅依照 electron-vue 的版本来开发,再参考官网文档,会发现有很多文档中有然而无奈应用的状况。所以尽量降级到最新的版本。

如果你是windows用户,在装置期间遇到了对于node-gyp、C++库等方面的问题的话,请参考官网文档给出的解决办法。

main 过程 和 renderer 过程

开发之前,有必要先理解一下 electronmain 过程 和 renderer 过程之间的关系,什么是 main 过程:electron 我的项目启动的时候会运行 main.js 的过程就是主过程,且一个我的项目有且只有一个主过程,创立窗口等所有零碎事件都要在主过程中进行。简略的说就是咱们的 electron 我的项目的主过程只有一个, 主过程的执行代码须要写到 main.js 中, 所有跟零碎事件相干的代码通通都要写在这里。

什么是 renderer 过程呢?最浅显的了解就是 Renderer 过程负责的就是咱们相熟的页面UI渲染。这里其实有篇博客总结的很好,想持续理解的能够查看 这里我先援用一下这外面的图。这张图蕴含了main 过程和 renderer 过程所具备的能力。

<img width=100% src=https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8210627debeb410da0317123dd40ff87~tplv-k3u1fbpfcp-watermark.image />

有了这些基础知识,假如你对 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 : 插件某个性能的识别码,在进入插件时会传递给你的代码,可用于辨别不同的性能,显示不同的 UI
  • features.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.jspreload.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.jsrequire(path.join(filePath, '../preload.js'));

到这里就曾经实现了插件的加载,咱们来看看成果:

结语

本篇次要介绍如何实现一个相似于 utools 的插件加载过程,当然这远远不是 utools 的全副,下期咱们再介绍如何实现 utools 的全局插件,比方屏幕取色、截图、搜寻等能力。欢送大家返回体验 Rubick 有问题能够随时提 issue 咱们会及时反馈。

另外,如果感觉设计实现思路对你有用,也欢送给个 Star:https://github.com/clouDr-f2e/rubick