從微信小程序開發者工具源碼看實現原理(二)- - 小程序技術實現

wxml與wxss的轉換

打開小程序開發者工具,在調試控制檯輸入openVendor就會打開小程序的WeappVendor目錄,該目錄包括如下幾個主要內容:css

  • wcc可執行程序,用於將wxml內容轉換爲js內容,執行方式:wcc xxx.wxml
  • wcsc可執行程序,用於將wxss內容轉換爲視圖可以使用css內容,執行方式 wcsc xxx.wxss
  • 不一樣版本小程序基礎庫x.x.x.wxvpkg, 裏面包含小程序基礎庫WAService和WAWebview

一、wxml使用wcc轉換

正如上面分析的,經過調用小程序內置的可執行程序執行wcc xxx.wxml,將指定的wxml轉換爲js腳本內容。其具體用法能夠--help查看,以下圖:
html

小程序開發者工具底層會將小程序項目中全部wxml轉爲js內容,能夠理解爲爲每一個頁面wxml進行了註冊。例如咱們小程序demo有兩個頁面index.wxml和logs.wxml,其中index.wxml內容以下圖:

經過wcc可執行程序生成的相關頁面註冊的代碼以下圖所示:
web

從頁面轉換的js內容來看,主要記錄標籤的屬性及其值等。另外,轉化的js腳本提供最核心的方法是$gwx方法,能夠在開發者工具開發控制檯訪問到,其方法簽名以下:json

$gwx = function(path, global) {
    ...
    return function(env,dd,global){
        ...
    }
}

該方法根據傳入具體的頁面wxml路徑,找到對應的頁面,而後返回一個函數,向該函數傳入頁面渲染須要的數據(即Page中data對象)就能獲得該頁面wxml對應的js對象形式表示的dom樹。其實每一個小程序頁面在頁面準備初始化渲染時會調用這個$gwx方法,調用以下圖所示:
小程序

另外,咱們直接在開發者工具的控制檯直接調用,輸入以下語句,能夠獲得的js對象表示以下圖:瀏覽器

$gwx('./pages/index/index.wxml')({show: true});

二、wxss使用wcsc轉換

wcsc可執行程序用於處理wxss,小程序底層使用該可執行程序轉換爲js內容來處理頁面css的引用。首先咱們來看下wxss提供功能,以下圖:
緩存

小程序底層使用wcsc -db -pc來轉換對應wxss文件的,其生成的js內容以下圖eval函數中的字符串所示:
微信

生成是js主要做用:app

  • 添加尺寸單位rpx轉換,可根據屏幕寬度自適應
  • 提供setCssToHead方法將轉換後的css內容添加到header

開發者工具主入口

小程序開發者工具的主入口也是小程序的啓動入口,是整個小程序開發者工具的控制層,例如建立或者銷燬webview等。它主要包括小程序的視圖層的webview,業務邏輯層webview,調試器的webview和編輯區的webview幾大塊;咱們只需關心視圖層和業務邏輯層的webview。啓動入口對應這一個index.html頁面,裏面引入主入口js,以下:框架

<div id=container class=container></div>
<script src=../js/core/index.js> </script>

最終初次進入小程序主頁後,主入口index.html的渲染html中有關視圖層和業務邏輯層結果以下圖所示:

由此能夠證實,小程序開發者工具業務邏輯層是在webview中執行的,該webview雖然提供瀏覽器相關接口,可是小程序只是在其中單純的執行js代碼。

在咱們小程序demo中有index首頁navigateTo到logs日誌頁時,能夠看主入口dom的變化,見下圖:

從dom變化能夠看出,調用navigateTo至關於新打開一個webview加載另外一個頁面視圖,隨着打開的頁面愈來愈多,內存就比較吃緊。這也是爲何小程序對打開頁面數量有限制的緣由。從圖中可能也看出了,爲啥多加載了一個pageframe.html的webview,這個是幹什麼用的?後面會說到它的做用。

視圖層頁面的實現

咱們在寫小程序頁面視圖時,貌似並不關心webview中的html結構,這些都是小程序底層幫咱們實現, 咱們只須要寫頁面ui和業務邏輯便可。下面咱們來看看view視圖層小程序幫咱們作了什麼。先來看一下視圖層pageframe.html的模板:

其中,模板中的註釋佔位符通過後臺服務處理會注入不一樣js腳本,主要js內容:

  • <!-- deviceinfo -->: 暫時無用的佔位符,會被空字符串""替換
  • <!-- jsdebug -->: 提供視圖層的WeixinJSBridge模擬實現以及一些事件的處理如enablePullDownRefresh,其對應的js內容爲extensions/pageframe/index.js.
  • <!-- plugincode -->: 小程序插件相關的代碼,若小程序使用插件則會注入
  • <!-- wxmlcode -->: 調用wcc可執行命令生成的小程序註冊全部頁面wxml對應的js腳本內容
  • <!-- wxsscode -->: 調用wcss可執行命令生成的js腳本內容,提供注入css到頁面的js方法;該內容會提早注入全局的css。
  • <!-- wxappcode -->: 小程序當前視圖頁面相關的配置json內容以及wxml和wxss轉換爲js的內容,可在控制檯輸入__wxAppCode__看相關信息
  • <!-- vendorlist -->: 小程序爲視圖層注入的基礎庫功能,包括WAWebview.js、WARemoteDebug.js和hls.js

視圖層頁面實現技術細節

本節來詳細介紹下小程序視圖層實現的一些技術細節

視圖層快速打開原理

首先看一下小程序官網頁面層級準備小節描述的一段內容:

wx.navigateTo會建立一個新的頁面層級,對於每個新的頁面層級,視圖層都須要進行一些額外的準備工做。在小程序啓動前,微信會提早準備好一個頁面層級用於展現小程序的首頁。除此之外,每當一個頁面層級被用於渲染頁面,微信都會提早開始準備一個新的頁面層級,使得每次調用wx.navigateTo都可以儘快展現一個新的頁面。

正如上文提到的,咱們在打開pages/logs/logs視圖頁面時,發現dom中多加載了一個__pageframe__/pageframe.html的視圖層,其模板內容正如上一節描述的。這個視圖層的做用正是爲了小程序提早爲一個新的頁面層準備的。

小程序每一個視圖層頁面內容都是經過pageframe.html模板來生成的,包括小程序啓動的首頁;下面來看看小程序爲快速打開小程序頁面作的技術優化:

  • 首頁啓動時,即第一次經過pageframe.html生成內容後,後臺服務會緩存pageframe.html模板首次生成的html內容
  • 非首次新打開頁面時,頁面請求的pageframe.html內容直接走後臺緩存
  • 非首次新打開頁面時,pageframe.html頁面引入的外鏈js資源(如上圖所示)走本地緩存

這樣在後續新打開頁面時,都會走緩存的pageframe的內容,避免重複生成,快速打開一個新頁面。

視圖層新打開頁面流程

其實在小程序開發者工具實現中,在建立每一個視圖層頁的webview時,都會爲其綁定了onLoadCommit事件(它會在頁面加載完成後觸發,包含當前文檔的導航和副框架的文檔加載)。初始時webview的src會被指定爲空頁面地址http://127.0.0.1:${global.proxyPort}/aboutblank?${c},其中c爲對應webview的id。webview從空頁面到具體頁面視圖的過程以下:

  • 空頁面地址webview加載完畢後執行事件中的reload方法,即設置webview的src爲pageframe地址http://127.0.0.1:${global.proxyPort}/__pageframe__/pageframe.html

加載完成後,設置其src爲pageframe.html:

  • 新的src內容加載完成後再次觸發onLoadCommit事件但根據條件不會執行reload方法。

  • pageframe.html頁面在dom ready以後觸發注入並執行具體頁面相關的代碼,此時經過history.pushState方法修改webview的src可是webview並不會發送頁面請求。

pageframe.html模板生成的內容除小程序基礎庫視圖層的底層功能以外,還包括小程序全部頁面的模板信息、配置信息以及樣式內容,這些均可以在生成pageframe.html的dom結構中窺探一二。

那麼,既然每一個視圖層頁面由pageframe模板生成,那麼小程序每一個頁面獨有的頁面內容如dom和樣式等如何生成呢,這主要是利用nw.js的executeScript方法來執行一段js腳原本注入只與當前頁面相關的代碼,包括當前頁面的配置,注入當前頁的css以及當前頁面的virtual dom的生成,注入的代碼以下:

最終生成的js代碼(拿pages/index/index爲例)以下圖:

其中:

history.pushState('','', 'http://127.0.0.1:59524/__pageframe__/pages/index/index')

這句代碼的做用修改當前webview的src,由於視圖層的webview的src爲pageframe.html,經過這句代碼將其變動爲具體的頁面地址。

另外,須要注意的是nw.js的executeScript方法注入的代碼是須要時機的,須要等到視圖層的初始化工做準備ready以後才行,那麼這個時機如何知道呢?細心的讀者可能發現,在pageframe模板的最後一個script的內容:

<script>alert("DOCUMENT_READY")</script>

這個從字面意思能夠看出此時應該是頁面dom ready的一個時機,經過alert來進行通知。
alert能通知消息?固然能夠的,在nw.js的webview中alert、prompt對應的彈框是被會阻止的,那麼經過爲webview綁定dialog事件來知道是那種彈框類型,以及提示內容,具體能夠查看這篇文章。例如小程序開發者工具綁定事件部分代碼(便於查看有修改):

this.webview.on('dialog', (a) => {
   a.preventDefault();
   const b = a.messageType || '',
            c = a.messageText,
            d = a.dialog;
    if ('alert' === b) {
        c === 'DOCUMENT_READY' && (this.documentReady = !0, this.loadPage())
    }
    ...
})

這樣方法loadPage就會觸發nw注入並執行頁面相關的代碼。最終生成的頁面視圖對應dom結構以下圖:

能夠看出,視圖頁面生成的dom結構中,document.body已無pageframe.html模板中對應body中的script內容,這是由於視圖層的WAWebview.js在經過virtual dom生成真實dom過程當中,它會掛載到頁面的document.body上,覆蓋掉pageframe.html模板中對應document.body的內容。

業務邏輯層頁面的實現

小程序將全部業務代碼置於同一個線程中運行,小程序開發者工具是在一個webview中執行;webview中的appservice.html引入業務代碼js以外,還有後臺服務內嵌的一些基礎功能代碼,appservice.html對應的模板內容以下:

通過後臺服務的處理,模板中的各類佔位符就被對應的js內容注入,下面就來簡單說幾個重要的注入內容:

  • <!-- wxconfig -->: 小程序的配置項,包括用戶自定義與系統默認的整合結果。在控制檯輸入__wxConfig能夠看出打印結果
  • <!--devtoolsconfig-->:小程序開發者配置,包括開發者工具版本,設置的請求域名、默認開發者工具的設置以及訪問Native方法須要permission的方法。控制檯輸入__devtoolsconfig能夠看到其對應的信息
  • <!-- wxmlxcjs -->: 調用wcc可執行命令生成的小程序註冊全部頁面wxml對應的js腳本內容,js腳本提供$gwx方法。
  • <!-- asdebug -->: 提供業務邏輯層的WeixinJSBridge模擬以及一些針對開發者工具的接口,如在控制檯輸入help能夠看到提供的接口。其內容爲extensions/appservice/index.js.
  • <!-- vendorlist -->: 爲業務邏輯層注入WAService.js,爲業務邏輯層提供小程序底層基礎庫的功能

此外,開發者工具服務還在appservice.html的body注入一段腳本,腳本的做用是將業務邏輯代碼經過script動態注入到head中執行,這段代碼以下:

最終生成的appservice.html中的head狀況以下圖所示:

經過上圖能夠看出,咱們寫的頁面邏輯都引入到頁面中,而且分別從app.js開始一一執行;小程序代碼調用Page構造器的時候,小程序基礎庫會記錄頁面的基礎信息,如初始數據(data)、方法等。須要注意的是,若是一個頁面被屢次建立,並不會使得這個頁面所在的JS文件被執行屢次,而僅僅是根據初始數據多生成了一個頁面實例(this),在頁面JS文件中直接定義的變量,在全部這個頁面的實例間是共享的。

參考

相關文章
相關標籤/搜索