At the end of May, we officially opened the industry’s first React Native to wechat applet engine Alita project (github.com/areslabs/al…). . This project was initiated because the team had a large number of business modules developed by React Native, and most of the business had requirements to be transplanted to H5 and wechat applet. So we began to think about how to convert React Native code into wechat applets through technical means. After an internal incubation and a lot of business landing, we finally contributed the Alita engine to the community. Her positioning is very clear, we don’t build new frameworks, Alita has to be low intrusive and do only one thing, which is to convert your React Native code and run it on the wechat applet side (maybe more in the future).

In fact, the open source community has been working on connecting React and wechat applet, and many excellent frameworks have emerged (Taro, also produced by Jingdong Cave Lab, is an excellent framework). We found that although most of them are based on React, they provide new frameworks and new syntax rules, and deal with React Native only a little. More importantly, existing frameworks use compile-time processing for React syntax, which is more restrictive to JSX syntax (more on this later). Our expectations for Alita were that the JSX syntax should not be too restrictive, intrusive, or too burdensome for React Native developers. So instead of building on any existing compile-time scheme, the Alita engine ended up with a groundbreaking run-time approach.

Technical details aside, there are two things that Alita users should know: 1) If you plan to convert complex RN applications, you need to pay special attention to the fact that wechat applets have a size limit of 4M. 2) Alita cannot directly convert native/third-party components into applets. However, Alita provides a way to extend these components, much like providing native platform components on RN. In addition, we will soon release AlitA-UI, a UI library that contains community-used RN components and is directly supported by the AlITA transformation engine.

Talk is cheap. Show me the code.

Go straight to the dry stuff! Let’s take a look at the core of the Alita engine from a purely technical perspective: How do you implement runtime processing of JSX

Limitations of existing community programmes

Before we dissect Alita, let’s take a look at the existing community solutions. We say that the existing solutions are bigger for JSX now. How do they do that? They mainly run React code on the wechat applet side by converting React code into equivalent wechat applet code during compilation. For example, the React logic expression:

xx && <Text>Hello</Text>
Copy the code

Will be converted to the equivalent applet wx:if instruction:

<Text wx:if="{{xx}}">Hello</Text>
Copy the code

What’s wrong with this compile-time approach? Take a look at the React code.

class App extends React.Component {
    render () {
        const a = <Text>Hello</Text>
        const b = a

        return (
            <View>
                {b}
            </View>)}}Copy the code

Here we declare variable a: const a =

Hello
, variable b: const b = a. Let’s take a look at how the compile scenario (Taro1.3 as an example) transforms the above code.

This example is not particularly complicated, but the error is reported.

To understand why the code above reported an error, we need to understand the compile phase first. The code is essentially a ‘string’ at compile stage, and the compile stage processing scheme needs to analyze the necessary information from this’ string ‘(via AST, re, etc.) and then do the corresponding equivalent conversion processing.

For the example above, what equivalent treatment is required? B = a =

Hello
, then replace {b} in

{b}
with

Hello
. However, at compile time it’s very difficult to determine the value of B, and some people say you can go back and determine the value of B, but think about it because b is equal to a, you have to determine the value of A, and how do you determine the value of A? Need in the scope chain of b can access to determine a, however a might again by other variable assignment, cycling, once appear, is not a simple assignment during the period of situation, such as the function call, three yuan runtime information, such as to determine trace is failed, if a itself is hung on global object variables, traceability is even more impossible.

So it’s not easy to determine the value of b at compile time.

Let’s look at the error message in the figure above: A is not defined.

Why is a undefined?

Hello
is equivalent to react. createElement(Text, null, ‘Hello’), and the react. createElement method returns a normal JS object of the form

/ / ReactElement object
{
   tag: Text,
   props: null.children: 'Hello'. }Copy the code


Hello
The JSX fragment is processed specially. A is no longer a normal JS object. Here we see that the A variable is even lost, which exposes a serious problem: The code semantics are broken.

Alita processing solution

The compile-time solution, which is more like an enhanced template, is completely possible from an engineering point of view as long as you follow the conventions and syntax constraints. But at a higher level of abstraction, React is itself a rethinking of UI, whether it’s UI as code or React is “value UI”.

Alita doesn’t change React semantics. Instead, it uses a dynamic approach to JSX. Next, we’ll take a step-by-step look at Alita’s runtime solution, along the schematic below. We illustrate this in two aspects: the Alita compile phase and the Alita runtime.

Compilation phase

To summarize, the static compilation phase does two things:

  1. translationReactCode so that it can be recognized by small programs, such as specific useReact.createElementreplaceJSX, such asasync/awaitSyntactic processing and so on.
  2. Enumerates and identifies independenceJSXFragment, generate appletswxmlFile.

To illustrate how Alita differs from other compile-time solutions in the community, let’s take a look at what Alita static compilation does, assuming the following JSX fragment.

const x = <Text>x</Text>

const y = (
	<View>
		<Button/>
		<Image/>
		<View>
			<Text>HI</Text>
		</View>
	</View>
)
Copy the code

After the Alita compilation phase:

const x = React.createElement(Text, {uuid: "000001"}, "x");
const y = React.createElement(
    View,
    {uuid: "000002"},
    React.createElement(Button, null),
    React.createElement(Image, null),
    React.createElement(
        View,
        null,
        React.createElement(Text, null."HI")));Copy the code

Each individual JSX fragment is uniquely identified by a UUID. The WXML file is also generated

<template name="000001">
	<Text>x</Text>
</template>

<template name="000002">
	<View>
		<Button/>
		<Image/>
		<View>
			<Text>HI</Text>
		</View>
	</View>
</template>

<! - to take the template - >
<template is="{{uiDes.name}}" data="{{... uiDes}}"/>
Copy the code

Separate JSX fragments are wrapped in a small program template whose name attribute is the UUID above. Finally, you need to generate a placeholder template at the end:

The dynamic nature of [Template is] in conjunction with the UUID identifier will be key to runtime handling of JSX, as discussed below.

This is the end of the compile phase.

Alita runtime

As for Alita runtime, the core is the embedded Mini-React, which is a react that applies to wechat mini program and has all the five elements. Let’s review the React rendering process briefly:

Recursively (react16.x no longer uses recursion after Fiber was introduced) builds component tree structures, creates component instances, performs component lifecycle, context calculations, refs, etc. When the state is updated, the corresponding life cycle is called again to determine whether the node is multiplexed (virtual-DOM), etc. In addition, the browser DOM API (appendChild, removeChild, insertBefore, etc.) is called during execution to continuously manipulate the native node, resulting in the UI view.

The execution process of Alita Mini-React is basically the same as this, and it also builds the component tree recursively and calls the life cycle, etc. The difference is that Alita cannot call DOM API. Students familiar with wechat applets development know that wechat applets shield DOM API. How do you dynamically handle React syntax without DOM API and with static WXML templates for applets?

Remember the Uuid generated at compile time? Each UUID represents a separate JSX fragment. During the reactdom.render recursive execution phase, Alita collects the UUID attribute of the aggregated JSX fragment and generates and returns the uiDes data structure. This uiDes data contains all the fragment information to render, which is passed to the applet.
recursively render the final view.

React code: Alita React code: Alita React code

class App extends React.Component {

    getHeader() {
        return (
            <View>
                <Image/>
                <Text>Header</Text>
            </View>
        )
    }

    f(a) {
        if (!this.props.xx) {
            return a
        }

        return null
    }

    render() {
        const a = <Text>Hello World</Text>
        const b = this.f(a)

        return <View>
            {this.getHeader()}
            {b}
        </View>}}Copy the code

First identify the individual JSX fragments with uUID and escape the above code with Babel as follows:

class App extends React.Component {
    getHeader() {
        return React.createElement(
            View, 
            {uuid: "000001"},
            React.createElement(Image, null),
            React.createElement(Text, null."Header")); } f(a) {if (!this.props.xx) {
            return a;
        }

        return null;
    }

    render() {
        const a = React.createElement(Text, {uuid: "000002"}, "Hello World");
        const b = this.f(a);
        return React.createElement(View, {uuid: "000003"}, this.getHeader(), b); }}Copy the code

Meanwhile, separate JSX fragments are extracted to generate WXML files:

<template name="000001">
    <View>
        <Image/>
        <Text>Header</Text>
    </View>
</template>

<template name="000002">
	<Text>Hello World</Text>
</template>

<template name="000003">
	<View>
		<template is="{{child001.name}}" data="{{... child001}}"/>
		<template is="{{child003.name}}" data="{{... child002}}"/> </View> </template> <! Template --> <template is="{{uiDes.name}}" data="{{... uiDes}}"/>
Copy the code

Reactdom.render (
, parent) : ReactDOM (
, parent)

  1. Reactdom.render determines that App is a custom component, creates an example, and executes the life cycle componentWillMount, etc. React.createelement (View, {uuid: “000003”}, this.getheader (), b); .

  2. UiDes = {name: “000003”}

  3. Traverse the children

  4. Process the first child: this.getheader (), whose value is react.createElement (Text,{name: “000001”}, “Header”). The value of uiDes is as follows:

    uiDes = {
    	name: "000003",
    	
    	child001: {
    	    name: "000001"}}Copy the code
  5. Process the second child node, B. If this.props. Xx is true, b is null and is ignored. B = a = react. createElement(Text, {name: “000002”}, “Hello World”) Text is the base element, the recursion terminates, the second child finishes processing, and the uiDes value is as follows:

    uiDes = {
    	name: "000003",
    	
    	child001: {
    	    name: "000001"
    	},
    	
    	child002: {
    	    name: "000002"}}Copy the code
  6. Children traversal ends.

  7. The micro app obtained uiDes, set it to the following placeholder template, and render the corresponding view. The first one is the outer template 000003, and then its two children nodes, 000001 template and 000002 template respectively, and finally render the complete view.

    <template is="{{uiDes.name}}" data="{{... uiDes}}"/>
    Copy the code

During this process, all of your JS code runs in the React process, semantically identical, and the JSX fragment doesn’t get any special processing, just a simple react.createElement call. Finally, a uiDes data is output to the applet, which uses the uiDes to render the view. In addition, the React process here is only pure JS operation and does not involve DOM operation, so the execution is very fast, usually only a few ms, which means the overhead of mini-React is very small.

Alita’s handling of JSX fragments is dynamic, you can show any JSX fragment anywhere, in any function, and the final code execution result determines which fragment to render. Only the UUID of the resulting fragment is written to the uiDes. This is fundamentally different from static identification of compile-time schemas.

In addition, since the upper layer runs React, Alita has a natural advantage in supporting libraries such as Redux.

conclusion

We need a more React approach to small programs. Alita in this direction into some more, Alita source code is completely open, we will continuously provide anatomy Alita principle, and hope to bring a new train of thought, the community also provide a new choice for developers, in addition to more developers to understand the principle of Alita, also hope that more people can participate in, “The World needs more heroes!! .

Can Alita convert React applications? Based on our internal requirements, we only dealt with React Native code. However, the React syntax is the same, and it is not difficult to extend Alita to convert React applications. However, we have no expansion schedule yet. Our next plan is to optimize/reconstruct the internal RN-to-H5 tools, and the final form should be based on RN development ecology. Full coverage is supported through the Alita transformation engine.

As an added bonus, Flutter can also run on the Web, and wechat applet runs on the Web. Therefore, based on the Alita runtime solution, it is possible to imagine the fusion of Flutter and wechat applet.

Making: github.com/areslabs/al…