天天看点

使用Nashorn Engine进行React Server-Side Rendering

几个月前上线了一个电子商务系统平台,运用React开发前web前端,Groovy开发后端 REST API,应用性能及前端交互的响应非常好,但是有一个非常大的痛点。

整个React应用包括库与应用代码在minfy之后仍然超过<code>2MB</code>。当用户第一次访问应用浏览器无缓存时,页面一片空白,原因是浏览器需要下载JavaScript文件。即使已经使用webpack进行代码分割,访问页面仍需要下载<code>1.5MB</code>以上Javascript。在一个<code>250KB/s</code>的下载带宽下,页面可能需要<code>~8秒</code>才能首次渲染。这严重影响用户体验。

解决单页应用的首次渲染方案很明显是进行Server-Side渲染。在google了一些解决方案后,大多数文章及demo都是基于NodeJS,这也很自然,前后端都是JavaScript技术栈。因为web端采用了React,手机端采用了React Native,所以使用NodeJS也行。不过因为后端技术栈主要是Groovy运行于JVM上,所以采用NodeJS会增加部署的复杂度,尤其是当每个用户企业需要部署一个应用实例的话,每个用户企业都要部署一个NodeJS与JVM,从扩展角度来看不够经济。

<code>React</code>包括<code>Angular 2</code>与<code>VueJS</code>,都不直接操作DOM,而是操作所谓<code>Virtual DOM</code>(一种实际DOM的中间表示),通过定期的reconciliation比较上次渲染的差异后批量进行DOM操作。 在Server端渲染时可直接生成html,因此不需要DOM的环境。

由下图可见

使用Nashorn Engine进行React Server-Side Rendering

Nashorn仅实现了<code>ECMAScript</code>,如同Chrome的JavaScript内核一样。因此如果在Nashorn上进行React应用渲染,我们需要自己提供<code>XMLHttpRequest spec</code>与<code>HTML 5 Spec(section 6)</code>的polyfill。在JavaScript中经常使用的<code>Promise</code>, <code>Fetch</code>等可以通过JavaScript进行polyfill,如很多人使用的<code>core-js</code>。

经过仔细理解与验证,原来在之前版本的polyfill中使用了<code>Timer Task</code>运行<code>function callback</code>,实际上它是运行于另外一个线程(不同于<code>Nashorn Engine</code>的线程),可能会同时操作一个JavaScript对象从而损坏<code>Nashorn Engine</code>线程的Javascript调用栈。在后面版本<code>Timer</code>的线程只是往<code>nashornEventLoop</code>队列上增加<code>function callback</code>对象,从而不会损坏<code>Nashorn Engine</code>线程JavaScript调用栈,同时通过<code>Phaser</code>协调<code>Nashorn Engine</code>线程与<code>Timer</code>线程同一时间只有一个能访问<code>nashornEventLoop</code>队列。

性能测试显示,在Macbook Pro (2.3G 4核,16G内存,SSD), 能够实现<code>~40</code>请求/秒,JVM内存占用<code>600MB~1.1GB</code>,服务器上理应更高

使用Nashorn Engine进行React Server-Side Rendering

现在Server-Side Rendering解决方案除了<code>NodeJS</code>,<code>JVM Nashorn Engine</code>也是可靠的一种。

相关github库:

<a href="https://github.com/zloirock/core-js">https://github.com/zloirock/core-js</a>

<a href="https://github.com/morungos/java-xmlhttprequest/blob/develop/src/main/resources/polyfill.nashorn.js">https://github.com/morungos/java-xmlhttprequest/blob/develop/src/main/resources/polyfill.nashorn.js</a>

<a href="https://github.com/shendepu/nashorn-polyfill">https://github.com/shendepu/nashorn-polyfill</a>

<a href="https://github.com/shendepu/react-ssr-starter-kit">https://github.com/shendepu/react-ssr-starter-kit</a>

<a href="https://github.com/shendepu/moqui-react-ssr">https://github.com/shendepu/moqui-react-ssr</a>

<a href="https://github.com/shendepu/moqui-react-ssr-demo">https://github.com/shendepu/moqui-react-ssr-demo</a>