實現工具自由,開源的桌面工具箱


在一切開始之前,首先要致敬 uTools!​如果沒有它就沒有 Rubick。

大家好,我是“拉比克”(Rubick)項目的作者木偶。我做的 Rubick 是一款基於 Electron 的開源桌面工具箱,簡單講就是好多工具的集合,然后加上快速啟動、豐富的插件擴展等功能於一體。

沒錯!它的使用方式和外觀幾乎和 uTools 一摸一樣。那我為什么放着免費的 uTools 不用,非要自己搞一個呢?

事情的起因是這樣的,出於安全方面的考慮有一些僅適用於公司內部的插件不能發布到插件市場,所以不能接入 uTools。但實在眼饞 uTools 式的便捷、用完即走的極簡操作體驗。在搜尋解決方案無果,同時也發現其他的小伙伴也有同樣的訴求,所以我動手做了,然后把它開源了。

Rubick 一款呼出超快、用完即走的開源工具箱,因為開源所以更自由!

項目地址:https://github.com/clouDr-f2e/rubick

希望它能幫助你解決同樣的煩惱,但目前僅支持 Windows 和 macOS,Linux 版本正在開發中。想借助開源的力量讓 Rubick 變強,成為金牌輔助!幫助大家輕松“超神”!

在做 Rubick 的過程中還是遇到了不少問題和挑戰,下面就分享下我的心路歷程。

一、緣起

1.1 初識 Electron

Electron 是 GitHub 開源的一個框架。它通過 Node.js 和 Chromium 的渲染引擎完成跨平台的桌面 GUI 應用程序的開發。我起初沒有接觸過 Electron,最開始接觸它是因為看到了 PicGo 的一個核心功能非常吸引我,就是 macOS 下可以直接拖拽圖片進入任務托盤上傳圖片:

當時正好我們團隊也需要搞一個內部的 CDN 圖片資源管理圖床,用於項目圖片資源壓縮並直接上傳到 CDN 上,之前我們做了個網頁版。而這里我深刻的感受到了 Electron 的強大,可以極大的提高工作效率,參考 PicGo 我嘗試做了第一個 Electron 項目,完成了圖片壓縮上傳到內部 CDN 的桌面端應用。

1.2 演化

之后公司內部因為開發和后端進行接口聯調測試環境時,經常會涉及到一些狀態改變要看交互樣式的問題。比如測試需要測商品的待支付、支付中、支付完成等各種節點的交互樣式是否符合預期,這種情況測試一般會去造數據或者讓后端改數據庫接口。有的小伙伴可能會用 Charles 修改返回數據進行測試,但 Charles 的抓包體驗和配置體驗感覺有點麻煩,對新人不是很友好所以我們自己做了個非常易用 抓包&mock 工具:

這也是 Rubick 最早的雛形。隨后,我們發現當頁面發布線上的時候,沒有辦法在微信環境內對線上頁面進行調試,所以開發了一個基於 winner 的遠程調試功能。

但隨着該 Rubick 在內部不斷推廣和使用,所需功能也越來越多。我們需要 需求管理、性能評估、埋點檢測 等等工具。這些工具的增加一方面導致 Rubick 體積暴增,一方面又導致了用戶需要不斷更新軟件,導致用戶體驗非常差。

其次,我們在推廣給測試、UI 同學使用的時候,發現他們其實並不關注前面的頁面調試、性能測評等功能,可能只是用到其中某一項,所以整個項目對他們來說就顯得很臃腫。

1.3 靈感

直到有一天,我在掘金上看到這樣一個沸點:

下面有個評論提到了 uTools 這是我第一次和 uTools 產生了交集,在體驗了 uTools 功能后,我長吸一口氣:這不就是我想要的嘛!然后就去 GitHub 上找 uTools 的源碼,發現它並沒有開源。

所以就想把上面提到的那些工具, 發布到 uTools 市場在 uTools 里通過插件的方式使用他們。但我發現發布插件只能發布到公網,但這又涉及到數據安全的問題。

無奈,難道真的要自己做一個這樣的工具嗎?真的是有點頭大。不過想想也挺有意思的。至此,我萌生了要開發一個媲美 uTools 的開源工具箱的念頭。

二、研發

開篇第一步,按照我之前的套路都是先取好名字先占個坑。我是個 Dota 玩家,之前寫了一本《從0開始可視化搭建》的小冊,里面使用了 Dota 中一個英雄的名字 coco(船長)。這次我取名的是 rubick 即 拉比克。Rubick(拉比克) 也是 Dota 里面的英雄之一,其核心技能是插件化使用其他英雄的技能,用完即走。非常符合本工具的設計理念,所以取名 Rubick。

我的核心目標就是需要讓 Rubick 支持插件化,解決前面提到的問題:

  • 每個人的工具箱不同
  • 軟件體積暴增
  • 每增加一個工具就需要更新版本

其次,通過調研了解到團隊內有些同學已經在使用 uTools 了,要想讓他們從 uTools 上把插件零成本遷移到 Rubick 上,就必須實現 uTools 的部分 API 能力,以及插件的定義和寫法也需要和 uTools 規范保持一致。

2.1 開發者模式

插件開發需要和 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"
}

2.1.1 核心字段

  • name 插件倉庫名稱
  • pluginName 插件名稱
  • description 插件描述,簡潔的說明這個插件的作用
  • main 入口文件,如果沒有定義入口文件,此插件將變成一個模版插件
  • version 插件的版本,用於版本更新提示
  • features 插件核心功能列表
  • features.code 插件某個功能的識別碼,可用於區分不同的功能
  • features.cmds 通過哪些方式可以進入這個功能

2.1.2 示例

開發插件的方式是復制 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://xxx.com/img.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://xxx.com/img.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 中跑起來的。

2.3 插件運行原理

運行插件需要容器 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'));

到這里就已經實現了一個最基礎的插件加載,效果如下:

2.4 支持更多體驗能力

隨后為了更加貼近 uTools 的體驗,我又開始着手讓 Rubick 支持更多原生體驗增強的特性:超級面板、模版、系統命令、全局快捷鍵等

三、最后

再次致敬 uTools!我做 Rubick 旨在技術分享,並不以商業化為目的。

以上就是我和 Rubick 的故事,如果 Rubick ​對您有幫助,那么就請給個 Star ✨ 鼓勵一下:

https://github.com/clouDr-f2e/rubick


機緣巧合我發現了 HelloGitHub 一個推薦開源項目的平台,了解到鹵蛋也是喜歡打 Dota,我想那他應該能感受到 Rubick 的魅力,所以我就抱着試一試的心態投稿了。先是有幸入選了月刊第 64 期,然后受邀寫了這篇關於 Rubick 的故事。

​最后,感謝 HelloGitHub 讓 Rubick 被更多人發現和喜歡,特別感謝鹵蛋對文章的潤色和修改,讓本文增色不少。


關注 HelloGitHub 公眾號 第一時間收到更新。

還有更多開源項目的介紹和寶藏項目等待你的發現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM