Author: Fushen Nigui, front-end engineer of Alibaba Tao Department

Rax making Repo Rax applet website – https://github.com/alibaba/rax – https://rax.js.org/miniapp

After continuing iterations, the RAX applet has received a major upgrade to support a new runtime solution. Standing at the beginning of 2020 this time point, we want to start from the characteristics of RAX small program, a comprehensive combing and summary, and at the end of the paper attached with the RAX and the current mainstream small program development framework comparison. This paper will expand from four aspects: API design and performance, dual engine architecture, excellent multi-terminal component protocol design and Webpack-based engineering architecture.

I. API design and performance

When deciding the technical selection of a product, we tend to consider it from several aspects: (1) available ecology, that is, whether peripherally related tools meet the conditions of product development; (2) Risk rate, that is, whether problems can be quickly located and solved, and whether the technology used will be maintained continuously; (3) the starting cost, that is, does it need a great cost to reach the stage of being able to use; (4) Performance, that is, to meet the established product performance standards and user experience.

This section will focus on the latter two advantages of RAX applets.

API design

The overall adoption cost of the framework is relatively small, and the RAX applet link is inherited from the RAX framework (a progressive React -like framework that builds multiterminal applications). So if you know RAX Web/Weex development or React, then you can develop small programs in RAX that can be simultaneously deployed on other sides of RAX.

But because of the particularity of the small program side, there will always be unable to smooth and need to be handled separately. Thanks to the multi-terminal solution that RAX has been doing for a long time, we believe that the independent attributes of each end should not intrude on the basic framework itself, and the purity of the basic framework is conducive to doing more extensions.

Take the following code for example:

Taro:

import Taro, { Component } from '@tarojs/taro' import { View, Text } from '@tarojs/components' export default class Index extends Component { config = { navigationBarTitleText: } componentWillUnmount () {} componentWillUnMount () {} componentDidShow () {} componentDidHide () { } render () { return ( <View> <Text>1</Text> </View> ) } }

Rax:

import { createElement, Component } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import { isMiniApp } from 'universal-api'; 
import { registerNativeListeners, addNativeEventListener, removeNativeEventListener } from 'rax-app';

function handlePageShow() {}

class Index extends Component {
  componentWillMount () { }

  componentDidMount () { 
    if (isMiniApp) {
      addNativeEventListener('onShow', handlePageShow);
    }
  }

  componentWillUnmount () {
    if (isMiniApp) {
      removeNativeEventListener('onShow', handlePageShow);
    }
  }

  render () {
    return (
      <View>
        <Text>1</Text>
      </View>
    )
  }
}

if (isMiniApp) {
  registerNativeListeners(Index, ['onShow']);
}

export default Index;

In contrast to Taro, there are two main differences: (1) RAX does not have the concept of ComponentDidShow ComponentDidHide, and adds the API such as AddNativeEventLisEnter RemoveEventListener which is similar to W3C standard; (2) There is no static property called config on the component instance to set the configuration of the page title and so on.

React itself does not have the concept of ComponentDidShow, because it has nothing to do with the lifecycle of the component itself. Rather, we want to guide users to write business code using standard APIs. At the same time, this design also brings performance related improvements, which will be explained later in this article.

Of course, this design itself can lead to a certain amount of bloated code, but by engineering it is possible to ensure that the resulting code is almost identical in size.

performance

The performance of applets is a common problem in business development, and a lot of effort has been put into the existing compile-time scheme of RAX applets. We tested an infinite drop-down list through Ali’s small program cloud testing function.

The page structure is as follows:

<img src=”https://img.alicdn.com/tfs/TB19YlZv7L0gK0jSZFxXXXWHVXa-542-962.png” width=”300″ />

According to the real machine test report, the average native applet three times is 2008 ms, TARO is 2003ms, RAX is 1924ms, of course, the difference is not much, but the actual business scene is actually far more complex than the above page structure.

Similar to Taro, the basic framework on the small program side of Rax does not have a VDOM at the logical level, but instead uses data consolidation, traditional data diff, to keep users from updating redundant data. What’s more, Ali applet provides the private method $spliceData to optimize the performance. The RAX layer will recognize whether the user needs to update the value of an array or not, and then automatically use $spliceData to optimize the rendering according to the scene.

The applet itself needs to be pre-registered to listen for events (this is also to ensure performance), that is, it needs to:

Page({
  onShow() {}
});

Instead of dynamically registering:

const config = {}
Page(config);
setTimeout(() => {
  config.onShow = () => {};
}, 1000);

So it’s really not a good idea to add a concept like ComponentDidShow, which will cause the page to register all the native events because it doesn’t know whether to register the onShow property or not, which will not only cause the developer to not be flexible in scaling, but also lead to the risk of memory leaks.

The RAX applet introduced registerNativeListeners, which gave developers a new understanding of the need to register events on the page in order to listen. This not only addresses scalability issues, but also addresses potential performance issues.

Of course, is this the end of the performance optimizations that RAX applets can do? For the foreseeable future, the RAX applet compile time solution already has some specific actions, such as further easing the framework’s props update management and making more use of the native capabilities of applets to implement component updates to avoid the performance cost of doing the same thing as the applet base framework.

Two, twin engine architecture

RAX is (possibly) the industry’s first small program solution that supports both compile-time and run-time solutions. Switching between the two approaches is extremely simple, and we truly give the user the choice of high-performance or full syntax. The dual-engine driven RAX applet architecture is as follows:



We will look at the two compilation schemes below.

Compile-time scheme

The RAX applet compile-time scheme is a process of translating RAX code into applet native code through lexical and grammatical analysis on the premise of AST translation. Due to the dynamic capabilities of JavaScript and the fact that JSX itself is not a traditional template-type syntax, a relatively lightweight runtime gasket was introduced into this scenario.

The core of the RAX applet compile-time architecture is mainly divided into two parts, AST translation and run-time gasket. A brief introduction to these two parts will be given below.

AST translation

The AST translation section architecture is cleaner and more maintainable than its Taro counterpart. It has to be mentioned here that its sub-syntax scenario translation and the onion model. We can take a cursory look at the code structure of the subsyntax scenario translation section:

<img src=”https://img.alicdn.com/tfs/TB19kyNwaL7gK0jSZFBXXXZZpXa-318-664.png” width=”200″ />

It can be clearly seen that for each grammatical scenario, there is a module dedicated to translation, which makes the whole process of translation easy. As long as the translation results of each part meet the expectations, then the translation results are as expected. This design allows us to make full use of unit tests to compare pre – and post-translation code.

The design of onion model is another major design of AST translation. The whole translation process is actually divided into four steps:

<img src=”https://img.alicdn.com/tfs/TB1xNrbweH2gK0jSZJnXXaT1FXa-1592-154.png” height=”60″ />

The Onion model is mainly carried out in the following three steps: the original AST tree is modified into a new AST tree that meets the expectation at the Parser layer, and the new AST tree is translated into small program code at the Generate layer.

Run time gasket

Because of the dynamic capabilities of JSX and some of the features that RAX originally provided, such as hooks. So, the RAX applet compile-time solution provides a run-time gasket to align the emulated RAX Core API.

Now that the runtime is introduced, it is possible to do more management of the data flow based on this mechanism, as well as provide APIs for the RAX project on other sides, such as routing related history location, etc.

Runtime scenario

The run-time solution of RAX applets is not self-developed, but “stands on the shoulders of giants”, reusing Kbone’s architecture and making some changes to it to access RAX applets’ engineering system. The implementation principle of the runtime solution can be viewed here, and will not be described in detail here. First of all, we need to introduce the advantages of RAX applet which is also KBONE:

  1. Support for more complete front-end framework features. In contrast to the RAX compile-time scenario, you can now use the full RAX syntax, and all of RAX’s features are supported. Forget the grammatical constraints of the rules;
  2. Highly reusable Web-side logic. If the user has the RAX program code on the Web side, it may only need a little modification to transplant the entire application to the small program side, which greatly improves the development efficiency;
  3. When the small program side runs, it can still use the features of the small program itself, such as the built-in components of the small program.

On the basis of Kbone, RAX applet runtime solution also added a lot of features, summed up as the following:

  1. Support Alipay applet. Due to Kbone’s positioning, it only supports WeChat applets. Rax based on Kbone, combined with the characteristics of Alipay applet, expand the support for Alipay applet. Now, if you want to use the run-time solution when developing the Alipay applet, you can use not only Remax, but also Rax.
  2. Access to complete RAX engineering system. Now, you can experience all the features of the RAX project, such as the RAX multi-terminal API, multi-terminal components, multi-terminal builders, and so on, while using the runtime solution for a complete and consistent experience.

Finally, we can’t avoid the fact that the RAX applet runtime solution suffers from the same problem that all runtime solutions suffer from: performance depletion. In fact, the run-time solution is to trade some performance penalty for more full Web-side feature support. Therefore, if you have certain performance requirements for small programs, it is recommended to use the compile-time scheme; If you don’t have high performance requirements, the runtime solution is a great tool to help you develop small programs quickly. The twin-engine RAX applet has a way of hitting your heart.

Three, excellent multi-terminal component protocol design

The RAX applet compile-time scheme supports project-level development and component-level development. Unlike TARO, where components are compiled into small program code in a single project, RAX builds small program components in a component project. Combined with a set of excellent multi-terminal component protocol design, we can make use of RAX applet components in both RAX applet projects and native applet projects, while maintaining a unified multi-terminal development experience. The protocol is defined in the miniAppConfig field of package.json, and its specific usage design can be seen in the document RAX applet – Multiterminal Component Development.

Support for progressive access to RAX

For developers who have developed complete applets using native syntax, it is a reasonable requirement to gradually switch to the RAX development link, since the entire project migration can be expensive. RAX, on the other hand, relies on the multi-terminal component protocol to help smooth the transition.

By design, the RAX applet component project builds components that conform to the applet syntax, so it makes sense that they can be used directly in native applet projects. This means that if you want to develop small programs using RAX incrementally, you can gradually migrate small programs that were previously developed using native syntax to RAX on a component or page basis. This is something other frameworks, such as Taro, do not have. Among the users of RAX, Zhejiang online government platform “Zheliban” Alipay applet adopts the way of gradually accessing RAX.

Multi-terminal unified component use experience

When a small program component published using the RAX Component Project is used in a RAX project, the builder automatically finds the small program implementation of the component through the path specified by miniAppConfig to implement the replacement. Users do not need to write specific paths in the business code level like the traditional way of introducing native applet components, but keep the same with the Web/Weex side. Examples are as follows:

// Wrong
import CustomComponent from 'custom-component/miniapp/index'

// Correct
import CustomComponent from 'custom-component'

In addition, the multi-terminal component protocol can be extended to the multi-terminal component library protocol, supporting more flexible writing like import {Button} from ‘fusion-mobile’. Based on the RAX multiterminal component protocol, you can quickly develop common components or component libraries for your multiterminal projects. For example, RAX base components are developed in this way.

IV. Webpack-based engineering architecture

RAX project is based on the unified front-end CLI tool @alib/build-script of Alibaba Group. It relies on Webpack, supports all scenarios through plug-in system, and provides flexible Webpack configuration ability based on Webpack-chain. Users can combine various plug-ins to achieve engineering requirements. The compile-time scheme of the RAX applet handles its own logic through the Webpack loader. Webpack Loader classified as App/Page/Component will call JSX-Compiler for AST analysis and processing of the code, and then hand the processed code to the loader to generate the corresponding applet file. The runtime solution directly multiplexes the Web-side compilation configuration and generates specific applet code through additional Webpack plug-ins.

Compared with other self-developed engineering systems, the whole structure has the following advantages:

  • Webpack-based, flexible and extensible
  • Plug-in system, strong reusability
  • The command is simple and the experience is uniform

conclusion

Finally, I attach a comparison between RAX and the existing mainstream applet frameworks.



The above is our RAX small program of the core competitiveness of the stage summary and thinking. Small programs are not new, and there are plenty of small program solutions, but we believe that with the introduction of RAX, your small program development will be a little different.

More about Rax applet, welcome to visit https://rax.js.org/miniapp for!