小紅書(shū)自研小程序:電商體驗與效果優(yōu)化的運行時(shí)體系設計

發(fā)布時(shí)間:2022-11-21 11:16    發(fā)布者:焦點(diǎn)訊

小紅書(shū)自研小程序:電商體驗與效果優(yōu)化的運行時(shí)體系設計


一、背景介紹


小程序在其誕生后的幾年內,憑借其簡(jiǎn)單、輕量、流暢、無(wú)需安裝等特點(diǎn),引來(lái)了爆發(fā)式的增長(cháng)。伴隨小紅書(shū)電商業(yè)務(wù)的發(fā)展,我們洞察到越來(lái)越多的商家和品牌大客戶(hù)有自己定制化需求場(chǎng)景,傳統的電商和薯店存在下面三大問(wèn)題:


為了解決上述問(wèn)題,并快速打通基于小紅書(shū)體系的支付與賬號體系。過(guò)去的一年內,我們踏上了自研小程序之路。目前,在小紅書(shū)店鋪主頁(yè)、 筆記詳情、品牌專(zhuān)區、開(kāi)屏均可喚起小程序。



本文將主要介紹小紅書(shū)進(jìn)行小程序自研時(shí)的一些業(yè)務(wù)背景及工程化、容器能力的落地方案,以及運行時(shí)針對雙線(xiàn)程架構 bridge,framework 能力的設計。


二、運行時(shí)工程能力建設



2.1 小程序 "運行時(shí)" 定義

運行時(shí)在不同語(yǔ)言中含義有所不同,但基本可以概括為 「運行在代碼執行階段的代碼」,類(lèi)似 vue-runtime, 提供了對于頁(yè)面狀態(tài)的劫持,生命周期的解析,api 的調用能;nodejs 提供了 JS 運行時(shí)執行能力等。小程序 “運行時(shí)” 則提供了在不同線(xiàn)程內,借助 Bridge 消息通道,進(jìn)行邏輯調度的能力。


那么可以基本概括為:運行在小程序代碼執行階段、用于提供在獨立線(xiàn)程中操作其他線(xiàn)程的頁(yè)面(或視圖),正確響應用戶(hù)交互行為、并調度用戶(hù)業(yè)務(wù)邏輯能力的代碼。


2.2 小程序基礎架構

小紅書(shū)小程序也是對齊業(yè)界經(jīng)典架構進(jìn)行建設:

經(jīng)典雙線(xiàn)程架構


經(jīng)典架構下,運行時(shí)主要分為 渲染層 - Render、邏輯層 - Service。Service 用于與系統能力進(jìn)行交互,在安全的 JS 線(xiàn)程內調度用戶(hù)業(yè)務(wù)邏輯。而 Render 則負責接受渲染指令、進(jìn)行視圖的繪制與用戶(hù)交互的響應。邏輯層與渲染層則通過(guò) js-bridge 進(jìn)行消息的通訊,容器則負責接受 api 指令進(jìn)行端能力的調用。


之所以需要一個(gè)獨立的線(xiàn)程來(lái)執行 JS,其主要目的是為了限制 JS 靈活性。為了提供一個(gè)可用的 JS 環(huán)境,其實(shí)也有比較多的方案。比如,我們可以使用瀏覽器內核提供的 Service Worker ,來(lái)單獨運行 service 層 JS 代碼;蛘呶覀兛梢允褂枚鄠(gè) webview 實(shí)例來(lái)分別承載雙端 js 的執行環(huán)境。


2.3 容器架構實(shí)現

按照經(jīng)典架構的設計,我們需要在三端 (iOS、android、小程序開(kāi)發(fā)者工具) 提供面向雙線(xiàn)程的容器方案。在不同的容器環(huán)境下,渲染層和邏輯層選用的方案會(huì )存在一定差異,小紅書(shū)三端選用容器的分布如下:


雖然運行環(huán)境存在一定差異,但容器對于基礎庫和業(yè)務(wù)腳本的加載順序是基本一致的。我們可以將整個(gè)啟動(dòng)階段拆解為下面幾個(gè)階段:


首先,當用戶(hù)點(diǎn)擊時(shí),會(huì )經(jīng)歷一個(gè)基本的啟動(dòng)過(guò)程。


在這個(gè)啟動(dòng)流程的背后,會(huì )對應著(zhù)上面提到渲染層 webview 容器被加載出來(lái)。于此同時(shí),在用戶(hù)看不到的地方,容器會(huì )進(jìn)行邏輯層 v8/JsCore 的初始化, 同時(shí)會(huì )加載小程序的基礎庫代碼。


腳本注入結束后,容器會(huì )立即通知「運行時(shí)邏輯層框架」進(jìn)行依賴(lài)分析、并準備初渲染數據。


渲染層接受到 initialData 消息后,會(huì )進(jìn)行后續渲染操作,用戶(hù)即刻看到了頁(yè)面的內容 至此,初渲染的流程基本結束。


當然,實(shí)際的容器的啟動(dòng)過(guò)程中的流程會(huì )更加復雜,整個(gè)啟動(dòng)流程可以用下面的這張圖來(lái)表示:

黑色、藍色、橙色分別代表了 端側、邏輯層、渲染層三個(gè)線(xiàn)程


實(shí)際場(chǎng)景中,容器還面臨更多的挑戰,比如如何確保雙線(xiàn)程的是否 ready,再進(jìn)行消息的推送等。核心在于,我們通過(guò)不同線(xiàn)程的容器,完成了頁(yè)面渲染行為的控制。


可以看到,上述啟動(dòng)流程中容器側分別在 Render 和 Service 分別注入了 page.render.js 和 service.js 的業(yè)務(wù)代碼。那么如何進(jìn)行業(yè)務(wù)代碼構建,來(lái)分別在雙線(xiàn)程下執行呢?這就需要依靠前端工程化的能力來(lái)實(shí)現了。


2.4 實(shí)現基礎架構的工程化能力

通過(guò)前端工程化能力,我們可以對資源進(jìn)行分類(lèi)構建,小紅書(shū)小程序使用 webpack 作為工程化構建工具。通常,小程序的構建分兩塊,一塊是針對基礎庫的打包,一塊是針對業(yè)務(wù)組件的構建。


基礎庫的打包需要構建基礎庫代碼,產(chǎn)出分別用于提供運行時(shí)框架能力的 render.base.js 及 service.base.js


而業(yè)務(wù)組件的構建,則相對復雜。一個(gè)原生小程序組件或頁(yè)面通常包含下面四個(gè)文件:


通過(guò)拆分多個(gè)文件,我們可以在構建時(shí)指定入口依賴(lài),將對應的依賴(lài)打入所需要的模塊內,在工程構建時(shí),需要對文件進(jìn)行分類(lèi)打包:


我們使用 loader 作為 webpack 的 entry 入口進(jìn)行構建,每個(gè)頁(yè)面都會(huì )作為一個(gè) entry 獨立打包。這使得從行為上來(lái)說(shuō)小程序更像一個(gè) MPA(多頁(yè)應用)。入口側會(huì )進(jìn)行 app.json 的校驗,對配置以頁(yè)面維度來(lái)進(jìn)行解析,針對小程序業(yè)務(wù)代碼,會(huì )分別構建出 page.render.js 和 service.js 分別交給不同的線(xiàn)程進(jìn)行加載(如上圖)。


構建會(huì )將代碼打包成 UMD 格式文件,當在不同線(xiàn)程內執行基礎庫腳本時(shí),部分腳本會(huì )自動(dòng)執行,端側只需要關(guān)注容器加載 Js腳本的時(shí)機及消息發(fā)送的順序即可。


運行時(shí)基礎能力與框架

容器和工程化能力是小程序運行的基石,但小程序之所以可以做到高效開(kāi)發(fā)、并擁有極強的跨平臺能力和優(yōu)秀的體驗,這也得益于在框架底層提供了完善的組件及模塊化能力,更有豐富的 api 來(lái)滿(mǎn)足原生場(chǎng)景下各種系統能力調用的述求。


3.1  運行時(shí)總架構


·這張圖主要將運行時(shí)架構分為了渲染層、邏輯層和jsBridge:

·渲染層面向業(yè)務(wù)提供了組件、沙箱、性能收集等框架能力,這一層業(yè)務(wù)是無(wú)法接觸到的

·邏輯層則在 JsContext 內提供了 invoke 層來(lái)與端側進(jìn)行數據交互

·邏輯層通過(guò)適配層,完成導航、Render和頁(yè)面實(shí)例的管理

·邏輯層內核主要用于向業(yè)務(wù)代碼的執行環(huán)境,提供 Page、Component、behavior ·這類(lèi)能力,并預置 JS Polyfill 來(lái)確保業(yè)務(wù)的 js 正常運行。

·JSON schema 則用于定義 api 標準結構和定義,并通過(guò) js-bridge 層完成端能力調用與通訊


3.2 基礎能力分布

為了豐富小程序的基礎能力,初期我們盤(pán)點(diǎn)了業(yè)界的功能矩陣,盡可能豐富小紅書(shū)小程序的基礎能力,目前運行時(shí)的基礎能力分布如下:

灰色部分為暫未支持的能力


其中包含了:


·App, Compnent, Page 等基礎能力

·網(wǎng)絡(luò )、文件系統,設備等 API 能力

·xhs-view, xhs-button 等面向業(yè)務(wù)的組件能力

·目前,矩陣列出的功能,在小紅書(shū)小程序基礎庫 ≥ v3.32.x 版本上已經(jīng)得到支持。


3.3 雙線(xiàn)程框架能力建設

熟悉小程序語(yǔ)法的同學(xué)都知道,小程序可以通過(guò) Page、Component 來(lái)進(jìn)行非常靈活的組件化開(kāi)發(fā)。通過(guò) selectComponent 、triggerEvent這類(lèi)功能可以非常方便的進(jìn)行 子 → 父 或 父 → 子 實(shí)例的追溯,這就要求框架側需要維護組件之間的依賴(lài)關(guān)系。


實(shí)現這種架構有多種思路,不同廠(chǎng)商的做法也不同。譬如微信在 Page 體系和 Component 自定義組件的實(shí)現上就采用了不同的設計。微信在渲染側通過(guò) Exparser模塊 完成小程序內的所有組件,包括內置組件和自定義組件組織管理。


小紅書(shū)側在渲染層則是 Fork Vue 框架,通過(guò)定制 Vue 的一些能力來(lái)完成頁(yè)面渲染工作。借助 Vue優(yōu)秀的組件化能力的來(lái)完成 Page, Component 的渲染工作。在邏輯層,則通過(guò)消息維護一棵類(lèi) vdom 樹(shù) , 來(lái)完成 視圖 ←→ 邏輯 的映射與綁定關(guān)系,整個(gè)關(guān)系大概如下圖所示:


3.4 事件系統

有了上述基礎能力和雙線(xiàn)程架構,運行時(shí)還需要實(shí)現一套事件系統,讓 UI 界面與用戶(hù)產(chǎn)生互動(dòng)。事件通常分為兩塊,一塊是服務(wù)于用戶(hù)的手勢交互,比如用戶(hù)的點(diǎn)擊 tap, 長(cháng)按 longtap 等事件,另一塊則是渲染層交互組件的回調時(shí)間,譬如 swiper 組件的 onChange 等回調。


在小程序的事件系統下,我們把這些用戶(hù)的手勢操作和組件回調,進(jìn)行攔截與收集,全部轉入消息隊列轉發(fā)到邏輯線(xiàn)程。每條消息攜帶自己的實(shí)例 ID,找到邏輯層實(shí)例進(jìn)行對應函數的觸發(fā)。


3.5 bridge 能力設計

框架側借助 bridge 通道可以非常方便進(jìn)行消息的轉發(fā)。但實(shí)際上,一條消息需要經(jīng)過(guò)多次序列化和反序列化,才可以到達“目的地”。小紅書(shū)小程序的 bridge 側是如何實(shí)現的呢?


我們以渲染層事件消息舉例,當渲染層收到一條點(diǎn)擊 消息,會(huì )經(jīng)過(guò)如下幾個(gè)階段:


不同容器下,對 webview 內核消息的攔截機制不同,ios 使用 messageHandler, android 則使用 console 通道攔截消息,但內核底層對消息的處理流程基本一致。


這個(gè)過(guò)程可以簡(jiǎn)單描述為以下幾個(gè)環(huán)節:


·Render 側發(fā)送 postMessage 消息,此時(shí)消息需要經(jīng)過(guò)一次序列化轉成字符串

·瀏覽器攔截到消息,反序列化成 JSONObject 并發(fā)送到 Naive 容器側



·容器開(kāi)始進(jìn)行跨線(xiàn)程事件分發(fā),并轉發(fā)消息到 service

·Service 運行環(huán)境將消息反序列化成 string,并轉成 JS 數據類(lèi)型,傳到 Service 所在的  JsContext 中

·JsContext 中 invokeCallback 函數被調用

·至此,render 消息已成功轉發(fā)至 service 層


可以看到,這個(gè)過(guò)程非常復雜,不僅要完成消息的轉發(fā),還要完成 jsonObject 和 js 數據類(lèi)型的互轉。為了在兩個(gè)線(xiàn)程內方便的完成這種互調,并保證 bridge 的安全線(xiàn),我們分別在雙端分別實(shí)現了 handleMessage 和 postMessage 的封裝,通過(guò) schema 來(lái)定義 bridge 和 api  標準協(xié)議,來(lái)完成線(xiàn)程消息的轉發(fā)和消息類(lèi)型的校驗工作。


一個(gè)標準的 api schema 定義大概是這樣:


消息會(huì )在 JsContext 內完成校驗,并在校驗通過(guò)后以序列化的方式完成上述流程的傳遞。


3.6 數據編譯能力與 JS 沙箱

為什么這里要提下數據編譯能力和 js 沙箱呢。因為小程序雙線(xiàn)程的框架下,邏輯層通過(guò)setData 發(fā)起頁(yè)面更新請求,攜帶的數組字段在被渲染層對應的組件解析時(shí),需要配合小程序的一些語(yǔ)法特性進(jìn)行特殊轉換。


在運行時(shí)側,我們將字段的解析能力與數據字段的處理,都收攏到沙箱 環(huán)境中進(jìn)行字段編譯。通過(guò)沙箱,我們可以攔截業(yè)務(wù)代碼對于變量的訪(fǎng)問(wèn),從而實(shí)現變量的劫持,并配合完成 sjs 這類(lèi)能力的實(shí)現。同時(shí),沙箱可以有效防止業(yè)務(wù)動(dòng)態(tài)注入一些變量或函數,帶來(lái)的變量訪(fǎng)問(wèn)逃逸的安全問(wèn)題。因此,沙箱在小程序語(yǔ)法和變量計算的過(guò)程中起到了至關(guān)重要的作用。


例如,下面這段代碼片段:


在編譯側,我們會(huì )將 loader上面代碼通過(guò) ast 進(jìn)行轉換:

通過(guò)沙箱,我們可以攔截到業(yè)務(wù)對 sjs 模塊訪(fǎng)問(wèn),將訪(fǎng)問(wèn)屬性替換為 sjs 的模塊導出,從而實(shí)現類(lèi)似 sjs 這樣的腳本拓展能力。


性能優(yōu)化與監控


雙線(xiàn)程在線(xiàn)程隔離方案上,將原本在同一線(xiàn)程內執行的腳本、渲染等工作分散到多個(gè)線(xiàn)程內執行,帶來(lái)了更好的性能。但如果單個(gè) webview 線(xiàn)程的渲染負擔過(guò)重或對設備內存占用過(guò)大一樣會(huì )影響到整體的體驗。


于此同時(shí),線(xiàn)程隔離也帶來(lái)了通訊的損耗,對于一次消息需要經(jīng)過(guò)多次序列化和反序列化,消息序列化的損耗與轉發(fā)也對性能有著(zhù)直接的影響。


因此,小程序的性能優(yōu)化不同于傳統的 web,需要從框架、通道、容器三方面來(lái)考慮。


4.1 bridge 消息調度機制

bridge 消息通道的繁忙程度,會(huì )在很大程度上影響小程序的性能表現。通過(guò)上面對于 bridge 消息轉發(fā)機制的介紹也可以看出,頻繁的借助 bridge 進(jìn)行消息轉發(fā),意味著(zhù)消息要不斷進(jìn)行序列化和反序列化的操作。


實(shí)際場(chǎng)景中,數據量小于64KB時(shí),時(shí)長(cháng)基本在 10 - 40ms 內。傳輸時(shí)間與數據量上呈現正相關(guān)關(guān)系,傳輸過(guò)大的數據將使這一時(shí)間顯著(zhù)增加,因此減少傳輸數據量是降低數據傳輸時(shí)間的有效方式。


但是如果數據量較小,確在短時(shí)間內多次使用 bridge ,也會(huì )導致通道過(guò)于繁忙。小紅書(shū)在 bridge 側,通過(guò)一定消息調度能力,將特定場(chǎng)景下的消息進(jìn)行聚合,確保一次序列化盡可能在不影響序列化性能的情況下,多攜帶一些消息到對應的線(xiàn)程內。


4.2 渲染層任務(wù)調度與優(yōu)先級隊列

前面我們曾經(jīng)提到,雙線(xiàn)程背景下,小程序的更新機制與事件系統全部都是通過(guò)消息進(jìn)行處理的,但消息本身的收發(fā)都存在一定的延時(shí)性,這就注定了小程序是一個(gè)異步通訊的世界。那么在一個(gè)異步多線(xiàn)程的場(chǎng)景下,線(xiàn)程之間“生產(chǎn)“和“消費“的消息的速度會(huì )因性能、穩定性等因素而不一致,這時(shí),我們便要借助消息隊列的思想來(lái)管理我們的消息:


有了消息隊列,我們可以更好的管理框架層拋出的消息體,但小程序框架內,除了更新消息和事件消息外,還有不同的消息體會(huì )與這些框架消息搶占消息通道。比如,框架收集不同的 render 線(xiàn)程 webview 內的性能指標,這些性能消息會(huì )與事件消息共享同一隊列。但有些場(chǎng)景下,事件消息的優(yōu)先級要遠高于性能指標消息。


此外,不同的渲染層 render 實(shí)例的消息所擁有的優(yōu)先級也不同,比如 A、B 頁(yè)面在同一時(shí)間段內,因其“棧頂的地位”會(huì )因用戶(hù)操作而不斷變化,此時(shí)棧頂頁(yè)面的框架消息優(yōu)先級高于 B 頁(yè)面的框架消息優(yōu)先級,在底層。我們使用 二叉堆 結構來(lái)維護優(yōu)先級隊列。


4.3 容器預加載

小程序的啟動(dòng)分為冷啟動(dòng)和熱啟動(dòng),  從用戶(hù)的角度看:


冷啟動(dòng):如果用戶(hù)首次打開(kāi),或小程序銷(xiāo)毀后被用戶(hù)再次打開(kāi),此時(shí)小程序需要重新加載啟動(dòng),即冷啟動(dòng)。


熱啟動(dòng):如果用戶(hù)已經(jīng)打開(kāi)過(guò)某小程序,然后在一定時(shí)間內再次打開(kāi)該小程序,此時(shí)小程序并未被銷(xiāo)毀,只是從后臺狀態(tài)進(jìn)入前臺狀態(tài),這個(gè)過(guò)程就是熱啟動(dòng)。


通常在容器側的優(yōu)化,就是針對冷啟動(dòng)來(lái)進(jìn)行。那么容器的預載,顧名思義,就是在合適的時(shí)間提前預載小程序容器,預載的同時(shí),會(huì )提前進(jìn)行基礎庫的下載和渲染容器(webview)的加載。


通過(guò)前置容器的初始化時(shí)機,來(lái)達到快速換起小程序,提高首屏的優(yōu)化效果。這是小程序這類(lèi)容器技術(shù)方案常用的優(yōu)化策略。


4.4 性能監控與告警

性能優(yōu)化的同時(shí),框架側需要對業(yè)務(wù)代碼的性能和行為有一定感知能力。在底層,我們通過(guò) aop 的方式,建設了一套監控和插件機制。在開(kāi)發(fā)階段,可以感知到業(yè)務(wù)各項指標的健康狀況,業(yè)務(wù)可以接收到底層框架給出的性能告警信息,并通過(guò)告警信息中的修復建議,針對性的進(jìn)行優(yōu)化。


業(yè)務(wù)側,則可以通過(guò) performance api 拿到這些性能指標,來(lái)進(jìn)行基礎性能數據的收集與上報。


性能告警會(huì )結合性能標準閾值來(lái)給出提示和修復建議,未來(lái)在審核階段也會(huì )結合這些指標進(jìn)行小程序健康度的洞察。


4.5 ServiceTiming 與 RenderTiming

除了卡頓、渲染指標外,為了滿(mǎn)足高級開(kāi)發(fā)者洞察平臺的性能信息的需要,我們對容器和框架在啟動(dòng)階段的關(guān)鍵節點(diǎn),都預留了性能點(diǎn)位。開(kāi)發(fā)者可以通過(guò) performance.serviceTiming和performance.renderTiming 來(lái)分別獲取到各個(gè)關(guān)鍵階段的時(shí)間戳信息。


各個(gè)線(xiàn)程內所預留的性能點(diǎn)位和其在啟動(dòng)階段中的位置如下圖所示:


五、總結


以上就是小紅書(shū)小程序運行時(shí)方案的原理解析。


小程序本身是一個(gè)依托宿主流量體系衍生出的技術(shù)體系,它的價(jià)值往往緊貼應用主體的流量,而應用主體本身,又依賴(lài)小程序的靈活性及低成本的特點(diǎn)快速完成流量的轉換。社交、支付與搜索,這些都是互聯(lián)網(wǎng)產(chǎn)品提供的服務(wù)形態(tài),各大廠(chǎng)商都是結合用戶(hù)的需求和行為差異進(jìn)行更開(kāi)放、安全的技術(shù)方案探索,小紅書(shū)亦是如此。小紅書(shū)依靠用戶(hù)產(chǎn)生內容,而內容產(chǎn)生商品,那么結合各類(lèi)消費場(chǎng)景,如店鋪、筆記等都可以通過(guò)小程序容器快速進(jìn)行交易鏈路閉環(huán)。


未來(lái),我們也將在不同的品牌和賽道上,尋找更多的服務(wù)商與品牌大客戶(hù)商家與我們一起,共同豐富小紅書(shū)的商品服務(wù)供給,增加小紅書(shū)商業(yè)收入。技術(shù)上,我們則會(huì )不斷對齊業(yè)界,優(yōu)化技術(shù)架構,在提高框架性能的同時(shí),建立完善的服務(wù)市場(chǎng)、巡檢機制等來(lái)幫助小紅書(shū)服務(wù)商與自開(kāi)發(fā)商家細致、高效的開(kāi)發(fā)與管理自己的小程序。


小程序是一個(gè)比較龐大的技術(shù)體系,如果你覺(jué)得本文對你有幫助,歡迎點(diǎn)贊轉發(fā)。我們后續會(huì )根據反饋繼續展開(kāi)介紹更多的技術(shù)建設細節。也歡迎訪(fǎng)問(wèn)我們的小程序官方網(wǎng)站與我們交流:


·小紅書(shū)小程序介紹:https://miniapp.xiaohongshu.com/docs/guide/miniIntroduce

·小紅書(shū)社區專(zhuān)業(yè)號:https://pro.xiaohongshu.com/

·你也可以通過(guò) https://github.com/redengineer/redmini 與我們進(jìn)行交流


六、作者信息


哈笛

商業(yè)技術(shù)組 - 小程序團隊成員(tailiang@xiaohongshu.com),目前負責小紅書(shū)運行時(shí)相關(guān)技術(shù)開(kāi)發(fā)工作。


本文地址:http://selenalain.com/thread-806012-1-1.html     【打印本頁(yè)】

本站部分文章為轉載或網(wǎng)友發(fā)布,目的在于傳遞和分享信息,并不代表本網(wǎng)贊同其觀(guān)點(diǎn)和對其真實(shí)性負責;文章版權歸原作者及原出處所有,如涉及作品內容、版權和其它問(wèn)題,我們將根據著(zhù)作權人的要求,第一時(shí)間更正或刪除。
您需要登錄后才可以發(fā)表評論 登錄 | 立即注冊

關(guān)于我們  -  服務(wù)條款  -  使用指南  -  站點(diǎn)地圖  -  友情鏈接  -  聯(lián)系我們
電子工程網(wǎng) © 版權所有   京ICP備16069177號 | 京公網(wǎng)安備11010502021702
快速回復 返回頂部 返回列表
午夜高清国产拍精品福利|亚洲色精品88色婷婷七月丁香|91久久精品无码一区|99久久国语露脸精品|动漫卡通亚洲综合专区48页