In ’18, the company was promoting weeX technology stack for a period of time, and we happened to have a project here, so we had the opportunity to experience weex development. Today, I want to sort out and summarize some of the weeX content output, and review the working principle of WEEX. BTW, go to work during the day, write in the evening, keep a daily really good difficult 🤦
Weex introduction
Weex is a cross-platform, dynamically updated solution for native APP development using Javascript. Weex’s ultimate goal is to deliver a consistent development experience and code reuse on iOS, Android and H5.
Of course, so far, Weex from her ultimate goal is still a certain distance, it is of course bless her early goal, but she solved the fast version, improve performance, unified three terminal three difficulties.
Weex implements a unified JSEngine and DOM API, so it is not completely restricted to the JS framework used by Weex. In theory, Weex allows Vue, React, and Angular to be used on Weex. We use Vue on Weex.
Working principles of WEEX
1. Generate JS Bundle from weeX source code
Weex will first write Weex source code, that is. We files, template, style and script tags organized content, using Transformer (weex-Toolkit tools) into JS Bundle.
The process is divided into three steps:
- Transform the contents of the template into a JSON-like tree data structure, and transform the data binding into a function prototype that returns data
- Convert style to a JSON-like tree data structure
- Combine the contents of the above two sections with the contents of script into a JavaScript AMD module (AMD: Asynchronous Module specification)
In addition, the converter does a few additional things: merge bundles, add bootstrap functions, configure external data, and so on. Most Weex tools output JS bundles in Webpack format, so the actual output of JS bundles may be different.
2. Deploy the JS Bundle on the server
Deploy THE JS Bundle on the server. When receiving the JS Bundle request from the terminal (Web terminal, iOS terminal or Android terminal), send the JS Bundle to the terminal. After the client updates the package from the server, it can execute the new version on the next startup without re-downloading the app, because the WeexSDK that runs on already exists on the client, unless the new package relies on the new SDK.
3. Initialize the WEEX SDK
The JS Framework, Vue and Rax code are built into the Weex SDK and initialized with the Weex SDK. The INITIALIZATION of the Weex SDK is usually completed when the App is started and is performed only once.
The Weex SDK initialization includes the following operations:
- Initialize the JS engine, prepare the JS execution environment, register some variables and interfaces to it, such as WXEnvironment, callNative.
- Executes JS Framework code.
- Register native components and native modules.
For step 2, the process of executing JS Framework code can be divided into the following steps:
- Register with upper-layer DSL frameworks such as Vue and Rax. This procedure simply tells the JS Framework which DSLS are available and ADAPTS to the interfaces they provide, such as init and createInstance, but does not execute the logic in the front-end Framework.
- The environment variable is initialized, and the prototype chain of the native object is frozen. The built-in JS Service, such as BroadcastChannel, is also registered.
- If the INIT interface is implemented in the DSL framework, it will be called at this point.
- Inject the global environment with client-callable interfaces, such as callJS, createInstance, and registerComponents, which trigger the corresponding interface in the DSL.
Let’s take a closer look at the JS Framework.
JS Framework functionality
Weex is a Framework that supports both multiple front-end frameworks and cross-platform rendering. JS Framework is between front-end frameworks and native rendering engines, which is the key to cross-framework cross-platform. Whether you’re using Vue or Rax, and whether you’re rendering on Android or iOS, the JS Framework code will run (if you’re running in a browser or WebView, it doesn’t depend on the JS Framework).
1. Adapt to the front-end framework
It should come as no surprise that front-end frameworks are executed differently in Weex than in browsers. How to make a front-end Framework run on Weex platform is a key function of JS Framework.
Taking vue.js as an example, to run a page in a browser, there are several steps: First, prepare a page container, either a browser or a WebView, which provides a standard Web API. It then passes an address to the page container, which eventually retrieves an HTML file, parses the HTML file, loads and executes the script. To render correctly, you should first load the code that implements the vue.js framework, add the Vue variable to the browser environment, then create the DOM element of the mount point, and finally execute the page code, starting with the entry component, layer by layer and mount to the configured mount point.
In Weex, the implementation process is similar, but Weex pages correspond to a JS file, not AN HTML file, and there is no need to import vue.js framework code, and there is no need to set mount points. The process looks like this: first initialize Weex container, which initializes the JS Framework, including vue.js code. The Weex container is then passed the page address to obtain a JS file. The client calls createInstance to create the page and provides the interface for refreshing and destroying the page. The rendering behavior is basically the same as that of the browser, but not the same as that of the browser. The front-end framework can run in Weex only when it ADAPTS to the behavior of the client to open and destroy pages (push and pop).
The JS Framework provides an interface as shown in the figure above to connect the front-end Framework. The four interfaces on the left of the figure are related to page functions, which are respectively used to obtain page nodes, monitor client tasks, register components and register modules. At present, these functions have been transferred to the JS Framework and are optional in the front-end Framework, which need to be implemented only when there is special processing logic. The four interfaces on the right are related to the life cycle of the page. They are called when the page is initialized, created, refreshed, and destroyed, respectively. Only createInstance is mandatory, and the others are optional. CreateInstance has been changed to createInstanceContext.
2. Build the render instruction tree
They all use a consistent DOM API to convert the Virtual DOM into a real HTMLElement on the browser. The logic in Weex is similar, except that in the last step of generating real elements, we do not use the native DOM API. Instead, we use a set of Weex DOM API defined in the JS Framework to convert the operation into rendering instructions and send them to the client.
The FUNCTIONS of the Weex DOM API provided by the JS Framework are basically the same as those provided by the browser. These interfaces are adapted in Vue and Rax. Cross-platform rendering can be achieved by invoking different interfaces for Weex and browser platforms.
3. JS – Native communication
In the process of page development, in addition to node rendering, there are native module call, event binding, callback and other functions, these functions are dependent on the communication between JS and native to achieve.
First, the JS code for the page runs on the JS thread, whereas the drawing of native components and capturing of events all happen on the UI thread. The communication between these two threads is the callNative and callJS underlying interfaces (now extended to many), both of which are asynchronous by default and are wrapped around these two methods within the JS Framework and native renderer.
CallNative is an interface injected into the JS execution environment by the client and provided to the JS Framework for invocation. Nodes of the interface (rendering instruction tree mentioned above), methods and parameters of module invocation are sent to the client through this interface. In order to reduce the overhead of calling the interface, more direct communication interfaces have been opened, some of which also support synchronous calls (support return values), which are in principle the same as callNative.
CallJS is implemented by the JS Framework and is injected into the execution environment to make client calls. Event dispatches and module callbacks are notified to the JS Framework through this interface and then passed to the upper-level front-end Framework.
4. JS Service
Weex is a multi-page framework. The JS bundle of each page runs in an independent environment. Different Weex pages correspond to different “TAB pages” in the browser.
The function of JS Service is realized in THE JS Framework, which is mainly used to solve the problem of cross-page reuse and state sharing. For example, BroadcastChannel is realized based on JS Service, which can communicate between multiple Weex pages
5. Prepare the environment interface
Due to the Weex operating environment and browser environment is very different, in the JS Framework also on some environment variables to do the packaging, mainly in order to solve the compatibility problem in the native environment, the underlying use of rendering engine provided interface. The major changes are:
- Console: The nativeLog interface is provided natively, encapsulated as console. XXX, familiar to the front end, and you can control the output level of the log.
- Timer: In the native environment, the timer interface is incomplete and the name and parameters are inconsistent. This layer can now be removed with the native C/C++ implementation of timer.
- Freeze: Freezes the prototype chain of global variables in the current environment (e.g. Array.prototype).
There are also ployfill: Promise, Arary. From, Object.assign, Object.setPrototypeOf, etc.
4. Execute the JS Bundle
After the Weex SDK is initialized, you can start rendering the page. In general, a Weex page corresponds to a JS bundle file. The rendering process of the page is also the process of loading and executing the JS bundle.
The first step is to load the execution JS bundle by calling the interface provided in the native rendering engine, renderByUrl on Android and renderWithURL on iOS. After getting the code from the JS bundle, we continue with the SDK’s native createInstance method, give the current page a unique ID, and pass the code and some configuration items to the createInstance method provided by the JS Framework.
After the JS Framework receives the page code, it determines the type of DSL used (Vue or Rax), finds the appropriate Framework, and executes createInstanceContext to create the environment variable needed to create the page.
In the old scheme, the JS Framework would call the runInContex Function to execute the JS code in a specific environment, internally based on new Function. In the new Sandbox scenario, the JS bundle code is no longer sent to the JS Framework, nor is new Function used. Instead, the JS code is executed directly by the client.
Example Create a WEEX instance
When the WEEX SDK gets the JS Bundle, it does not render the JS Bundle immediately, but creates an instance of WEEX first.
Each JS bundle has an instance, and each instance has an instance ID.
Since all JS bundles are executed in the same JS execution engine, the instance ID is required to know which WEEX instance to pass to when the JS execution engine sends a rendering instruction via WXBridge.
After the instance is created, the next step is to actually deliver the JS bundle to the JS execution engine.
Weex rendering process
Weex page rendering process is similar to the browser rendering process, the whole can be divided into [create front-end components] -> [build Virtual DOM] -> [generate “real” DOM] -> [send render instructions] -> [draw native UI] these five steps. The first two steps occur in the front-end Framework, the third and fourth steps are handled in the JS Framework, and the last step is implemented by the native rendering engine. The general process of page rendering is as follows:
Creating front-end components
Before rendering, the Vue framework will create the corresponding Component instance according to the template written at development time. It can be called Vue Component, which contains the Component’s internal data, life cycle, render function and so on. If you pass multiple pieces of data to the same template, multiple Component instances will be generated, and multiple Vue Component instances will be created for rendering, each with a different internal state.
Building a Virtual DOM
The rendering process of Vue Component can be simply understood as the process of the Component instance executing the render function to generate the VNode tree, which is also the generation process of constructing the Virtual DOM.
Generate the “real” DOM
The above two processes are identical in Weex and in browsers. Weex uses different rendering methods, starting with the actual DOM generation step. The JS Framework provides the Weex DOM API, which is similar to the DOM interface. In Vue, these interfaces are used to render VNode into an Element object for Weex, which is similar to the DOM but not the “real” DOM. These interfaces have been adapted in Vue and Rax, so that cross-platform rendering can be achieved by calling different interfaces for Weex and browser platforms.
Send render instructions
In the JS Framework and the client rendering engine agreed a series of instructions interface, corresponding to an element DOM operation, such as addElement removeElement updateAttrs updateStyle. The JS Framework uses these interfaces to send its own internally built Tree of Element nodes to the client in the form of render instructions.
Drawing native UI
The client receives the rendering instructions sent by the JS Framework, creates the corresponding native components, and finally invokes the interface provided by the system to draw the native UI.
The same JSON data can be rendered into different versions of the UI in different rendering engines on different platforms, which is why Weex can be dynamic.
The response process of an event
In both Weex and browser, events are captured by the native UI, whereas event handlers are written in the front end, so there is a passing process.
As shown in the figure above, if an event is bound to a tag in vue.js, the addEventListener is executed internally to bind the event to the node. This interface is called in Weex using the addEvent method provided by the JS Framework to add the event to the element. Passed the event type and handler. The JS Framework does not immediately send an instruction to the client to add events. Instead, it records the event types and handlers, and sends them to the client together after the node is built. The sent node contains only the event type, not the event handler. The client listens for the specified event on the native UI if it finds an event on the node while rendering it.
When the native UI listens for user-triggered events, it issues the fireEvent command to send the node’s ref, event type, and event object to the JS Framework. The JS Framework finds the corresponding event handler based on the ref and event type, and executes the event handler with the event object as a parameter. The current Weex event model is relatively simple. It does not distinguish between the capture phase and the bubbling phase. Instead, it only sends the event to the node that triggered the event and does not bubble up, similar to the level 0 event in the DOM model.
In the above process, the event is bound only once, but it may trigger many times. For example, the TouchMove event may be sent dozens of times per second during finger movement. Each event corresponds to the process of fireEvent -> invokeHandler, which is easy to damage the performance. The same goes for browsers. In this case, an expression binding can be used to turn event handlers into expressions that are sent to the client at the time of the binding so that the client can parse and execute the bound expression after listening for the native event without sending the event back to the front end.
The above is the basic working principle of WEEX. Here is the application of WEEX.
Three weeX working modes
1. Full-page mode
It currently supports single page or whole App weeX development (not perfect, requiring Router and lifecycle management), which is similar to React Native.
2. Native Component mode
Use WEEx as an IOS/Android component, analogous to ImageView. But local dynamic demand will lead to frequent version.
3. H5 Component mode
Weex is used in H5, similar to WMC. Tweaking the existing H5 page and introducing Native to solve the problems of long list memory explosion, poor scrolling, poor animation/gesture experience, etc.
Weex compared with H5 Hybird
In the past, implementing a requirement required three programmers (iOS, Android, front-end) to write three pieces of code, which brought great development costs, so the industry has been exploring cross-platform technology solutions. From the previous Hybrid, to Weex and React Native, the fundamental purpose of these solutions is to have a set of code and run multi-terminal.
The essence of H5 Hybrid solution is to make use of the built-in browser function (namely Webview) of the client APP to realize the communication between the client Native and front-end JS through JSBridge, and then develop H5 pages embedded in the APP. This solution improves the development efficiency and also meets cross-end requirements. One problem, however, is that the performance of the front-end H5 is far from that of the client.
Weex adopts the H5 page development mode, and the running experience of terminals is not transmitted to Native apps. Weex uses Native’s capabilities to do some of the work that browsers do.
The advantage of weex
Reference: This section describes the JS Framework of Weex in detail