With the increasing heat of small program development, the framework of small program development emerges in endlessly. However, currently each framework is bound to its own DSL, such as class React or class Vue. Within a framework, developers cannot freely choose DSLS based on their team’s technology stack, nor can they share the framework’s own ecosystem and tools.

Taro discusses how to implement a framework that supports multiple DSLS, enabling developers to use any popular framework/syntax /DSL to write applets while reusing the relevant ecosystem.

The history of small program development

In the early morning of January 9, 2017, the much-anticipated wechat mini program was officially launched.

Before this, jingdong into a front small team, after a month of closed development, a version of the speed of iteration in one week, finally released in the first time her “jingdong shopping” small program, although the function and interface now seem humble, but it was completely accords with WeChat small program “within reach, finished with the” concept.

Of course, with the continuous iteration of the whole project, the design, interaction and functional complexity of the “JINGdong Shopping” small program has been fully comparable to the APP side. The engineering practice here has been shared by Liu Huimin at the GMTC Global Big Front-end Technology Conference (Beijing station) 2019. If you are interested, you can download PPT: Jingdong shopping small program engineering road.

At that time, there were some shortcomings in the development of wechat small programs, such as chaotic dependency management, backward engineering process, imperfect ES Next support, inconsistent naming norms, etc. All of these problems are now known to have various official and unofficial solutions, but at the exploratory stage of applets development, they were pain points.

One of my personal favorites is that a language becomes a “compilation target” language when its capabilities are inadequate and the user’s operating environment does not support other options.

Throughout the history of the front-end, whether it is the popularity of CSS preprocessors, the popularity of various templates, or the birth of CoffeeScript and TypeScript, all confirm this statement, wechat small program here is no exception. Therefore, a variety of small program development frameworks such as a hundred flowers bloom, emerge in endlessly.

The main difference between these small application development frameworks is DSL, which can be seen from the logo color. In addition to Chameleon’s custom DSL, the other green logos follow Vue syntax (such as MPVUE). The blue logo follows the React syntax (e.g. Taro).

After WeChat small procedures, the major manufacturers have released their own small program platform, such as: alipay, baidu, headlines, QQ, etc., coupled with fast application, netease, 360, jingdong, small application circuit is more and more crowded, developers need to fit more and more small application platform, as a result, application development framework have also carried out various size adaptation.

Therefore, if you look back at the progress of the applets development framework at this point in time, you will find that the applets development framework has been differentiated and focused on DSL and multi-endpoint adaptation throughout 2018 and early 2019.

The origin and origin of Taro

As the so-called “business incubation technology, technology service business”, Taro was born from the increase of business needs, at that time, our team needed to be responsible for: JINGdong Shopping, TOPLIFE and other businesses. At the same time, the above businesses have more or less multiple requirements, such as wechat mini program, H5 and React Native(mainstream APPS of JD are basically built with React Native rendering engine), and it can be predicted that, In the future, it is likely to need to adapt to more small program platforms, and it is not practical to develop a set of code for each end, which will lead to: increased research and development costs, difficult code maintenance.

At that time, our team developed a React framework: Nervjs, the entire technical stack was switched to React, and there wasn’t a small application framework that followed the React syntax. We developed Taro, who was able to write small applications using the React syntax. Write Once Run Anywhere to achieve cross-end.

The Taro Framework has been continuously iterating in three areas since it was opened on June 7, 2018:

  • Multi-end adaptation: From one end to multiple ends
  • Development experience: support React Hooks, CSS Modules, Mobx, etc
  • Community building: such as Taro Forum, Taro Material Market and other platforms, as well as the community building plan to be released later

After more than a year of work by the team, Taro has gained widespread community recognition and as of December 18, 2019, Taro has 22,254 Stars and 250 ficolin-3 with 150+ community-initiated development cases: Taro -user-cases include many cases.

But despite all this, Taro still has some problems that can’t be solved, or rather, aren’t easy to solve. For example, React DSL is strongly bound, JSX adaptation is a lot of work, and community contribution is complex. In the final analysis, a large part of these problems are related to Taro’s architecture.

Therefore, our team has been waiting for the right opportunity to upgrade the entire architecture and repair some of the technical debt caused by the rapid iteration of the project.

Most of all, a simple project maintenance iteration was no longer enough to satisfy our team’s restless heart, and we were eager to take this opportunity to make a technological breakthrough.

Exploration of small program cross – framework development

Before we talk about the Taro architecture, let’s review the architecture of the applet.

Wechat applets are mainly divided into logical layer and view layer, and the native part below them. The logic layer is mainly responsible for JS running, and the view layer is mainly responsible for page rendering. They mainly communicate with each other through Events and Data, and call the native API through JSBridge. This is also the architecture of most small programs led by wechat small program.

Since the native part is like a black box to the front-end developer, the native part of the entire architecture diagram can be omitted. At the same time, we also simplify the logic layer and the view layer, and finally get a minimalist version of the applet architecture diagram:

In other words, you just need to call the corresponding App()/Page() methods in the logic layer, and provide data, lifecycle/event functions in the method, and provide the corresponding templates and styles in the view layer for rendering to run the applet. This is what most applets development frameworks focus on and deal with.

Taro’s Current Structure

Taro’s current architecture is divided into compile time and run time.

The compile time is mainly to convert Taro code through Babel into small program code, such as: JS, WXML, WXSS, JSON.

Runtime is mainly for some parts: life cycle, events, data processing and docking.

Taro at compile time

If you have experience developing Babel, you should be familiar with the following process: Compile Taro using babel-Parser to parse the Taro code into an abstract syntax tree, and then use babel-types to modify and convert the abstract syntax tree. Finally, the corresponding object code is generated through babel-generate.

Please refer to babel-Handbook for details

The most complex part of compile time is JSX compilation.

We all know that JSX is a JavaScript syntactic extension that can be written in a variety of ways. Here we have adapted the possible JSX writing methods one by one in an exhaustive way. This part is a lot of work. In fact, Taro has made a large number of commitments to better support the various JSX writing methods.

However, it is not possible to cover all cases, so we recommend that you write React code according to the official specification. We also provide a rich ESlint plugin to help you write the specification.

There is a long-standing myth within our team that if you use Taro to develop bugs, your React code is well-written.

Taro runtime

Next, we can compare the compiled code and see that the core Render method of React is missing. Also added to the code are BaseComponent and createComponent, which are the core of the Taro runtime.

/ / before compilation
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.scss'

export default class Index extends Component {

  config = {
    navigationBarTitleText'home'
  }

  componentDidMount () { }

  render () {
    return (
      <View className='index' onClick={this.onClick}>
        <Text>Hello world!</Text>
      </View>)}}/ / the compiled
import {BaseComponent, createComponent} from '@tarojs/taro-weapp'

class Index extends BaseComponent {

// ... 

  _createDate(){
    //process state and props}}export default createComponent(Index)

Copy the code

React BaseComponent (setState, forceUpdate, etc.); render (BaseComponent, forceUpdate, etc.); Taro’s current architecture only follows the React syntax when it is developed, and has nothing to do with React when it is actually run after the code is compiled.

CreateComponent basically calls Component() to build the page; Docking events, life cycles, etc. Diff Data and call the setData method to update the Data.

conclusion

Therefore, the characteristics of the entire Taro current structure are:

  • Recompile, run light: this can be seen by comparing the lines of code on both sides.
  • Compiled code has nothing to do with React: Taro simply follows the React syntax while developing.
  • Compile directly with Babel: This also leads to Taro’s current weakness in engineering and plugins.

Architecture for other solutions

Applets have blossomed, and we’ve taken a lot of inspiration from the community.

Let’s take a look at how MPVue is implemented, which represents a small program development framework that follows vUE syntax.

Mpvue is essentially a fork of vuejs/[email protected] code, preserving Vue Runtime capabilities while adding support for the applets platform.

Specific performance in the source is: under the platforms of the Vue source folder increased mp directory, inside has realized the complier (compile time) and the runtime (runtime) support.

Mpvue is also implemented at compile time and run time.

Mpvue compile-time

What you do at compile time is similar to Taro: compile Vue SFC code into small program code files (JS, WXML, WXSS, JSON).

The biggest difference is that Taro compiles JSX to a small program template, while MPVue compiles Vue to a small program template. However, due to the similarity between the Vue template and the applets template, MPVue has much less work to do in this area than Taro.

Mpvue runtime

The mpVue runtime is strongly correlated with the Vue runtime, so let’s look at the Vue runtime first.

A.vue single file consists of three parts: template, script, and style.

In the orange path part, template will be analyzed through ast in vue- Loader during compilation and finally generate a render function. Executing the render function will generate a virtual DOM tree, which is an abstraction of the real DOM tree. The nodes in the tree are called vnodes.

After Vue gets the virtual DOM tree, it can make patch diff comparison with the old virtual DOM tree. After the patch phase, vue uses real DOM manipulation methods (insertBefore, appendChild, etc.) to manipulate DOM nodes and update views.

At the same time, the part of the green path, when instantiating Vue, will do responsive processing to the data data, when monitoring the change of data, will call the render function to generate the latest virtual DOM tree, Then patch was compared with the old virtual DOM tree to find the vNode with the lowest modification cost for modification.

When MPvue runs, it will first empty the relevant methods of DOM operation in the patch stage, that is, do nothing. Second, while creating the Vue instance, Page() is surreptitiously called to generate the Page instance of the applet. The patch phase of the runtime then calls the $updateDataToMp() method directly, which takes the data that is maintained hanging on the Page instance and updates it to the view layer via the setData method.

The overall mpVue schematic is as follows:

Some summary and thinking

Therefore, unlike Taro, mpvue is half compile-time, half run-time. This can also be roughly reflected in the comparison of the amount of code.

Mpvue’s WXML template, like Taro’s, is compiled from code; Unlike Taro runtime and React runtime, mpVue essentially runs Vue in applets and implements most of the features of [email protected] (only a few features are not implemented due to the limitations of applets templates, such as filter, slot, v-html). And the whole framework based on Webpack to achieve a more perfect engineering.

The differences in the implementation principles and effects of other small program frameworks also bring us some thoughts:

  • Compile time OR runtime: The main reason Taro chose recompile time was to consider performance. After all, under the same conditions, the more work done at compile time, the less work done at runtime, the better performance. In addition, recompiling also ensures that Taro’s code is readable after compilation. But in the long run, as computer hardware becomes more and more redundant in performance, we think it’s worth sacrificing a little more tolerable performance in exchange for greater flexibility and better adaptation of the overall framework.
  • Statically compiled OR dynamically built templates: Although Taro’s and MPvue’s templates are statically compiled, the community has plenty of examples of dynamically built templates, such as Remax.
  • DSL constraints: Can we implement a small application development framework that is free of DSL constraints?

Adaptation and implementation of the new architecture Taro Next

This time, we think about the nature of the front-end from the perspective of the browser: no matter what framework is used for development, React or Vue, the final code after running will call the BOM/DOM API of the browser, such as: CreateElement, appendChild, removeChild, etc.

Therefore, we created the taro-Runtime package and implemented an efficient, compact VERSION of the DOM/BOM API in this package (the UML diagrams below only reflect the structure and relationships of a few major classes) :

Then, we inject into the logic layer of the applet through the ProvidePlugin of Webpack.

As a result, you have an efficient, streamlined VERSION of the DOM/BOM API for running small programs.

The React to realize

After DOM/BOM injection, Nerv/Preact is theoretically ready to run directly. However, React is a bit special because the React DOM contains a lot of code for browser-compatible classes, which makes the package too large. We don’t need this code, so we need to do some customization and optimization.

React 16+ uses the following architecture:

The react-Core, the core of React, is at the top, with the React-Reconciler in the middle, which is responsible for maintaining the VirtualDOM tree, and internally implements the Diff/Fiber algorithm that determines when and what to update.

The Renderer is responsible for platform-specific rendering, providing hosting components, handling events, and so on. For example, react-DOM is a renderer that renders DOM nodes and handles DOM events.

As a result, we implemented the taro-React package, which connects the React-Reconciler to the Taro-Runtime BOM/DOM API:

The specific implementation is mainly divided into two steps:

  1. implementationreact-reconcilerhostConfigConfiguration, i.ehostConfigMethod to call the corresponding Taro BOM/DOM API.
  2. Implement the render function (similar toReactDOM.render) method, which can be thought of as creatingTaro DOM TreeThe container.

The React code actually works when the applet runs and generates the Taro DOM Tree. How do you update the large Taro DOM Tree to the page?

First of all, we template all the components of the applet one by one, so as to get the corresponding template of the applet component. The following picture is what the view component of the applet looks like after template processing:

Then, we dynamically “recursively” render the entire tree based on the component’s template.

The specific process is to first traverse the child elements of the Taro DOM Tree root node, and then select the corresponding template to render the child elements according to the type of each child element. Then we will traverse the child elements of the current element in each template, so as to recursively traverse the entire node Tree.

Taro Next’s React implementation flowchart is as follows:

Vue implementation

React and Vue are very different in development, but after the BOM/DOM API is implemented, the differences between them are very small.

The biggest difference between Vue and React is the CreateVuePage method at runtime, which does some runtime processing, such as life cycle alignment.

Other parts, such as building with BOM/DOM, modifying the DOM Tree, and rendering principles, are the same as React.

Flutter implementation

When it comes to Flutter, we have to mention The Flutter Web, which is the core drawing layer that implements Flutter on top of the standard browser API, essentially calling the BOM/DOM API at the end. So, in theory, it can be adapted, but we don’t put too much effort into it, and it will be implemented and maintained by the community like a quick app.

For more details

Next, we’ll talk about Taro Next’s implementation in more detail, such as events, updates, and life cycles.

The event

First, the Taro Next event is implemented in the following ways:

  1. In the template process of the applets component, all event methods are specified to call ev functions, such as:bindtap,bindchange,bindsubmitAnd so on.
  2. Implemented at run timeeventHandlerFunction, bound to eh methods, to collect all applet events
  3. throughdocument.getElementById()Method that corresponds to the trigger eventTaroNode
  4. throughcreateEvent()Create compliant specificationsTaroEvent
  5. callTaroNode.dispatchEventRetrigger event

As you can see, Taro Next events essentially implement their own event mechanism based on the Taro DOM. One of the benefits of doing this is that they support bubbling and capturing events regardless of whether the applet supports it.

update

Both React and Vue will eventually call Taro DOM methods such as appendChild and insertChild.

[0].cn.[4].value: {root.cn.[0].cn. “1”} and updated to the view layer with the setData method.

As you can see, the granularity of the update here is DOM level, and only the DOM that is changed will be updated. Compared with the previous data level, the update will be more accurate and have better performance.

The life cycle

The life cycle is probably one of the least changed parts of the system, compared to the drastic upgrades of other parts. As before, the lifecycle implementation is a one-to-one correspondence of the lifecycle methods between App instances/Page instances maintained at runtime.

const config: PageInstance = {
  onLoad (this: MpInstance, options) {
    / /...
  },
  onUnload () {
    / /...
  },
  onShow () {
    safeExecute('onShow')
  },
  onHide () {
    safeExecute('onHide')
  },
  onPullDownRefresh () {
    safeExecute('onPullDownRefresh')}/ /...
}

Copy the code

New Framework Features

Unlike previous architectures, Taro Next is nearly fully operational.

The new architecture basically solves the previous legacy problems:

  • No DSL restrictions: Whether your team is on the React or Vue technology stack, you can use Taro to develop
  • Dynamic template building: Unlike previous templates, which are compiled to create them, Taro Next’s templates are fixed and then dynamically “recursively” render the entire Taro DOM tree based on the component’s template.
  • New features are seamlessly supported: Since Taro Next essentially runs React/Vue on small applications, new features are seamlessly supported.
  • Community contributions are simpler: the error stack will be the same as React/Vue, and the team only needs to maintain the core taro-Runtime.
  • Based on Webpack: Taro Next implements multi-terminal engineering based on Webpack and provides plug-in functions.

Performance optimization

As mentioned earlier, all things being equal, more work done at compile time means less work done at runtime and better performance. Taro Next put a lot of effort into performance optimization after its new architecture became nearly fully operational.

And before that. Take a look at Taro Next’s process and compare it to the native applet process.

It can be found that compared to the native applet, Taro Next has more performance risks caused by the red part, such as the increase in package Size caused by the introduction of React/Vue, runtime wear, Taro DOM Tree construction and update, DOM data initialization and update.

We can only do the green part, namely: Taro DOM Tree build and update, DOM data initialization and update.

Size

First of all, let’s look at the package Size. The following table is an example of TodoMVC’s package Size comparison in the case of native, Taro Old, Taro Next, etc. It can be seen that after introducing React/Vue, the package Size will increase by about 30K in the case of Gzip.

However, we emphasize that unlike previous templates generated by compilation, Taro Next’s templates are fixed and then dynamically “recursively” render the entire Taro DOM tree based on the component’s template. That said, Taro Next’s WXML size is limited.

As the project grows and pages grow, the native project WXML will grow in size, while Taro Next will not. That said, Taro Next’s packages may get smaller when the number of pages passes a tipping point. Therefore, the package Size problem is not a concern.

DOM Tree

During the Taro DOM Tree construction and update phase, we implemented a set of DOM/BOM apis that were only efficient and streamlined, and only necessary.

Github has a repository jsDOM, which basically implements a Web standard DOM/BOM on Node.js. The repository is about 2.1 MB of code before compression, whereas Taro Next’s core DOM/BOM API is less than 1000 lines.

Therefore, we maximized performance during the Taro DOM Tree build and update phases.

Update Date

In the Data update phase, as mentioned above, Taro Next’s update is DOM level, which is more efficient than Data level update, because Data granularity update is actually redundant, and not all Data changes will eventually cause DOM update.

Secondly, Taro compresses the Path of the Taro DOM Tree during the update, which also greatly improves performance.

The end result: In some business scenarios write, add, select data, and Taro Next perform better than native.

taro-benchmark

Of course, there will always be defects in the experimental data, and the final performance depends on the verification of various complex business scenarios. If you’re interested in Taro Next’s performance, take a run at the Taro-Benchmark package and compare the results.

We have also been continuously improving The performance of Taro Next in all aspects. Please pay attention to Taro Next’s latest Commit.

Summary and Outlook

Taro’s Future Plans

Taro Next will be released shortly in version 3.0, supporting cross-application development using React/Vue, and then expanding to other applications and improving the ecosystem in subsequent iterations.

The Taro team will focus on supporting React/Vue. Flutter and Angular will be left to the community to adapt and maintain, just like Kuaiapp. Qiyu8 and Issacpeng from Huawei are helping us adapt.

At the same time, we have also built the Taro Mobile One-stop R&D platform, which integrates the previously accumulated multi-stage development workflow and engineering solutions, and has built-in data monitoring, component market and visual construction. Currently, it is in the internal testing stage.

A little thought

  • Business incubation technology, technical service business: these are the most important and deepest feelings of the Taro project from inception to iteration.
  • Top-down OR bottom-up: From a developer’s perspective, React/Vue code is written differently; From a bottom-up perspective, however, the differences are not that great, as they all call the BOM/DOM API. If we go a little further down the rendering layer, won’t the difference between platforms be that great? For example, Flutter.
  • Learn Once Write AnyWhere & Write Once Run AnyWhereMany developers prefer the React approachLearn Once Write AnyWhereAnd the slogan of our Taro isWrite Once Run AnyWhere“, which often leads to us being yelled at. Here are some of my own thoughts:Learn Once Write AnyWhereIn essence, it is more developer-friendly. For example, developers only need to learn the React stack to develop Web/ mobile applications, but it is less project-friendly. Each project has to maintain a copy of the code. whileWrite Once Run AnyWhereIt is not so friendly to developers (the more adaptors, the higher the cost of adaptation, the higher the requirements for developers), but according to our practice, it will be more friendly to the project, “one set of code, multiple adaptors”. Of course, the granularity of adaptation here is not necessarily project-level. In fact, in our specific practice, there are quite a few:Business level or even page level.

Write in the last

As the saying goes, “a single thread does not make a thread, and a single tree does not make a forest”, the development of Taro is no longer a project of a single team, but a common project of the entire Taro development community.

Finally, I would like to take this opportunity to thank all the community members who have helped Taro’s growth, especially the contributors to Taro. Thank you very much!

Thanks also to Garfield550 (Little Sister), Liang Yin and ShaoQian Liu, who have been invited to be TaroUI’s core maintainers, who will support TaroUI’s subsequent iterations and maintenance.

Of course, there are zacksleo, Jay Fong, loveonelong, lolipop99, bozai cake, original sin, lentoo, childe of white-collar summer, YuanQuan, tourze, lingxiaoZhu and so on who are willing to help and contribute to the community.

In addition, I would like to thank our r&d teams, including Tencent Cloud, Digital Guangdong, Tencent CDC, Netease Yanxuan, Huawei Open Source team, Zhaopin Consumer Finance, and many others, who have quietly provided valuable suggestions for the development of Taro.

Long wind and waves will sometimes, straight sail to the sea.

Welcome to the bump Lab blog: AOtu.io

Or pay attention to the bump Laboratory public account (AOTULabs), push the article from time to time: