滴滴Dokit项目Web模块源码阅读

502 阅读14分钟

本学期学校开设了开源这门课程,为了扩宽自己的知识面与技术栈我选择了滴滴的Dokit-Web方向,对源代码进行了阅读与分析

一、Dokit项目概述

Dokit是DoraemonKit的简称,意味着能够像哆啦A梦一样提供给他的主人各种各样的工具。Dokit是一个功能平台,能够让每一个 App 快速接入一些常用的或者你没有实现的一些辅助开发工具、测试效率工具、视觉辅助工具,而且能够完美在 Doraemon 面板中接入你已经实现的与业务紧密耦合的一些非通有的辅助工具,并搭配dokit平台,让功能得到延伸,接入方便,便于扩展。Dokit是一款泛前端的应用平台,包含了安卓、ios、小程序、Web、Flutter等多个平台的应用,并且可以实现跨平台的交流,为用户实现实现方便快捷的功能接口。

bd8a0f449619ee2ce4c2ea4bfadb30bf.png

Dokit的主要功能可以总结如下:

1、DoraemonKit 能够快速让你的业务测试代码能够在这里统一管理,统一收口;

2、DoraemonKit 内置很多常用的工具,避免重复实现,一次接入,你将会拥有强大的工具集合;

3、搭配dokit平台,借助接口Mock、健康体检、文件同步助手让你方便和他人协同,极大的提升研发过程中的效率。

二、Web模块简介

Web模块是整个Dokit生态的重要组成部分,主要面向HTML页面实现一些功能接口,并且可以与移动端产生互联。主要的编程语言为前端的HTML、CSS、Javascript(JS),其中JS的主要应用是Vue3框架,可以负责实现网页中各种事件、组件、交互数据、通信跳转的实现与渲染;HTML与CSS主要负责网页的排版布局与美观设计。目前Web端主要覆盖了日志、接口抓取、视图元素查看、资源查看、应用存储、接口mock等。 各部分的基础功能与接近功能如下表所示:

表2-1 Web各部分功能简介

模块日志接口抓取查看试图元素资源查看应用存储接口mock
基础功能- 支持所有的 `console` 方法 - 支持日志的筛选和清除 - 支持执行简易的 `javascript`命令- 支持抓取常用的接口类型 `xhr` 和`fetch` - 展示请求的详细内容 - 展示响应的详细内容- 展示整个页面的 `Dom`树 - 支持展开、关闭、选择(高亮元素) - 支持查看特定元素的内容 - 查看元素的样式 - 查看元素的盒模型- 展示`javascript`资源 - 展示`css`资源 - 展示`image`等媒体资源- 支持查看并修改`LocalStorage` - 支持查看并修改 `SessionStorage` - 支持查看 `Cookie`- 支持无侵入式一键 Mock - 支持自动同步真实请求数据到平台侧,并与快速创建
进阶功能- 打通平台侧的日志查看,支持查看多个设备

三、代码阅读工具

IDE:VS Code 1.54. VS Code是一个运行于 Mac OS X、Windows和 Linux 之上的,针对于编写现代Web和云应用的跨平台源代码编辑器,可在桌面上运行,并且可用于Windows,macOS和Linux。它具有对JavaScript,TypeScript和Node.js的内置支持,并具有丰富的其他语言(例如C++,C#,Java,Python,PHP,Go)和运行时(例如.NET和Unity)扩展的生态系统。VS Code也集成了所有一款现代编辑器所应该具备的特性,包括语法高亮,可定制的热键绑定,括号匹配以及代码片段收集, 也拥有对 Git 的开箱即用的支持。 操作系统:Windows 阅读方式:离线阅读,利用git将项目clone到本地,并且可以随时跟进项目的更新。

33fc5733a6dffbe263b9cf162c929698.png

四、Web项目解析

4.1 整体结构

Web项目的主要结构如图4-1所示,readme文件是对整个项目的简要说明,主要介绍了每个模块及其功能,与前文类似;package.json和lerna.json文件主要说明了一些依赖库的版本说明;node_modules目录下是项目用到的底层支持库;packages是项目主要组成,关键的功能模块都打包在了packages目录下;playground相当于主程序或启动程序;script是一个js文件,用于项目运行时请求服务器。

9898e370977400bc665c2ad57df90042.png

4.2 主程序

主程序为index.html文件,是需要打开的主网页,主程序十分简洁明了,部分说明了编码形式与标题等主要内容,调用部分只有dokit.js文件,也就是各模块的所有功能都整合在了dokit.js文件中。

62f67c211d7786a072bbaf098c703284.png

4.3 packages目录 前文说到,packages包含了Web项目的所有主要成分,其中又细分为三个模块:core、utils、web。其中core部分主要定义了网页需要的容器,比如框架、画布等内容,并且实现了容器之间的通信,以及路由管理;utils封装一些工具类,如对操作系统的区分,主要属于比较底层应用;web部分是网页层面的应用接口,也是顶层应用,比如前面提到的日志功能都来自web封装。使用这样三级模块,可以实现容器与应用的解耦,使得应用开发者可以专心设计应用对应的功能,而不需要过多的应用与容器的对接。本文将重点针对应用部分进行解析,因为剩余的课程项目也是围绕应用功能展开的。

86061023fc7efe4cbe459b8b0b167da7.png

4.4 Web模块

Web模块中主要实现了应用接口,在Dokit框架中网页中的应用功能可以与容器设定解耦,因此首先关注应用的实现。目前有三个比较简易的功能:hello world、应用信息展示、日志同步。这三个功能彼此独立,分开实现后绑定在统一容器内。

Hello World: 此功能最为简易,只需要在页面特定位置显示对应信息,并设置对应的风格,如字体大小颜色、对齐方式等。在语言分工上,html语言定义了网页中不同的div块,在此应用中主要是需要展示的文字,css代码定义了每个块的渲染风格,此例中为补齐长度和文字对齐位置 abf4b9aa48a6257d43f9416721d4ac4f.png

应用信息: 在展示应用信息,需要将对应信息绑定一个组件中,此例中为在common文件下的Card组件,Card组件只有一个Title属性。.dokit-card定义了Card组件的某些渲染特征比较明显的,将背景色设为了蓝色。组件的主要功能即为显示Title字符串。

1d6982a3a4e789844b6210b93ee33dd0.png

efa12555849140dcc5f42a5b3b166419.png

与Hello World相比,应用展示功能需要获取数据,比如页面数据以及用户当前的设备数据,而这些数据可通过全局的window类中调用方法获取,也可以从document类获取窗口中的DOM元素element。 HTML 文档的主干是标签(tag),根据文档对象模型(DOM),每个 HTML 标签都是一个对象。嵌套的标签是闭合标签的“子标签(children)”。标签内的文本也是一个对象。所有这些对象都可以通过 JavaScript 来访问,我们可以使用它们来修改页面。

每个树的节点都是一个对象。标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:<html> 在根节点,<head> <body> 是其子项。元素内的文本形成文本节点,被标记为 #text。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。例如,<title> 标签里面有文本 "About elk"。 应用信息主要分为“用户信息”和“设备信息”,这两个标题分别显示在前面所述的Card组件中;具体的数据呈现在html中的表格:

export default {
  components: {
    Card  //Card作为当前Vue应用的一个组件
  },
  data() {// AppInfo 需要获取的数据
    return {
      ua: window.navigator.userAgent,  //浏览器以及用户数据
      url: window.location.href,       //浏览器url地址
      ratio: window.devicePixelRatio,  // 页面放缩比例
      screen: window.screen,            //屏幕尺寸信息
      viewport: {
        width: document.documentElement.clientWidth,    //屏幕用户使用范围宽度
        height: document.documentElement.clientHeight   //屏幕用户使用范围高度
      }
    }
  },
}
<template>
  <div class="app-info-container">     <!--绑定一级容器风格-->
    <div class="info-wrapper">         <!--绑定二级容器风格-->
      <Card title="Page Info">         <!--将要显示的信息分别绑定在两个Card容器中,分别是页面信息和设备信息-->
        <table border="1">             <!--用表格展示具体数据-->
          <tr>
            <td>UA</td>                <!--表头:用户信息-->
            <td>{{ua}}</td>             <!--具体数据,用vue对象中data的对应变量ua绑定-->
          </tr>
          <tr>
            <td>URL</td>                <!--表头:url地址-->
            <td>{{url}}</td>            <!--具体数据,用vue对象中data的对应变量url绑定-->
          </tr>
        </table>
      </Card>
    </div>

console日志功能: 日志功能的内容,是可以在网页中显示控制台命令console.log中输出的数据,并且可以折叠/展开对应的标号与内容细节。与前面两部分相比,此部分内容略复杂,需要两个组件Detail和Logitem的支持。 其中Detail组件可以通过点击控制数据是否折叠,其中可折叠的类型有对象和数组,即可迭代对象: const TYPE_CAN_FOLD = ['Object', 'Array']

Detail实例中只有一个unfold数据,用于控制是否折叠的转换,转换的触发发生在组件被点击时,即组件实例的@click事件绑定了一个方法unfoldDetail(),

  data () {
    return {
      unfold: false
    }
  },
  methods: {
    unfoldDetail() {
      this.unfold = !this.unfold
    }
  }
 <div @click="unfoldDetail" v-html="displayDetailValue"></div>

Detail可以根据日志的数据类型显示不同的细节内容, Detail的关键在于自我的嵌套,这样可以实现可迭代数据的嵌套,如果日志中的数据本身属于不可迭代的类型,则直接显示数据内容,如字符串、整形数据等;如果是可迭代数据,利用canfold数据进行判断,则将数据再次封装为一个Detail组件,可以以树形结构展示迭代对象内部的细节。

export default {
  components: {
    Detail
  },
    <template v-if="canFold">
      <div v-show="unfold" v-for="(key, index) in detailValue" :key="index">
        <Detail :detailValue="key" :detailIndex="index"></Detail>
      </div>
    </template>

Logitem对应了每一条console.log的日志信息,将Detail作为一个子组件,并且可以展示日志细节。在默认情况下将以文本形式展示当前日志中每个元数据的内容,如果有可迭代类型将标明数据类型(object/Array),每条日志中的每个元数据又被封装成了detail,类似的定义了点击事件“toggleDetail”,决定是否展开/折叠事件。由于包含了子组件Detail,显示细节的任务又完全由Detail内部实现。

export default {
  components: {
    Detail
  },
  methods: {
    toggleDetail () {
      this.showDetail = !this.showDetail
    }
  }
<div v-if="showDetail">
      <div class="list-item" v-if="typeof value === 'object'" v-for="(key, index) in value">
        <Detail :detailValue="key" :detailIndex="index"></Detail>
      </div>
    </div>

console日志功能: 回到具体的日志功能,将Logitem作为子组件,需要把每条日志数据封装在一个Logitem中,并用一个loglist的数据存储每条日志

  components: {
    LogItem
  },
  data() {
    return {
      logList: []
    }
  },

调用生命周期中的created()方法,即在创建之后就可以随时绑定控制台中的console.log()中的内容。具体实现时,需要绑定全局窗口的window.console方法获取日志中的数据,并不对更新到自己的loglist属性中。

  created () {
    let self = this
    let originConsole = window.console;
    ['log'].forEach(type => {
      let origin =  originConsole[type].bind(originConsole)
      originConsole[type] = function (...args) {
        self.logList.push(args)
        origin(...args)
      }
    })
  }

4.5 应用的封装

现有的三个应用彼此独立,在完成单独的功能设计后,需要将其视为三个独立的组件包裹成一个应用容器ToolsContainer。包裹时利用tabs数据储存所有的组件功能和提示名,用v-for遍历所有的应用组件,利用<keep-alive>标签确定被激活的应用,在点击对应应用时,进行激活应用的动态切换。

  components: {
    ToolConsole,
    ToolHelloWorld,
    ToolAppInfo
  },
  data() {
    return {
      tabs: [{
        component: 'console',
        displayName: 'Console'
      },{
        component: 'app-info',
        displayName: 'AppInfo'
      },{
        component: 'hello-world',
        displayName: 'HelloWorld'
      }],
      currentTab: 'console'
    }
  },

4.6 web模块设计小结

由前面的解析可以看出,web应用层面的设计具有很强的层次结构,即上一层的功能仅需要对下一层进行封装,而不需要涉及更底层的模块调用,这在设计上逻辑层次非常清晰,使得模块之间实现解耦,这样可以更好的对单一模块进行优化,避免牵一发而动全身。以日志功能为例,单条日志内的每一条数据,如字符串、整数、数组、对象封装在Detail组件中,而一整条的日志封装在Logitem组件中,整体的日志功能可以以logitem为最小单位进行调用,而不需要再考虑更下一层的Detail; 更进一步的,基础功能的封装可以把单个功能单位(helloworld、应用信息、日志)进行组合,而不需要考虑每个功能内部需要的组件,这种层次结构,可简单的表示为下图:

80ba44fe9daf210ec1ef6db7669078f4.png

五、实验验证

下面将项目运行从而更加直观的体验Web功能的设计

1.将项目从github上进行clone,按照DEVELOPMENT.md中的说明,在终端执行命令行npm run bootstrap 和 npm run dev:playground,可以申请网络服务打开页面,端口为http://localhost:3000/playground 目前界面比较简单,下方的图标即为功能入口

ecd8b8e0ac86cdfe24f43f87dd4cba5f.png

2.点击图标即可看到现有的功能,可以看到整个页面的容器框架可以分为标题栏、基础功能栏、平台功能栏和版本信息栏,由红色箭头指明,这些内容定义在了core文件夹下。在功能栏内部的容器时每个功能对应的bar,即为蓝框划定的图标,bar类同样属于core模块中定义的容器。

a1c5c92fe944df507042574487f954e7.png

3.分别点击“基础功能”栏内的图标即可看到对应的应用页面,下图分别显示了Hello World、应用信息、日志功能的界面。Hello World页面比较简单只有标题文字;应用信息部分,红色箭头指向的蓝色标题框即为前文提到过的Card容器,蓝色箭头指向的表格为Card组件内部的表格标签

ec98c0fe135bf45e239ba74a52ae616b.png

a1badc0b4a66500b10f5774906cc81ca.png

a6e7455ca3f3ba58d2638de4127b0399.png

4.日志功能需要与网页控制台中的console.log进行交互,因此我们打开控制台利用函数输入一些数据查看效果,可以看到控制台中的信息可以重现在界面中,下方右图中的两行数据即为前文提到的logitem组件,点击可以触发事件,进行细节的展示,细节展示的功能即为Detail组件的功能,如蓝色箭头所指。

d08715a1950a79079d6ea9c7700781f3.png

5.前文提到,Detail组件可以自我嵌套,从而实现可迭代数据的详细嵌套关系,我们在console中输入此类数据查看效果,此时从日志维度观察,界面中已经有了两条日志,也就是对应了两个Logitem组件,loglist数组中包含了两个元素。

211966e51bcf16c9086952cd3bd1b746.png e33e4821ad0586114e6ac8106a62cedb.png

在第二条日志中,输入了一个字符串“abc”、一个数组Array、和一个对象Object,每条数据成为了一个Detail组件,点击可以触发对应事件,查看详细的数据信息:而数组和对象本身又是可迭代数据,在Detail中被嵌套成了一个新的Detail,而Array中的第三个元素又是一个Array[3,4,5],它再次被嵌套成了一个Detail组件,因此可以不断点击组件查看数据内容,最终可以得到如下的效果:由此可以看到,通过嵌套Detail组件,可以以树形结构展示迭代数据的详细内容.

4afec942bd07323f93713241f8291294.png

六、总结

Dokit的web项目主要由core、utils、web三部分组成,其中core部分主要实现了容器的定义和路由管理;utils部分封装了部分底层的应用接口;web部分定义了网页中的应用功能,这三部分实现了解耦,使得是实现页面功能时不需要过度考虑容器问题。我们重点针对网页应用部分,分析了现有的三个功能的设计框架思想,其中日志部分利用了嵌套组件,实现了可迭代数据类型的展开。整体上web项目的设计实现了层层递进,模块解离,这使得项目具有更好的可拓展性,也便于bug的定位与改进,这种代码设计风格值得学习与借鉴。