JD Daojia Mini Program - Exploration and practice in performance and multi-terminal capabilities | JD Cloud technical team

I. Introduction

The JD Daojia Mini Program initially only had WeChat Mini Programs. With the development of business, the same function needs to support more and more containers, including Alipay Mini Programs, JD Mini Programs, Daojia APP, JD APP, etc. However, each end needs to be implemented separately. Facing problems such as high R&D costs and inconsistencies.

In order to improve R&D efficiency, the taro3+native hybrid development model was adopted after technology selection. This article mainly explains how we explore multi-terminal capabilities and optimize performance based on the taro framework.

2. Exploration of multi-terminal capabilities

1. The architecture flow chart of Daojia applet based on taro3

Framework Layered Interpretation

1. Configuration layer: mainly includes compilation configuration, routing configuration, subpackage loading, and expansion port.

2. View layer: It mainly completes App life cycle initialization, page initialization, injecting host events, and parsing configuration for page and component binding events and properties.

3. Component library: It is a separately maintained project. The multi-terminal component library includes business components and atomic components, and the components can be dynamically loaded by the view layer according to the configuration.

    //渲染主入口
     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. Logic layer: including business processing logic, requests, exceptions, status, performance, public tools, and the ability to adapt to the base library.

5. Basic library: Provide basic capabilities, basic functions such as positioning, login, request, and embedding, mainly to smooth out the differences in the basic functions of each end.

2. Basic library

1. Unified interface, split implementation, smoothing out differences internally

Regarding the basic library, we adopt the method of split-end implementation, that is, a multi-end file with a unified interface.

How to dock the basic library in the project, modify config/index.js, and compile with the MultiPlatformPlugin plug-in provided by taro.


  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
        })
    }


How to use it in business

import { goToLogin } from '@djmp/login/index';

goToLogin()


2. High multiplexing

The basic library should not be coupled to the framework, so how should the basic library be designed so that it can satisfy both the taro project and the native project?

The npm base library is generated as a vendors file after taro is compiled

The npm base library generates miniprogram_npm after the miniprogram native project npm is built

The same basic library will exist in two forms after compilation, which takes up an extra space.

We are sensitive to the size of the small program package. In order to save space, how to make taro use the miniprogram_npm of the small program?

Briefly talk about the idea first, change the configuration items of webpack, process the introduction of public methods and public modules through externals configuration , keep these imported statements, and set the import method to the relative path of commonjs. The detailed code is as follows.

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. Component library

There are three difficulties in implementing cross-end components

First: How to find the most appropriate smoothing solution in multiple technology stacks. Different solutions will lead to different development and adaptation costs, and the improvement of human efficiency is what we want to achieve in the end;

Second: How to ensure that the performance of each component is not affected after implementing the components in one code and multiple terminals

Third: How to use cross-end components in each project

Based on the above, under the premise of the Taro-based development framework we have determined, we have carried out the planning and design of the overall cross-end component solution implementation:

At the component level, it is divided into three layers: UI basic components and business components are the bottom layer; container components are the middle layer, and the top layer is business template components; we first start with the UI basic components and business components to finalize the plan;

During the research process, UI components and business components mainly investigate the cross-terminal reuse rate from three aspects: API, style, and logic:

After the above research, it is concluded that the API level still needs to use its own technology stack for practice, and the API level should be smoothed in a way with consistent attributes; in terms of style, the Sass syntax is basically used, and the babel tool is used during the transformation process. Recognized style form; logically, it is basically translation and does not need to be changed; so when we want to make cross-end components, our biggest workload lies in: smoothing the API and exploring the cross-end writing of styles;

Example: smoothing of image components:

Based on the above, the reuse scheme of cross-end components is feasible after investigation, but then, how can we ensure that the converted components can match the performance of native components? How should our cross-end components be used in various projects?

In this process, we mainly investigate and compare two solutions:

First: Directly use the cross-end editing function provided by Taro to convert, convert and compile into RN. WeChat applet and H5;

Second: compile through babel, directly convert into RN native code, WeChat applet native code, and H5 native code

contrast direction original size compilation cost Generated component properties
Taro compiles directly Large (carries the Taro environment) Medium (directly provided by Taro, but requires debugging at each end) Same as native
Escaping via babel Small (only the source code of the current component) Medium (requires the development of Babel escape components) Same as native

After the above comparisons, we finally chose the babel escape method. When used in a project, it is published to the Npm server for use by various projects.

Program implementation and future planning:

After confirming the overall solution direction, we implemented the project. First, we built the running project of the cross-end component library: it can support previewing the pages generated by JD applets, WeChat applets, and H5 components; the following is the entire component generated from From publishing to the entire process of the corresponding project.

At present, the realization of 5 kinds of UI components and 4 kinds of business components has been completed; among them, the coupon module has been implemented in the Daojia applet project, and the design rules and solutions of cross-terminal components have been precipitated. In the coming year, we will continue to implement and implement cross-end components, from UI and business layer to complex containers and complex pages.

4. Engineering construction

1. Build a WeChat Mini Program

Because there are multiple taro projects that are responsible for different businesses, it is necessary to aggregate the compiled products of taro and WeChat natively to form a complete mini program project.

Below is the build flow for the design.

In order to automate it and reduce manual operations, it is enough to create dependent tasks in the Dijia release background ( the small program release background developed by Daojia ) , complete the overall construction and upload.

Among them, the link of executing [dependency tasks] will be carried out, the taro project will be aggregated and compiled, and the product will be merged into the original project.

Dijia release background

2. Build JD Mini Program

yarn deploy:jd version number description

//集成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. Build release h5

yarn deploy:h5

The application of h5 usually adopts the mode of cdn resource + html entrance. Publish the cdn resource first to warm up, and then publish the html entry to go online.

Mainly perform 3 operations

1. Compile the h5dist product, that is, html+ static resources

2. For static resources, use the integrated @jd/upload-oss-tools tool to upload to cdn.

3. Trigger [Xingyun Deployment Orchestration] to publish the html file entry

About cdn: We have integrated a cdn upload tool to assist in quick launch.


//集成 @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();
  })
}


3. Performance optimization

Performance optimization is an eternal topic. In summary, the direction of optimization is: package download phase, js injection phase, request phase, and rendering phase.

The following mainly introduces how to optimize the package size in the download phase and how to improve the request efficiency in the request phase.

(1) Volume optimization

I believe that students who have used taro3 have the same experience, that is, the compiled product is too large, and the main package may exceed 2M!

1. Whether the main package is opened

Optimize the size of the main package: optimizeMainPackage.

After a simple configuration like the following, you can avoid modules that are not introduced by the main package from being extracted commonChunks. This function will analyze the dependencies between modules and chunks during packaging, and filter out modules that are not referenced by the main package and extract them into subpackages. Inside.

  module.exports = {
  // ...
  mini: {
    // ...
    optimizeMainPackage: {
      enable: true,
    },
  },
}
    


2. Use the compression plugin 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. Extract the public files to the subpackage.

mini.addChunkPages ​Specify the public files that need to be referenced separately for

For example, when subpackaging a small program, in order to reduce the size of the main package, the pages of the subpackage want to import their own public files instead of directly placing them in the main package. Then we can first separate the subpackaged public files through the webpackChain configurationmini.addChunkPages , and then introduce the subpackaged public files through the configuration of the subpackaged page. The usage method is as follows:

mini.addChunkPagesConfigure as a function that accepts two parameters

pagesThe parameter is of type Map, which is used to add public files to the page

pagesNamesThe parameter is a list of all page IDs currently applied, and the page IDs can be checked by printing

For example, to pages/index/indexadd eatingand morningtwo extracted public files for the page:


mini: {
    // ...
    addChunkPages(pages: Map<string, string[]>, pagesNames: string[]) {
      pages.set('pages/index/index', ['eating', 'morning'])
    },
  },


4. Code analysis

If the above methods do not achieve the desired effect, then we can only calm down and analyze the packaging logic of taro.

可以执行 npm run dev 模式查看产物里的 ``xxx .LICENSE.txt file, which lists which files are packaged, needs to be analyzed by itself to remove redundancy.

The following takes vendors.LICENSE.txt as an example

runtime.js: webpack runtime entry, only 2k, no room for optimization.

taro.js: Taro-related dependencies in node_modules, 112k, you can modify the source code, otherwise there is no room for optimization.

vendors.js: public dependencies of node_modules except Taro, check the vendors.js.LICENSE.txt file to analyze which files are included

common.js: Common logic of the business code in the project, check which files are included in common the .js.LICENSE.txt file analysis

• app.js is a dependent file in the app life cycle. View app.js.LICENSE.txt file analysis which files are included

• app.wxss public style file, look at the optimization of business requirements, and remove unnecessary global styles.

• base.wxml depends on how the component is used, and there is less room for optimization.

(2) Network request optimization:

I believe that there are many types of requests in your business, such as business class, buried point class, behavior analysis, monitoring, and other sdk-encapsulated requests. However, different host environments have different concurrency limits. For example, WeChat applet requests are limited to 10 concurrent requests, and JD.com and other applets are limited to 5 requests.

As shown in the figure below, taking the WeChat applet as an example, when there are too many requests, the business and the buried point requests compete for the request resources, causing business requests to queue up, causing the page display to lag, and weak network conditions even causing freezes.

So based on the above questions, how to balance business requests and non-business requests?

Here we have 2 options:

1. Dynamic scheduling scheme https://www.cnblogs.com/rsapaper/p/15047813.html

The idea is to divide the requests into high-quality requests and low-quality requests. When blocking occurs, put high-quality requests into the request queue, and low-quality requests into the waiting queue.

Request Distributor QueueRequest: Distribute new requests.

◦Join the waiting queue: when the number of ongoing requests exceeds the set threshold and the request is of low priority;

◦Join the request pool: the request is of high priority, or the number of concurrency has not reached the threshold.

Waiting queue WaitingQueue: Maintain the waiting queue for requests that need to be sent with a delay. When the request pool is idle or the request exceeds the maximum waiting time, reissue the waiting request.

Request Pool RequestPool: Sends requests and maintains the state of all ongoing requests. Expose the number of ongoing requests to the outside world, and notify the waiting queue to try to reissue when a request is completed.

2. Virtual request pool solution

The idea is to divide the 10 request resources of WeChat into 3 request pools, and the ratio of business requests: buried points: other requests is 6:2:2. The ratio can be adjusted by yourself.

In this way, each type of request is in its own request pool, and there is no competition for resources in other request pools, which ensures that the business will not be blocked by other requests.

Method to realize

Scheme comparison

Advantages and disadvantages Dynamic Scheduling (Scheme 1) Virtual request pool (Scheme 2)
Expansibility Low high
Cost (development, testing, maintenance) high Low
request efficiency Low high

Both solutions can complete the allocation of request resources, but the virtual request solution is adopted in combination with the actual business. After testing, the request efficiency can be increased by 15% in the case of weak network.

4. Summary and Outlook

The future must be the direction of one code and multiple terminals, so we will invest more energy in infrastructure construction in the future, including framework layer upgrade and optimization, basic library construction, component library construction, and engineering construction for rapid deployment of multiple terminals.

In terms of performance optimization, we can also explore directions such as subpackage preloading of JD applets, subpackage asynchronization, JD container flutter rendering, Tencent skyLine rendering engine, etc.

In terms of team communication and cooperation, I will learn and communicate with the Taro team, JD.com’s small program container team, nut-ui, Pinpin and other teams, and hope to cooperate with everyone to build.

5. Conclusion

The JD Mini Program Open Platform is a self-developed platform of JD. It provides rich open capabilities and underlying engine support. Currently, there are various development tools such as developer tools, conversion tools, and visual drag and drop available for internal R&D colleagues to improve development quality and at the same time Quickly realize the online of business functions. Internally, businesses such as JD Payment, JD Reading, and JD Home have used JD Mini Programs as a technical framework to carry out their business.

If you want to learn more about and experience the JD Mini Program, you can go to the JD Mini Program official website ( https://mp.jd.com/?entrance=shendeng) for more information.

reference:

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

Author: Jingdong Retail Deng Shuhai, Jiang Wei

Source: JD Cloud Developer Community

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10085445