故事背景
之前在網上有看到很多小夥伴基於 electron
實現了非常多好用的桌面端工具,比如圖床管理工具 PicGo,就專門做圖床工具。也有一些其他的類似的小工具,比如 saladict-desktop 專門做沙拉翻譯查詞的桌面端應用,colorpicker 專做桌面端取色工具...
我們也參考了這些小工具的設計理念,嘗試在公司內部做一款桌面端工具,解決網路抓包、代理、圖床、效能測評等常見場景的使用問題。最後在推廣的時候,遇到了一個比較嚴重的問題,就是很多小工具對特定使用者來說並不需要。比如測試只需要使用網路抓包、代理的功能,其他功能並不關心。此時就需要設計一款桌面端應用,類似於 App Store
那樣,用到什麼下載安裝什麼即可。這就需要實現桌面端應用的外掛化。
於是乎,我們看到了 uTools 是支援外掛化的桌面端應用,但是前提是我們的外掛必須釋出到 uTools
外掛市場,才能實現多端同步下載的功能,但是公司內部的工具庫有些涉及到安全資訊又無法釋出到 uTools
外掛中,所以我們特別渴望有一款類似於 uTools
的內部工具箱。
為了進一步提高開發工作效率,最近我們基於 electron 開發了一款媲美 uTools 的開源工具箱 rubick。該工具箱不僅僅開源,最重要的是可以使用 uTools 生態內所有開源外掛!這將是巨大的能力,意味著 uTools 生態內所有外掛可以無差異化使用到 rubick 中。
程式碼倉庫:https://github.com/clouDr-f2e/rubick
外掛化之旅
一開始想到做外掛化,無非就是使用 electron
的 webview
能力,實現類似於原生內嵌h5
那樣的方式,h5
頁面可以做獨立釋出,原生提供 nativaAPI
之間通過 jsBridge
來橋接呼叫原生的方法。這樣實現並無問題,我們也嘗試了做了一次。最終思路大概是:
electron webview 方式
1. electron 中使用 webview
<webview src="https://xxx.xx.com/index.html" preload="preload.js" />
2. 實現 bridge
// preload.js
window.rubickBridge = {
sayHello() {
console.log('hello world')
}
}
3. 外掛藉助 bridge
呼叫 electron
的能力
<html>
<body>
<div>這是一個外掛<div>
</body>
<script>
window.rubickBridge.sayHello()
</script>
</html>
4. 通訊
因為 proload.js
是 electron
的 renderer
程式的,所以如果需要使用部分 main
程式的能力,則需要使用通訊機制:
// main process
ipcMain.on('msg-trigger', async (event, arg) => {
const window = arg.winId ? BrowserWindow.fromId(arg.winId) : mainWindow
const operators = arg.type.split('.');
let fn = Api;
operators.forEach((op) => {
fn = fn[op];
});
const data = await fn(arg, window);
event.sender.send(`msg-back-${arg.type}`, data);
});
// renderer process
ipcRenderer.send('msg-trigger', {
type: 'getPath',
name,
});
ipcRenderer.on(`msg-back-getPath`, (e, result) => {
console.log(result)
});
為什麼後來我們又放棄了這條路? ?
其實上面的思路大致是沒啥問題的,我們也基於上面的思路成功把功能抽成了外掛,按照外掛的方式進行安裝載入。直到我們注意到 utools
的強大,感覺 utools
的生態非常豐富,我們要是能整合 utools
的生成那該多好呀!所以我們秉持著幹不過他就成為他的原則,我們嘗試著成為他。但是 utools
本身並沒有開源,所以沒有辦法去吸取一些優秀的程式碼實現,但是我們可以看他的官方文件。
我們發現其實 utools
大多數外掛都是和 container
層分離的,也就是說 utools
只是一個外掛的容器,為外掛提供了一些 api
能力和方法。所以一旦我們實現了utools
載入外掛的能力,實現 utools
的所有 API
函式,是不是就約等於實現了 utools
! 我們就可以使用 utools
的外掛?
utools 方式
按照 utools 的 文件,首先我們需要實現一個外掛,必須要有個 plugin.json
,這玩意就是用來告訴 utools
外掛的資訊。我們也按照文件來寫:
{
"pluginName": "helloWorld",
"description": "我的第一個uTools外掛",
"main": "index.html",
"version": "0.0.1",
"logo": "logo.png",
"features": [
{
"code": "hello",
"explain": "hello world",
"cmds":["hello", "你好"]
}
]
}
接下來是將寫好的外掛用 utools
跑起來,按照 utools
的互動是複製 plugin.json
到utools
搜尋框即可,我們也可以實現:
// 監聽 input change
// 讀取剪下板內容
const fileUrl = clipboard.read('public.file-url').replace('file://', '');
// 複製檔案
if (fileUrl && value === 'plugin.json') {
// 讀取 plugin.json 配置
const config = JSON.parse(fs.readFileSync(fileUrl, 'utf-8'));
const pluginConfig = {
...config,
// index.html 檔案位置,用於webview載入
sourceFile: path.join(fileUrl, `../${config.main || 'index.html'}`),
id: uuidv4(),
type: 'dev',
icon: 'image://' + path.join(fileUrl, `../${config.logo}`),
subType: (() => {
if (config.main) {
return ''
}
return 'template';
})()
};
}
實現效果如下:
接下來就是進行命令搜尋外掛:
實現這個功能其實也就是對之前儲存的pluginConfig
的裡面的 features
進行遍歷,找到相應的 cmd
後進行下拉框展示即可。
然後我們要去實現選擇功能,用 webview
載入頁面的能力:
<template>
<div>
<webview id="webview" :src="path" :preload="preload"/>
</div>
</template>
<script>
export default {
data() {
return {
path: `File://${this.$route.query.sourceFile}`,
preload: `File://${path.join(__static, './preload.js')}`,
webview: null,
query: this.$route.query,
config: {}
}
}
}
</script>
到此結束了?並沒有!!!由於篇幅的原因,我們後續再說。本出寫的外掛demo已上傳github: https://github.com/clouDr-f2e/rubick-plugin-demo
目前支援能力
載入utools生態外掛
拿 github
上開源的 鬥圖 外掛舉例,要載入鬥圖外掛,只需要將程式碼 clone下來後,複製其 plugin.json
進入搜尋框即可使用
鬥圖:https://github.com/vst93/doutu-uToolsPlugin
超級皮膚
長按滑鼠右鍵,即可呼起超級皮膚,可以根據當前滑鼠選擇內容,匹配對應外掛能力。比如當前選擇圖片後長按右擊,則會呼起上傳圖床外掛:
模板
為了更貼合 uTools
的外掛能力,需要實現模板功能,模板即是一個內建 UI 樣式的功能外掛。
utools 自帶的系統命令
取色
截圖
全域性快捷鍵
最後
目前 rubick
已經實現 utools
大多數核心能力,最重要的是可以使用 utools 所有生態 ! 更多能力可以前往 github 體驗。如果感覺有用,可以幫忙反手一個 star ✨