Blanche
Engineer
Engineer
  • UID619
  • Fans3
  • Follows2
  • Posts59
Reads:2277Replies:0

Insight into the Weex JS Framework Compilation Procedure

Created#
More Posted time:Sep 22, 2016 9:43 AM
JS Framework compilation procedure is to  convert a JS bundle into a virtual DOM and send the virtual DOM to a native module for rendering.
This procedure involves three types of data, namely JS bundle, virtual DOM, and view model (Vm).
JS bundle data is converted from .we files and is executed as code.
Virtual DOM data is JSON data that describes page structures and is used to send messages to native modules.
Vm data is part of an MVVM structure and implements operations such as template compilation and data binding.
The simplified Vm constructor is as follows:
// html5/default/vm/index.js
function Vm() {
  // ...

  //Initialize the lifecycle.
  initEvents(this, externalEvents)
  this.$emit('hook:init')
  // ...

  //Monitor data in data.
  initState(this)
  this.$emit('hook:created')
  // ...

  //Start template compilation.
  build(this)
}


The Vm constructor calls the build function to start template compilation at last, which is tail recursion that facilitates JS engine optimization.
Compiling Process
After the build function is called to start compilation, it is the compile method (the code of the method is in html5/default/vm/compiler.js) that implements the compilation function. The build method does not implement recursion, but selects parameters based on configuration items and calls the compile method.
compile Method
The compile method allows the following parameters:
1. vm:  It specifies the Vm object to be compiled.
2. target:  It specifies the block to be compiled and represents a structure created by the transformer based on tags in a template.
3. dest:  It specifies the virtual DOM of the parent block of the current block.
4. meta:  It specifies metadata, which can be used internally to transfer data.
At last, the compile function calls compileNativeComponent to generate native components. Calling methods other than compileNativeComponent and createBlock triggers recursion.
Distribution and Compilation Logic


The compile function does not provide an internal rendering logic. It distributes different types of blocks to different functions for compilation. In other words, it is in charge of logical distribution and recursion. Playing the same role as the compile function, the compileChildren function calls the compile function for each sub-block (indicated by a sub-tag in a template).  The compileFragment method can compile arrays. The compile method is called for compiling each piece of data in an array but all the data pieces share the same block.
Create a Block
As mentioned above, the createBlock method is used to create blocks and creating block can be regarded as a recursion termination condition. A block is an encapsulated virtual DOM node, which has the following structure:
{
  blockId,
  start, // Start position of the node. It is located at the first position as a comment.
  element, //Actual content of the node.
  end, // End position of the node. It is located at the last position as a comment.
}


This mechanism aims to unify element and fragment operations and to enable quick node locating in case of an UI update.
Compilation Instructions
Weex template tags allow instructions, including the most common instructions if and repeat. For details about how to use the instructions, see the official documentation.
if instruction
The if instruction can hide or unhide nodes. It is used as follows:
<text if=pw_visible>Show something here.</text>

The template is converted by the transformer into the following structure:
{
  "type": "text",
  "shown": function () { return this.visible }
}


When the if instruction is compiled, a block is created first, and then a Watcher is created to bind data. When this.visible changes, view update is triggered. If the shown function returns the true value, the compile method is called to recompile the node. Otherwise, the removeTarget method is called to remove the node from its parent node.
repeat instruction
The repeat instruction can render all the data one by one in an array based on a template. Assume that there is a .we file with the following content:
<script>
  module.exports = {
    data: {
      images: [
        { source: 'somewhere/a.png' },
        { source: 'somewhere/b.png' },
        { source: 'somewhere/c.png' }
      ]
    }
  }
</script>
<template>
  <list>
    <cell repeat=pw_images>
      <image src="pw_source"></image>
    </cell>
  </list>
</template>


The file compilation result is as follows:
{
  "type": "list",
  "children": [
    {
      "type": "cell",
      "append": "tree",
      "repeat": function () { return this.images },
      "children": [
        {
          "type": "image",
          "attr": {
            "src": function () { return this.source }
          }
        }
      ]
    }
  ]
}


In case of the repeat instruction, the compileRepeat method is called to prepare data, and then the bindRepeat method is called to compile the data. During compilation, the node where the repeat instruction resides is regarded as a template, relevant data is expanded in a loop, and then compileItem is called to render other nodes one by one based on the template. Data is bound during compilation as well. When data in an array changes, a list update is triggered.
append attribute
The preceding code generated by the repeat instruction indicates that the cell node contains an append attribute, which has been described clearly in the official documentation. It is a low-level attribute used to control the rendering order. It is used in internal instructions and generally not used by developers. The following briefly introduces the attribute:
append="tree": Child nodes of a node are compiled before the parent node. In this mode, compilation is fast but a white screen may be displayed for a long time.
append="node": A node is compiled before its child nodes. In this mode, compilation is relatively slow but user experience is better.
• The default mode is node, that is, a container is created before the content of the container.
However, the repeat adopts tree as the default mode. Because content is variable, all child nodes are compiled before their parent node so as to prevent frequent data insertion operations. This compilation mode better matches list features.
Compilation Components
In addition to internal tags, Weex supports user-defined components (tags). This is its most basic yet useful feature.
Compile User-Defined Components
Each component (.we file) corresponds to one Vm instance. In case of a user-defined tag during compilation, a new Vm instance is created, the styles added to the parent and child components are combined, and a lifecycle hook is added.
1. init:  It sets a child component ID.
2. created:  bindSubVm combines the attributes defined in parent and child components.
3. ready:  It calls compileChildren to compile nodes in child components.
It should be noted that, when a new Vm instance is created, all the steps described in this article, including initializing data, binding data, and compiling various nodes in an recursive manner. The entire compilation procedure involves a large number of recursive operations and a deep function invocation stack, which consumes much time and memory.
Generate Native Components during Compilation
At the end of compile function execution, compileNativeComponent is called to generate native components. That is, native component generation is performed during recursive compilation and native UIs can be created before virtual DOM tree construction is complete. Therefore, stream rendering can be applied to pages to continuously render virtual DOMs.
The compile function is used in various procedures to generate virtual DOMs,while the compileNativeComponent function is used to send rendering instructions through callNative to notify native components to create native UIs based on the virtual DOMs. If an error occurs when native components create UIs, the value -1 is returned and recorded in app.lastSignal. In that case, the JS Framework stops compilation.
Concluding remarks
This article elaborates on the component compilation procedure and provides many technical details. These details many be changed in later versions. Understanding JS Framework implementation details enables developers to use the JS Framework correctly. I hope that this article will be helpful to you. You are welcome to discuss any doubts or different ideas with me.
Guest