Community Blog Create a Native App Experience for Web Apps at a Low Cost

Create a Native App Experience for Web Apps at a Low Cost

This article focuses on a cost-effective way of progressively creating a native app experience for web apps using the same screen rendering scenario.

By Dangxuan

Web App Experience

While progressive web apps (PWAs) support capabilities such as desktop and offline access, the user experience seems very different from native apps.

Let us take a look at screenshots of Alibaba's mobile web app and iOS app:


As you can see, access is split when going back and forth between pages in the mobile web app. Each page must be loaded, which causes a delay. This overloads memory, which results in a failure to locate the previous location on the list. The severity depends on the browser you use and the implementation of lists. Some workarounds can be used to avoid the issue. This article exemplifies just one of the possible scenarios where this issue may occur.

Here, the potential negative impact on the user experience is very obvious. Users will feel that they are visiting a website instead of an app. Executing complex operations may also be tedious as the breadcrumb trail can be easily disrupted.

The root of this difference in experience lies in the contrast between the browser/server (B/S) and client/server (C/S) architecture. Although service workers of browsers provide solutions, such as the app shell architecture, to enhance the user experience at a comparatively lower cost, it is still difficult to address the access splitting problem. The same code repeatedly runs on different pages, which leads to the loss of cached states for every new visit.

Rendering Performance

Talking about the experience seems a bit subjective. It might be more realistic to evaluate performance by comparison. By far, the most intuitive experience gap caused by access splitting comes from rendering performance.

On the web site, client-side rendering (CSR) typically goes through the following process:


There are many areas that fail to meet user expectations, which may be attributed to the following issues:

  • HTML and JavaScript files will not start loading until the user taps. (This can be solved by preloading, implemented through a service worker.)
  • Frameworks and JavaScript files repeatedly run on different pages.
  • The application programming interface (API) loading time may be delayed or prolonged. This part runs in parallel with other parts. In this way, API loading can be performed earlier. In practice, the faster the loading time, the better.

The ideal rendering process is illustrated in the figure below.


The same is true for a native app. When a user taps, the app will start loading the API and run the logic of the next page. A well-optimized single-page application (SPA), for example, with preloading configured, has a similar effect. The objects of the next page are loaded in advance and directly run upon tapping.

However, it is not feasible to maintain a large site, for example, m.alibaba.com, like a SPA. Such an approach is not suitable for multi-person coordination. Stability will also be affected if the entire website needs to be published each time a page is modified.

Therefore, how do we achieve a native-like experience at a low cost?

Same Screen Rendering

Based on the analysis mentioned above, we decided to develop a solution without any modification to pages. With this new solution, the parallel loading of data will start immediately and the next page will be dynamically loaded once a user taps. Some objects of the previous page are selectively retained (such as loading data, JSONP, and framework layer objects) to reduce interference.

LightHub, the same screen rendering solution, is presented below. Here, the page does not need to be unloaded during the rendering process, and all rendering behaviors occur in context.


A checklist of essentials would include:

  • Access to the sandbox of the existing page. This restores the page to its original state while allowing shared parts to be retained.
  • Transition animation.
  • API parallel loading.
  • HTML rendering according to browser behaviors.
  • Event triggering according to browser behaviors.



A low-cost sandbox mechanism is needed to restore pages to the original state while allowing some objects to be retained. More importantly, this mechanism must be able to be deployed directly on existing pages at a low cost. The challenges posed to us are also encountered by micro-frontends. Inspired by qiankun's sandbox mechanism, we only need to insert inline JavaScript code to the page code, including the following information:

  • Global variables for the window containing a DOM document
  • eventListener of window/document
  • Timer: setInterval/setTimeout/requestAnimationFrame/requestIdleCallback
  • MutationObserver

If needed, we can clear the document object model (DOM) document of the page and restore the changed global variables (here we use a shallow copy like qiankun), eventListener, timer, and MutationObserver to restore the page to its original state.

At the same time, the recorded state can be archived in an object. In this way, the page can be restored to its original state when the user returns to the previous page.

To implement this, make sure that objects (such as the public framework and JSONP request label) are selectively retained when the DOM document is cleared.

Transition Animation

Transition animation is relatively simple. When a page does not need to be unloaded and reloaded, we configure the app to immediately display an animation upon user tapping. Currently, a simple slide-in animation from the right is supported.

It should be noted that the logic of the next page often runs during the animation process. To ensure that the animation is not blocked by JavaScript execution, the graphics processing unit (GPU) needs to be used to create the animation. This is especially critical for low-end machines.

API Parallel Loading

With the sandbox mechanism in place, API parallel loading is not very difficult. It should be noted that we need to retain the state generated during API parallel loading, for example, by using the setTimeout method. We need to implement runInSharedContext to ensure that the timer will not be unloaded when the page is switched.

runInSharedContext(() => {
  // The setTimeout method cannot be recorded or unloaded.
    setTimeout(() => window.sharedfetchDataPromise = fetch(res));

On the next page, use window.sharedfetchDataPromise || fetch(url) to reuse parallel API requests.

The toolbox Redfox is used to ease the process for developers. Requests having the same configuration that are run multiple times in the same page environment will be automatically reused without manual intervention by developers.

HTML Rendering According to Browser Behaviors

This step is quite complicated. After obtaining the HTML file of the next page, we cannot simply write document.innerHTML = nextHTML. This will lead to inconsistency with normal browser behaviors and is prone to issues. For example, style loading may lead to splash screens, or the script execution sequence may not go as expected.


We need to implement renderHTML, specifically, parse the captured HTML file and simulate the browser's behavior for rendering.

  • Play the animation.
  • Hide the body by style.
  • Asynchronously append CSS to the page and then wait for CSS to finish loading. After playing the animation, unhide the body.
  • Append the JavaScript file to the page according to the type and sequence.
  • Inline and normal scripts will block the appending of DOM documents and JavaScript files.
  • Defer is dropped into the defer queue.
  • Asynchronous execution of scripts will not block subsequent appending of DOM documents and JavaScript files.
  • Run the defer queue in sequence.

The behavior here is relatively complicated and should be tested in various scenarios. Corresponding unit tests are also needed to ensure the accuracy of the logic.


Event Triggering According to Browser Behaviors

Similar to HTML rendering described earlier, the corresponding event needs to be triggered according to the behavior of the browser during the rendering process.

For example, beforeunload, pagehide, unload are triggered in sequence when the previous page is unloaded. When the next page is loaded, readyState is firstly reset and then domInteractive defer and DOMContentLoaded are triggered in sequence.

Unit testing is also necessary at this stage.


Timeline Analysis

Based on the final timeline of Chrome, the execution logic is basically in line with our expectations. After tapping, the API starts to load and the rendering logic of the next page starts to run.

There is no need to repeat the code in the framework layer.


Memory Pressure

When we do not unload pages, a likely cause for concern is the memory leak problem. According to the sandbox mechanism, as long as we ensure that the DOM document, global variables, timers, and time listeners are cleaned up properly, page closures related to them will not be retained in the memory.

From our local test of frequently tapping to switch between pages, the memory will return to the initial state each time the page is switched. In theory, this architecture should not cause memory leaks.


However, there is still the risk of memory leaks for some nonstandard writing methods. This is because we allow part of the common area (service layer) to be retained during the switching between pages, and the sandbox itself is a conventional sandbox rather than a security sandbox (for example, writing something to the Element.prototype.xxx property cannot be intercepted).

This may require long-term monitoring of the memory pressure, similar to the native side.

Stage Management

Since we implemented the entire HTML rendering process, we were able to monitor the time points across the various stages of rendering. Here is an example: The API took 124 ms to fetch data after the JavaScript request is sent, but the whole data fetch process takes 350 ms, with advanced parallel fetch included. The execution start time and execution time period of each script can also be marked this way.


This may also provide guidance for page optimization, such as whether the JavaScript execution time is too late, or whether the execution time period of a certain JavaScript section is too long.


The following figure shows the final comparison effects of the same screen rendering (left) and normal jump (right). Based on the online data, the performance is improved, with the page switching time decreased from 2.8s to 1.8s.


In addition to the pages rendered asynchronously, we enabled very low-cost access to some pages that originally adopted server-side rendering. While there is no need to modify the pages, the benefits are relatively limited.


Improvements in both the jump experience and return experience have led to a steady increase of 3% in the number of exposure screens of the Just For U module.


  • Client-side rendering similar to the SPA brings the user experience of web apps closer to that of a native app.
  • Same screen rendering is a way for websites to provide a native experience at a lower cost.
  • A more immersive experience encourages users to browse more.


The solution described here still has limitations. For example, developers need to prevent the memory leak problem. At the same time, due to the limitation of the History API, pages must be in the same domain. Otherwise, the jump URL cannot fulfill expected actions.

Future Work

Those paying close attention to Chrome's latest events will know that Chrome has announced a new proposal: Portals API, which aims to resolve the issue of access splitting.

It does so by providing a sandbox similar to iframe that will enable jump transitions between pages at a lower cost. In the future, after Portals API is widely used, for example, after Chrome releases the API and Safari follows up, we can abandon the sandbox mechanism being implemented in JavaScript from the latest version of the browser and use Portals API to achieve the same screen rendering with added security. Portals API can also help overcome the problem of cross-domain jumping.

The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

0 0 0
Share on

Alibaba Clouder

2,630 posts | 682 followers

You may also like