关于小程序:京东到家小程序在性能及多端能力的探索实践-京东云技术团队

46次阅读

共计 8158 个字符,预计需要花费 21 分钟才能阅读完成。

一、前言

京东到家小程序最后只有微信小程序,随着业务的倒退,同样的性能须要反对容器越来越多,包含支付宝小程序、京东小程序、到家 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-ci
const {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 页面增加 eatingmorning 两个抽离的公共文件:


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://mp.jd.com/?entrance=shendeng)查看更多信息。

参考:

https://www.cnblogs.com/rsapaper/p/15047813.html

https://taro-docs.jd.com/docs/next/config-detail#minioptimize…

https://taro-docs.jd.com/docs/next/dynamic-import

https://zhuanlan.zhihu.com/p/396763942

作者:京东批发 邓树海、姜微

起源:京东云开发者社区

正文完
 0