Project background

In recent years, with the vigorous development of open source in China, some colleges and universities have begun to explore open source into the campus, so that students can feel the charm of open source when they are students. This is also a new teaching mode that colleges and universities and domestic leading Internet enterprises try together. This project will document the learning results of the students during this period.

More topic background reference: [DoKit& Peking University topic] origin

series

[DoKit& Peking University] Origin

【DoKit& Peking University special Topic 】- Read the small program source code (a)

【DoKit& Peking University special Topic 】- Read the small program source code (two)

【DoKit& Peking University Special Topics 】- Read the small program source code (three)

Implementation of DoKit For Web request capture tool (I) Product research

【DoKit& Peking University topics 】-DoKit For small program source analysis

[DoKit& Peking University Special Topic] – Talk about didi DoKit Business Code zero intrusion thought (small program)

[DoKit& Peking University Topic] – Didi DoKit For Web module source code reading

Didi DoKit For Web module source code reading (2)

【DoKit& Peking University topics 】-DoKit For iOS visual tools module source reading

DoKit For iOSUI component Color Picker source code reading analysis

【DoKit& Peking University Topics 】-DoKit For Web source reading report

The original

directory

  • Project introduction
  • Source code reader
  • The project structure
  • Source code analysis
  • The experiment

Project introduction

DoraemonKit, or DoKit for short, is a functional platform that allows every App to quickly access some commonly used or unimplemented auxiliary development tools, test efficiency tools, visual AIDS.

DoKit For Web is a front-end eco-oriented development tool, which enables developers to view various data information and network requests on mobile terminals during Web development, rather than just simulating various mobile phones through development tools on PCS.

Currently, there are log and application information functions, and it is expected that there will be interface capture, view element view, resource view, application storage, interface Mock, screen recording and playback, automatic testing and other functions in the future.

Source code reader

Clone the DoKit project code locally and read the source code and tests using VSCode 1.52.1.

VScode is a cross-platform source editor for writing modern Web and cloud applications running on Mac OS X, Windows, and Linux, with built-in support for JavaScript, TypeScript, and Node.js. Plug-ins such as Vetur make it easy to read and run DoKit For Web source code.

The project structure

The project structure For Dokit For Web is shown below.

Node_modules is the directory where the dependencies for the project are stored.

Packages is the core part of this project, which consists of core, Utils and Web. Core is the container layer, where containers are defined and routing is managed; Utils encapsulates some application interfaces; Web contains various functional plug-ins in DoKit For Web.

Playground is the beginning of the project;

In the scripts directory is the script used by the project, which currently only contains dev-playground.js, which is used to create and connect to the HTTP server while the project is running.

Package. json and lerna.json describe the project information, the scripts used, and the versions of various dependencies;

Readme. md provides a brief introduction to the project functions, while development. md provides a brief introduction to the installation and operation of the project.

Source code analysis

In this part, we analyze the source code of two existing plug-ins.

AppInfo

AppInfo displays application information, including page information and device information.

The interface is shown in the figure.

The plugin is relatively simple, and the file directory structure is as follows:

App-info ├─ index.js ├─ ToolAppInfo.vueCopy the code

In addition to that… /common/Card.vue

index.js

As you can see, this page uses the Appinfo component from ToolAppInfo.vue

import AppInfo from './ToolAppInfo.vue'
import {RouterPlugin} from '@dokit/web-core'

export default new RouterPlugin({
  nameZh: 'Application Information'.name: 'app-info'.icon: 'https://pt-starimg.didistatic.com/static/starimg/img/z1346TQD531618997547642.png'.component: AppInfo
})
Copy the code

ToolAppInfo.vue

This page consists of two cards containing a title and a table for displaying information.

As you can see, the information shown here is all the property values in window and Document.

<template>
  <div class="app-info-container">
    <div class="info-wrapper">
      <Card title="Page Info">
        <table border="1">
          <tr>
            <td>UA</td>
            <td>{{ua}}</td>
          </tr>
          <tr>
            <td>URL</td>
            <td>{{url}}</td>
          </tr>
        </table>
      </Card>
    </div>
    <div class="info-wrapper">
      <Card title="Device Info">
        <table border="1">
          <tr>
            <td>Equipment scaling ratio</td>
            <td>{{ratio}}</td>
          </tr>
          <tr>
            <td>screen</td>
            <td>{{screen.width}}X{{screen.height}}</td>
          </tr>
          <tr>
            <td>viewport</td>
            <td>{{viewport.width}}X{{viewport.height}}</td>
          </tr>
        </table>
      </Card>
    </div>
  </div>
</template>
<script>
import Card from '.. /.. /common/Card'

export default {
  components: {
    Card
  },
  data() {
    return {
      ua: window.navigator.userAgent,
      url: window.location.href,
      ratio: window.devicePixelRatio,
      screen: window.screen,
      viewport: {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    }
  },
}
</script>
Copy the code

Card.vue

The structure of the Card component is also very simple, divided into header and body parts.

<template>
  <div class="dokit-card">
    <div class="dokit-card__header">{{title}}</div>
    <div class="dokit-card__body">
      <slot/>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    title: String
  }
}
</script>
Copy the code

Console

The Console plug-in acts as the Console for the browser itself.

Its interface is shown in the figure below. As you can see, there are five tabs at the top of the page to display different types of console information, including All, Log, Info, Warn, and Error. The middle area outputs console information, and the lower command line is used to input relevant commands.

The file directory structure of Console is as follows.

The console ├ ─ ─ CSS | ├ ─ ─ var. Less ├ ─ ─ js | ├ ─ ─ the console. The js ├ ─ ─ the console - tap. Vue ├ ─ ─ index. The js ├ ─ ─ the log - container. Vue ├ ─ ─ Log-detail. vue Exercises ── log-item.vue Exercises ─ main.vue Exercises ─ op-commandCopy the code

index.js

As you can see from the index.js entry of the Console plug-in, the Console module in main.vue is used. OverrideConsole is used for onLoad and restoreConsole is used for onUnload. Both functions are written in./js/console.js.

import Console from './main.vue'
import {overrideConsole,restoreConsole} from './js/console'
import {getGlobalData, RouterPlugin} from '@dokit/web-core'

export default new RouterPlugin({
  name: 'console'.nameZh: 'log'.component: Console,
  icon: 'https://pt-starimg.didistatic.com/static/starimg/img/PbNXVyzTbq1618997544543.png'.onLoad(){
    console.log('Load')
    overrideConsole(({name, type, value}) = > {
      let state = getGlobalData();
      state.logList = state.logList || [];
      state.logList.push({
        type: type,
        name: name,
        value: value
      });
    });
  },
  onUnload(){
    restoreConsole()
  }
})
Copy the code

main.vue

As you can see from the code, the top-down component layout of the page is console-tap, log-Container, and Operation-Command, as opposed to the plug-in interface we saw earlier.

There are also some important variables and computed properties involved:

LogTabs specifies the name and number of each TAB. CurTab is the id of the current TAB. The default value is 0 (the All page is displayed by default).

The result of the evaluated property is cached, except for recalculation of dependent responsive property changes. LogList obtains the total logList globally. CurLogList is the list of logs that need to be displayed on the current TAB. When curTab changes, logList reads the requested type of logs from logList.

Note that the console object contains many other types of logs besides logs, Info, Warn, and Error, but this plug-in can only display the above four types of logs and their collections.

The handleChangeTab method changes the value of curTab when the TAB is switched, which affects the value of curLogList and changes the displayed log content.

<template>
  <div class="console-container">
    <console-tap :tabs="logTabs" @changeTap="handleChangeTab"></console-tap>
    <div class="log-container">
      <div class="info-container">
        <log-container :logList="curLogList"></log-container>
      </div>
      <div class="operation-container">
        <operation-command></operation-command>
      </div>
    </div>
  </div>
</template>
<script>
import ConsoleTap from './console-tap';
import LogContainer from './log-container';
import OperationCommand from './op-command';
import {LogTabs, LogEnum} from './js/console'
export default {
  components: {
    ConsoleTap,
    LogContainer,
    OperationCommand
  },
  data() {
    return {
      logTabs: LogTabs,
      curTab: LogEnum.ALL / / 0}},computed: {// The result of the calculated property is cached unless recalculated depending on a responsive property change.
    logList(){
      return this.$store.state.logList || []
    },
    curLogList(){
      if(this.curTab == LogEnum.ALL){
        return this.logList
      }
      return this.logList.filter(log= > {
        return log.type == this.curTab
      })
    }
  },
  created () {},
  methods: {
    handleChangeTab(type){
      this.curTab = type
    }
  }
}
</script>
Copy the code

Next, analyze the three components in Main.vue.

console-tap.vue

This component generates five tabs from the logTabs passed in to main.vue and identifies the current TAB.

HandleClickTab is also a method that involves TAB switching, but its main purpose is to mark the current TAB and only affect the contents of the console-tap component.

<template>
  <div class="tab-container">
    <div class="tab-list">
      <div
        class="tab-item"
        :class="curIndex === index? 'tab-active': 'tab-default'"
        v-for="(item, index) in tabs"
        :key="index"
        @click="handleClickTab(item, index)"
      >
        <span class="tab-item-text">{{ item.name }}</span>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    tabs: {
      type: Array}},data() {
    return {
      curIndex: 0
    };
  },
  methods: {
    handleClickTab(item, index) {
      let { type } = item;
      this.curIndex = index;
      this.$emit("changeTap", type); ,}}};</script>
Copy the code

log-container.vue

This is the component that displays log content in the middle of the page, generating multiple log-items based on the contents of the logList.

<template>
  <div class="log-container">
    <log-item
      v-for="(log, index) in logList"
      :key="index"
      :value="log.value"
      :type="log.type"
    ></log-item>
  </div>
</template>
<script>
import LogItem from './log-item'
export default {
  components: {
    LogItem
  },
  props: {
    logList: {
      type: Array.default: [].}},data() {
    return{}; }};</script>
Copy the code

log-item.vue

Each log is displayed in two parts: log-preview and Detail. By default, log-preview is displayed. You can click the content of this part to call toggleDetail to display or close the Detail.

To show the type of data output in the log, it is also extracted from.. /.. /assets/util.js introduces getDataType and getDataStructureStr, The data types included are Number, String, Boolean, RegExp, Symbol, Function, Null, Undefined, Array, and Object.

The contents of log-Preview are derived from the computed property logPreview, which in various cases displays text content and/or data types.

<template>
  <div class="log-ltem">
    <div class="log-preview" v-html="logPreview" @click="toggleDetail"></div>
    <div v-if="showDetail && typeof value === 'object'">
      <div class="list-item" v-for="(key, index) in value" :key="index">
        <Detail :detailValue="key" :detailIndex="index"></Detail>
      </div>
    </div>
  </div>
</template>
<script>
import { getDataType, getDataStructureStr } from '.. /.. /assets/util'
import Detail from './log-detail'

const DATATYPE_NOT_DISPLAY = ['Number'.'String'.'Boolean']

export default {
  components: {
    Detail
  },
  props: {
    type: [Number].value: [String.Number.Object]
  },
  data () {
    return {
      showDetail: false}},computed: {
    logPreview () {
      let dataType = ' '
      let html = `<div>`
      this.value.forEach(arg= > {
        dataType = getDataType(arg)
        if (DATATYPE_NOT_DISPLAY.indexOf(dataType) === -1) {
          html += `<span class="data-type">${dataType}</span>`
        }
        html += `<span class="data-structure">${getDataStructureStr(arg, true)}</span>`
      });
      html += `</div>`
      return html
    }
  },
  methods: {
    toggleDetail () {
      this.showDetail = !this.showDetail
    }
  }
}
</script>
Copy the code

log-detail.vue

Notice in the view section that the Detail section is collapsible and nested.

When the Detail section is expanded, the content displayed is generated by the displayDetailValue method.

If an Object or Array is encountered that can still be folded, only one layer of content is displayed that is not fully expanded. Object is displayed as Object, and Array is displayed as Array (length).

If the content to be displayed is no longer collapsible, the specific content is displayed directly. The design of this place is not perfect. According to detailValue, only three types of variables, String, Number and Object, can be displayed here. If other contents are encountered, an error will be reported. This problem will be shown in the lab section.

<template>
  <div class="detail-container" :class="[canFold ? 'can-unfold':'', unfold ? 'unfolded' : '']" >
    <div @click="unfoldDetail" v-html="displayDetailValue"></div>
    <template v-if="canFold">
      <div v-show="unfold" v-for="(key, index) in detailValue" :key="index">
        <Detail :detailValue="key" :detailIndex="index"></Detail>
      </div>
    </template>
  </div>
</template>
<script>
import Detail from './log-detail'
import { getDataType } from '.. /.. /assets/util'

const TYPE_CAN_FOLD = ['Object'.'Array']
export default {
  name: "Detail".components: {
    Detail
  },
  props: {
    detailValue: [String.Number.Object].detailIndex: [String.Number]
  },
  data () {
    return {
      unfold: false}},computed: {
    dataType () {
     return getDataType(this.detailValue)
    },
    canFold () {
      if (TYPE_CAN_FOLD.indexOf(this.dataType) > -1) {
        return true
      }
      return false
    },
    displayDetailValue () {
      let value = ' '
      if (this.canFold) {
        if (this.dataType === 'Object') {
          value = 'Object'
        }
        if (this.dataType === 'Array') {
          value = `Array(The ${this.detailValue.length}) `}}else {
        value = `<span style="color:#1802C7;" >The ${this.detailValue}</span>`
      }
      return `<span style="color:#7D208C;" >The ${this.detailIndex}</span>: ${value}`}},methods: {
    unfoldDetail() {
      this.unfold = !this.unfold
    }
  }
}
</script>
Copy the code

op-command.vue

This component is the command line at the bottom of the log page.

When the Excute button is clicked, the method excuteCommand checks if the command line is empty, and if not, invokes the excuteScript method from JS /console.js, executes the input, and then prints the returned content to the console. This effect is the same as executing commands on the console of a PC browser.

<template>
  <div class="operation">
    <div class="input-wrapper">
      <input class="input" placeholder="The Command..." v-model="command" />
    </div>
    <div class="button-wrapper" @click="excuteCommand">
      <span>Excute</span>
    </div>
  </div>
</template>
<script>
import {excuteScript} from './js/console'
export default {
  data(){
    return {
      command: ""}},methods: {
    excuteCommand(){
      if(!this.command){
        return
      }
      let ret = excuteScript(this.command)
      console.log(ret)
    }
  }
};
</script>
Copy the code

js/console.js

A number of important constants and methods are defined in console.js.

LogMap is used to generate LogTabs and is introduced in main.vue, which are the tabs in the Console plug-in; LogNum and ConsoleLogMap are mappings between log types and their subscripts. CONSOLE_METHODS are the different logging (method) types used in plug-ins.

export const LogMap = {
  0: 'All'.1: 'Log'.2: 'Info'.3: 'Warn'.4: 'Error'
}
export const LogEnum = {
  ALL: 0.LOG: 1.INFO: 2.WARN: 3.ERROR: 4
}

export const ConsoleLogMap = {
  'log': LogEnum.LOG,
  'info': LogEnum.INFO,
  'warn': LogEnum.WARN,
  'error': LogEnum.ERROR
}

export const CONSOLE_METHODS = ["log"."info".'warn'.'error']

export const LogTabs = Object.keys(LogMap).map(key= > {
  return {
    type: parseInt(key),
    name: LogMap[key]
  }
})
Copy the code

The excuteScript method is explicit in that it executes the input instruction.

export const excuteScript = function(command){
  let ret 
  try{
    ret = eval.call(window.` (${command}) `)}catch(e){
    ret = eval.call(window, command)
  }
  return ret
}
Copy the code

As mentioned earlier, the Console plug-in onLoad calls the overrideConsole method and onUnload calls the restoreConsole method.

The overrideConsole first saves the browser’s original console into Origin, intercepting the execution of part of the original console function, executing the callback passed in from index.js, and then executing the original function. The callback function here is to add the corresponding log to state.loglist.

One of the main ideas of DoKit For Web tools is to intercept the original methods in the browser and then execute them after obtaining the information you need.

We can see that the CONSOLE_METHODS and ConsoleLogMap constants called here, both of which contain only log, INFO, WARN, and error, indicate that in the overrideConsole, The original methods that are intercepted are console.log, console.info, console.warn, and console.error, the only four types of logs that are added to logList and displayed in the Console plug-in.

RestoreConsole restores the browser’s console.

export const origConsole = {}
export const noop = () = > {}
export const overrideConsole = function(callback) {
  const winConsole = window.console
  CONSOLE_METHODS.forEach((name) = > {
    let origin = (origConsole[name] = noop)
    if (winConsole[name]) {
      origin = origConsole[name] = winConsole[name].bind(winConsole)
    }

    winConsole[name] = (. args) = > {
      callback({
        name: name,
        type: ConsoleLogMap[name],
        value: args }) origin(... args) } }) }export const restoreConsole = function(){
  const winConsole = window.console
  CONSOLE_METHODS.forEach((name) = > {
    winConsole[name] = origConsole[name]
  })
}
Copy the code

The experiment

The experimental section of this article will show the main functional interfaces of DoKit For Web and focus on testing and discussing the Console module.

Clone the project to local, follow the instructions in development. md, and execute NPM run bootstrap, NPM run build, and NPM run dev:playground on the command line in sequence. Through the browser to access the browser to http://localhost:3000/playground, open the interface shown below. The source code for this interface is in playground/index.html.

Click the round button (draggable, initially in the upper left corner of the page) to expand the DoKit For Web toolbar. Among these tools, only logs and application information currently contain actual functions, and the rest are demo pages.

Let’s jump right into the logging plug-in and experiment with its main features.

From the previous source code analysis, we learned that the original methods intercepted in this plug-in are console.log, console.info, console.warn, and console.error, and that only logs generated by these four methods can be added to the logList displayed by this tool.

Execute the following four commands in the browser console:

console.log('log')
console.info('info')
console.warn('warn')
console.error('error')
Copy the code

As you can see, the output is displayed separately under four tabs (executing the command on the Console plug-in’s command line has a similar effect, but with an extra line of excuteScript return value undefined, affecting the display).

To verify the folding and expansion of log-item, execute

The console. The log (" aaa ", {name: 'Alice', id: 1}, [1, 2, [3, 4]])Copy the code

The three types are string, object, and array. We can see the difference between the browser’s Console and DoKit’s Console display. Browsers preview slightly better than DoKit; The browser puts the data type at the end of the detail expansion, and DoKit puts it in the preview/first-level detail. There is still room for improvement in the display of the Console plug-in.

Test other methods of the browser’s Console object. In the figure below, we also execute twice

console.count('log')
Copy the code

And in the browser console normal output log counting results. But since DoKit does not intercept console.count, this section is not displayed on the left.

In addition, current logging tools do not catch global exceptions.

console.log(qqqq)
Copy the code

That is, output an undefined variable.

If you execute the above statement in the browser console, there is no output on the left.

This is because in the dev environment, vue does not catch errors, but throws; The online environment catches the error and then console.error is printed.

In the current version, if inConsoleExecuting the above statement on the command line of the plug-in repeatedly generates vue WARN as shown in the figure and falls into an infinite loop.

In addition, if console.log is a function (or another variable of a type not included in the detailValue list), the detail will also fall into an infinite loop due to type checking errors when expanding.

function a(b){a=b}
console.log(a)
Copy the code

The author information

Author: MistyMist

Original link: juejin.cn/post/695693…

Source: Nuggets