Comparison of MiniApps and web apps

This document summarizes the differences of MiniApps and web apps.

Overview

This document summarizes the differences of MiniApps and web apps. Although the API and implementation of each MiniApp platform are different, due to time constraints, the editor can only summarize a subset of them.

The first chapter of the MiniApp Standardization White Paper contains some comparison from the user's point of view. The following text mainly documents the differences from the implementer's and developer's point of view.

A MiniApp runs in a hosting platform (aka hosting environment or user agent). A hosting platform can be a native app (similar to a web browser), or (an engine embedded in) the operating system.

MiniApps usually need to be reviewed by the hosting platform to go online, and web apps just need to have a web server (and optionally a domain name) to go online.

A MiniApp includes a global [[[#manifest]]] file and zero or more page manifest files. The manifest files are usually in JSON.

See MiniApp Packaging for a typical example of directory structure of a MiniApp.

The running environment of MiniApps is divided into a view layer and a logic layer. The view layer only deals with rendering and listens out for the events happening. The logic layer only deals with data and logic.

The view layer and the logic layer.
The commnication between the view layer and the logic layer.

The logic layer is usually written in JavaScript. It processes the data and sends it to the view layer, and receives feedback from the view layer.

Although using Web technologies, MiniApps usually does not run in the browser, so some capabilities of JavaScript in the Web are not available, such as document and window.

In a web app, rendering and script can block each other, which is why a long script running may cause the page to become unresponsive, but in a MiniApp, the two are separated and run in different threads. Web page developers can use the DOM APIs exposed by the browsers to do DOM manipulation. But since the logic layer and view layer of a MiniApp is separated, the logic layer (running in JavaScriptCore, for example) does not contain a document or window object, and can't use some web APIs. Therefore, some libraries like jQuery cannot run in MiniApps. Moreover, since the JavaScriptCore environment is not the same as Node.js, some npm packages cannot be run in MiniApps either.

In the [[[#view]]], the hosting platform will convert the layout language (WXML, for example) into a JavaScript object. When data changes in the logic layer, the data is passed from the logic layer to the view layer through a method provided by the hosting platform, and then the diff of the DOM before and after is generated. After that, the differences will be applied to the original DOM tree and render the changed UI.

View layer

There is usually a markup language with a template mechanism (such as WXML, swan, AXML, TTML etc.) for the view layer of the MiniApp pages, which is similar to HTML in Web development. The MiniApp runtime translates the markup language into HTML (each page is rendered in a different WebView, but all WebViews shares the image cache) or native code.

When styling a MiniApp page, CSS is usually used, sometimes with extensions such as responsive pixels (aka density-independent pixels).

The elements used in the view layer are described in the [[[#components]]] section.

Here are some examples:

Example 1

<!-- xxx.swan -->
<view>
  Hello My {{ name }}
</view>
// xxx.js
Page({
    data: {
    name: 'SWAN'
    }
});

Example 2

<view>
    <view s-for="p in persons">
    {{p.name}}
    </view>
</view>
Page({
    data: {
    persons: [
        {name: 'superman'},
        {name: 'spiderman'}
    ]
    }
});

Example 3

  <view s-if="is4G">4G</view>
  <view s-elif="isWifi">Wifi</view>
  <view s-else>Other</view>
Page({
    data: {
    is4G: true,
    isWifi: false
    }
});

Example 4

<view> Hello {{name}}! </view>
<button bindtap="changeName"> Click me! </button>
// This is our data.
var helloData = {
  name: 'WeChat'
}

// Register a MiniApp page.
Page({
  data: helloData,
  changeName: function(e) {
    // sent data change to view
    this.setData({
      name: 'MiniApp'
    })
  }
})

The user agent binds the name in the data in with the name in the view layer, so when the page is loaded, it will display "Hello WeChat!".

When the button is clicked, the view layer will fire an event for changeName to the logic layer, and the logic layer will find and execute the corresponding event handler.

After the callback function is triggered, the logic layer changes the name in the data from WeChat to MiniApp. Because the data and the view layer have been bound, the view layer will automatically change to "Hello MiniApp!".

Example 5

<view>
  <view class="test" style="background:{{background}};color:{{color}};height:{{height}}">This can be changed.</view>
  <button bindtap="changeStyle">Change the style</button>
</view>
/* The rpx unit does not exist in standard CSS, but since this is not the point of the
   example, it will not be explained in this section. */
.test {
  height: 150rpx;
  line-height: 150rpx;
  text-align: center;
  border: 1px solid #89dcf8;
  margin-bottom: 112rpx;
  margin: 13rpx;
}
Page({
  data: {},
  changeStyle: function() {
    this.setData({
      background: "#89dcf8",
      color:'#ffffff',
      height:"322rpx"
    })
  }
})

Pages

A MiniApp page represents a page of the MiniApp and is responsible for page display and interaction. Each page usually corresponds to a directory in the MiniApp package.

Each MiniApp page generally contains a JavaScript file for the logic layer, a template file for the [[[#view]]], and optional CSS and JSON files for page styling and metadata.

For each page in a MiniApp, the developer need to register in the JavaScript file corresponding to the page, and specify the initial data, lifecycle callback, event handlers, etc. of the page in the Page constructor.

  // index.js
  Page({
    data: {
      text: "This is page data."
    },
    onLoad: function(options) {
      // Execute when the page is created
    },
    onShow: function() {
      // Execute when the page appears in the foreground
    },
    onReady: function() {
      // Execute when the page is rendered for the first time
    },
    onHide: function() {
      // Execute when the page changes from the foreground to the background
    },
    onUnload: function() {
      // Execute when the page is destroyed
    },
    onPullDownRefresh: function() {
      // Execute when a pull-down refresh is triggered
    },
    onReachBottom: function() {
      // Execute when the page is scrolled to the bottom
    },
    onShareAppMessage: function () {
      // Execute when the page is shared by the user
    },
    onPageScroll: function() {
      // Execute when the page is being scrolled
    },
    onResize: function() {
      // Execute when the page size changes
    },
    // Event handler
    viewTap: function() {
      this.setData({
        text: 'Set some data for updating view.'
      }, function() {
        // this is setData callback
      })
    },
    // Custom data
    customData: {
      hi: 'MiniApps'
    }
  })

Behaviors (similar to mixins or traits in some programming languages) can be used to make multiple pages have the same data fields and methods.

  // my-behavior.js
  module.exports = Behavior({
    data: {
      sharedText: 'This is a piece of data shared between pages.'
    },
    methods: {
      sharedMethod: function() {
        this.data.sharedText === 'This is a piece of data shared between pages.'
      }
    }
  })


  // page-a.js
  var myBehavior = require('./my-behavior.js')
  Page({
    behaviors: [myBehavior],
    onLoad: function() {
      this.data.sharedText === 'This is a piece of data shared between pages.'
    }
  })

Page router

The routing of all pages in a MiniApp is managed by the hosting platform.

The hosting platform maintains all current pages in the form of a stack.

MiniApp hosting platforms usually provide a function to get the current page stack. For example, the getCurrentPages() function in WeChat Mini Programs (and some other implementations) returns an array, and each element of the array is an object representing a page.

Components

MiniApps usually prohibit the use of some HTML elements, and add some new components (elements), such as swipers, commonly used icons (warning, search, setting, loading etc.), maps, rich text editors, advertisement and so on.

Custom components

MiniApp developers can also create custom components. Here's an example directory structure with a custom component in a Baidu Smart Program:

  ├── app.js
  ├── app.json
  ├── project.swan.json
  └── components
      └── custom
          ├── custom.swan
          ├── custom.css
          ├── custom.js
          └── custom.json

Components have their own lifecycles too. For example, in the JavaScript file for a custom component, the developer can:

  Component({
      // ...
      pageLifetimes: {
          show: function() {
          // Triggered when the page where the component is located is displayed
          },
          hide: function() {
          // Triggered when the page where the component is located is hidden
          }
      }
      // ...
  });

Manifest

MiniApp uses JSON-based manifest file to enable the developer to set up basic information, window style, page route and other information of a MiniApp.

      {
        "dir": "ltr",
        "lang": "en-US",
        "appID": "org.w3c.miniapp",
        "appName": "MiniApp Demo",
        "shortName": "MiniApp",
        "versionName": "1.0.0",
        "versionCode": 1,
        "description": "A Simple MiniApp Demo",
        "icons": [
          {
            "src": "common/icons/icon.png",
            "sizes": "48x48"
          }
        ],
        "minPlatformVersion": "1.0.0",
        "pages": [
          "pages/index/index",
          "pages/detail/detail"
        ],
        "window": {
          "navigationBarTextStyle": "black",
          "navigationBarTitleText": "Demo",
          "navigationBarBackgroundColor": "#f8f8f8",
          "backgroundColor": "#ffffff",
          "fullscreen": false
        },
        "widgets": [
          {
            "name": "widget",
            "path": "widgets/index/index",
            "minPlatformVersion": "1.0.0"
          }
        ],
        "reqPermissions": [
          {
            "name": "system.permission.LOCATION",
            "reason": "To show user's position on the map"
          },
          {
            "name": "system.permission.CAMERA",
            "reason": "To scan the QR code"
          }
        ]
      }
    

The MiniApp Manifest comparison with Web App Manifest shows similarities between the 2 formats.

To facilitate the life of developers wanting to deploy a web app and a MiniApp of the same application, it would be beneficial to:

  1. Use the Web App Manifest as the base format, since all properties in Web App Manifest are optional. Indicate which Web App Manifest properties are relevant, the set of restrictions applied to the values (such as only using local URLs for the pages). When backward compatibilities is needed, indicate so and recommend using duplicate values (the frameworks can make that transparent).
  2. For properties that are specific to MiniApp, consider using a prefix or add them to a miniapp property to avoid future compatibility issues. For each MiniApp-specific property, consider raising an issue against the Web App manifest specification for future adoption in Web App manifest (such as the properties supported under window).
  3. For permissions, consider re-using the same features used by document.featurePolicy.features(), e.g., "geolocation", "gamepad", "magnetometer", "accelerometer", "xr-spatial-tracking", "picture-in-picture", "camera", "payment", "pointer-lock", etc. Again, a prefix may be used for MiniApp specific features.

Packaging

Since MiniApps are downloaded to the user's local device and run in the form of a package, the MiniApp itself does not have an "origin" (because there is no domain), and there is no "cross-origin".

Developers can divide the MiniApp into a few sub-packages, and the user agent can load the sub-packages as needed when the MiniApp is used. Dividing a MiniApp into reasonable sub-packages according to business characteristics can increase the loading speed of MiniApp and optimize the user experience.

In addition, through the manifest file or MiniApp APIs, developers can preload some sub-packages when the user accesses a certain page of the MiniApp.

See also the packaging explainer for a primer.

Runtime

Multi-process/thread architecture

The exact process model of a MiniApp is related to the operating system. The MiniApps hosting platform tries to ensure that a MiniApp is run in a separate process when possible. The advantage of this is higher performance and better security.

Under iOS, a thread-level runtime model is used, and each MiniApp runs in its own thread(s).

Each MiniApp page can have one or multiple independent threads (also known as workers) in the logic layer. In the view layer, there is usually one thread, but some MiniApp implementations uses multiple threads to preload WebViews to improve the performance of page navigation.

Examples

Different MiniApp implementations have different runtime environments. Here are a few examples:

WeChat Mini Programs

WeChat Mini Programs run on multiple platforms: iOS, Android, Windows, macOS, and WeChat's developer tools for debugging. The script execution environment and the environment used to render non-native components in these platforms are all different.

Because the CSS and ECMAScript features supported by the these environment are different, developers need to use feature detection. WeChat Mini Programs provide built-in polyfills for some features to mitigate the interoperability issue.

iOS

The JavaScript code in the logic layer of the MiniApp runs in JavaScriptCore, and the view layer is rendered in WKWebView.

Android

The JavaScript code in the logic layer of the MiniApp runs in V8, and the view layer is rendered in Tencent's X5/XWeb browser engine (based on Chromium).

DevTools

The JavaScript code in the logic layer of the MiniApp runs in NW.js (which is based on Chromium and Node.js), and the view layer is rendered by Chromium.

Baidu Smart Programs

Baidu Smart Programs run on three platforms: iOS, Android, and Baidu's developer tools for debugging. The script execution environment and the environment used to render non-native components in these three platforms are different.

Because the CSS and ECMAScript features supported by the three environment are different, developers need to use feature detection. Baidu Smart Programs provide built-in polyfills for some features to mitigate the interoperability issue.

iOS

In the legacy version, the logic layer and view layer of the MiniApp were all running in the WebView and rendered.

In the new version, the JavaScript code in the logic layer of the MiniApp runs in JavaScriptCore, and the view layer is rendered in WebView.

Android

In the legacy version, the logic layer and view layer of the MiniApp were all running in the WebView and rendered.

In the new version, the JavaScript code in the logic layer of the MiniApp runs in V8, and the view layer is rendered in the WebView based using Baidu's T7 browser engine.

DevTools

The JavaScript code in the logic layer of the MiniApp runs in Electron, and the view layer is rendered by Chromium.

360 Mini Programs

360 Mini Programs runs in 360 Secure Browser on Windows.

Lifecycle

MiniApps implementations provides some lifecycle events and the process to manage the whole MiniApp and each page's lifecycle. See the MiniApp Lifecycle specification for a typical example.

There are two ways to open a MiniApp:

When the MiniApp is cold-launched, the hosting platform will use the local package to load the MiniApp, and at the same time, it will automatically detect whether there is a new package version in the cloud and download it asynchronously. After the download is complete, the new version of the package will be used when the user triggers a cold launch next time.

Detailed comparison

In MiniApps, applications, pages, and components all have their own lifecycles. This section mainly compares the lifecycle states of MiniApp applications/pages with related Web technologies.

A MiniApp page is usually registered in a JavaScript file using a Page constructor and accept an object to specify the initial data, lifecycle callback, event handlers, etc.

The following example is a basic Page constructor:

  // pages/index/index.js
  Page({
    data: {
      title: "Alipay",
    },
    onLoad(query) {
      // Execute when the page is created
    },
    onShow() {
      // Execute when the page appears in the foreground
    },
    onReady() {
      // Execute when the page is rendered for the first time
    },
    onHide() {
      // Execute when the page changes from the foreground to the background
    },
    onUnload() {
      // Execute when the page is destroyed
    },
    onTitleClick() {
      // Execute when the title is clicked
    },
    onPullDownRefresh() {
      // Execute when a pull-down refresh is triggered
    },
    onReachBottom() {
      // Execute when the page is scrolled to the bottom
    },
    onShareAppMessage() {
     // Return to custom sharing information
    },
    // Event handler
    viewTap() {
      this.setData({
        text: 'Set data for update.',
      });
    },
    // Event handler
    go() {
      my.navigateTo({url:'/page/ui/index?type=mini'});
    },
    // Custom data
    customData: {
      name: 'alipay',
    },
  });

MiniApps are managed by a view thread and one or more logic thread(s) (also known as workers). When user opens a MiniApp for the first time, the view and logic threads will simultaneously start the initialization.

After the logic thread is initialized, it runs the MiniApp global lifecycle callback functions app.onLaunch and app.onShow to create the MiniApp instance. The "launched" state means that the MiniApp initialization is completed, and it is fired only once. After this event is fired, developers can obtain the basic information of the MiniApp, such as the URI. "Shown" is a lifecycle state for MiniApp running in foreground. It is triggered once the MiniApp launch is completed, or once the MiniApp switches from background to foreground.

After the global application is initialized, the logic thread runs the MiniApp page lifecycle callback function page.onLoad to create the MiniApp page instance. "Loaded" means that the MiniApp page initialization is completed. Through this event, developers can obtain the basic information of the page, such as path and query of the page.

After the pages are initialized, the logic thread wait for notification of completion of view thread initialization. When the view thread initialization is completed and the logic thread is notified, the logic thread sends the initialization data to the view thread for rendering. At this time, the view thread starts the first data rendering.

After the first rendering is completed, the view thread enters the "ready" state and notifies the logic thread. The logic thread calls the page.onReady function and the page is usable now.

After the page is ready, the logic thread will notify the view thread each time the data is modified, and the view thread will render it. When the page is switched into the background, the logic thread calls the page.onHide function. When the page returns to the foreground, the page.onShow function will be called. When the MiniApp is confronted with a script error, the app.onError function will be called. When the page is destroyed, the page.onUnload function is called before the page is destroyed.

In web apps, a "page" usually means the document of the top-level browsing context, and there is no (strict) concept of "applications", so some lifecycle states in MiniApps do not have a correspondence state in web applications.

For example, when the MiniApp is switched into the background, the logic thread calls the app.onHide function, while in web apps, the visibilityState property and the onvisibilitychange event handler are only useful when a web page is hidden. Like most native apps, a MiniApp can only have one instance, but a web app can appear in multiple browser tabs at the same time, so there is no correspondence for app.onHide in web apps.

Offline

In web apps, service workers provide the technical foundation for offline experiences. It is a script that the browser runs in the background and can intercept and handle network requests, including programmatically managing a cache of responses.

While in MiniApps, offline experience is achieved by downloading the MiniApp package to the user's device and updating it when needed.

Compared with web apps, MiniApp developers can focus more on business logic instead of caching static resources. The caching and update mechanism of the MiniApp package is automatically managed by the MiniApp hosting platform, and if needed, developers can influence this process through APIs provided by the hosting platform.

APIs

MiniApp platforms usually provide their own APIs, some have corresponding web APIs, some do not. See a comparison here.

Performance

Snapshot

Some MiniApp implementations like Quick Apps can perform a pre-rendering process on some pages in the cloud to generate a rendering intermediate format called snapshot. The snapshot format is very small after compression. For example, a 50K JavaScript file generates a ~3K snapshot.

Since the snapshot file is very small, it can be distributed as part of the MiniApp metadata. For example, when a user searches for a MiniApp in the app store, the snapshot has been loaded along with the name of the MiniApp, package download link and other information.

When the MiniApp is loaded and started for the first time, the download URL of the package and the snapshot file are passed to the MiniApp engine. When the engine requests to download the MiniApp package, it loads and parses the snapshot and renders it. When the package download is complete, the standard rendering process continues on the basis of the snapshot.

Virtual DOM

The page rendering of MiniApps often uses virtual DOM to ensure that only the changed data are updated when the page is updated. In web apps, virtual DOM is usually implemented with JavaScript, which consumes a lot of CPU and is not very efficient.

Some MiniApp engines use WebAssembly to implement the core logic of virtual DOM, and modifies the JavaScript engine to improve the performance of calls between JavaScript and WebAssembly.

Security and privacy

Since MiniApps run in the hosting platform, in a native app people only care about whether there are vulnerabilities in the app itself, in a MiniApp people also need to know whether the hosting platform is secure.

The relationship between MiniApps and the user agent.
The relationship between MiniApps and the user agent is similar to the relationship between a native app and the operating system, or the relationship between a web app and the brwoser.

Native apps will directly invoke system APIs, so many vulnerabilities are related to the operating system version, such as the WebView version in the user's operating system. However, beacuse some MiniApp implementations use their own browser engines (see [[[#runtime]]]), if the engine mitigates the vulnerability, there is no need to consider the impact of this vulnerability even on low-version operating systems. On the other hand, if there is a vulnerability specific to the browser engine in the MiniApp hosting platform, it will affect the MiniApps but not other parts of the system.

Since MiniApps can not access DOM and the global object window (by seperating the view layer and the logical layer execution environment), and can only use the APIs and components the user agents provide, so it is not possible (or very difficult) for malicious code to jump to a random web page or MiniApp, or change the content on the UI. Since there are MiniApps components that can display sensitive data like username, avatar, gender, and geolocation etc., if developers can manipulate the DOM, it means that they can get user's sensitive information at will.

Since most MiniApp implementations disallow dynamic code loading like eval and new Function, XSS is very difficult.

MiniApps usually have a domain name safelist, to permit scripts contained in a MiniApp to access data from a URL only when the domain name is in the safelist.

Since MiniApps restrict the use of cookies, CSRF attacks is harder in MiniApps than normal web apps.