隨著小程式的發展與功能的逐步完善,越來越多的產品需要小程式與 APP 的功能能有一些共性,社區跨平臺的解決方案越來越多,比如 taro 等為代表的把一套代碼編譯成多端運行的機制,本文會使用 Swift 作為原生語言,在 iOS 應用上運行一個小程式 Demo, 使用 Android && React Native 也可以采用同樣的思路實作,
相關代碼倉庫: https://github.com/taixw2/rmini
編譯層
編譯的目的是為了抹平小程式的與 H5 的差異,利用 Vue 實作資料系結,利用 Web Component 實作小程式的組件功能,
從官網檔案中可以看出來,運行一個小程式需要框架(資料系結渲染)、組件(小程式渲染單元)、api(與原始互動的能力),
框架實作
轉換成單頁應用(一種可行的方案)
把所有頁面打包成一個 js, 再由 js 管理所有的路由和狀態,這種方案適合在 web 端運行,并且是單引擎的方案,在模擬原生的右滑回傳等效果也會不盡人意,
轉換成多頁面
眾所周知,小程式是一個雙引擎的框架,上面的方案顯然不能達到要求, 雙引擎的特點是在運行 javascript 的黑盒子中,無法訪問到 DOM && BOM 等,將所有的邏輯代碼在原生的 JavascriptCore 中運行,WebView 中的 Javascript 引擎負責資料系結,需要解決的難點是 JavascriptCore 中的 setData 怎么通知 WebView 渲染, WebView 的事件怎么執行 JavascriptCore,接著往下看,
抹平WXML
wxml 是一種類 html 標記語言,他負責所有的渲染規則,包括條件渲染、串列渲染、資料系結等,與其再實作一種框架,還不如直接利用 Vue 實作同樣的功能,再利用各種轉換庫將 wxml 中的事件轉換成 Vue 能夠識別的事件,如利用 post-html 可以做到如下的轉換:
每一個事件系結的方法全都在原生的 JSContext 中運行,所以此時的事件只需要傳遞給 JSContext 的作用,
抹平WXSS
wxss 作為小程式的樣式語言,其余 css 的主要區別就是多了一個 rpx 單位,以下是官網的換算表:
根據上表可得知, rpx = (750 / 螢屏寬度) * px;
在傳統的移動端頁面,我們的高清方案,一般需要獲取 dpr, 然后修改動態修改 viewport 和 html 上的 font-size,但是小程式的代碼因為是放在了設備本地,所以可以在下載小程式頁面之后,我們還有一次編譯機會,這時就可以把 rpx 根據當前設備的螢屏寬度替換成對應的 px,
還有一個 @import,則利用 scss 或 less 就可以合并到同一個 css 檔案中,
而全域樣式則可以在構建 WXML 的時候再植入進入
抹平組件
組件具有獨特的功能和自己的渲染規則,比如 scroll-view 具有 scroll-x 和 scroll-y 等屬性控制滾動條,在 HTML5 中有一個重大的功能web-component,它能夠自定義 html 元素,并且能夠監控屬性的變化,非常適合實作小程式組件,如:(使用了 lit-element 框架)
這里用了 lit-element 這個框架,能夠簡化一些操作,
抹平 Page 和 App
App 負責整個應用的生命周期以及存一些全域的資料,getApp 能獲取到 app 的資訊, 所以類似的結構可能是這樣的:
getApp 能夠直接訪問到內部物件,并且在最頂層宣告,這樣每一個的地方都能訪問到 getApp,
初始化一個頁面都需要是實體化 PageClass, 即使再次進入(不是回傳到這個頁面)這個頁面頁需要再次重新實體化,每次實體化都需要關聯一個 webviewId, 這個 ID 與原始的 webview 關聯,這樣每個 PageClass 中的 setData 都能找到對應的 webview 進行再次渲染,所以對應的代碼可能是這樣的:
抹平 API
通過 API 能夠直接呼叫原生的功能,比如 wx.request, 如果直接在 webview 中的 JSContext 中運行的話,則可能存在跨域,但是放在原生就不會存在這個問題,
實作JSContext 呼叫原生代碼的功能,需要給 JSContext 中植入一個 JSBridge,如: JSBridge.invoke 和 JSBridge.on, invoke 負責同步任務,on 負責異步任務,原生再利用反射(原生的反射真麻煩)呼叫對應的原生方法,原生可以利用 while(true) 掛起 JSContext,既可以達到同步和異步的方法,
打包 Javascript
Javascript 代碼打包后被放在 JavascriptCore 中運行,唯一與 Webview 中的 JSContext 打交道的只有 setData, 先看一下打包流程:
- 利用 App.json 構建入口檔案
- 利用 rollup 等工具將所有 Javascript 打包成一個檔案(目前沒有分包)
打包流程及其簡單,接下來看一下兩個 Javascript 引擎的互動程序,
打通 JSContext 到 WebView JavascriptCore
每次進入一個頁面的時候都需要為這個頁面的 webview 分配一個 id, 這個 id 至關重要,作為 native 與 JSContext (原生運行 javascript 的背景關系物件) 與 webview 互動的唯一標識,JSContext 中需要實體化一個新的 PageClass 關聯這個 id, native 中通過 id 保留 webview 的參考,在 JSContext 中植入一個 JSBridge 用于與原生互動,如: JSBridge.setData(webviewId, appId, data), 當 JSBridge 的 setData 被呼叫后,通過 appId + webviewId 就能找到對應的 webview, 再將 setData 傳入 webview 中,在 Vue 接收到 data 后進行渲染, 整個程序如圖:
打通 Webview JavascriptCore 到 JSContext
有了前面的鋪墊,接下來再看 webview 如何呼叫 JSContext 的方法, Webview 唯一能與 JSContext 互動的方式只有事件,事件觸發后,需要通過某種方式觸發 JSContext 中的方法,最后呼叫 setData 再回傳來重新渲染 webview,
webview 中系結的方法名眾多,如:bindtap="a", bindtap="b", bindtap="c" 等,但是可以通過 “抹平 WXML” 的時候最終只保留一個出口,如:
v-on:click="callClick('a', $event)" 等,這樣 vue 中的 method 只需要實作對應的幾個事件便可:
結尾
利用原生作為橋梁,在兩個引擎之間通信,webview 中的 JSContext 負責接收渲染通知,以及發送事件到 Native 的 JSContext 中,JSContext 獨立運行,所以既訪問不到 window 物件,也訪問不到 document 物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/157942.html
標籤:JavaScript
上一篇:輕輕松松學CSS:Flex布局
