小红书自研小程序:电商体验与效果优化的运行时体系设计
一、背景介绍
小程序在其诞生后的几年内,凭借其简单、轻量、流畅、无需安装等特点,引来了爆发式的增长。伴随小红书电商业务的发展,我们洞察到越来越多的商家和品牌大客户有自己定制化需求场景,传统的电商和薯店存在下面三大问题:
为了解决上述问题,并快速打通基于小红书体系的支付与账号体系。过去的一年内,我们踏上了自研小程序之路。目前,在小红书店铺主页、笔记详情、品牌专区、开屏均可唤起小程序。
本文将主要介绍小红书进行小程序自研时的一些业务背景及工程化、容器能力的落地方案,以及运行时针对双线程架构bridge,framework能力的设计。
二、运行时工程能力建设
2.1小程序"运行时"定义
运行时在不同语言中含义有所不同,但基本可以概括为「运行在代码执行阶段的代码」,类似vue-runtime,提供了对于页面状态的劫持,生命周期的解析,api的调用能;nodejs提供了JS运行时执行能力等。小程序“运行时”则提供了在不同线程内,借助Bridge消息通道,进行逻辑调度的能力。
那么可以基本概括为:运行在小程序代码执行阶段、用于提供在独立线程中操作其他线程的页面(或视图),正确响应用户交互行为、并调度用户业务逻辑能力的代码。
2.2小程序基础架构
小红书小程序也是对齐业界经典架构进行建设:
经典双线程架构
经典架构下,运行时主要分为渲染层-Render、逻辑层-Service。Service用于与系统能力进行交互,在安全的JS线程内调度用户业务逻辑。而Render则负责接受渲染指令、进行视图的绘制与用户交互的响应。逻辑层与渲染层则通过js-bridge进行消息的通讯,容器则负责接受api指令进行端能力的调用。
之所以需要一个独立的线程来执行JS,其主要目的是为了限制JS灵活性。为了提供一个可用的JS环境,其实也有比较多的方案。比如,我们可以使用浏览器内核提供的ServiceWorker,来单独运行service层JS代码。或者我们可以使用多个webview实例来分别承载双端js的执行环境。
2.3容器架构实现
按照经典架构的设计,我们需要在三端(iOS、android、小程序开发者工具)提供面向双线程的容器方案。在不同的容器环境下,渲染层和逻辑层选用的方案会存在一定差异,小红书三端选用容器的分布如下:
虽然运行环境存在一定差异,但容器对于基础库和业务脚本的加载顺序是基本一致的。我们可以将整个启动阶段拆解为下面几个阶段:
首先,当用户点击时,会经历一个基本的启动过程。
在这个启动流程的背后,会对应着上面提到渲染层webview容器被加载出来。于此同时,在用户看不到的地方,容器会进行逻辑层v8/JsCore的初始化,同时会加载小程序的基础库代码。
脚本注入结束后,容器会立即通知「运行时逻辑层框架」进行依赖分析、并准备初渲染数据。
渲染层接受到initialData消息后,会进行后续渲染操作,用户即刻看到了页面的内容至此,初渲染的流程基本结束。
当然,实际的容器的启动过程中的流程会更加复杂,整个启动流程可以用下面的这张图来表示:
黑色、蓝色、橙色分别代表了端侧、逻辑层、渲染层三个线程
实际场景中,容器还面临更多的挑战,比如如何确保双线程的是否ready,再进行消息的推送等。核心在于,我们通过不同线程的容器,完成了页面渲染行为的控制。
可以看到,上述启动流程中容器侧分别在Render和Service分别注入了page.render.js和service.js的业务代码。那么如何进行业务代码构建,来分别在双线程下执行呢?这就需要依靠前端工程化的能力来实现了。
2.4实现基础架构的工程化能力
通过前端工程化能力,我们可以对资源进行分类构建,小红书小程序使用webpack作为工程化构建工具。通常,小程序的构建分两块,一块是针对基础库的打包,一块是针对业务组件的构建。
基础库的打包需要构建基础库代码,产出分别用于提供运行时框架能力的render.base.js及service.base.js
而业务组件的构建,则相对复杂。一个原生小程序组件或页面通常包含下面四个文件:
通过拆分多个文件,我们可以在构建时指定入口依赖,将对应的依赖打入所需要的模块内,在工程构建时,需要对文件进行分类打包:
我们使用loader作为webpack的entry入口进行构建,每个页面都会作为一个entry独立打包。这使得从行为上来说小程序更像一个MPA(多页应用)。入口侧会进行app.json的校验,对配置以页面维度来进行解析,针对小程序业务代码,会分别构建出page.render.js和service.js分别交给不同的线程进行加载(如上图)。
构建会将代码打包成UMD格式文件,当在不同线程内执行基础库脚本时,部分脚本会自动执行,端侧只需要