As of this writing, the Rax applet runtime solution has been released. The main content of this article is still to explain the principle of compile time scheme, hereby note.
Following the Rax to small program link principle analysis (a), explain the principle of engineering design, this talk about Rax code through the compilation engine into small program code process and its operation mechanism.
General introduction
Compilation is a process or action that uses the compiler to generate the target program from the source program written in the source language. The complete process is the process of converting the high-level language into a binary language that the computer can understand. The compilation of this article only considers the first half of the above process, that is, the conversion from high-level language A (Rax) to high-level language B (Applets DSL), and the subsequent conversion to binary languages is beyond the scope. The compilation process is divided into several stages: Tokenizing, parsing, and code generation. The most important concept is the Abstract Syntax Tree (AST), and it’s the most important part of what our compiler has to deal with. This is done by converting the source code to an AST, modifying it for our purposes, and finally generating the AST as code.
The build process
In the Rax applet compile-time scheme, we used Babel as a compilation tool to deal with Tokenizing, Parsing, and generate, while the specific logic to modify AST was placed in the JSX-Compiler. The overall architecture of JSX-Compiler is as follows:
<View>{foo ? <View /> : <Text />}</View>
Copy the code
To be processed into the axML template of the applet:
<View><block a:if={foo}><View /></block><block a:else><Text /></block></View>
Copy the code
Of course, this is just the simplest case. JSX is written in a variety of ways, and we have to write a lot of code to recognize these ways. This is also a universal pain point for Taro and other React DSL compiled applets. Many users complain that when writing small programs using Rax compile-time scheme, they will encounter inexplicable errors. In fact, JSX-Compiler does not recognize the current JSX syntax, resulting in unexpected errors. Over a long period of iteration, Rax has added a lot of syntactic case handling, and as long as you write code that conforms to the specification of the document, you should probably have no problem. In the future, we will also write special Lint plug-ins for the Rax applet compile-time scheme to help detect unsupported syntax and improve development efficiency.
For JSX-Compiler, the process of modifying AST is not that different from other compilation tools, and readers can use the AST Explorer to experience the process of shredding JavaScript code and then assembling it to their liking. In fact, the positioning of JSX-Compiler is more like a popular saying — “tool man”, that is, what kind of code the developer hopes to eventually produce, I will use JSX-Compiler to process and generate. What is important is how the generated code works normally in the small program environment.
Operation mechanism
Based on code generated by Rax scaffolding, a simple Rax page-level component is as follows:
import { createElement } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import './index.css';
import Logo from '.. /.. /components/Logo';
export default function Home() {
function clickMe() {
console.log('clicked');
}
const uri = '//gw.alicdn.com/tfs/TB1MRC_cvb2gK0jSZK9XXaEgFXa-1701-1535.png';
return (
<View className="home">
<Logo src={uri}/>
<Text onClick={clickMe} className="title">Welcome to Your Rax App</Text>
<Text className="info">More information about Rax</Text>
<Text className="info">Visit https://rax.js.org</Text>
</View>
);
}
Copy the code
It’s not much code, but it actually includes a lot of key elements: style references, component references, component props passing, and so on. Look at this code compiled with the JSX-Compiler:
- Js code:
import { createPage as __create_page__ } from ".. /.. /npm/jsx2mp-runtime";
function Home() {
function clickMe() {
console.log('clicked');
}
const uri = '//gw.alicdn.com/tfs/TB1MRC_cvb2gK0jSZK9XXaEgFXa-1701-1535.png';
this._updateChildProps("9", {
"src": uri
});
this._updateChildProps("10", {
"onClick": clickMe,
"className": "title"."class": "title"
});
this._updateChildProps("11", {
"className": "info"."class": "info"
});
this._updateChildProps("12", {
"className": "info"."class": "info"
});
this._updateData({
"_d0": uri
});
this._updateMethods({
"_e0": clickMe
});
}
let __def__ = Home;
Page(__create_page__(__def__, {
events: ["_e0"]}));Copy the code
- Axml code
<block a:if="{{$ready}}">
<view class="__rax-view 3-3-802 fuli New Line Park, Wuchang Street, Yuhang District, Hangzhou City, Zhejiang Province">
<c-d25621 src="{{_d0}}" __tagId="{{__tagId}}-9" />
<rax-text onClick="_e0" className="title" __tagId="{{__tagId}}-10" class="title">Welcome to Your Rax App</rax-text>
<rax-text className="info" __tagId="{{__tagId}}-11" class="info">More information about Rax</rax-text>
<rax-text className="info" __tagId="{{__tagId}}-12" class="info">Visit https://rax.js.org</rax-text>
</view>
</block>
Copy the code
- Json code
{
"usingComponents": {
"c-d25621": ".. /.. /components/Logo/index"."rax-text": ".. /.. /npm/rax-text/lib/miniapp/index"}}Copy the code
From the perspective of the production, we can think of JSX as being mapped to AXML in a very general way, while the import component statements are processed as usingComponents in JSON. But the JS code is a little harder to understand. In fact, to bridge the differences between the Page/Component of the applet and the Rax Component, we provide a runtime shim, the JSX2MP-Runtime in the code, to bridge the two. Let’s take a look at the implementation of jSX2MP-Runtime.
App
Before explaining the Page/Component update mechanism for the applet, let’s take a look at the App implementation. Applets declare top-level applications through the App function, whereas in Rax applications the startup logic is converged to the runApp function provided by RAx-app. Jsx2mp-runtime also declares the runApp function. Take a look at the app.js code before and after compilation:
// src/app.js
import { runApp } from 'rax-app';
import appConfig from './app.json';
runApp(appConfig);
// dist/app.js
import { runApp } from "./npm/jsx2mp-runtime";
import appConfig from "./app.config.js";
runApp(appConfig);
Copy the code
Obviously, the App function must eventually be called from within runApp. In addition, runApp does the following:
- Pass the user through
useAppXXX
Which are injected by the hooks registered lifecycle hook functions - Import the user-configured routing information in app.json and store it in memory
In this way, the App can run normally.
Bridge
Here are the key Page/Component implementations. Like apps, pages and custom components of applets are declarative, and instances are created by calling Page and Component functions, respectively. They are written in the same way, except for slightly different life cycles. As you can see from the compiled code above, jSX2MP-Runtime uses the createPage and createComponent functions to return the declared configuration of the page and component, respectively. Both of these functions call createConfig underneath and do the corresponding life cycle processing in it. The critical operations occur in the mount phase (take the Ali applet declaration cycle, i.e. the onLoad life cycle of the page and the didMount declaration cycle of the Component), Here we create a Rax Component instance and bind it to the Page/Component instance of the applet as follows:
Let’s look at the implementation of the Rax Component Class.
Rax Component Class
The Rax Component Class implemented by JSX2MP-Runtime implements a unique rendering mechanism based on the applet environment. Externally, still exposed componentDidMount, componentDidUpdate, Render and other standard Rax/React methods, and internally, through the trigger method in the form of event bus to collect the trigger of life cycle, Scattered callback execution is avoided. Let’s start with the rendering process.
mount
As mentioned in the previous section, the Page/Component instance creates the Rax Component instance in the mount phase and calls the _mountComponent method on the instance when the binding is complete. This method fires the ‘componentWillMount’ and ‘Render’ life cycles.
Apply colours to a drawing
When rendering, the Rax Component instance calls its own render function (class Component) or function Component itself (actually, internally, After function Component is converted to class Component, the body of the function is the content of the render function. Going back to the compiled code above, a render procedure calls the _updateData, _updateMethods, _updateChildProps, and other methods on the instance. Here are their roles:
_updateData
The _updateData argument is a variable used in JSX collected by the JSX-Compiler. So obviously the _updateData layer calls setData to update the view-level data. The reality is a little more complicated, and we did a lot of optimization before updating the data to improve overall performance. This is why, as mentioned in the introduction, the Rax applet compile time scheme achieves better performance than native in some scenarios. For details that developers may not be aware of that can be optimized, Rax does this at the framework level, using Data diff/$spliceData /$batchedUpdates. Improved performance of setData, the most time-consuming method.
_updateMethods
Similar to _updateData, the _updateMethods parameter is extracted by jSX-Compiler from the methods used in JSX, The summary is to merge all proxied methods into the Methods object of the Page/Component instance so that they can be used in the template. As for methods why proxy? In scenarios such as loops, temporary loop variables need to be declared in the template and cannot be used in bound methods. So the method broker gets around this limitation by passing these temporary variables on the template, which are collected and parsed by the JSX-Compiler and written to the template, as parameters to the bound method. In addition, the method broker has functions such as preventing event bubbling.
_updateChildProps
_updateChildProps Notifies subcomponents of updates. Notice that the first argument is a string number that corresponds to the __tagId passed to the child component in the template (see the example code above). When each Rax Component instance is created, an instanceId is assigned to it, either from the __tagId value of the props passed by the parent Component or from a global variable increment that generates a unique value. The instanceId is then written to memory. When _updateChildProps is invoked, locate the subcomponent instance in the memory based on the instanceId, update the props value of the subcomponent, and push the subcomponent instance to the update queue.
update
Either the class Component calls setState or the function Component calls the set function of hooks to trigger the update, the instance is pushed to the update queue. Updates are executed in batches after each macro task. Each instance update will roughly go through the following steps:
- Trigger componentWillReceiveProps life cycle
- Call getDerivedStateFromProps (if present)
- Call shouldComponentUpdate (if present) and decide whether to continue with the update
- Triggers the componentWillUpdate life cycle
- Triggering updates (the process described in the previous section)
- Triggers the componentDidUpdate life cycle
As you can see, the complete Rax/React life cycle in which, as a result, for shouldComponentUpdate/memo, the optimization of the component update in Rax applet compile-time scheme is feasible, and that this is often a lot of Rax small developers lax attention point.
other
Based on this running mechanism, we then implemented hooks, render props, context and other syntax capabilities to align the Rax development experience with the Web as much as possible within the limitations of small programs. As for the ability is how to achieve, here no longer repeat, interested partners can read the source code.
conclusion
The above is all the technical principles of Rax small program compile time scheme. Please visit Rax’s GitHub (github.com/alibaba/rax) to learn more. Next, we are expected to introduce the Rax applet runtime solution, please look forward to ~-