native-adapter重构与实现

1,137 阅读4分钟

介绍

native-adapter是一套开箱即用,易于拓展,无需引入多余代码的多环境适配框架 。目前涵盖转转app、找靓机app、m站、微信浏览器、微信小程序、qq、qq小程序、百度小程序、快手小程序、头条小程序、采货侠app等多种环境。

旧的方案

主要是以适配器模式的设计架构,前期平台较少的情况下,扩展和兼容比较灵活。随着适配平台的增多,不断地增加平台代码,没办法按需引入,最后导致包体积会越来越大。

面临的问题

native-adapter作为团队基础库中最重要的一个,在近几次的基建问题收集时,反馈native-adapter问题的不在少数。经过业务反馈和调研,主要问题如下:

  1. 包体积大,无法按需引用

  2. 缺少demo,不方便测试

  3. 文档不友好,使用成本过高

  4. 代码没有采用ts,不支持API调用提示

针对以上问题,经过梳理和评估,确立以下目标

  1. 新增测试demo,独立测试每个方法
  2. 重构文档,提供完善的API说明与测试二维码等
  3. 多端方法一致性兼容,产出多端兼容参考文档
  4. 升级代码为插件架构、支持TS
  5. 优化代码,精简逻辑

架构升级

对社区一些开源包的调研,发现针对包体积大,无法按需引用 这个痛点,基本上采用的是插件化架构。在此借鉴了一下BetterScroll插件体系以支持按需引用。

image-20220519133734547

新的架构特点
  • 架构更加清晰、分为核心层和插件层。
  • 插件继承关系更加简洁
  • 扩展灵活,升级为插件化,支持按需加载和全量加载
目录
├── src
│   ├── all.ts // 全量包入口
│   ├── core  // 核心
│   │   ├── conf.ts // 暴露的api方法
│   │   ├── index.ts
│   │   ├── native.ts // 核心插件化实现
│   │   └── utils // 工具方法
│   │       ├── ajax.ts
│   │       ├── compareVersion.ts
│   │       ├── cookies.ts
│   │       ├── events.ts
│   │       ├── index.ts
│   │       ├── urls.ts
│   │       ├── util.ts
│   │       ├── waitJsLoaded.ts // 进行异步加载初始化sdk和调用队列
│   │       └── wxAuth.ts
│   ├── index.ts // 核心包, 包括m站,转转app、找靓机app、微信浏览器、微信小程序、qq等
│   └── plugins
│       ├── base // 基础adapter
│       │   ├── BaseAdapter.ts
│       │   └── LoginAdapter.ts
│       ├── buildIn // 内置核心adapter
│       │   ├── QQAdapter.ts
│       │   ├── WechatAdapter.ts
│       │   ├── WechatMPAdapter.ts
│       │   ├── ZLJAdapter.ts
│       │   └── ZZAdapter.ts
│       ├── external // 扩展adapter
│       │   ├── BaiduMPAdapter.ts
│       │   ├── GAdapter.ts
│       │   ├── KrakenAdapter.ts
│       │   ├── KuaishouAdapter.ts
│       │   ├── QQMPAdapter.ts
│       │   ├── ToutiaoAdapter.ts
│       │   ├── WubaAdapter.ts
│       │   ├── ZZHunterAdapter.ts
│       │   ├── ZZSELLERAdapter.ts
│       │   └── ZZYigeAdapter.ts
│       └── index.ts
实现过程
  • 逻辑解耦,把每个适配平台的代码整合到插件文件中去,不和核心逻辑耦合,增加可维护性。

  • baseAdapter作为最小的adapter,其余的平台都基于baseAdapter来扩展

  • 充分利⽤了 TypeScript 接⼝⾃动合并的功能,让开发者在使⽤某个插件时,能够有对应的 native能够有对应的⽅法提示

    // ZZAdapter platform
    static platform = [
        { rule: /58ZhuanZhuan/g, name: 'zz', scheme: 'zhuanzhuan://' },
        {
          // 找靓机新webview
          rule: () =>
            /zhaoliangji-v2/g.test(navigator.userAgent) &&
            (getUrlParams().needNewWebview == '1' ||
              getUrlParams().needNewWebView == '1' ||
              compareVersion(getCookie('v'), '9.1.10') >= 0),
          name: 'zlj',
          isNewWebview: true,
        },
        // 转转门店
        {
          rule: /zzstore|offlinestorecomplex/g,
          name: 'zzstore',
        },
        // 上门超人
        {
          rule: /zzupdoor/g,
          name: 'zzupdoor',
        },
      ]
    
    static use(ctor: PluginCtor) {
        const name = ctor.pluginName
        const installed = NativeConstructor.plugins.some((plugin) => ctor === plugin.ctor)
        if (installed) return NativeConstructor
        if (isUndef(name)) {
          console.warn(`插件必须有一个静态pluginName字段`)
          return NativeConstructor
        }
        NativeConstructor.plugins.push({
          name,
          ctor,
        })
        return NativeConstructor
    }
    
    NativeConstructor.plugins.forEach(
          (item: PluginItem) => (this.pluginsMap[item.name] = item.ctor)
        )
        Object.keys(this.pluginsMap).forEach((key) => {
          const Ctor: PluginCtor = this.pluginsMap[key]
          let currentPlatform = this.getCurrentPlatform(Ctor.platform)
          if (currentPlatform) {
            // 实例化plugin时会注入当前this,即native实例
            this.plugin = new Ctor(this as unknown as Native)
            this.currentPlatform = currentPlatform
          }
        })
        // plugins没有匹配到平台、默认匹配baseAdapter
        if (!this.plugin) {
          let currentPlatform = this.getCurrentPlatform(MAdapter.platform)
          if (currentPlatform) {
            this.currentPlatform = currentPlatform
          }
          this.plugin = new MAdapter(this)
        }
    
  • 利用静态方法use进行注册 Native.use(ZLJAdapter).use(ZZAdapter) , 每个插件内部含有一个或者多个匹配规则不如上面匹配ZZAdapter, Native在内部通过Platform判断匹配到的规则执行相应的组件实例

灵活的包使用方式
// 核心包:包括m站,转转app、找靓机app、微信浏览器、微信小程序、qq
import native from '@zz-common/native-adapter'

// 全量包:包含插件中所有平台
import native from '@zz-common/native-adapter/es/all'

// 按需加载, 已经默认包含核心包,可以扩展qq小程序、百度小程序、快手小程序、头条小程序、采货侠app、Kraken等
import { Native } from '@zz-common/native-adapter'
import QQAdapter from '@zz-common/native-adapter/plugins/external/QQAdapter'
Native.use(QQAdapter)
let native = new Native(options)
API方法提示

image-20220712173719901

参数提示

image-20220712173719901

完善demo

完善文档

image-20220712195416052

总结

本次代码升级和优化一方面使得代码维护更加简单清晰,趋于稳定。另一方面对代码进行的瘦身和优化使得核心包代码体积减少了40%左右。同时文档和demo对使用人员来说,更加方便快捷,提高了集团内开发人员的效率。