Atom Shell vs Node-Webkit

仅供参考,因为在使用node-webkit的过程当中发现一些问题理解有非常大的差异,因此极有可能导致结论不正确,由于没有给出testcase方便验证,我将给出一些修正并重写该文。

尝试用过 Node-Webkit 做过东西的,或多或少会关注到 Atom Shell。但是中文社区里面讨论两者之间的差异的文档大部分都集中在对官方提供的技术细节上面。当然,官方提供的已经是最核心的差异,足以解释接下来所想讨论的其他差异。本文只是肤浅的记录这些差异的影响。

BTW: Atom Team 已经从一开始的18人缩减到6人。这个项目具体Github怎么想我们已经无从得知了。

历史

2011年12月8日,RogerWang 提交了Node-Webkit的 initial commit,正式让后端的 Node 以及前端的 Webkit 协同工作,借助nodejs丰富的后端库让前端的JavaScript具备了本地访问的能力,也同时更具通用性。

2013年4月11日,Node-Webkit 主力开发人员 @zcbenz 提交了 Atom Shell 的第一个 commit 。

2014年1月26日,GitHub 宣布 Atom 编辑器,其邀请制度让开发社区一码难求。

2014年5月6日,GitHub Atom 团队开源了所有组件,并且让Atom Shell曝光于众人眼前,同道而来的 APM 和 Atom Core 都没有其耀眼(因为后两者完全是Atom Editor的产物)

官方版本的差异比较及其对使用者产生的影响

关于入口的影响(重要)

  • Node-Webkit:入口文件格式为HTML。
  • Atom Shell:入口文件格式为Javascript。

Atom Shell的写法应该参考的是传统的nodejs的写法,在使用上则带有[Chrome Packaged App]1的影子。以Chrome Packaged App的理念来理解Atom Shell的做法是很容易的,在这里,main.js实际上就是一个background.js。

如果只是一个简单的应用,或者只是简单的将Web版本的小应用包装一下,这两者在使用上没有任何一方有一定的优势,并且相对的Node-Webkit用起来更加简单。因为有GUI的情况下,用户的使用方法和对应用的理解,并没有脱离浏览器这个概念。只是相对的能使用一下nodejs的库而已。

Atom Shell则相对脱离了浏览器的概念,更加接近于底层调用,这对传统的前端新人并不是很容易理解,且还需要增加一个javascript的文件来启动真正的界面。但是Atom Shell的这种调整,则避免了Node-Webkit的一些坑(后续会有提到),并且与chrome pakcaged app 类似,提供了一个background process。而Node-Webkit要实现这样的效果,则需要将一开始的入口窗口给隐藏。

关于 Build system 差异的影响

Atom Shell强调自己在架构上优于Node-Webkit,从一个伪程序员的角度来说的确是的,但是对使用者没有太多的影响。如果从技术选型的角度来说,现阶段会是Node-Webkit获胜,因为Node-Webkit经历了历史的沉淀,长于稳定性和大量学习教程。但是从长远的角度考虑,Atom Shell则更容易吃进chromium的升级。

关于Node集成方式的影响

要伪程序员看C++去理解Atom Shell的架构优越性是相对困难的。对使用者来说这个是不需要知道的技术细节,只要知道Atom Shell说它比Node-Webkit优越就好了。

关于Multi-context的影响(非常重要)

这里面的描述是有问题的,事实上通过node-webkit的自己的demo2可以发现在require里面可以使用jQuery。jQuery不能直接使用的原因是因为jQuery本身不自带module.exports的实现。自行修改的版本在node_modules里面也可以直接使用。 此处虽然依旧还是很重要的一个特性,但是显然之前我理解错误了,因此极有可能导致结论不正确。
如果想要详细了解这块的区别,这里有较为详细的关于Context部分的分析

  • Node-Webkit:两个context,node context以及 web context
  • Atom Shell:只有一个context

在这个地方,则是使用者非常需要注意到的一个特点,这将直接影响到你整个应用的架构和协作模式。

由于Node-Webkit的有两个context,且node context里面没办法访问到web context里面的内容,那也就是说如果你要使用到Node-Webkit原生的require,就要小心在require的代码里面不要写入对DOM环境的操作。Atom Shell和Node-Webkit不一样,因为Atom Shell只有一个context,它允许用户在任何模块里面操作任何用户想要的东西 -- 除了Shared Worker。上次阅读Atom-Editor的源码的时候,不太明白它的模块里面何时引入了jQuery,想来就是这个特性了。

架构层面上的影响:

  • 如果还是想做Cloud Mode的,选择带有两个Context的Node-Webkit比较容易理解,并能其Context天然的形成了Server和Client的环境隔离。
  • 如果打算专心做Client Mode,Atom Shell应该算是最好的选择,可以利用其官方模块实现非常多的本地特性,天生自带CMD的require,也不用再去理解不同的context下面require的加载顺序的问题(Node-Webkit一般来说配合requirejs,目前用seajs的比较少)。

协作模式的影响:

  • Node-Webkit的协作方式更加传统,可以以任何传统Web开发的模式来做。
  • Atom Shell的协作方式也可以传统,但是理解了它的应用架构的话,可以明白它更加推崇的是module的方式。并且官方也发布了APM来为其应用做包管理。

官方没有说的一些影响

官方只是简单的给了四个架构层面的技术差异,其他的差异则暗含在了Atom Shell的API设计以及相对黯淡的存在:APM。

Node-Webkit的API相对来说比较全面,并且稳定。Atom Shell的API在Node-Webkit的基础上更近一步,也带来了其对本地WebApp实现的不同理解。如果说Node-Webkit是让浏览器增加一些Node的访问本地的能力,那么Atom Shell则是让Nodejs能操作DOM。从个人的角度来看,Atom Shell的实现相对Node-Webkit:更本地,更激进,不妥协。

API全面借鉴 Node-Webkit,但不完整

在常用API方面,Atom Shell 是全面的借鉴了Node-Webkit的API,因此使用Node-webkit的人迁移起来也相对简单。只是相对于Node-webkit而言,API的完整度需要继续完善,但是足以开发应用了。

入口书写方案更加熟悉

写过 Chrome packaged app 以及 Phantomjs的人并不陌生Atom Shell的写法,这种从Nodejs启动的方式让开发者有了相对简单的入口,入口并不强制HTML的做法使得看起来更加本地一些,而且这也是本地JavaScript开发者更加熟悉的模式。

Render Process 隔离与通信

Node-webkit的node-main也有类似的效果,是否会阻塞还需测试。

这里要先简单介绍一下 Atom Shell里面的一个被分开的概念:Browser Process 和 Render Process。或许有人会想,该死,又引入新概念了。其实,这个和Chromium的概念有点类似,使用过Worker的人对这个也一定容易理解,不就是多线程概念么。

开发者的main.js脚本执行的环境,就是 Browser Process,而每个Browser Window,则是一个个Render Process。要理解起来,就是你在Nodejs里面开了一个浏览器,恩,然后你再开一个浏览器这样的。当然这两个 Process 环境还是有一定差异的,官方列举的Module就有大部分只能在 Browser Process 下面的才能直接 Require。至于如何在 Render Process 里面调用,下面会提到。

由于Atom Shell要求开发者在 Browser Process 下自己去管理 Render Process,也就是Browser Window,这带来了另外两个好处:

  • 天然的server Process。
  • Render Process的相互隔离。

简单介绍一下这两个好处的使用场景:

在Node-Webkit里面开发应用的时候,曾经多次想要额外的开一个隐藏的窗口来做一些高耗时的渲染截图工作,并且希望这个额外的窗口不会阻塞到主窗口的渲染。这个时候才发现Node-Webkit的Window.open实际上和iFrame一样也是会影响主窗口的。

翻阅Wiki的时候发现new-instance,尝试了一番却发现两个窗口无法通过官方介绍的方法进行通讯。在和RogerWang Twitter上讨论也没有结果。

当然Node-Webkit里面有很多方式让窗口间进行通信,比如另外起一个server,通过file中转,通过db中转,使用Native module等等。但是这里面要么不是非常可行或者很麻烦,要么就是要占用一个端口。

以上的几种方式都让人觉得不是很原生。并且个人认为这个应该是Node-Webkit应该要解决的一个点,因为多窗口通讯在原生需求里面很常见。

就在苦恼之际,Atom Shell出现并顺利的解决了这两个问题。

  • Browser Process 是一个天然的 Server Process,通过这个Process可以做很多事情。
  • 在Browser Process里面开出来的 Browser Window 都是一个个独立的Render Process, 每个 Process 之间相互独立,也具备Node的功能,而Render Process并不阻塞另外一个Render Process(注意:Browser Process依旧还是会阻塞所有的Render Process)。Atom Shell也提供unresponsive这样的事件,当你其中一个窗口死掉,你并不需要完全关闭程序,而是可以通过简单的重启则个窗口即可。
  • 提供一个IPC模块,让Render Process和 Browser Process 能进行简单的通信。
  • 提供一个Remote模块,让 Render Process能调用Browser Process里面的模块。解决了Render Process调用Browser Process的问题(当然我觉得这里面应该是通过sendMessage来协作,但是这等细节隐藏在Module下面了)。

在这个问题上Atom Shell相对Node-Webkit来说解决得更加完善。当然,如果Node-Webkit要实现的话,也可以通过Named pipe(Windows)或者 Unix domain sockets(*inux)。

child_process.fork 终于可以使用

这个功能在Node-Webkit里面尚且没有办法能完整的使用。但是在Atom Shell里面是可以实现并且能很好的按照Node方式来执行的。当然在其本身就具备多线程可通信的功能下,fork这个功能也属于锦上添花了。

protocol

虽然Backbone.Wreqr里面也有RequestResponse的这个概念,并且将其name约定成为协议方式,以便可以和其他的command还有event区别开来。但是 APM Module化 让这种使用场景更加标准化,因为本人也在考虑同样的东西(虽然也是Backbone.Wreqr偷窃过来的,并且比它多两个):组件模块的隔离和复用。

APM下载下来的组件,通过一定的手段require进来,每个组件模块通过各自的方式注册自己的protocol到Browser Process上面,这样其他的组件可以通过protocol来对这个组件进行操作。这个就等于让每一个组件都成为了一个Server。

个人现在的框架模式本身也有这种机制,借助Wreqr也很容易实现。Atom Shell的实现则是以自定义协议名的方式实现。按照官方的例子,打出了它在响应请求的时候的参数:

{ method: 'GET', url: 'atom://test', referrer: '' }

意识到这个method参数可能大有作为,于是尝试了在$.ajax里面设置了一下:

{ method: 'BBS', url: 'atom://test', referrer: '' }

从上面的实践来看。protocol具备初级的http的功能,同时也让组件的耦合度更低,并具备跨Render Process执行的可能性。其优点是可以借助method参数来做普通CRUD映射,并可以通过自定义method来扩展使用范围。但是其缺点也显而易见,只能通过url传递参数,开发者需要自己解析参数。在同一个Render Process 里面的话优势不是非常明显。

或者未来可能将protocol扩展出简易传参的功能。不过这个初级功能也足以影响开发模式了。

统一的模块管理 (原标题 APM)

之前理解全错。正确理解为APM为Atom Editor而非Atom Shell,apm install . 只是方便用户安装native module 的简便方法,但是用户无法使用apm install package来安装。Atom shell本身还是基于npm进行管理的,但是其原理并没偏离。

要理解Atom Shell的模块管理APM,需要先了解下Node-Webkit的现实。

Node-Webkit由于其更加传统,且具备两个context,因此开发者在 web context的情况下,主要是利用bower这类前端的库来管理,而后端则自然是使用npm来进行管理。

但是Atom Shell不一样,它没有两个context,因此也就不需要两种方式来进行管理。加上它的require方式和Node-Webkit一样完全借鉴的是nodejs的,所以它可以将包管理完全建立在npm这种模式之上。只是因为它前不前,后不后的特性,导致真正为它开发的模块无法在其他的地方使用。在这种情况下,从NPM 派生出来一个APM就成为一个很自然的事情

个人认为APM有一点高不成低不就的感觉,那就是向后多了点,向前少了点,只能配合Atom Shell来用,这与Atom Shell这种两方通吃的感觉不太一样,对开发者的影响相对较小。 影响较小的另外一个层面,则在于APM尚未提供一个build环境(或者有可能它不需要考虑)。

与nodejs不同,前端一旦文件过多,加载速度就会变慢。一个所见即所得的编辑器,功能复杂一点超过150个文件是很轻松的事情。在传统的情况下,打成一个文件minify之后提速非常明显,但是在nodejs的模块架构里面,这个事情似乎不是那么容易考虑,因为NPM 貌似也没有提供类似的功能。

技术角度的总结

整体上来说Atom Shell要优于Node-Webkit,并解决了Node-Webkit的一些坑。从程序架构的角度上来说,Atom Shell允许你使用对传统的一套,但是也提供了优于传统方式的解决方案,对于想要提供更佳原生体验的人来说,Atom Shell则是不二选择。

  • 在Node-Webkit里面的任何模块,可以相对稳定的迁移Web环境里面,并且现有的Node-Webkit已经比较稳定了。
  • Atom Shell里面的模块也可以兼容Node-Webkit的这种方式。现在已经相对稳定,可以进行一些开发测试。
  • Atom Shell的API和Node-Webkit差距不大,迁移相对容易。
  • 但是Atom Shell允许你使用CMD的方式来写模块,不需要再依赖requirejs。
  • 并且这些模块不需要再考虑是为Web而写的还是为Node而写的。这些模块就是为了应用而写的。开发者可以专注于程序逻辑,避免了context的坑。
  • 只是这些模块里面有部分模块不再能通用化,它们被绑死了只能在Atom Shell的环境下生存。
  • 如果一定要将CMD再次包装,则需要借助于sea.js的来做,这个对用惯了requirejs的人又是一个不小的冲击。(无意比较两者,两者执行顺序有一定差异)。

就因为这样,Node-Webkit适合于长期浸泡在requirejs环境里面并且熟悉Web开发方式的人。而Atom Shell,个人认为是和sea.js的气质近似的一个东西,同属于CMD阵营且模块都具备访问DOM的能力,Atom Shell的Web类模块理论上应该是能和sea.js直接兼容(如果不兼容的话倒是可以让sea.js做一些兼容库),这也可能带给sea.js更多的模块。

Q.E.D.