全球百事通!京东到家小程序-在性能及多端能力的探索实践
一、前言
京东到家小程序最初只有微信小程序,随着业务的发展,同样的功能需要支持容器越来越多,包括支付宝小程序、京东小程序、到家APP、京东APP等,然而每个端分开实现要面临研发成本高、不一致等问题。
为了提高研发效率,经过技术选型采用了taro3+原生混合开发模式,本文主要讲解我们是如何基于taro框架,进行多端能力的探索和性能优化。
二、多端能力的探索
1.到家小程序基于taro3的架构流程图
框架分层解释
1.配置层:主要包含编译配置、路由配置、分包加载、拓展口子。
(相关资料图)
2.视图层:主要完成App生命周期初始化、页面初始化、注入宿主事件、解析配置为页面和组件绑定事件和属性。
3.组件库:是一个单独维护的项目,多端组件库包括业务组件和原子组件,可由视图层根据配置动态加载组件。
//渲染主入口 render() { let { configData, isDefault, isLoading } = this.state; const pageInfo = { ...this.pageInfoValue, ...this._pageInfo } return ( <MyContext.Provider value={pageInfo}> <View className={style.bg} > {//动态渲染模板组件 configData && configData.map((item, key) => { return this.renderComponent(item, key); }) } </View> {isLoading && <Loading></Loading>} </MyContext.Provider> ); } //渲染组件 注入下发配置事件和属性 renderComponent(item, key) { const AsyncComponent = BussinesComponent[item.templateName]; if (AsyncComponent) { return ( <AsyncComponent key={key} dataSource={item.data} {...item.config} pageEvent={pageEvent} ></AsyncComponent> ); } else { return null; } }
4.逻辑层:包括业务处理逻辑,请求、异常、状态、性能、公共工具类,以及与基础库对接的适配能力。
5.基础库: 提供基本能力,定位、登录、请求、埋点等基础功能,主要是抹平各端基础功能的差异。
2、基础库
1.统一接口,分端实现,差异内部抹平
关于基础库我们采用分端实现的方式,即统一接口的多端文件。
基础库如何对接在项目里,修改config/index.js,结合taro提供的MultiPlatformPlugin插件编译。
const baseLib = "@dj-lib/login" //增加别名,便于后续基础库调整切换 alias: { "@djmp": path.resolve(__dirname, "..", `./node_modules/${baseLib}/build`), }, //修改webpack配置,h5和mini都要修改 webpackChain(chain, webpack) { chain.resolve.plugin("MultiPlatformPlugin") .tap(args => { args[2]["include"] = [`${baseLib}`] return args }) }
业务里使用方式
import { goToLogin } from "@djmp/login/index";goToLogin()
2.高复用
基础库不应该耦合框架,那么基础库应该如何设计,使其既能满足taro项目又能满足原生项目使用呢?
npm基础库 在taro经过编译后生成为 vendors文件
npm基础库 在小程序原生项目npm构建后 生成miniprogram_npm
一样的基础库经过编译后会存在2种形态,多占了一份空间呢。
我们对小程序包体积大小是比较敏感的,为了节约空间,那么如何让taro使用小程序的miniprogram_npm呢?
先简单说一下思路,更改 webpack 的配置项,通过externals 配置处理公共方法和公共模块的引入,保留这些引入的语句,并将引入方式设置成 commonjs 相对路径的方式,详细代码如下所示。
const config = { // ... mini: { // ... webpackChain (chain) { chain.merge({ externals: [ (context, request, callback) => { const externalDirs = ["@djmp/login"] const externalDir = externalDirs.find(dir => request.startsWith(dir)) if (process.env.NODE_ENV === "production" && externalDir) { const res = request.replace(externalDir, `../../../../${externalDir.substr(1)}`) return callback(null, `commonjs ${res}`) } callback() }, ], }) } // ... } // ...}
3、组件库
想要实现跨端组件,难点有三个
第一:如何在多个技术栈中找到最恰当的磨平方案,不同的方案会导致 开发适配的成本不同,而人效提升才是我们最终想要实现的目的;
第二:如何在一码多端实现组件之后,确保没有对各个组件的性能产生影响
第三:如何在各项目中进行跨端组件的使用
基于以上,在我们已经确定的以Taro为基础开发框架的前提下,我们进行了整体跨端组件方案实现的规划设计:
在组件层面,划分为三层:UI基础组件和业务组件 为最底层;容器组件是中间层,最上层是业务模板组件;我们首先从UI基础组件与业务组件入手,进行方案的最终确认;
调研过程中,UI组件和业务组件主要从API、样式、逻辑三个方面去调研跨端的复用率:
经过以上调研得出结论:API层面仍需要使用各自技术栈进行实践,通过属性一致的方式进行API层面的磨平;样式上,基础都使用Sass语法,通过babel工具在转化过程中生成各端可识别的样式形式;逻辑上基本是平移,不需要做改动;所以当我们想做跨端组件时,我们最大工作量在于:API的磨平和样式的跨端写法的探索;
例:图片组件的磨平:
基于以上,跨端组件的复用方案经过调研是可行的,但是接下来,我们该如何保证转化后的组件能够和原生组件的性能媲美呢?我们的跨端组件又该如何在各个项目中使用呢?
在这个过程中,我们主要调研对比两种方案:
第一:直接利用Taro提供的跨端编辑功能进行转换,转换编译成RN . 微信小程序 以及H5;
第二:通过babel进行编译,直接转换成RN原生代码,微信小程序原生代码,以及H5原生代码
对比方向 | 原码大小 | 编译成本 | 生成的组件性能 |
Taro直接编译 | 大(携带了Taro环境) | 中(Taro直接提供,但需要各端调试) | 与原生相同 |
通过babel转义 | 小(只有当前组件的源码代码) | 中(需要开发Babel转义组件) | 与原生相同 |
经过以上几组对比,我们最终选用了babel转义的方式。在项目中使用时,发布到Npm服务器上,供各个项目进行使用。
方案落地与未来规划:
在确认整体的方案方向之后,我们进行了项目的落地,首先搭建了跨端组件库的运行项目:能够支持预览京东小程序、微信小程序以及H5的组件生成的页面;以下是整个组件从生成到发布到对应项目的全部流程。
目前已经完成了个5种UI组件的实现,4种业务组件;其中优惠券模块已经落地在到家小程序项目中,并已经沉淀了跨端组件的设计规则和方案。未来一年中,会继续跨端组件的实现与落地,从UI、业务层到复杂容器以及复杂页面中。
4、工程化构建
1.构建微信小程序
因为存在多个taro项目由不同业务负责,需要将taro聚合编译后的产物,和微信原生聚合在一起,才能构成完整的小程序项目。
下面是设计的构建流程。
为了使其自动化,减少人工操作,在迪迦发布后台(到家自研的小程序发布后台)创建依赖任务即可,完成整体构建并上传。
其中执行【依赖任务】这个环节会进行,taro项目聚合编译,并将产物合并到原生项目。
迪迦发布后台
2.构建京东小程序
yarn deploy:jd 版本号 描述
//集成CI上传工具 jd-miniprogram-ciconst { upload, preview } = require("jd-miniprogram-ci")const path = require("path")const privateKey = "xxxxx"//要上传的目录-正式const projectPath = path.resolve(__dirname, "../../", `dist/jddist`)//要上传的目录-本地调试const projectPathDev = path.resolve(__dirname, "../../", `dist/jddevdist`)const version = process.argv[2] const desc = process.argv[3]//预览版preview({ privateKey: privateKey, projectPath: projectPathDev, base64: false,})//体验版upload({ privateKey: privateKey, projectPath: projectPath, uv: version, desc: desc, base64: false,})
3.构建发布h5
yarn deploy:h5
h5的应用通常采用 cdn资源 +html入口 这种模式。先发布cdn资源进行预热,在发布html入口进行上线。
主要进行3个操作
1.编译出h5dist产物,即html+静态资源
2.静态资源,利用集成 @jd/upload-oss-tools 工具上传到 cdn。
3.触发【行云部署编排】发布html文件入口
关于cdn: 我们集成了cdn上传工具,辅助快速上线。
//集成 @jd/upload-oss-tools上传工具const UploadOssPlugin = require("@jd/upload-oss-tools");const accessKey = new Buffer.from("xxx", "base64").toString()const secretKey = new Buffer.from("xxx", "base64").toString()module.exports = function (localFullPath, folder) { return new Promise((resolve) => { console.log("localFullPath", localFullPath) console.log("folder", folder) // 初始化上传应用 let _ploadOssPlugin = new UploadOssPlugin({ localFullPath: localFullPath, // 被上传的本地绝对路径,自行配置 access: accessKey, // http://oss.jd.com/user/glist 生成的 access key secret: secretKey, // http://oss.jd.com/user/glist 生成的 secret key site: "storage.jd.local", cover: true, // 是否覆盖远程空间文件 默认true printCdnFile: true, // 是否手动刷新cdn文件 默认false bucket: "wxconfig", // 空间名字 仅能由小写字母、数字、点号(.)、中划线(-)组成 folder: folder, // 空间文件夹名称 非必填(1、默认创建当前文件所在的文件夹,2、屏蔽字段或传undefined则按照localFullPath的路径一层层创建文件夹) ignoreRegexp: "", // 排除的文件规则,直接写正则不加双引号,无规则时空字符串。正则字符串,匹配到的文件和文件夹都会忽略 timeout: "", // 上传请求超时的毫秒数 单位毫秒,默认30秒 uploadStart: function (files) { }, // 文件开始上传回调函数,返回文件列表参数 uploadProgress: function (progress) { }, // 文件上传过程回调函数,返回文件上传进度 uploadEnd: (res) =>{ console.log("上传完成") resolve() }, // 文件上传完毕回调函数,返回 {上传文件数组、上传文件的总数,成功数量,失败数量,未上传数量 }); _ploadOssPlugin.upload(); })}
三、性能优化
性能优化是一个亘古不变的话题,总结来说优化方向:包下载阶段、js注入阶段、请求阶段、渲染阶段。
以下主要介绍在下载阶段如何优化包体积,请求阶段如何提高请求效率。
(一)体积优化
相信使用过taro3的同学,都有个同样的体会,就是编译出来的产物过大,主包可能超2M!
1.主包是否开启
优化主包的体积大小 :optimizeMainPackage。
像下面这样简单配置之后,可以避免主包没有引入的 module 不被提取到commonChunks中,该功能会在打包时分析 module 和 chunk 的依赖关系,筛选出主包没有引用到的 module 把它提取到分包内。
module.exports = { // ... mini: { // ... optimizeMainPackage: { enable: true, }, },}
2.使用压缩插件 terser-webpack-plugin
//使用压缩插件 webpackChain(chain, webpack) { chain.merge({ plugin: { install: { plugin: require("terser-webpack-plugin"), args: [{ terserOptions: { compress: true, // 默认使用terser压缩 keep_classnames: true, // 不改变class名称 keep_fnames: true // 不改变函数名称 } }] } } }) }
3.把公共文件提取到分包。
mini.addChunkPages:为某些页面单独指定需要引用的公共文件。
例如在使用小程序分包的时候,为了减少主包大小,分包的页面希望引入自己的公共文件,而不希望直接放在主包内。那么我们首先可以通过 webpackChain 配置 来单独抽离分包的公共文件,然后通过 mini.addChunkPages 为分包页面配置引入分包的公共文件,其使用方式如下:
mini.addChunkPages 配置为一个函数,接受两个参数
•pages 参数为 Map 类型,用于为页面添加公共文件
•pagesNames 参数为当前应用的所有页面标识列表,可以通过打印的方式进行查看页面的标识
例如,为 pages/index/index 页面添加 eating 和 morning 两个抽离的公共文件:
mini: { // ... addChunkPages(pages: Map<string, string[]>, pagesNames: string[]) { pages.set("pages/index/index", ["eating", "morning"]) }, },
4.代码分析
如果以上方式,还达不到我们想要的效果,那么我们只能静下心来分析下taro的打包逻辑。
可以执行 npm run dev 模式查看产物里的 xxx.LICENSE.txt文件,里面罗列打包了哪些文件,需要自行分析去除冗余。
以下以vendors.LICENSE.txt 为例
•runtime.js: webpack 运行时入口 ,只有2k,没有优化空间。
•taro.js: node_modules 中 Taro 相关依赖,112k,可以魔改源码,否则没有优化空间。
•vendors.js: node_modules 除 Taro 外的公共依赖,查看vendors.js.LICENSE.txt文件分析包括哪些文件
•common.js: 项目中业务代码公共逻辑,查看common.js.LICENSE.txt文件分析包括哪些文件
•app.js app生命周期中依赖的文件。查看app.js.LICENSE.txt文件分析包括哪些文件
•app.wxss 公共样式文件 ,看业务需求优化,去除非必要的全局样式。
•base.wxml 取决于使用组件的方式,可优化空间较小。
(二)网络请求优化:
相信大家的业务里有多种类型的请求,业务类、埋点类、行为分析、监控、其他sdk封装的请求。然而在不同的宿主环境有不同的并发限制,比如,微信小程序请求并发限制 10个,京东等小程序限制为5个。
如下图,以微信小程序为例,在请求过多时,业务与埋点类的请求争抢请求资源,造成业务请求排队,导致页面展示滞后,弱网情况甚至造成卡顿。
那么基于以上问题,如何平衡业务请求和非业务请求呢?
这里我们有2个方案:
1.动态调度方案 https://www.cnblogs.com/rsapaper/p/15047813.html
思路就行将请求分为高优和低优请求,当发生阻塞时,将高优请求放入请求队列,低优进入等待队列。
请求分发器 QueueRequest:对新的请求进行分发。
◦加入等待队列:正在进行的请求数超过设置的 threshold,且请求为低优先级时;
◦加入请求池:请求为高优先级,或并发数未达到 threshold。
等待队列 WaitingQueue:维护需要延时发送的请求等待队列。在请求池空闲或请求超过最长等待时间时,补发等待请求。
请求池 RequestPool:发送请求并维护所有正在进行的请求的状态。对外暴露正在进行的请求数量,并在有请求完成时通知等待队列尝试补发。
2.虚拟请求池方案
该思路是将微信的10个请求资源,分成3个请求池,业务请求:埋点类:其他请求的比例为6:2:2。比例可以自行调整。
这样各类型请求都在自己的请求池,不存在争抢其他请求池资源,保障了业务不被其他请求阻塞。
实现方式
方案对比
优缺点 | 动态调度(方案一) | 虚拟请求池(方案二) |
拓展性 | 低 | 高 |
成本(开发、测试、维护) | 高 | 低 |
请求效率 | 低 | 高 |
2个方案都可以完成请求资源的分配,但结合业务实际采用的是虚拟请求方案,经测试在弱网情况下,请求效率可以提升15%.
四、总结和展望
未来一定是一码多端的方向,所以我们未来在基础建设上会投入更多的精力,包括框架层升级优化、基础库建设、组件库建设、工程化建设快速部署多端。
在性能优化上我们还可以探索的方向有京东小程序分包预加载、分包异步化、京东容器flutter渲染、腾讯skyLine渲染引擎等。
在团队沟通协作上会与Taro团队、京东小程序容器团队、nut-ui、拼拼等团队进行学习沟通, 也希望能与大家合作共建。
五、结束语
京东小程序开放平台是京东自研平台,提供丰富的开放能力和底层的引擎支持,目前有开发者工具、转化工具、可视化拖拽等多种开发工具可供内部研发同事使用,提升开发质量同时快速实现业务功能的上线。内部已有京东支付、京东读书、京东居家等业务使用京东小程序作为技术框架开展其业务。
参考:
https://www.cnblogs.com/rsapaper/p/15047813.html
https://taro-docs.jd.com/docs/next/config-detail#minioptimizemainpackage
https://taro-docs.jd.com/docs/next/dynamic-import
https://zhuanlan.zhihu.com/p/396763942
关键词:
推荐阅读
月壤形成的主要原因 月壤与土壤有什么区别
月壤形成的主要原因月壤形成过程没有生物活动参与,没有有机质,还极度缺水干燥;组成月壤的矿物粉末基本是由陨石撞击破砰形成,因此,粉末 【详细】
域名抢注是是什么意思?投资角度来看什么域名好?
域名抢注是是什么意思域名抢注是通过抢先注册的方式获得互联网删除的域名的使用权。域名是由点分隔的一串数字,用于标记一台计算机或一组计 【详细】
捷达保养费用是多少?捷达是哪个国家的品牌?
捷达保养费用是多少?全新捷达的保修期为2年或6万公里,以先到者为准,新车可享受一次免费保养,首次免费保养在5000-7500km或1年内进行。如 【详细】
天然气泄露会造成爆炸吗?天然气泄漏怎么办?
天然气泄露会造成爆炸吗?家里用的天然气如果泄露是会发生爆炸的。当空气中含有混合天然气时,在与火源接触的一系列爆炸危险中,就会发生爆 【详细】
四部门明确App收集个人信息范围 个人信息保护范围判断标准
四部门明确App收集个人信息范围近日,国家互联网信息办公室、工业和信息化部、公安部、国家市场监督管理总局联合印发《常见类型移动互联网 【详细】
相关新闻
- 全球百事通!京东到家小程序-在性能及多端能力的探索实践
- 【全球热闻】苹果蓝牙耳机怎么充电_苹果蓝牙耳机使用说明
- 一群中年人试图在 直播间拯救“天涯”
- 环球热消息:上市仅一天!谷歌首款折叠屏手机出现内屏开裂等问题
- 安恒信息范渊:杭州亚运会链接科学、艺术与年轻人
- 最新消息:KeePassXC一款开源的密码管理器
- Zoom修复了关于其平台的最大投诉 每日热闻
- 在字可以组什么词(在字组什么词语)-今日精选
- 每日看点!被六公主点评“疯狂撒糖甜到自己”,《偷偷藏不住》有多上头?
- 全球热资讯!微信再回应「校园支付费率」问题:0.6% 费率纯属误传
- 职场中,如果遇到领导跟你说这10句话,就是在欺负你老实
- 陈晨:欧美人工智能监管差异,给我们带来哪些启示?
- 微信输了,学校赢了,背后的商户笑了,但取胜的手段太不地道_当前看点
- GOOVIS G3 Max 让我“身临” iMax巨幕厅,直接把影院戴在头上!-世界观热点
- 微信支付也开始要收费了
- 工商银行与陕西省人民政府 签署战略合作协议
- 每日资讯:思念的花语是什么花(什么花代表思念?)
- 辛芷蕾自曝小时候曾遭遇轻微猥亵 基本信息讲解
- 国庆将至举国同庆什么意思(举国同庆什么意思) 今日播报
- 济宁邹城构筑“一站式”多元解纷体系 夯实基层社会治理现代化基础 微动态