Atom-Shell vs Node-Webkit (Context篇)
上篇 Atom-Shell 和 Node-Webkit 的对比,有谈到当中 Atom-Shell 的 multi-context 对架构选型方面的影响,虽然结论还有稍微有那么一点点靠谱,但是当中的原理错得一塌糊涂。鉴于上一篇篇幅长度已经不适合再追加分析内容,故单独提出一章来说明两者的区别。
尝试理解架构的差异
借用Atom-Shell 的 PPT来大致理解下当中的一些问题。
Atom-Shell
在上篇里面有说到Atom-Shell里面有 Browser Process
和 Render Process
两种进程,通过这张图可以更加容易理解当中的差异。从图上可以理解Atom-shell这两个进程是隔离的,当中通过IPC进行通讯。但是要注意到这里面两个Render Process
是相互隔离的。
Node-Webkit
Node-Webkit 没有找到类似的图,从使用上理解可以认为是 Node-Webkit 的入口并没有进入到 Browser Process
,而是直接进入了上图当中的 Render Process
。
但是 Node-Webkit 提供了node-main
机制来提供简单却又不那么简单的 Browser Process
功能。这个参数可以让用户指定的一个javascript文件在 Node Context
下一直执行。对比Atom-Shell的 Browser Process
来说,Node-Webkit 这个功能相对隐晦一些,这里利用官方的例子做一个说明:
简单的方面
只有非常纯粹的 Node 的 API,与 Atom-Shell 相比缺乏了很多 Browser 控制类的API。
不简单的方面
全局共享变量。这里的全局共享并非只是针对于其中的一个窗体而言,而是针对所有非new-instance
开出来的窗体。尽管例子上面是要用process.mainModule.exports
去访问,但事实上它允许一开始将值挂在global
上。如果你使用Window.open
去打开其他的页面,在其他的窗体或者模块里面,通过global
就可以访问和修改之前定义的值,并且这些是当前 Node-Webkit 实例共享的。
因此,Node-Webkit 给人的感觉如下:
小结
Node-Webkit 的架构不明,抽象理解起来主要是 Window Context
和 Node Context
两个Context之间的区别。其中 Node Context
是完全共享同一个,非new-instance
关系的窗体之间可以通过global
对象进行值共享。
Atom-Shell 的架构上面区分 Browser Process
和 Render Process
两种进程,这两个进程完全隔离。如果需要在两个窗体之间共享存储变量,最好的方式是通过require('remote').getGlobal('sharedObject').someProperty = 'some value'
来保存在 Browser Process
这边1,算是变相的实现了 Node-Webkit的特性。
Two Processes vs Two Contexts
上面小结提到的两个概念,则是本文所要细说的差异。让我们先从最熟悉的 Node-Webkit 来说起。
Two Contexts (Node-Webkit)
Node-Webkit 官方网站上面有提到这个概念2,在上篇文章里面我的理解是两者相互隔离无法访问,但是显然错了,这里我们还是借用 Chrome DevTools 去理解一下。
首先我们先简单的给入口文件做一个断点:
然后在test.js
模块里面同样设置一个断点:
接下来我们可以观测到,在 HTML 文件里面得到的 Global 为:
而在test.js
这个里面得到的Global为:
这就是 Node-Webkit 的两个Context。也就是说,通过HTML标准手段加载的东西在 Window Context
,这个时候你不能直接使用 GLOBAL
下面的任何内容, 例如 Buffer
。而通过require
加载进来的,则可以直接使用 Buffer
,但是没有办法直接使用 Worker
这个在 Window Context
下面的内容。
所谓的相互隔离,其实质只是不能直接使用而已。事实上,Node-Webkit 用相互引用的方法解决不同 Context 下相互访问的问题,即window.global
和global.window
。也就是说,Node Context
里面已经存在了一个window
的全局变量,如果想让Window Context
下面的alert()
在 Node Context
下面使用,代码上就要写成window.alert()
。
陷阱
在官方解释文档里面提到两种 Context 会导致一些使用上的陷阱。
例如 window.Function == global.Function
这个判断条件为false
就是因为两个 Context 的基础类的构造函数不一样,这也因此导致一个instance of
使用上的风险。如果我们在window
里面去创建一个函数,然后在global
环境下用instance of Function
判断,那么你永远得到的都是false
. 因此在使用这类函数的时候必须要注意。
而官方的解释文档3提到的相对路径的问题,这个坑比较容易跳过去。
Two Processes (Atom-Shell)
由于 Atom-Shell 的集成方式与 Node-Webkit 不一样,因此在讨论 Atom-Shell 的时候,Process 的概念要远远比 Context 要重要。虽然如官方所说,通过 Multi-Context 的 patch 他们并没有引入新的 Context,但是应该重点这个统一的 Context,说的是在Render Process
里面。
Atom-Shell 本质上还是有两个 Context,但是与Node-webkit不一样的是,单一的 Process 只有单一的 Context 。
如以前所说 Browser Process
实际上就是一个纯粹的 Node Context
,目前尚未找到方法能直接操作Render Process
层面的内容。
而使用之前的方法我们可以比较简单的得到 Atom-Shell Render Process
的Context
:
通过这张截图可以发现,Render Process
的 Context 就是我们常用的Window
。但是与 Node-Webkit的Window
不一样的是它同时包含Node Context
下面的所有内容,甚至于在这个里面还能得到__dirname
这样的变量。由于和传统的 Context,之后讨论先称其为Render Context
。
这个Render Context
统一了传统的Context和Node Context,这样在任何一个层面,无论使用哪种方式加载进来的,环境都是类似的。但是其中有些方法 Atom-Shell 做了一些处理,例如setImmediate
,其本质是:
陷阱
尽管统一了Context,但是其也会带来一些负面的效果,类似 Node-Webkit 的路径问题实际上还是存在的4,举例说明:
先准备一个 HTML 文件,里面加载外部的Javascript 文件。
在这个 javascript 里面打出当前的__dirname
。
结果我们的到的会是 HTML 文件的目录路径。
另外一个则是remote
模块带来的远程执行效果。从它的源码 上来看,这个远程模块实际上是走的IPC通信+本地创建映射Object的模式,与之前热炒过一阵子的C/S函数共用的模式有点类似。由于remote
得到的 Function
或者 Object
不一定就是原来的那个,所以在某些特定情况会导致问题,例如官方所说的截图Buffer的问题。
小结
Atom-Shell 和 Node-Webkit 差异其是还挺大的。整体上来看之前的结论没有太多的问题。
Atom-Shell
优点
- 由于
Render Process
Context的统一性,npm
管理以及require
加载模块依旧是 Atom-Shell 使用者的首选,也因此,CMD 的 Fan 也相应的推荐 Atom-Shell 。 - Process 之间的相互隔离可以减少开发的风险。
- 开发上用户只需要区分具体是为哪个Process开发即可,更加符合C/S的开发思维,简化了开发者的选择。
缺点
- API目前还没办法完全映射。
- 目前没找到办法指定
dataPath
,因此千万不要使用localStorage
,filesystem
,websql
,cookie
等去保存数据。其他 Atom-Shell 应用有可能覆盖你的数据。 Browser Process
目前尚未找到调试的方式,官方说将会有类似node debug
这样形式的调试方法。- 目前还没办法打包成为 Node-Webkit 那种只有一个exe的文件的形式。
npm
模式的优化方式还有待讨论。
Node-Webkit
优点
- API相对完整,有大量相关教程。
- 能指定
dataPath
,也因此开发者无需关心是否与其他应用有冲突(对于测试非常友善)。 - 调试相对方便。
- 两个Context符合传统的C/S模式的开发,方便迁移成为 web services。
- 允许将源码与执行文件打包成为一个文件。
缺点
- 混用两个Context会导致Bug率增加,如果不是非常熟悉,最好的方式还是不混用两个Context。
- 一旦某个模块的代码没有写好,会污染
Node Context
,属于全局污染(之前Mocha有这样的bug)。 - 窗体之前不相互独立,其中一个死掉会导致所有的全部不能用,只能重启,并且需要注意窗体之前不要同时进行大量的render。