Atom-Shell vs Node-Webkit (Context篇)

上篇 Atom-Shell 和 Node-Webkit 的对比,有谈到当中 Atom-Shell 的 multi-context 对架构选型方面的影响,虽然结论还有稍微有那么一点点靠谱,但是当中的原理错得一塌糊涂。鉴于上一篇篇幅长度已经不适合再追加分析内容,故单独提出一章来说明两者的区别。

尝试理解架构的差异

atom-shell-structure

借用Atom-Shell 的 PPT来大致理解下当中的一些问题。

Atom-Shell

在上篇里面有说到Atom-Shell里面有 Browser ProcessRender 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-context

小结

Node-Webkit 的架构不明,抽象理解起来主要是 Window ContextNode Context 两个Context之间的区别。其中 Node Context 是完全共享同一个,非new-instance关系的窗体之间可以通过global对象进行值共享。

Atom-Shell 的架构上面区分 Browser ProcessRender 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 去理解一下。

首先我们先简单的给入口文件做一个断点:

 <script>debugger; require("./test");</script>

然后在test.js模块里面同样设置一个断点:

 debugger;

接下来我们可以观测到,在 HTML 文件里面得到的 Global 为:

node-webkit window context

而在test.js这个里面得到的Global为:

node-webkit node context

这就是 Node-Webkit 的两个Context。也就是说,通过HTML标准手段加载的东西在 Window Context,这个时候你不能直接使用 GLOBAL 下面的任何内容, 例如 Buffer 。而通过require加载进来的,则可以直接使用 Buffer,但是没有办法直接使用 Worker 这个在 Window Context 下面的内容。

所谓的相互隔离,其实质只是不能直接使用而已。事实上,Node-Webkit 用相互引用的方法解决不同 Context 下相互访问的问题,即window.globalglobal.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 ProcessContext:

atom-shell render context

通过这张截图可以发现,Render Process的 Context 就是我们常用的Window。但是与 Node-Webkit的Window不一样的是它同时包含Node Context 下面的所有内容,甚至于在这个里面还能得到__dirname这样的变量。由于和传统的 Context,之后讨论先称其为Render Context

这个Render Context 统一了传统的Context和Node Context,这样在任何一个层面,无论使用哪种方式加载进来的,环境都是类似的。但是其中有些方法 Atom-Shell 做了一些处理,例如setImmediate,其本质是:

    function () {
      process.activateUvLoop(); // 激活到Nodejs的环境下面
      return func.apply(this, arguments); //执行真正的setImmediate函数
    }

陷阱

尽管统一了Context,但是其也会带来一些负面的效果,类似 Node-Webkit 的路径问题实际上还是存在的4,举例说明:

先准备一个 HTML 文件,里面加载外部的Javascript 文件。

   <script src="test1/test.js"></script>
  

在这个 javascript 里面打出当前的__dirname

	console.log(__dirname);
 

结果我们的到的会是 HTML 文件的目录路径。

另外一个则是remote 模块带来的远程执行效果。从它的源码 上来看,这个远程模块实际上是走的IPC通信+本地创建映射Object的模式,与之前热炒过一阵子的C/S函数共用的模式有点类似。由于remote得到的 Function 或者 Object 不一定就是原来的那个,所以在某些特定情况会导致问题,例如官方所说的截图Buffer的问题

小结

Atom-Shell 和 Node-Webkit 差异其是还挺大的。整体上来看之前的结论没有太多的问题。

Atom-Shell

优点

  1. 由于Render Process Context的统一性, npm管理以及require加载模块依旧是 Atom-Shell 使用者的首选,也因此,CMD 的 Fan 也相应的推荐 Atom-Shell 。
  2. Process 之间的相互隔离可以减少开发的风险。
  3. 开发上用户只需要区分具体是为哪个Process开发即可,更加符合C/S的开发思维,简化了开发者的选择。

缺点

  1. API目前还没办法完全映射。
  2. 目前没找到办法指定 dataPath ,因此千万不要使用localStorage,filesystem,websql,cookie 等去保存数据。其他 Atom-Shell 应用有可能覆盖你的数据。
  3. Browser Process 目前尚未找到调试的方式,官方说将会有类似node debug这样形式的调试方法。
  4. 目前还没办法打包成为 Node-Webkit 那种只有一个exe的文件的形式。
  5. npm模式的优化方式还有待讨论。

Node-Webkit

优点

  1. API相对完整,有大量相关教程。
  2. 能指定 dataPath,也因此开发者无需关心是否与其他应用有冲突(对于测试非常友善)。
  3. 调试相对方便。
  4. 两个Context符合传统的C/S模式的开发,方便迁移成为 web services。
  5. 允许将源码与执行文件打包成为一个文件。

缺点

  1. 混用两个Context会导致Bug率增加,如果不是非常熟悉,最好的方式还是不混用两个Context。
  2. 一旦某个模块的代码没有写好,会污染Node Context,属于全局污染(之前Mocha有这样的bug)。
  3. 窗体之前不相互独立,其中一个死掉会导致所有的全部不能用,只能重启,并且需要注意窗体之前不要同时进行大量的render。
Q.E.D.