wxml與wxss的轉換
打開小程序開發者工具,在調試控制台輸入openVendor
就會打開小程序的WeappVendor目錄,該目錄包括以下幾個主要內容:
- wcc可執行程序,用於將wxml內容轉換為js內容,執行方式:
wcc xxx.wxml
- wcsc可執行程序,用於將wxss內容轉換為視圖可使用css內容,執行方式
wcsc xxx.wxss
- 不同版本小程序基礎庫x.x.x.wxvpkg, 里面包含小程序基礎庫WAService和WAWebview
1、wxml使用wcc轉換
正如上面分析的,通過調用小程序內置的可執行程序執行wcc xxx.wxml
,將指定的wxml轉換為js腳本內容。其具體用法可以--help查看,如下圖:
小程序開發者工具底層會將小程序項目中所有wxml轉為js內容,可以理解為為每個頁面wxml進行了注冊。例如我們小程序demo有兩個頁面index.wxml和logs.wxml,其中index.wxml內容如下圖:
通過wcc可執行程序生成的相關頁面注冊的代碼如下圖所示:
從頁面轉換的js內容來看,主要記錄標簽的屬性及其值等。另外,轉化的js腳本提供最核心的方法是$gwx
方法,可以在開發者工具開發控制台訪問到,其方法簽名如下:
$gwx = function(path, global) {
...
return function(env,dd,global){
...
}
}
該方法根據傳入具體的頁面wxml路徑,找到對應的頁面,然后返回一個函數,向該函數傳入頁面渲染需要的數據(即Page中data對象)就能得到該頁面wxml對應的js對象形式表示的dom樹。其實每個小程序頁面在頁面准備初始化渲染時會調用這個$gwx
方法,調用如下圖所示:
另外,我們直接在開發者工具的控制台直接調用,輸入如下語句,可以得到的js對象表示如下圖:
$gwx('./pages/index/index.wxml')({show: true});

2、wxss使用wcsc轉換
wcsc
可執行程序用於處理wxss,小程序底層使用該可執行程序轉換為js內容來處理頁面css的引用。首先我們來看下wxss
提供功能,如下圖:
小程序底層使用wcsc -db -pc
來轉換對應wxss文件的,其生成的js內容如下圖eval函數中的字符串所示:
生成是js主要作用:
- 添加尺寸單位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文件中直接定義的變量,在所有這個頁面的實例間是共享的。