Explore MiniProgram Container

Explore MiniProgram Container

2022, Oct 20    

This article elaborates the overall architecture of a miniprogram container which is commonly seen in super apps like Alipay/WeChat. The overall architecture of a miniprogram container can be summarised into the following component diagram:

The container engine consists all the core container models, which maps to concrete concepts when the user interact with a miniprogram instance, such as service/session/scene/view/page. The engine also specifies the JSBridge protocols which further allows the inter-communication between WebView, JS Engine, and Native JSAPIs. Both of the Plugins and JSApis can be customised by the application which integrates the miniprogram container, and the default implementations are carried inside the container engine, however they are designed to serve for different purposes:

  • the Plugins monitors events dispatched from kenerl models and reacts to those events: network requests/page refresh, which describes the container internal behaviours.
  • the JSAPIs provide native capabilities to be used by miniprograms. Both of the Plugins and JSApis can be customised by the application which integrates the miniprogram container.

Besides of the core container engine, there are also a dozen of dependencie libraries provided to support the container engine as well as those embed native JSAPIs. For example:

  • the JS Runtime library provides the JS execution context for miniprogram’s javascripts(based on various js engine provided by different platforms, JSCore for iOS, V8 for Android).
  • the Device Core library provides the senor related capabilities like gyroscope, compass, accellormeters, etc. (Which are used by those motion related JSAPIs).
  • the File Manager library provides the file sandbox structure management of miniprograms, as well as common file logics of unzip/sign check on miniprogram app bundles.
  • the Utils library provides basic utility extension functions for Images/Strings/Fonts etc.

1. Critical Designs

1.1 Core Models

Kernel: Kernels acts as the base model of all the models, which consists of plugin/jsapi manager for that layer. It also contains list of child nodes and keeps a reference back to the parent node.

  • JSAPIs and Plugins are declared with their scope information, only the relevant jsapi and plugins are registered to the kernel instance.
  • Default scope of plugin/jsapi falls into Service level, which prevents duplicated creation of JSAPI instances.
  • Service level JSAPIs (default scope) is stateless. If a JSAPI is stateful, it should come under session scope or below~.
  • For dependencie frameworks such as websockets, http, worker and etc. Their internal JSAPIs (I think there should be plugins as well~ 😋) are grouped into the concept of extension, the extension is registered in to the extension registry object under the corresponding kernel instance.

Service: Service is an global singleton which serves as the unified entry for starting/terminating a miniprogram as well as internal logics which controls the refresh/examine of miniprograms configurations.

Session: Everytime the miniprogram container launches an miniprogram with a unique id, a corresponding session object is created. Which maintains the meta information like the type of webview/viewcontroller the session is going to use.

Scene: This model maps to a view controller, which contains the set of javascript objects to be loaded into the JS runtime everytime a webview finish loading. (Assuming the js file for each miniprogram is fixed).

View: As the name suggests, this model wraps an instance of the containers internal webview object, passing the delegate to the current page for jsbridge and network proxy invocation.

Page: Page model describes a single loading of an webpage, which internally creates JSBridge instance everytime a new page is loaded. The JSBridge intercepts all the webview lifecycles and establishes the communication channels between JSCore, render layer and the native JSAPIs.

Event: Internal events are emmited by the different layers of kernel models and consumed by internal/external plugins. The event propagation and plugin handling process is summarised into the diagram below:

1.2 Security

In order to provide enhanced secure container environment as well as protect user privacy, multiple strategies are adopted:

  • Storage Sandboxing: In the JSBridge layer, each miniprogram is allocated with a isolated file directory. From the point of frontend javascript, a virtual root directory is provided to allow file JSAPIs to create/read/update/delete directories and files towards the directory. For each miniprogram instance, there is no way to get the file system information of the rest from the execution context. For legacy web application using the container, the storage is further isolated from those miniprogram instance, which means for the same key used by miniprogram and web application, there will be different actual storage locations.
  • Network Request Inspection: In miniprogram container, all the network requests are delegated to the native, usually via a http session instance. From the miniprogram operation web console, the operator can specify the whitelist of domain names which the miniprogram is allowed to send request to. The console also enforces rules like mini versions of http protocol to be adopted, whether SSL is a must, and etc.
  • Limited JSAPIs Access: Before using those user privacy sensitive JSAPIs, a user authorisation form needs to be acknowleged by miniprogram user. The content of the agreement form is auto generated via collecting the operating consoles JSAPI scope choice. For miniprogram which embeds mutiple H5 pages, the H5 domain name should be put in the whitelist in order to access those JSAPIs.

Besides of the three strategies above, in order to provide an isolated context, a new JSBridge and JS runtime will be created when a session is loaded. For traditional webpages opened by a miniprogram, the webpage is isolated from communication with the JS runtime of that miniprogram instance.

1.3 Network Delegation

As what has been mentioned earlier in this article, all the network requests is delegated to the native proxy for the purpose of security and shortening request time. When the network proxy receives the request from JSBridge and UIRenderer, the static resource requests are recognised, and the path are matched with the minirpgrams local resource bundle, if a match is found in the bundle structure, the item is directly returned. For those remote network request, proper network library will be selected to execute the request based on the specification in the miniprogram configuration meta-data.

Every time during the launch of a miniprogram, the miniprogram’s local resource bundle was checked with the version number in mini-programs meta-data. Based on the update policy, both blocking and non-blocking of bundle initialisation process can be achieved. In such ways, it can be guaranteed for any successful initialised miniprogram instance, the delegated network request should always be able to load the pre-configured static file resources from local and those remote request should be route to the correct network libraries.

1.4 UI & JS Runtime Isolation

In order to optimise the webpage rendering speed, the rendering of page and loading/execution of javascript is splited into different threads:

  • The JS runtime is responsible to run the javascript data logic and provide update to UI Renderer for UI changes.
/*
Can have multiple page
pages/index/index.js
*/ 
Page({
  // template data, to be filled back to renderer
  data: {
    title: 'xxxx',
    price: 'yyyy'
  },
  onLoad(query) { },
  onShow() { },
  onReady() { },
  onHide() { },
  onUnload() { },
  ....
  // embeded events 
  events: {
    onBack() {
      console.log('onBack');
    },
  },
  // customised events
  viewTap() {
    this.setData({
      title: 'xxxx updated',
    });
  },
});
  • The UI Renderer(webview) is responsible to render the html dom.
<view>%text%</view>
<view>%price%</view>
<view>
  <button type="primary" onTap="viewTap">
    click to update
  </button>
</view>

The UI renderer and JS runtime communicate with each other via the native layer. The JS runtime invokes jsapi and update the business logic, if there is any changes in data model, the data is synchronized to UI renderder and the UI refreshes. Optionally, each miniprogram can have one and only one JS worker for handling parallel tasks, the JS worker and the default JS runtime communicate with each other in the form of message handlers via the native layer. The UI renderer and JS runtime communicate with each other via the native layer. The JS runtime invokes jsapi and update the business logic, if there is any changes in data model, the data is synchronized to UI renderder and the UI refreshes. Optionally, each miniprogram can have one and only one JS worker for handling parallel tasks, the JS worker and the default JS runtime communicate with each other in the form of message handlers via the native layer.

When each miniprogram instance is created, the corresponding JS runtime is initialised and inject the default miniprogram js library and followed by the loading of the index page model, in the meanwhile the initialisation of the UI renderer layer will be done in the UI renderer layer(loading of js library as well as the UI model). The series of lifecycle events in UI will be pipelined to JS runtime for receiving when the JS runtime finishes loading. There is an internal evnet protocol in UI renderer layer and a message protocol in JS runtime layer to facilate the inter-communication between the two threads. When an internal redirect/navigation happens, the JS runtime triggers JSAPI and the native layer will respond and coordinate the navigation.

As depicted in the above diagram, core events of the miniprogram page models are listed as below:

Lifecycle Stage Descriptions
onLoad(query) Fired when the page loads
onShow Fired when the page is displayed.
onReady Fired when the initial rendering of the page is complete.
onHide Fired when the page is hidden.
onUnload Fired when the page is unloaded.

Based on the discussion and analysis so far, to further combine with the kernel models which have been introduced earlier in this article, the whole picture for a miniprogram container can be concluded. The default JS runtime create a javascript context for each of the miniprogram (if a miniprogram comes with external plugins, there might be isolated javascript context for that plugin as well). Each of the default javascript context belongs to an isolate JSVirtual machine, hence the javascript contexts between different miniprogram instances are isolated. In the worker JS runtime layer, there is a thread pool(with fixed size) to execute the JSContexts. But by default since each miniprogram should have only one worker and the worker should stop execution once the page is changed to onHide status, a single thread should be enough.

1.5 Debug & Diagnostics

Besides of the container SDK usecases we discussed so far, debug and diagnostic tools are also indispensible part in the whole picture to facilitate the integration. Since the article is too long to include this part of content, we will discuss in future relevant article (if there is any in future :p).

2. Web v.s. Miniprogram Container

In the evolution progress of miniprogram container technology, web-container is an intermediate state, since the layering of core models and offline resource loading mechanism is firstly developed by web container, and then based on those characters of the web container, the latter one further enhanced its design in security/sandboxing, and improved the efficiency in UI rendering as well as enriched its developer tool chain experiences.

Characters Web Container Miniprogram Container
Core Model Layering
JSAPIs & Plugins
Offline Loading
Sandboxing ✓(*)
Web Socket ✓(*)
Worker -
JS Engine & Render Isolation -
Debug Tools/IDE/DSL -
Targeting Users Second Party Commencialized
Cost Low/Medium High

Note: "*" means partially supported.

3. Challenges

Based previous application teams’ integration feedbacks, there are some common challanges facing:

  • Compatibility of legacy web applications in miniprogram container.
  • Performance regarding latency in jsbridge queue communication.
  • Memory footprint consumption under each session instance (JSCore, Kernel Models, JSApis & Plugins may need make a comparison with using the original system webview).
  • Memory leak inspection in kernel instance/jsbridge/worker/jscore.
  • Hybrid rendering of native views inside of webview? so far I don’t think it’s implemented yet.

4. References

TOC