1. History of applets
1.1 Native App
In the era of smart phones, the Internet was not very developed, and web browsing was slow and text-based. The apps on the market are mainly Native apps.
Native App is a Native App based on iOS or Android. It is characterized by high development cost, slow iteration, good performance and experience, and timely message push, such as QQ and wechat.
1.2 the H5
In 2014, HTML5 completed the standard customization. Its design purpose is to support multimedia on mobile devices, introducing Video, Audio and other technologies.
It is very convenient to browse videos on the web, which is characterized by low development and release costs, convenient opening, and no need to download to the local. However, the performance is limited by the processing capacity of the browser, which is worse than that of the native App, and the message push is not timely.
1.3 Hybrid App
Hybrid App is a Hybrid App, that is, on the basis of the mobile terminal native App, through JSBrdige and other methods, access the API of the native App for JS interaction, and achieve HTML and CSS rendering through WebView and other technologies.
A WebView can be thought of as a mobile component that has a browser kernel (such as WebKit) nested within it.
Applications implemented with this technology are generally cross-platform and easy to maintain, with performance between H5 and native applications.
1.4 a small program
Applet is a kind of application that can be used without downloading and installing. It realizes the dream of application “at your fingertips”. Users can open the application by scanning or searching it. It also embodies the concept of “use it and go”, so users don’t have to worry about installing too many apps. Applications will be everywhere, ready to use, but not installed or uninstalled. — Baidu Encyclopedia
Since the official release of wechat applets in January 2017, there have been a variety of applets on the Internet:
Wechat applets | Baidu intelligent small program | Alipay small program | QQ small programs |
---|---|---|---|
2017.1 | 2018.7 | 2018.9 | 2019.6 |
Based on the almost the same technical principle of small program, as well as the convenience and quick characteristics of small program, also derived a number of small programs, such as Douyin small program, Kuaishou small program, JINGdong small program, Meituan small program, etc., to help major manufacturers to better provide convenient services for users.
In 2018, the micro channel small program “jump jump” exploded. I remember that many students were playing when they queued up for dinner in the canteen, which helped the micro channel small program expand in the user, and also inspired the upsurge of other manufacturers to develop small programs.
2. Principle analysis
2.1 Two-thread model
Whether it is wechat small program or Alipay small program or Baidu intelligent small program and so on, their overall architecture is based on dual threads.
The JS code used to process the business logic runs in a separate thread, and the rendering layer (template, CSS) runs in a separate thread.
Take wechat applets as an example:
The two-thread model is different from the single-thread model. The data interaction between the logic layer and the rendering layer needs to be done through JSBridge, which realizes the two-way binding of data by publishing and subscribing based on the currently famous MVVM, so as to realize data communication.
In this way, we can realize asynchronous update of view data by changing the data of Model layer by setData in logical layer in wechat applet.
The following is the life cycle of wechat applets:
2.2 Overall Architecture
Note: All of the following content revolves around wechat developer tools.
Open the source code of wechat developer tool. It runs on nw.js, so package. NW in the figure below is what we will focus on.
Prettier-code formatter in VSCode there is a lot of Code that is confused and compressed, so formatting the source Code by opening it in VSCode and installing prettier-code formatter However, there are no semantic variables in the code at this point, and you can only infer roughly what the code is doing through the API.
It is worth noting that there is a vendor folder in the source code, through which you can quickly create a sample project. At the same time, there is a very important 2.17.0.wxVPkg package, which is the basic library of wechat applets. This includes processing of logical and render layers such as WebService and WebView mentioned below.
2.2.1 WAWebview
Applet View layer base library, which provides basic capabilities of view layer:
var __wxLibrary = {
fileName: 'WAWebview.js'.envType: 'WebView'.contextType: 'others'.execStart: Date.now()
};
var __WAWebviewStartTime__ = Date.now();
var __libVersionInfo__ = {
"updateTime": "2020.4.4 10:25:02"."version": "2.10.4"
};
/** * core-js module */
!function(n, o, Ye) {... },function(e, t, i) {
var n = i(3),
o = "__core-js_shared__",
r = n[o] || (n[o] = {});
e.exports = function(e) {
returnr[e] || (r[e] = {}) } ... } (1.1);
var __wxConfig;
var __wxTest__ = false;
var wxRunOnDebug = function(e) {
e()
};
/** * Base module */
var Foundation = function(i) {... }]).default;var nativeTrans = function(e) {
...
}(this);
/** * Message communication module */
var WeixinJSBridge = function(e) {
...
}(this);
/** * Listen for nativeTrans related events */
!function() {
...
}();
/** * parse the configuration */
!function(r) {... __wxConfig = _(__wxConfig), __wxConfig = v(__wxConfig), Foundation.onConfigReady(function() {
m()
}), n ? __wxConfig.__readyHandler = A : d ? Foundation.onBridgeReady(function() {
WeixinJSBridge.on("onWxConfigReady", A)
}) : Foundation.onLibraryReady(A)
}(this);
/** * error, onunHandledrejection */
!function(e) {
function t(e) {
Foundation.emit("unhandledRejection", e) || console.error("Uncaught (in promise)", e.reason)
}
"object"= =typeof e && "function"= =typeof e.addEventListener ? (e.addEventListener("unhandledrejection".function(e) {
t({
reason: e.reason,
promise: e.promise
}), e.preventDefault()
}), e.addEventListener("error".function(e) {
var t;
t = e.error, Foundation.emit("error", t) || console.error("Uncaught", t), e.preventDefault()
})) : void 0 === e.onunhandledrejection && Object.defineProperty(e, "onunhandledrejection", {
value: function(e) {
t({
reason: (e = e || {}).reason,
promise: e.promise
})
}
})
}(this);
/** * native buffer */
var NativeBuffer = function(e) {
...
}(this);
var WeixinNativeBuffer = NativeBuffer;
var NativeBuffer = null;
/** * Log modules: wxConsole, wxPerfConsole, wxNativeConsole, __webviewConsole__ */
var wxConsole = ["log"."info"."warn"."error"."debug"."time"."timeEnd"."group"."groupEnd"].reduce(function(e, t) {
return e[t] = function() {}, e
}, {});
var wxPerfConsole = ["log"."info"."warn"."error"."time"."timeEnd"."trace"."profile"."profileSync"].reduce(function(e, t) {
return e[t] = function() {}, e
}, {});
var wxNativeConsole = function(i) {
...
}([function(e, t, i) {... }]).default;var __webviewConsole__ = function(i) {
...
}([function(e, t, i) {... }]);/** * Report module */
var Reporter = function(i) {
...
}([function(e, L, O) {... }]).default;var Perf = function(i) {
...
}([function(e, t, i) {... }]).default;/** * View layer API */
var __webViewSDK__ = function(i) {
...
}([function(e, L, O) {... }]).default;var wx = __webViewSDK__.wx;
/** * Component system */
var exparser = function(i) {
...
}([function(e, t, i) {... }]);Framework glue layer / * * * * * use exparser registerBehavior and exparser registerElement method forward registered built-in component * window, wx object to the event forwarded to exparser * /
!function(i) {
...
}([function(e, t) {... },function(e, t) {},function(e, t) {}]);
/** * Virtual DOM */
var __virtualDOMDataThread__ = false;
var __virtualDOM__ = function(i) {
...
}([function(e, t, i) {... }]);/** * __webviewEngine__ */
var __webviewEngine__ = function(i) {
...
}([function(e, t, i) {... }]);/** * Inject default styles into the page */
!function() {...function e() {
var e = i('... ');
__wxConfig.isReady ? void 0! == __wxConfig.theme && i(t, e.nextElementSibling) : __wxConfig.onReady(function() {
void 0! == __wxConfig.theme && i(t, e.nextElementSibling) }) }window.document && "complete"= = =window.document.readyState ? e() : window.onload = e
}();
var __WAWebviewEndTime__ = Date.now();
typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();
__wxLibrary = undefined;
Copy the code
WAWebview is mainly composed of the following components:
Foundation
: Basic moduleWeixinJSBridge
: Message communication moduleexparser
: component System module__virtualDOM__
: Virtual DOM module__webViewSDK__
: WebView SDK moduleReporter
: Log reporting module (exceptions and performance statistics)
2.2.2 WAService
Small program logic layer base library, providing logic layer base capabilities:
var __wxLibrary = {
fileName: 'WAService.js'.envType: 'Service'.contextType: 'App:Uncertain'.execStart: Date.now()
};
var __WAServiceStartTime__ = Date.now();
(function(global) {
var __exportGlobal__ = {};
var __libVersionInfo__ = {
"updateTime": "2020.4.4 10:25:02"."version": "2.10.4"
};
var __Function__ = global.Function;
var Function = __Function__;
/** * core-js module */
!function(r, o, Ke) {},1.1);
var __wxTest__ = false;
var wxRunOnDebug = function(e) {
e()
};
var __wxConfig;
/** * Base module */
var Foundation = function(n) {
...
}([function(e, t, n) {... }]).default;var nativeTrans = function(e) {
...
}(this);
/** * Message communication module */
var WeixinJSBridge = function(e) {
...
}(this);
/** * Listen for nativeTrans related events */
!function() {
...
}();
/** * parse the configuration */
!function(i) {
...
}(this);
/** * error, onunHandledrejection */
!function(e) {
...
}(this);
/** * native buffer */
var NativeBuffer = function(e) {
...
}(this);
WeixinNativeBuffer = NativeBuffer;
NativeBuffer = null;
var wxConsole = ["log"."info"."warn"."error"."debug"."time"."timeEnd"."group"."groupEnd"].reduce(function(e, t) {
return e[t] = function() {}, e
}, {});
var wxPerfConsole = ["log"."info"."warn"."error"."time"."timeEnd"."trace"."profile"."profileSync"].reduce(function(e, t) {
return e[t] = function() {}, e
}, {});
var wxNativeConsole = function(n) {
...
}([function(e, t, n) {... }]).default;/** * Worker module */
var WeixinWorker = function(e) {
...
}(this);
/** * JSContext */
var JSContext = function(n) {... }([ ... }]).default;var __appServiceConsole__ = function(n) {
...
}([function(e, N, R) {... }]).default;var Protect = function(n) {
...
}([function(e, t, n) {... }]);var Reporter = function(n) {
...
}([function(e, N, R) {... }]).default;var __subContextEngine__ = function(n) {
...
}([function(e, t, n) {... }]);var __waServiceInit__ = function() {... }function __doWAServiceInit__() {
var e;
"undefined"! =typeof wx && wx.version && (e = wx.version), __waServiceInit__(), e && "undefined"! =typeof__exportGlobal__ && __exportGlobal__.wx && (__exportGlobal__.wx.version = e) } __subContextEngine__.isIsolateContext(); __subContextEngine__.isIsolateContext() || __doWAServiceInit__(); __subContextEngine__.initAppRelatedContexts(__exportGlobal__); }) (this);
var __WAServiceEndTime__ = Date.now();
typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();
__wxLibrary = undefined;
Copy the code
WAService basic composition:
Foundation
: Basic moduleWeixinJSBridge
: Message communication moduleWeixinNativeBuffer
: native BufferWeixinWorker
: the Worker threadJSContext
: JS Engine ContextProtect
: object protected by JS__subContextEngine__
: Provides methods such as App, Page, Component, Behavior, getApp, and getCurrentPages
2.2.3 virtual DOM
Wechat applet in WAService to realize the small program __virtualDOM__, through __virtualDOM__ module, can realize JS object to DOM object mapping.
However, after diff and patch, this virtual DOM is not converted into a native DOM element, but a custom DOM element in wechat applet. The operations of these DOM elements are managed uniformly by Exparser module:
All wX custom tags are included in WAWebview:
Also, the __virtualDOM__ module provides a number of basic apis, such as:
- GetAll: Gets all nodes
- GetNodeById: Obtains a Node based on the Id
- GetNodeId: Obtains the NodeId
- AddNode: Adds a node
- RemoveNode: Deletes a node
- GetExparser: Gets an Exparser object (based on WebComponent’s shadow DOM model, it can run in A JS environment and all operations related to the node tree depend on it)
- .
(More API definitions can be found in WAService. Js.)
2.2.4 WeiXinJSBridge
WeixinJSBridge provides a message communication mechanism between VIEW layer JS and Native and between view layer and logic layer, and provides the following methods:
The most important ones are ON and Invoke, where on registers an event and invoke triggers an event.
2.3 wechat developer tools
WeChat developer tools in the small program is run in NW in js, here is his official API documentation: NWJS. Readthedocs. IO/en/latest /.
It’s based on Chromium and Node.js, so we’ll run it when we convert the compiled virtual DOM into the real DOM.
2.3.1 Some decompiling tips
We can use developer tools to get a number of commands by typing help in Devtools:
One of the more useful is openVendor. This function opens the source code for the current project, which is essentially a folder containing the WCC and WCSC compilation tools:
With these documents, it will be very helpful for our subsequent analysis.
We can copy these files to a separate directory, open the project in VSCode, and install the following plug-ins:
This plugin can unzip all files ending in.wxvpkg in wechat developer tools.
At the same time through him to quickstart miniProgramjs.wxvpkg for decompression, we get the source file in the developer tools.
2.3.2 Compilation Principle
2.3.2.1 WCC Compiling WXML
Wechat applet provides WCC tools to compile WXML code. Using the code above, we can compile WXML, using the home page of the Demo project created by the developer tools as an example:
<! --index.wxml-->
<view class="container">
<view class="userinfo">
<block wx:if="{{canIUseOpenData}}">
<view class="userinfo-avatar" bindtap="bindViewTap">
<open-data type="userAvatarUrl"></open-data>
</view>
<open-data type="userNickName"></open-data>
</block>
<block wx:elif="{{! hasUserInfo}}">
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile">Get avatar nickname</button>
<button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo">Get avatar nickname</button>
<view wx:else>Please use base library 1.4.4 or later</view>
</block>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
Copy the code
Compiled with the following instructions:
./wcc ./quickstart/miniProgramJs.unpack/pages/index/index.wxml > index.js
Copy the code
You get a JS description file:
It declares a $GWX function from which to retrieve the Virtual DOM. We then add a few lines of code to the file to invoke it and execute the file from Node.js or nw.js:
var data = $gwx('./quickstart/miniProgramJs.unpack/pages/index/index.wxml') ();console.log(JSON.stringify(data, null.2));
Copy the code
We can get the final Virtual DOM structure we want:
{
"tag": "wx-page"."children": [{"tag": "wx-view"."attr": {
"class": "container"
},
"children": [{"tag": "wx-view"."attr": {
"class": "userinfo"
},
"children": [{"tag": "wx-view"."attr": {},
"children": [
"Please use 1.4.4 and above base library"]."raw": {},
"generics": {}}],"raw": {},
"generics": {}}, {"tag": "wx-view"."attr": {
"class": "usermotto"
},
"children": [{"tag": "wx-text"."attr": {
"class": "user-motto"
},
"children": [
""]."raw": {},
"generics": {}}],"raw": {},
"generics": {}}],"raw": {},
"generics": {}}]}Copy the code
Then through the window. The exparser. RegisterElemtent methods to convert these tag to true DOM.
For example, the above wX-text would be converted to something like the following DOM:
<span>
<span style="display: none"></span>
<span>{{here is the literal content}}</span>
</span>
Copy the code
2.3.2.2 WCSC Compiles WXSS
Again, using index. WXSS in the Demo project as an example, run the following command:
./wcsc ./quickstart/miniProgramJs.unpack/pages/index/index.wxss -js -o ./css.js
Copy the code
This file can be converted from WXSS to JS:
This generates the setCssToHead method, which is used to insert the corresponding CSS transform (RPX to PX, etc.) into the head of the document with the style tag.
2.4 Communication Principles
The communication between the logic layer and the rendering layer of the small program will be transferred by Native (wechat client), and the network request sent by the logic layer will also be forwarded by Native.
View layer components:
Some of the built-in components take advantage of the capabilities provided by the client natively, and since the capabilities provided by the client natively are required, the interaction between the view layer and the client is involved. This communication mechanism is implemented differently in iOS and Android. IOS uses the messageHandlers feature of WKWebView, while Android injects a native method into the Window object of the WebView. This will eventually be encapsulated into a compatibility layer called WeiXinJSBridge, which mainly provides two methods: invoke and on.
Logical layer interface:
The logic layer and client native communication mechanism are similar to the render layer, except that the iOS platform can inject a global native method into the JavaScriptCore framework, while the Android side is consistent with the render layer.
Whether at the view or logical level, the developer is indirectly invoking the underlying interface that communicates natively with the client. Generally, wechat applets will be exposed to developers only after layer encapsulation of the logical layer interface. The details of encapsulation may be unified entry, some parameter verification, compatibility with various platforms or versions and so on.
2.5 Startup Mechanism
Small programs have two ways of cold start and hot start:
- If the user has opened a small program, and then in a certain period of time to open the small program again, at this time no need to restart, only the background state of the small program switch to the front, this process is hot start.
- Cold start refers to the situation that the user opens it for the first time or the mini program is actively destroyed by wechat and opened again. In this case, the mini program needs to be reloaded and started.
Applets have no concept of restart:
- When the small program enters the background, the client will maintain the running state for a period of time, after a certain period of time (currently 5 minutes) will be actively destroyed by wechat.
- If the system receives more than two memory alarms in a short period of 5 seconds, the system destroys the small program.
Startup process:
3. Summary
- Applets have a near-native App experience.
- Applets are not really “download-free”, but they are so small that they can be downloaded quickly in today’s high-speed network environment, without the user’s awareness, or rather “sensorless download”.
- Based on the limitations of mobile terminal layout, it can be developed efficiently and simply, with fast iteration.
- Applets are two-threaded models where the logic layer and the rendering layer run in separate threads and communicate via JSBridge.
- There are special JSBridge implementations in the small program to achieve two-way calls between JS and Native.
- WXML files are compiled by WCC: WXML => JS => VirtualDOM.
- WXSS files are compiled with WCSC: WXSS => JS => style.
- Applet is also a hybrid technology, but it focuses on the host application to achieve a stronger ecosystem and provide more convenient services.
4. References
- Micro channel small program technical principle analysis
- Brief introduction to the historical development and status quo of small programs and some multi-terminal solutions
- Aladdin Institute – Internet Industry: White Paper on small Program Internet Development 2020
- An article about JsBridge