大眾點評點餐小程式開發經驗 - 原始碼解析

美團點評點餐發表於2017-03-05

作者介紹:週中堅,美團點評工程師,4年 Web 前端開發經驗,主要負責過會員卡、外賣、預訂、商家平臺等業務的前端開發,現在是美團點評點餐團隊的一員。

我們團隊的小程式開發經驗系列文章已經發布了4篇,這些文章主要介紹了小程式開發概述小程式的檢視層小程式的邏輯層小程式開發中碰到的坑(幾個設計例項)。相信大家看了這些文章,再結合官方文件已經可以毫無壓力地開發小程式了,但是為什麼有這些坑,是不是可以繞過去,怎麼排查問題,我們還想從源頭——小程式的原始碼的角度來嘗試分析,因此有了這篇原始碼解析。

程式碼結構

以 mac 電腦為例,首先進入應用程式資料夾,再右鍵微信開發者工具顯示包內容,最後讓我們進入 ./Contents/Resources/app.nw 目錄下就可以檢視小程式的原始碼了,程式碼結構如圖:

大眾點評點餐小程式開發經驗 - 原始碼解析

資料夾看起來很多,但命名還算清晰,現在讓我們先從開發者工具介面的角度來看下都用到了哪些檔案吧。

開發者工具

首頁

大眾點評點餐小程式開發經驗 - 原始碼解析

首頁的很多資訊可以和這個專案中的package.json對應起來,比如name, icon, version等。

代理

大眾點評點餐小程式開發經驗 - 原始碼解析

代理的設定在./app/dist/components/setting/setting.js,而使用者設定的儲存(包括後面要說的模擬器裝置、網路等資訊)是呼叫了./app/dist/stores/*.js方法。

選單

大眾點評點餐小程式開發經驗 - 原始碼解析

上圖可以看到我對選單做的一些定製。
選單的設定在./app/dist/common/menu/menu.js,動作在./app/dist/common/actions/actions.js,大家可以自行到程式碼中檢視檔案的require進一步分析。

裝置及網路

大眾點評點餐小程式開發經驗 - 原始碼解析

上圖可以看到我自己新增了一個裝置以及一個網路型別。
模擬器的裝置配置在./app/dist/config/DeviceModules.js,網路配置在./app/dist/common/jssdk/osInfoSdk.js

除錯工具

大眾點評點餐小程式開發經驗 - 原始碼解析

除錯工具是這一節最核心的內容了,乍一看微信的除錯工具和 chrome 的 DevTools 長的很像,檢視原始碼發現果然就是藉助 chrome 的 DevTools 實現的。

大眾點評點餐小程式開發經驗 - 原始碼解析

其中 Console, Sources, Network 就是直接使用的 DevTools, 而 Storage, AppData, Wxml, Sensor 是自己實現的。

參照 Storage, AppData, Wxml, Sensor 這些除錯工具,這些我們要自己新增一個其實非常簡單,只要在./app/dist/extensions目錄下新建一個資料夾,用html/css/js完成這個工具的功能,再改devtools.html將這個工具引入進來chrome.devtools.panels.create()即可,如圖:

大眾點評點餐小程式開發經驗 - 原始碼解析

有趣的是,在0.15.150201這個測試版中已經發現了一個名為Bluetooth的開發工具。

大眾點評點餐小程式開發經驗 - 原始碼解析

weapp

上面一節主要講的是小程式開發者工具的原始碼,我們藉助分析原始碼可以搞清楚代理是怎麼設定的,模擬器的裝置和網路如何新增,怎樣開發一個滿足自己特定需求的 DevTool。

這一節主要介紹我們寫的微信小程式的程式碼是如何變成頁面在使用者的終端執行的:

  • tpl 資料夾下是頁面模板。
  • onlinevendor/wcc 在編譯時把 wxml 檔案 轉為 js,onlinevendor/wcsc 在編譯時把 wxss 檔案轉化為 js,這也是編譯包比程式碼庫要大不少的重要原因。
  • trans 資料夾下有五個方法,其中 transConfigToPf 將配置轉成pageFrame,trans/transWxmlToHtml 將 wxml 轉成 dom tree, 再進一步用 webview 渲染,trans/transWxssToCss 將 wxss 轉成 css,提供 view 層樣式。
  • onlinevendor/WAService.js 提供了service 層幾乎一切功能。

pageFrame

首先是看一下剛才提到的 pageFrame,對應的 transConfigToPf 主要用字串替換的方式完成轉換。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self' *.qq.com 'unsafe-inline' 'unsafe-eval'">
  <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 -->

  <!--{{appconfig}}-->

  <!--{{pageconfig}}-->

  <!--{{WAWebview}}-->

  <!--{{reportSDK}}-->

  <!--{{webviewSDK}}-->

  <!--{{exparser}}-->

  <!--{{components_js}}-->

  <!--{{virtual_dom}}-->

  <!--{{components_css}}-->

  <!--{{allWXML}}-->

  <!--{{eruda}}-->

  <!--{{style}}-->

  <!--{{currentstyle}}-->

  <!--{{generateFunc}}-->

</head>

<body>
  <div></div>
</body>

</html>複製程式碼

appservice 頁面模板

開發者工具提供了封裝過的 wxml pannel, 我們並不能從中看到頁面完整的 dom 結構,但是用 $('*')選擇器我們可以看到頁面的 appservice 模板,看這段程式碼我們可以分析出小程式是如何使用 wxml, wxss, js 將頁面生成出來的。

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'">
  <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
  <script>
  var __wxAppData = {}
  var __wxRoute
  var __wxRouteBegin
  global = {}
  </script>
  <script></script><!-- 載入一堆script標籤 -->
</head>

<body>
  <p>
    開發者工具使用 nwjs 來模擬小程式的實現,幫助大家來開發和除錯微信小程式,所以這裡是一個 webview,但真實
    的手機端是執行在 jscore 中的,所以請不要使用任何 bom 物件。
  </p>
  <p>
    我們建議你先完整閱讀該開發文件,這將有助於更快地完成開發。如果發現我們的文件有任何錯漏,
    或者開發過程中有任何疑問或者你有更好的建議,歡迎通過下列郵箱聯絡我們

    weixin_developer@qq.com

    或者訪問微信小程式開發者社群提交問題:

    https://developers.weixin.qq.com
  </p>
  <script>
    window._____sendMsgToNW({
      sdkName: 'APP_SERVICE_COMPLETE'
    })
  </script>
</body>

</html>複製程式碼

WAService.js

WAService.js 是小程式頁面執行的核心方法,主要有幾大功能:

  • 內建的 report 方法定義
  • 微信小程式 API 封裝
  • WeixinJSBridge 封裝
  • appServiceEngine 模組
    // 內建的 report 方法定義,用於內部 API 的呼叫日誌 & 報錯記錄等。
    var Reporter = {
      surroundThirdByTryCatch,
      slowReport,
      speedReport,
      reportKeyValue,
      reportIDKey,
      thirdErrorReport,
      errorReport,
      log,
      submit,
      registerErrorListener,
      unRegisterErrorListener,
      triggerErrorMessage
    }
    // 微信小程式 API 封裝,所有文件中的 [API](https://mp.weixin.qq.com/debug/wxadoc/dev/api/)都在這裡封裝了,以showModal為例簡單分析一下
    showModal: function() {
      var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}
        , t = {
          title: "",
          content: "",
          confirmText: "確定",
          cancelText: "取消",
          showCancel: !0,
          confirmColor: "#3CC51F",
          cancelColor: "#000000"
      };// 預設值,此處比文件準確
      if (t = (0,
      f.extend)(t, e),
      a("showModal", t, {// 呼叫 jsbridge,見下方程式碼
          title: "",
          content: "",
          confirmText: "",
          cancelText: "",
          confirmColor: "",
          cancelColor: ""
      }))
          return t.confirmText.length > 4 ? void B("showModal", e, "showModal:fail confirmText length should not large then 4") : t.cancelText.length > 4 ? void B("showModal", e, "showModal:fail cancelText length should not large then 4") : void (0, // 各種校驗
          u.invokeMethod)("showModal", t, {
              beforeSuccess: function(e) {
                  e.confirm = Boolean(e.confirm)// 返回值處理
              }
          })
    }
    // 此處呼叫 WeixinJSBridge,此外還對每個 API 呼叫記 log,方便微信小程式的問題排查
    function a() {
      var e = Array.prototype.slice.call(arguments)
        , t = e[1];
      e[1] = function(e, n) {
          var o = e.data
            , r = e.options
            , i = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {}
            , a = r && r.timestamp || 0
            , s = Date.now();
          "function" == typeof t && t(o, n),
          Reporter.speedReport({
              key: "webview2AppService",
              data: o || {},
              timeMark: {
                  startTime: a,
                  endTime: s,
                  nativeTime: i.nativeTime || 0
              }
          })
      }
      ,
      WeixinJSBridge.subscribe.apply(WeixinJSBridge, e)
    }
    // WeixinJSBridge 封裝,底層是呼叫 WeixinJSCore
    e.WeixinJSBridge = {
      invoke: d,
      invokeCallbackHandler: p,
      on: h,
      publish: v,
      subscribe: g,
      subscribeHandler: y
    }
    // 內建的 jsbridge core
    WeixinJSCore = {
      invokeHandler,
      publishHandler
    }
    // setData 方法定義,邏輯層通過 setData 方法改變 virtual dom,改變 dom tree,從而改變檢視層
    // appServiceEngine 模組,提供 App 和 Page 相關的介面複製程式碼

總結

如果是為了原始碼分析而進行原始碼分析,我覺得大可不必,在小程式的場景下,原始碼分析的價值在於:

  • 官方文件不一定和實際情況是對齊的,開發時碰到不一致的情況可以查閱原始碼,以此為準。
  • 熟悉原始碼結構可以快速定位問題,提升開發效率,甚至給自己開發合適的 DevTool。
  • 小程式可以認為是前端的一個子集,而且相對封閉,開發時會有各種約束,查閱原始碼可以有助於小程式的設計。

本文對你有幫助?歡迎掃碼加入前端學習小組微信群:

大眾點評點餐小程式開發經驗 - 原始碼解析

相關文章