A Quick Look At WeChat's Mini Programs
While preparing for my presentation at the Google Developer Days 2019 in Shanghai, China I was reminded again that China is a market where few super apps like WeChat host a gazillion mini apps or mini programs that fulfill everyday needs like booking cabs, reserving tables, etc.
I got curious and downloaded the SDK and after playing a bit with the Your First Mini App tutorial, I realized the whole thing is so close to building for the actual web, it both fascinates, intrigues, and honestly somewhat infuriates me.
App Architecture
- You style your apps with WXSS, which is essentially CSS with some neat additions like responsive pixels.
page-section-gap {
box-sizing: border-box;
padding: 0 30rpx;
}
.page-body-button {
margin-bottom: 30rpx;
}
- You write your app logic with JavaScript (or TypeScript), with
App
as the top level object andwx
as the object you get all the cool capabilities from. The API is incredibly powerful.
Here are the keys of the wx
object:
[
'addCard',
'addNativeDownloadTask',
'addPhoneContact',
'addWeRunData',
'arrayBufferToBase64',
'authorize',
'base64ToArrayBuffer',
'batchGetContactDirectly',
'bindPaymentCard',
'calRqt',
'canIUse',
'cancelDownloadAppTask',
'canvasGetImageData',
'canvasPutImageData',
'canvasToTempFilePath',
'captureScreen',
'checkIsSoterEnrolledInDevice',
'checkIsSupportFacialRecognition',
'checkIsSupportSoterAuthentication',
'checkSession',
'chooseAddress',
'chooseContact',
'chooseImage',
'chooseInvoice',
'chooseInvoiceTitle',
'chooseLocation',
'chooseMedia',
'chooseMessageFile',
'chooseMultiMedia',
'chooseShareGroup',
'chooseVideo',
'chooseWeChatContact',
'clearStorage',
'clearStorageSync',
'closeBLEConnection',
'closeBluetoothAdapter',
'closeSocket',
'cloud',
'compressImage',
'connectSocket',
'connectWifi',
'createAnimation',
'createAudioContext',
'createBLEConnection',
'createCameraContext',
'createCanvasContext',
'createContext',
'createInnerAudioContext',
'createIntersectionObserver',
'createInterstitialAd',
'createLivePlayerContext',
'createLivePusherContext',
'createMapContext',
'createOffscreenCanvas',
'createRewardedVideoAd',
'createSelectorQuery',
'createUDPSocket',
'createVideoContext',
'createWorker',
'downloadApp',
'downloadAppForIOS',
'downloadFile',
'downloadSilkVoice',
'drawCanvas',
'enterContact',
'env',
'error',
'exitVoIPChat',
'faceVerifyForPay',
'getABTestConfig',
'getAccountInfoSync',
'getAppInstallState',
'getAvailableAudioSources',
'getBLEDeviceCharacteristics',
'getBLEDeviceServices',
'getBackgroundAudioManager',
'getBackgroundAudioPlayerState',
'getBackgroundFetchData',
'getBackgroundFetchToken',
'getBatteryInfo',
'getBatteryInfoSync',
'getBeacons',
'getBluetoothAdapterState',
'getBluetoothDevices',
'getClipboardData',
'getConnectedBluetoothDevices',
'getConnectedWifi',
'getCookies',
'getExtConfig',
'getExtConfigSync',
'getFileInfo',
'getFileSystemManager',
'getHCEState',
'getImageInfo',
'getLabInfo',
'getLaunchOptionsSync',
'getLocation',
'getLogManager',
'getMenuButtonBoundingClientRect',
'getNetworkType',
'getOpenDeviceId',
'getPublicLibVersion',
'getRealtimeLogManager',
'getRecorderManager',
'getResPath',
'getSavedFileInfo',
'getSavedFileList',
'getScreenBrightness',
'getSelectedTextRange',
'getSetting',
'getShareInfo',
'getStorage',
'getStorageInfo',
'getStorageInfoSync',
'getStorageSync',
'getSystemInfo',
'getSystemInfoSync',
'getUpdateManager',
'getUserInfo',
'getWeRunData',
'getWifiList',
'getWxSecData',
'hideKeyboard',
'hideLoading',
'hideNavigationBarLoading',
'hideShareMenu',
'hideTabBar',
'hideTabBarRedDot',
'hideToast',
'installDownloadApp',
'isSDKError',
'isSystemError',
'isThirdError',
'joinVoIPChat',
'launchApplicationDirectly',
'launchApplicationForNative',
'launchMiniProgram',
'loadFontFace',
'loadPaySpeechDialectConfig',
'login',
'makePhoneCall',
'makeVoIPCall',
'navigateBack',
'navigateBackApplication',
'navigateBackH5',
'navigateBackMiniProgram',
'navigateBackNative',
'navigateTo',
'navigateToDevMiniProgram',
'navigateToMiniProgram',
'navigateToMiniProgramDirectly',
'nextTick',
'notifyBLECharacteristicValueChange',
'notifyBLECharacteristicValueChanged',
'offAppHide',
'offAppShow',
'offAudioInterruptionBegin',
'offAudioInterruptionEnd',
'offError',
'offLocalServiceDiscoveryStop',
'offLocalServiceFound',
'offLocalServiceLost',
'offLocalServiceResolveFail',
'offPageNotFound',
'offWindowResize',
'onAccelerometerChange',
'onAppEnterBackground',
'onAppEnterForeground',
'onAppHide',
'onAppRoute',
'onAppRouteDone',
'onAppShow',
'onAppUnhang',
'onAudioInterruptionBegin',
'onAudioInterruptionEnd',
'onBLECharacteristicValueChange',
'onBLEConnectionStateChange',
'onBLEConnectionStateChanged',
'onBackgroundAudioPause',
'onBackgroundAudioPlay',
'onBackgroundAudioStop',
'onBackgroundFetchData',
'onBeaconServiceChange',
'onBeaconUpdate',
'onBluetoothAdapterStateChange',
'onBluetoothDeviceFound',
'onCompassChange',
'onDeviceMotionChange',
'onDownloadAppStateChange',
'onError',
'onEvaluateWifi',
'onGetWifiList',
'onGyroscopeChange',
'onHCEMessage',
'onKeyboardHeightChange',
'onLocalServiceDiscoveryStop',
'onLocalServiceFound',
'onLocalServiceLost',
'onLocalServiceResolveFail',
'onLocationChange',
'onMemoryWarning',
'onNativeEvent',
'onNetworkStatusChange',
'onPageNotFound',
'onPageReload',
'onSocketClose',
'onSocketError',
'onSocketMessage',
'onSocketOpen',
'onTapNavigationBarRightButton',
'onUploadEncryptedFileToCDNProgress',
'onUserCaptureScreen',
'onVoIPChatInterrupted',
'onVoIPChatMembersChanged',
'onVoIPChatSpeakersChanged',
'onVoicePlayEnd',
'onWebviewEvent',
'onWifiConnected',
'onWindowResize',
'openBluetoothAdapter',
'openBusinessView',
'openCard',
'openDeliveryList',
'openDocument',
'openGoldenRedPacketDetail',
'openLocation',
'openMiniProgramHistoryList',
'openMiniProgramProfile',
'openMiniProgramSearch',
'openMiniProgramStarList',
'openOfficialAccountProfile',
'openOfflinePayView',
'openQRCode',
'openSetting',
'openUrl',
'openUserProfile',
'openWCPayCardList',
'operateWXData',
'pageScrollTo',
'pauseBackgroundAudio',
'pauseDownloadAppTask',
'pauseVoice',
'playBackgroundAudio',
'playVoice',
'presetWifiList',
'preventApplePayUI',
'previewImage',
'queryDownloadAppTask',
'reLaunch',
'readBLECharacteristicValue',
'redirectTo',
'removeSavedFile',
'removeStorage',
'removeStorageSync',
'removeTabBarBadge',
'removeUserCloudStorage',
'reportAction',
'reportAnalytics',
'reportIDKey',
'reportKeyValue',
'reportMonitor',
'request',
'requestMallPayment',
'requestPayment',
'requestPaymentToBank',
'requestVirtualPayment',
'resumeDownloadAppTask',
'saveFile',
'saveImageToPhotosAlbum',
'saveVideoToPhotosAlbum',
'scanCode',
'secureTunnel',
'seekBackgroundAudio',
'sendBizRedPacket',
'sendGoldenRedPacket',
'sendHCEMessage',
'sendSocketMessage',
'setBackgroundColor',
'setBackgroundFetchToken',
'setBackgroundTextStyle',
'setClipboardData',
'setCookies',
'setCurrentPaySpeech',
'setEnableDebug',
'setInnerAudioOption',
'setKeepScreenOn',
'setLabInfo',
'setNavigationBarAlpha',
'setNavigationBarColor',
'setNavigationBarRightButton',
'setNavigationBarTitle',
'setPageStyle',
'setResPath',
'setScreenBrightness',
'setStorage',
'setStorageSync',
'setTabBarBadge',
'setTabBarItem',
'setTabBarStyle',
'setTopBarText',
'setUserCloudStorage',
'setWifiList',
'shareAppMessageForFakeNative',
'showActionSheet',
'showLoading',
'showModal',
'showNavigationBarLoading',
'showShareActionSheet',
'showShareMenu',
'showTabBar',
'showTabBarRedDot',
'showToast',
'startAccelerometer',
'startBeaconDiscovery',
'startBluetoothDevicesDiscovery',
'startCompass',
'startCustomFacialRecognitionVerify',
'startCustomFacialRecognitionVerifyAndUploadVideo',
'startDeviceMotionListening',
'startFacialRecognitionVerify',
'startFacialRecognitionVerifyAndUploadVideo',
'startGyroscope',
'startHCE',
'startLocalServiceDiscovery',
'startLocationUpdate',
'startLocationUpdateBackground',
'startPullDownRefresh',
'startRecord',
'startSoterAuthentication',
'startWifi',
'stopAccelerometer',
'stopBackgroundAudio',
'stopBeaconDiscovery',
'stopBluetoothDevicesDiscovery',
'stopCompass',
'stopDeviceMotionListening',
'stopGyroscope',
'stopHCE',
'stopLocalServiceDiscovery',
'stopLocationUpdate',
'stopPullDownRefresh',
'stopRecord',
'stopVoice',
'stopWifi',
'switchTab',
'traceEvent',
'triggerGettingWidgetData',
'updatePerfData',
'updateShareMenu',
'updateVoIPChatMuteConfig',
'uploadEncryptedFileToCDN',
'uploadFile',
'uploadSilkVoice',
'uploadWeRunData',
'verifyPaymentPassword',
'version',
'vibrateLong',
'vibrateShort',
'voiceSplitJoint',
'writeBLECharacteristicValue',
];
- You describe your interface with WXML, which is something between JSX, Vue's declarative rendering; it also reminds me of old concepts like XSLT's
<xsl:if>
:
<view wx:if="false"> WEBVIEW </view>
<view wx:elif="false"> APP </view>
<view wx:else="false"> MINA </view>
Overall a really nice separation of concerns. I could very well imagine being productive with this in no time. The onboarding experience of the documentation is pretty neat. The SDK is well made with (essentially an adapted) Chrome DevTools integrated and some VS Code like features like code completion.
Programming Concept
The overall programming concept reminds of OpenSocial (if anyone remembers that) where there is a baseline assumption of a logged in user whose social graph can be explored:
// OpenSocial
osapi.people.getViewer().execute(function (result) {
if (!result.error) {
console.log('Your name is ' + result.displayName + '!');
}
});
// WeChat
wx.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
wx.getUserInfo({
success: (res) => {
console.log(res.userInfo);
},
});
}
},
});
Component Library
They also have nice set of declarative components, think web components essentially, that you can create with WXML and interact with from JavaScript:
<map
id="myMap"
style="width: 100%; height: 300px;"
latitude=""
longitude=""
markers=""
covers=""
show-location
></map>
Page({
data: {
latitude: 23.099994,
longitude: 113.32452,
markers: [
{
id: 1,
latitude: 23.099994,
longitude: 113.32452,
name: 'T.I.T εζε',
},
],
covers: [
{
latitude: 23.099994,
longitude: 113.34452,
iconPath: '/image/location.png',
},
{
latitude: 23.099994,
longitude: 113.30452,
iconPath: '/image/location.png',
},
],
},
onReady: function (e) {
this.mapCtx = wx.createMapContext('myMap');
},
getCenterLocation: function () {
this.mapCtx.getCenterLocation({
success: function (res) {
console.log(res.longitude);
console.log(res.latitude);
},
});
},
});
And of course they have a <web-view>
.
Inner Mechanics
From what I understand, it's all running in an iframe, Chrome/WeChat DevTools then hides the container, and all you see is the WXML layer. When you look at the SDK's package contents, they make no real effort at hiding any of the inner mechanics: it's all HTML (of particular interest: /Applications/wechatwebdevtools.app/Contents/Resources/package.nw/html/standalone.html
), CSS (of particular interest: /Applications/wechatwebdevtools.app/Contents/Resources/package.nw/static/app.css
), and JavaScript (of particular interest: /Applications/wechatwebdevtools.app/Contents/Resources/package.nw/js/vendor/index.js
).
Finally there is a local web server running that allows them to link from the online docs to local URLs like 127.0.0.1:32123/minicode/VBZ3Jim26zYu, which in turn allows them to open code samples from the docs that are ready to play with a click in WeChat DevTools. The web server luckily only runs when the WeChat DevTools are open.