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
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
{b}
Hello
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
/ / ReactElement object
{
tag: Text,
props: null.children: 'Hello'. }Copy the code
Hello
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:
- translation
React
Code so that it can be recognized by small programs, such as specific useReact.createElement
replaceJSX
, such asasync/await
Syntactic processing and so on. - Enumerates and identifies independence
JSX
Fragment, generate appletswxml
File.
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 (
-
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); .
-
UiDes = {name: “000003”}
-
Traverse the children
-
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
-
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
-
Children traversal ends.
-
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…