On this page

How it works

Ray is a runtime built on top of Remax. It is essentially a react-reconciler based miniapp-side renderer. For more information about react-reconciler and React renderer, check out this video.

MiniApp shields the Document Object Model (DOM), so the code is run in a worker thread and is not allowed to directly manipulate the DOM in the view layer. Ray imports VNode to make React update VNode first in the reconciliation process instead of directly changing the DOM.

The basic structure of VNode is as follows:

interface VNode {
  id: number;
  container: Container;
  children: VNode[];
  mounted: boolean;
  type: string | symbol;
  props?: any;
  parent: VNode | null;
  text?: string;
  appendChild(node: VNode): void;
  removeChild(node: VNode): void;
  insertBefore(newNode: VNode, referenceNode: VNode): void;
  toJSON(): RawNode;
}
  • id: The node ID that is auto-incremented to be unique for each node.
  • container: It is similar to ReactDOM.render(<App />, document.getElementById('root')). Ray renders components to a container that is used to save the reference to VNode.
  • children: The child node.
  • mounted: Indicates whether a node has been displayed in the view layer.
  • type: The node type, that is the basic component of a miniapp, such as view and text.
  • props: The node property.
  • parent: The parent node.
  • text: The text of the text node.

VNode adopts a tree structure. In VNode, we implement node operation methods similar to those in DOM. After React updates VNode, the toJSON method is invoked to convert VNode into a JSON object.

Take the following page component as an example.

import React from 'react';
import { View, Text } from '@ray-js/ray';
const IndexPage = () => {
  return (
    <View className="greeting">
      <Text>Hello Ray</Text>
    </View>
  );
};
export default IndexPage;

Ray renders this component into the following VNode structure.

{
  "id": 0,
  "type": "root",
  "children": [
    {
      "id": 1,
      "type": "view",
      "props": {
        "className": "greeting"
      },
      "children": [
        {
          "id": 2,
          "type": "text",
          "props": {},
          "children": [
            {
              "type": "plain-text",
              "text": "Hello Ray"
            }
          ]
        }
      ]
    }
  ]
}

The root node is created internally by Ray. The rendered VNode becomes the data of the Page in a miniapp.

Display interfaces in the view layer

In the previous section, the React component is rendered into a VNode in JSON object that becomes the data of the Page in a miniapp. In this section, we will show you how to display this data in the miniapp template.

When we build a Ray application, a page template is generated to display VNode, as shown below.

<block a:for="{{root.children}}" a:key="{{item.id}}">
  <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
</block>

<template name="REMAX_TPL_view">
  <view class="{{item.props['className']}}">
    <block a:for="{{item.children}}" key="{{item.id}}">
      <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
    </block>
  </view>
</template>

<template name="REMAX_TPL_text">
  <text>
    <block a:for="{{item.children}}" key="{{item.id}}">
      <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
    </block>
  </text>
</template>

<template name="REMAX_TPL_plain-text">
  <block>{{item.text}}</block>
</template>

The above code traverses the child element of the root node and selects the corresponding template by the type of the child element to render this child element. Then, it traverses the child element of the current element in each template to traverse the entire node tree recursively.

This is how Ray is implemented. You can optimize the code as needed.