系列文章:
0x00 Electron 启动
思源桌面都基于 Electron,当思源启动的时候,首先启动的就是 electron 的代码,也即是 electron 的主线程代码 app/electron/main.js
。
main.js
主要包含了以下几个内容:
-
错误显示
showErrorWindow
-
日志输出
writeLog
-
引导
boot
-
初始化内核
initKernel
-
窗口控制
-
electron app 初始化及后续事件监听,包括:
- ipcMain 事件监听,即主线程与渲染线程的交互
- app 事件监听
作为 electron 程序,思源程序的生命周期实际上是包含在 electron app 的生命周期之中的。除开函数声明和监听之外,我们可以看到的整体流程为:
- 思源初始化文件存储
- Electron APP 初始化
- Electron 菜单初始化
- 开启第一个 workspace 的窗口
- 判断是否未初次使用用户,是则等待渲染进程中发送 siyuan-first-init 事件,否则直接使用端口和 workspace 配置,初始化内核
- 内核初始化成功完成后,boot 引导创建主窗口。
其余的多 workspace 管理、进程退出管理、系统休眠、关机等事件处理,在此先不做研究。
思源启动的重点,主要在于 initKernel 和 boot 两个部分
initKernel
思源启动时,我们首先看到的是一个半透明的加载窗口(Linux 为非透明),这个窗口显示着系统内核目前的加载状态,当失败时也会显示对应的失败信息。而背后的工作,实际就是思源的 electron 的 app 在获取可用端口和确定内核文件存在之后,使用 nodejs 的 child_process.spawn
方法,创建了内核子进程。并持续到维护管理内核的运行状态,例如接收系统启动信息,内核异常退出管理等。当内核启动失败时,会通过 showErrorWindow 的方式显示错误,关闭程序。通过轮询内核接口的方式,获取启动进度和信息,接收到内核启动 100% 时,完成 initKernel。
boot
当 boot 方法执行时,就是我们实际使用中初始化窗口关闭,显示思源主窗体的过程。
主窗口 currentWindow
的内容信息是通过访问内核的界面接口获取的
currentWindow.loadURL(getServer() + '/stage/build/app/index.html?v=' +new Date().getTime())
主窗体加载完成后,加入到 workspaces 中,剩余的启动相关内容都是在主窗体的渲染进程中进行的。
0x01 主窗体运行
根据目前的加载方式和架构可以发现,思源与传统的“前后端分离架构”非常类似。而现在,我们就从主窗体的界面加载进行探索。
主模板入口
通过查看 webpack.desktop.js
可知,前端界面的 html 是通过 app/src/assets/template/index.tpl
渲染来获得的。当然,这个 tpl 中并没有包含后端模板渲染的内容。可以直接当做 html 来了解。
出去主入口 js 会后续添加到 head 中之外,主窗口的结构包括了
-
loading
为加载界面,也就是一上来我们看到的包含思源 logo 的黑屏加载界面 -
toolbar
、dockTop
、dockerLeft
、layouts
、dockRight
、dockerBottom
、status
均为页面骨架组件 -
commonMenu
、message
为其他相关的工具内容
主脚本入口
主脚本入口为编译之后的 src/index.ts
,我们从源文件开始看起。
index.ts
中包含了 App
这个唯一的类,并对齐进行了单次创建操作。在其构造函数中,显示了 App 的创建过程。
-
同步添加脚本 lute,
lute
是由思源开发者创建的 Markdown 引擎。Lute 是一款结构化的 Markdown 引擎,完整实现了最新的 GFM/CommonMark 规范,对中文语境支持更好。
-
异步加载
protyle
,protyle-html
是 HTMLElement 的继承子类,可以看作是一类思源中特有的 DOM 元素,与 lute 相关,后续我们再做深度挖掘。 -
addBaseURL()
根据后端内容初始化部分配置。 -
window.siyuan
,是在 window 对象上创建的 siyuan 公开 API,包含了大量的原始信息,可以作为插件或者挂件需要的内容进行调用。 -
ws
,我们可以看到是一个创建的 Model 对象,Model 对象可以在 src/layout/Model.ts 查询到源码。Model 类包含的主要工作为维护 websocket,思源的 app 除了 http 请求与内核进行消息传输之外,还可以通过 websocket 技术从思源内核中主动获取到信息。例如在 App 的 Model 对象 ws 中,我们可以看到在此处它进行了多种类型消息的处理,例如进程加载处理、配置值变化处理、下载进度处理、状态栏内容处理等。 -
menus
,思源右键的的初始化菜单。 -
配置加载,我们可以看到
fetchPost
方法调用内核的/api/system/getConf
接口,然后再获取本地存储接口,获取多语言国际化配置接口,然后进行了bootSync()
同步启动,再获取云端用户信息。
上面这些调用是必须在一个完成之后调用另一个,我们看到思源这部分代码是通过回调控制的,调用越多,缩进越深,也就是我们常说的“回调地狱”。 -
setNoteBook()
,笔记本数据查询 -
initBlockPopover()
,初始化块的悬浮弹出框。 -
promiseTransactions()
,用于循环控制内核发出的操作执行。例如引用快、嵌入快的更新等。
基于以上步骤还很难了解具体界面是如何出来的,主要内容可以查看 onGetConfig()
这个方法。
0x03 启动时间轴
综合前后端来看,我们可以尝试画出时间轴
0x04 小结
思源桌面版启动流程比较简洁,而真正复杂度都在内核的启动流程与 APP 从入口到真正渲染完成这两个部分,后续,我们继续对这两部分进行探讨。
内容首发 b3log 平台,未经允许,请勿转载
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于