utools内部实现探秘

前言:

imgutools

utools是基于electron和react构建的快捷工具箱,我也推荐大家使用这款软件来提一提使用电脑的效率和体验。
本着学习的目的,我想研究一下这款软件的运行机制,特别是插件加载相关内容。

img修改main.js的几个字符

由于是商业软件,utools并不开源,我只能拿到被构建工具压缩后的版本。但是通过asar解包和代码格式化,修改main.js的几个字符即可让utools认为自己处于开发模式,让我们能够随时打开devtools一探它的奥秘。


一、模版插件的运行

需要借助的内容:

1.plugin由一个子窗口单独显示:

electron官网对创建子窗口部分的api描述如下:

Parent and child windows

By using parent option, you can create child windows:

1
2
3
4
5
6
const { BrowserWindow } = require('electron')

const top = new BrowserWindow()
const child = new BrowserWindow({ parent: top })
child.show()
top.show()

The child window will always show on top of the top window.

2.preload
为了预加载preload.js,utools利用了broswerWindow的preload属性。


另外,还有另一条不使用子窗口的技术路线:

1.webview tag webview与iframe有些类似,但是electron在webview的实现中提供了独立进程来确保安全性和独立性。

一个坑点:

如果你想在webview里边使用remote,必须在主进程里面enable相应的BrowserWindow实例,这就涉及到获取WebContentsId了。

然而dom-ready事件之后才能拿到webview webcontents的instance,在这之前貌似会返回null。

1
2
3
4
5
let webview = document.querySelector('webview');
// 巨坑,dom-ready之后才能拿到webcontents实例
webview.addEventListener('dom-ready', () => {
ipcRenderer.send("utools_get_webview_webcontents_instance", webview.getWebContentsId());
});

2.contextBridge
根据electron官方文档,preload结束后全局对象window会被清理。若要在preload里往window对象上挂载自定义内容,供页面使用,需要使用electron提供的contextBridge API。它为isolated content暴露接口,提供底层调用机会。官网的具体实现例子:

1
2
3
4
5
6
7
8
9
// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)

img

在source标签页,可以发现模版插件页面的主体文件是位于./tpl下的index.html和index.js。


二、runningPluginPool

utools维持了一个runningPluginPool,类似于操作系统的进程池来管理正在运行的插件。例如,killAllPlugins()会关闭所有运行中的插件。

三、插件的存储

一般情况下,utools将插件存储在如下目录:

1
C:\Users\%username%\AppData\Roaming\uTools\plugins

img

一个插件就是一个.asar文件。install是一个大小为768字节的16进制字符串,每次安装插件后就更新一次。

四、Utools API实现

在根目录下,不难发现一个apisdk结尾的js。这就是开发者调用的utools api对象的直接来源。但是它仅仅封装了一堆ipc,具体的实现在主进程,并不在这里。

img

来到main.js,

img

API被封装在了mainServices的类中。