你可以在app.nw/app/dist/weapp/tpl/pageFrameTpl.js 和app.nw/app/dist/weapp/tpl/appserviceTpl.js 文件內(nèi)找到頁(yè)面的模板。
打開微信 web 開發(fā)者工具,然后輸入 openVendor() 便會(huì)打開 WeappVendor這個(gè)目錄,這里包含了 view 模塊和 service 模塊使用的幾個(gè)核心文件:
wcc 可執(zhí)行程序,用于將 wxml 轉(zhuǎn)為 view 模塊使用的 js 代碼,使用方式為wcc xxx.wxml
wcsc 可執(zhí)行程序,用于將 wxss 轉(zhuǎn)為 view 模塊使用的 css 代碼,使用方式為 wcsc xxx.wxss
WAService.js 提供 service 模塊大部分功能,下面會(huì)有詳細(xì)介紹
WAWebview.js 提供 view 模塊大部分功能,下面會(huì)有詳細(xì)介紹
view 頁(yè)面的 template 如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon"> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /> <script> var __webviewId__; </script> <!-- percodes --> <!--{{WAWebview}}--> <!--{{reportSDK}}--> <!--{{webviewSDK}}--> <!--{{exparser}}--> <!--{{components_js}}--> <!--{{virtual_dom}}--> <!--{{components_css}}--> <!--{{allWXML}}--> <!--{{eruda}}--> <!--{{style}}--> <!--{{currentstyle}}--> <!--{{generateFunc}}--> </head> <body> <div></div> </body> </html>
其中 <!-- percodes --> 會(huì)在 dev 模式開啟后被替換為一個(gè)時(shí)間錨點(diǎn),例如:
<script>var pageFrameStartTime = new Date();</script>
<!--{{WAWebview}}--> 會(huì)被 WAWebview.js 內(nèi)代碼替換
<!--{{WAWebview}}--> 到 <!--{{generateFunc}}--> 之間暫時(shí)沒有被使用到
<!--{{generateFunc}}--> 會(huì)被 wcc 命令生成后的 js 代碼替換
除了上面這些,頁(yè)面上還會(huì)被插入頁(yè)面和應(yīng)用的 style 標(biāo)簽,如:
<link rel="stylesheet" type="text/css" href="index.wxss">
這里的 wxss 文件包含的是原始 wxss 文件轉(zhuǎn)換后的 css
以及生成 DOM 的啟動(dòng)腳本:
<script> document.dispatchEvent(new CustomEvent("generateFuncReady", { detail: { generateFunc: $gwx('./page/index.wxml') } })) </script>
WAWebview.js 文件中的各個(gè)模塊(行號(hào)為 jsbeautify 之后代碼行號(hào),開發(fā)者工具版本:092300):
1-77 行: WeixinJSBridge 對(duì)象兼容層,這個(gè)大概只會(huì)在調(diào)試時(shí)用到,因?yàn)殚_發(fā)時(shí)和運(yùn)行時(shí)頁(yè)面都會(huì)被后臺(tái)以注入的方式添加 WeixinJSBridge 這個(gè)對(duì)象。我們可以通過這段代碼看到它暴露的方法: invoke invokeCallbackHandleron publish subscribe subscribe subscribeHandler。
78-235 行:Reporter 對(duì)象,它的作用就是發(fā)送錯(cuò)誤和性能統(tǒng)計(jì)數(shù)據(jù)給后臺(tái)
236-596 行:wx 對(duì)象,頁(yè)面的核心之一,一方面封裝 WeixinJSBridge 的 invokeMethod 方位為易于調(diào)用的形式(例如 redirectTo, navigateTo等),另一方面封裝 WeixinJSBridge 回調(diào)方法,調(diào)用者可以使用wx.onAppDataChange(callback) 添加數(shù)據(jù)變更的回調(diào)函數(shù),最后提供wx.publishPageEvent 發(fā)送頁(yè)面事件到后臺(tái)
607-1267 行:wxparser 對(duì)象,提供 dom 到 wx element 對(duì)象之間的映射操作,提供元素操作管理和事件管理功能
1268-1285 行:轉(zhuǎn)發(fā) window 上的 animation 和 transition 相關(guān)的動(dòng)畫事件到 exparser
1286-1313 行:訂閱并轉(zhuǎn)發(fā) WeixinJSBridge 提供的全局事件到 exparser
1324-1345 行:轉(zhuǎn)發(fā) window 上的 error 以及各種表單事件到 exparser
1347-3744 行:使用 exparser.registerBehavior 和exparser.registerElement 方法注冊(cè)各種以 wx- 做為標(biāo)簽開頭的元素到 exparser
3744-4498 行:virtual dom 渲染算法實(shí)現(xiàn),提供 diff apply render 等方法,該模塊接口基本與 virtual-dom 一致,這里特別的地方在于它所 diff 和生成的并不是原生 DOM,而是各種模擬了 DOM 接口的 wx element 對(duì)象
4599-4510 行:插入默認(rèn)樣式到頁(yè)面
從頁(yè)面 data 到 dom 的主要流程如下:
var vtree var rootNode document.addEventListener("generateFuncReady", function(e) { var generateFunc = e.detail.generateFunc; wx.onAppDataChange(function(obj) { // 合并 data 到現(xiàn)有 data DataStore.setData(obj.data) // 生成 virtual dom 的 javascript plain object var props = generateFunc(DataStore.getData()) // 第一次渲染 if (obj.options.firstRender) { vtree = createVirtualTree(props, true) rootNode = vtree.render() rootNode.replaceDocumentElement(document.body) wx.initReady() } else { var other_vtree = createVirtualTree(props, false) var patches = vtree.diff(other_vtree) patches.apply(rootNode) vtree = other_vtree document.dispatchEvent(new CustomEvent("pageReRender", {})); } }) })
上面的 DataStore 對(duì)象提供合并和獲取當(dāng)前頁(yè)面 data 對(duì)象的功能,其實(shí)現(xiàn)如下:
var DataStore = (function() { var data = {} return { getData: function() { return data }, setData: function(e) { for (var t in e) { for (var n = (0, parsePath)(t), o = data, a = void 0, s = void 0, c = 0; c < n.length; c++) Number(n[c]) === n[c] && Number(n[c]) % 1 === 0 ? Array.isArray(o) || (a[s] = [], o = a[s]) : "[object Object]" !== Object.prototype.toString.call(o) && (a[s] = {}, o = a[s]), s = n[c], a = o, o = o[n[c]]; a && (a[s] = e[t]) } } } })() // 解析 key 為 data 內(nèi)對(duì)象的路徑字符串 function parsePath(e) { for (var t = e.length, n = [], i = "", r = 0, o = !1, a = !1, s = 0; s < t; s++) { var c = e[s]; if ("\\" === c) s + 1 < t && ("." === e[s + 1] || "[" === e[s + 1] || "]" === e[s + 1]) ? (i += e[s + 1], s++) : i += "\\"; else if ("." === c) i && (n.push(i), i = ""); else if ("[" === c) { if (i && (n.push(i), i = ""), 0 === n.length) throw new Error("path can not start with []: " + e); a = !0, o = !1 } else if ("]" === c) { if (!o) throw new Error("must have number in []: " + e); a = !1, n.push(r), r = 0 } else if (a) { if (c < "0" || c > "9") throw new Error("only number 0-9 could inside []: " + e); o = !0, r = 10 * r + c.charCodeAt(0) - 48 } else i += c } if (i && n.push(i), 0 === n.length) throw new Error("path can not be empty"); return n }
可以看到,每次 data 變化之后,小程序就會(huì)開始整個(gè)頁(yè)面的 diff patch 過程。
對(duì)于原生實(shí)現(xiàn)的組件, exparser 會(huì)在監(jiān)視到數(shù)據(jù)變化后發(fā)送對(duì)應(yīng)事件到 WeixinJSBridge。
service 頁(yè)面會(huì)被被拼接為以下的樣子:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon"> <script> var __wxAppData = {} var __wxRoute var __wxRouteBegin </script> <script>var __wxConfig = {"pages":["page/index"], // app 相關(guān)各種配置 }</script> <script src="http://70475629.appservice.open.weixin.qq.com/asdebug.js"></script> <script src="http://70475629.appservice.open.weixin.qq.com/WAService.js"></script> <script src="http://70475629.appservice.open.weixin.qq.com/app.js"></script> <script> __wxRoute = 'page/index'; __wxRouteBegin = true </script> <script src="http://70475629.appservice.open.weixin.qq.com/page/index.js"></script> </head> <body> <script> window._____sendMsgToNW({ sdkName: 'APP_SERVICE_COMPLETE' }) </script> </body> </html>
除了配置和開發(fā)者編寫的頁(yè)面、app.js,頁(yè)面還在加載了 asdebug.js 和 WAService.js 兩個(gè)文件。
asdebug.js 文件位于 nwjs 項(xiàng)目目錄下,路徑為app/dist/weapp/appservice/asdebug.js。 它包含了兩個(gè)部分,一個(gè)是 WeixinJSBridge 針對(duì) service 模塊的實(shí)現(xiàn),另一塊是一些方便命令使用的接口, 例如:help() 會(huì)告訴你一些可用的函數(shù):
該文件只會(huì)在開發(fā)者工具內(nèi)被引入,如果小程序在微信內(nèi)運(yùn)行,應(yīng)該會(huì)由微信底層提供 WeixinJSBridge。
WAService 負(fù)責(zé) service 模塊的一些核心邏輯,它包含以下部分 (行號(hào)為 jsbeautify 之后代碼行號(hào),開發(fā)者工具版本:092300):
現(xiàn)在的 WAService 還有有很多地方依賴 window 對(duì)象,所以很有可能它在微信中和開發(fā)者工具內(nèi)一樣,依然運(yùn)行于 webview 標(biāo)簽之內(nèi)。