DoKit&Peking University Topic -DoKit For Web source code reading

DoKit&Peking University Topic -DoKit For Web source code reading

Thematic background

In recent years, with the vigorous development of open source in China, some colleges and universities have also begun to explore to let open source enter the campus, so that students can feel the charm of open source during their student years. This is also a brand new attempt by universities and leading domestic Internet companies. Teaching mode. This topic will record the students' learning achievements during this period.

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

Series of articles

[DoKit&Peking University Topic] Origin

DoKit&Peking University Topic -Read Mini Program Source Code (1)

DoKit&Peking University Topic -Read Mini Program Source Code (2)

DoKit&Peking University Topic -Read Mini Program Source Code (3)

DoKit&Peking University Topic -Implementing DoKit For Web Request Capture Tool (1) Product Research

[DoKit&Peking University Topic]-DoKit For applet source code analysis

DoKit&Peking University Topic -Discussion on the zero-intrusion concept of Didi DoKit business code (small terminal)

[DoKit&Peking University Topics]-Didi DoKit For Web Module Source Code Reading

DoKit&Peking University Topic -Didi DoKit For Web Module Source Code Reading (2)

DoKit&Peking University Topic -DoKit For iOS Visual Tool Module Source Code Reading

[DoKit&Peking University Special]-DoKit For iOSUI component Color Picker source code reading analysis

DoKit&Peking University Topic -DoKit For Web Source Code Reading

original

Some first-time bags

npm-run-all

One

npm
Library, this tool is to solve the official
npm run
Command cannot run multiple scripts at the same time

This package provides three commands, namely

npm-run-all
run-s
run-p
, Of which the latter two are
npm-run-all
The abbreviations with parameters correspond to serial and parallel respectively.

lerna

Lerna
Is a management tool used to manage multiple software packages (
package
)of
JavaScript
project.

github repository

getting Started

# Installation npm install --global lerna # Create a new Warehouse Code git init lerna-repo && cd lerna-repo # Becomes lerna warehouse lerna init Copy code

Your code repository should currently have the following structure:

my-lerna-repo/ package.json packages/ package-1/ package.json package-2/ package.json Copy code

Common instructions

The two primary commands in Lerna are

lerna bootstrap
and
lerna publish
.

bootstrap
will link dependencies in the repo together.
publish
will help publish any updated packages.

rollup.js

Rollup is a JavaScript module packager that can compile small pieces of code into large pieces of complex code, such as libraries or applications.

To be added...

Source code analysis-organizational structure

The directory structure is a standard

lerna
The structure of the project, as described above

package.json

First analyze the root directory

package.json

"scripts" : { "bootstrap" : "run-s bootstrap:project bootstrap:package" , "bootstrap:project" : "npm install" , "bootstrap:package" : "lerna bootstrap" , "build" : "lerna run build" , "dev" : "lerna run dev --parallel" , "dev:playground" : "run-p serve:playground dev" , "serve:playground" : "node scripts/dev-playground.js" , "clean" : "run-s clean:lerna clean:lock" , "clean:lerna": "lerna clean --yes" , "clean:lock" : "run-s clean:lock-package clean:lock-subpackage" , "clean:lock-package" : "rm -rf ./package-lock.json " , "clean:lock-subpackage" : "rm -rf ./packages/**/package-lock.json" }, Copy code

The commands to run are

npm run bootstrap
,
npm run build
with
npm run dev:playground

The first two are both installing and relying on related content, and the main running part is

node scripts/dev-playground.jsCopy code

scripts/dev-playground.js

You can see the entrance is

dev-playground.js
In the file, the code is as follows:

const http = require ( 'http' ); const serveHandler = require ( 'serve-handler' ); const open = require ( 'open' ); run(); function run () { const server = http.createServer( ( request, response ) => { return serveHandler(request, response); }) server.listen( 3000 , () => { console .log( 'Dev Server Running at http://localhost:3000' ); open( 'http://localhost:3000/playground' ); }) } Copy code

This part is to run a

server
And pointed to
./playground/index.html
file

/playground/index.html

<!DOCTYPE html > < html lang = "en" > < head > < meta charset = "UTF-8" > < meta http-equiv = "X-UA-Compatible" content = "IE=edge" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < title > Dokit For Web </title > </head > < body > < h1 > Dokit For Web </h1 > < h2 > Playground </h2 > </body > < script src = "https://unpkg.com/vue@next" > </script > < script src = "../packages/ web/dist/dokit.js" > </script > </html > Copy code

Here is the main web page entrance, and the content introduced is in

web/dist/dokit.js
in

packages/web/

Under this directory is an exploit

rollup
Packaged modules, view
rollup.config.js
As you can see, the quoted above
dokit.js
Is the packaged output file, the input file is
src/index.js

src/index.js

import {Dokit} from '@dokit/web-core' import {Features} from './feature' /* * TODO Global Register Dokit */ window .Dokit = new Dokit({ features : Features, }); Copy code

according to

js
Content, continue to find
feature.js
with
@dokit/web-core

src/feature.js

In this file, you can see that a total of 4 plug-ins have been introduced

import Console from './plugins/ console/index' import AppInfo from './plugins/app-info/index' import DemoPlugin from './plugins/demo-plugin/index' import HelloWorld from './components/ToolHelloWorld' Copy code

However, there are many plug-ins displayed on the webpage, continue to look down

export const BasicFeatures = { title : 'Common Tools' , list : [Console, AppInfo, DemoPlugin, { nameZh : 'H5 any door a ' , name : 'h5-door' , icon : 'https://pt-starimg. didistatic.com/static/starimg/img/FHqpI3InaS1618997548865.png' , component : AppInfo }] } export const DokitFeatures = { title : 'Platform Features' , list : [{ nameZh : 'Mock data' , name : 'mock' , icon : 'https : //pt-starimg.didistatic.com/static/starimg/img/aDn77poRDB1618997545078.png' , component : HelloWorld }] } export const UIFeatures = { title : 'Visual features' , list : [{ nameZh : 'Color picker' , name : 'color-selector' , icon : 'https : //pt-starimg.didistatic.com/static/starimg /img/QYUvEE8FnN1618997536890.png' , component : HelloWorld }, { nameZh : 'align-ruler' , name : 'align-ruler' , icon : 'https : //pt-starimg.didistatic.com/static/starimg/img/a5UTjMn6lO1618997535798.png ' , component : HelloWorld }, { nameZh : 'UI structure' , name : 'view-selector' , icon : 'https : //pt-starimg.didistatic.com/static/starimg/img/XNViIWzG7N1618997548483.png ' , component : HelloWorld }] } Export const Features = [BasicFeatures, DokitFeatures, UIFeatures] copy the code

can be seen,

DokitFeatures
with
UIFeatures
Is just occupying a position, the actual corresponding plug-in is still
HelloWorld
of
demo
, And finally all of these
export
Just get out.

packages/core

As seen above, a global component is declared

Dokit
, This one comes from
@dokit/web-core
, Continue to look for its defined location. Since it is
web-core
, Then
core/
Try to find under the folder. View
package.json
, The files in this directory
name
Just
@dokit/web-core
,its
main
The file is
dist/index.js
. It s not hard to guess that this file is still
rollup
The packaged files, view
rollup.config.js
, The source file is
src/index.js
. Here, found
Dokit
Definition:

Import {createApp} from 'VUE' Import the App from './components/app' Import Store from './store' Import {applyLifecyle, LifecycleHooks} from './common/js/lifecycle' Import {getRouter} from './router' export class Dokit { options = null constructor(options){ this.options = options let app = createApp(App); let {features} = options; app.use(getRouter(features)); app.use(Store); Store.state.features = features; this.app = app; this.init(); this.onLoad(); } onLoad(){ //Lifecycle Load applyLifecyle(this.options.features, LifecycleHooks.LOAD) } onUnload(){ //Lifecycle UnLoad applyLifecyle(this.options.features, LifecycleHooks.UNLOAD) } init() { let dokitRoot = document .createElement( 'div' ) dokitRoot.id = "dokit-root" document .documentElement.appendChild(dokitRoot); //dokit container let el = document .createElement( 'div' ) el.id = "dokit-container" this .app.mount(el) dokitRoot.appendChild(el) } } export * from './store' export * from './common/js/feature' export default { Dokit } Copy code

Dokit
The initialization process is now based on
App
Create an instance, then load two templates
getRouter
with
Store
And put the plugin
Features
Passed to
Store
.
init()
Function will
dokit
The plug-in is inserted into the document flow.

components/app.vue

<template> <div class="dokit-app"> <div class="dokit-entry-btn" v-dragable @click="toggleContainer"></div> <div class="mask" v-show="showContainer" @click="toggleContainer"></div> <router-container v-show="showContainer"></router-container> </div > </template> < script > import dragable from "../common/directives/dragable" ; import RouterContainer from './container' ; export default { components : { RouterContainer }, directives : { dragable, }, data () { return { showContainer : false , }; }, methods : { toggleContainer () { this .showContainer =! this .showContainer; }, }, }; </script > copy code

app.vue
Declares a component, its corresponding should be
Dokit
Button through
showContainer
To control whether to display the plug-in container. I used it again
RouterContainer

components/container.vue

<template> <div class="container"> <top-bar :title="title" :canBack="canBack"></top-bar> <div class="router-container"> <router-view v-slot="{ Component }"> <keep-alive> <component :is="Component"/> </keep-alive> </router-view> </div> </div > </template> < script > import TopBar from "../common/components/top-bar" ; export default { components : { TopBar }, data () { return {} }, computed :{ curRoute () { return this .$router.currentRoute.value }, title () { return this .curRoute.meta.title || 'Dokit' }, canBack () { return this .curRoute.name !== 'index' } }, created () { } } </script > copy code

As you can see, here is

Dokit
Menu bar component

store/index.js

Continue to watch

Store
Source code

import {Store} from "../common/js/store" ; const store = new Store({ state : { features : [] } }) //Update global Store data export function updateGlobalData ( key, value ) { store.state[key] = value } //Get the status of the current Store data export function getGlobalData () { return store.state } Export default Store Copy the code

It looks like this class is used for data storage

Keep looking

Store
Definition

common/js/store.js

Import {reactive} from 'VUE' const Storekey = 'Store' /** * Simple version Store implementation * Support direct modification of Store data */ export class Store { constructor ( options ) { let {state} = options this .initData(state) } initData ( data = {} ) { this ._state = reactive({ data : data }) } get state (){ return this ._state.data } install ( app ) { app.provide(storeKey, this ) app.config.globalProperties.$store = this } } Copy code

What the source code shows is indeed the function of accessing data.

router/index.js

Continue to analyze

Dokit
The introduced components, the next one is
getRouter
Components:

app.use(getRouter(features)); Copy code

Here will

features
Passed to
getRouter

Import {createRouter, createMemoryHistory} from 'VUE-Router' Import {routes, getRoutes} from './routes' export function getRouter ( features ) { return createRouter({ routes : [...routes, ...getRoutes(features)], history : createMemoryHistory() }) } Copy code

getRouter
received
features
After the parameters, call
getRoutes
Function and further create a route

router/routers.js

import Index from '../ components /index' export const routes = [{ path : '/' , name : 'index' , component : Index }] export function getRoutes ( features ) { let routes = [] features.forEach( feature => { let {list, title :featureTitle} = feature list.forEach( item => { //TODO temporarily only supports the plug-in let in routing mode {name, title, component} = item routes.push({ path : `/${name} ` , name : name, component : component.component || component, meta : { title : title, feature : featureTitle } }) }) }) return routes } Copy code

The content of this part is also easy to understand, that is to say

features
The content in it generates routes in turn, so as to complete the jump of different plug-in pages.

As you can see, a root directory route is also introduced in the head

Index
, Continue to in-depth analysis

components/index.vue

<template> < div class = "index-container" > < card v-for = "(item, index) in features" :key = "index" :title = "item.title" :list = "item.list" > </card > < version-card :version = "version" > </version-card > </div > </template> < Script > Import topbar from "../common/components/top-bar" ; Import Card from "../common/components/card" ; Import VersionCard from "../common/components/version" ; Export default { components : { TopBar, Card, VersionCard }, data () { return { version : '1.3.0' } }, mounted () { }, computed : { features () { return this .$store.state.features } } }; </script > copy code

The component is

Dokit
The menu page of the
features
The contents of each generate one
<card>
. in
web/src/features.js
Can be seen in
features
There are also functional classifications for different plug-ins. There are several plug-ins under each function, so you can guess
<card>
Will traverse again
list
, And then generate buttons for different plug-ins.

turn up

card.vue
:

comon/components/card.vue

<template> < div class = "card" > < div class = "card-title" > < span class = "card-title-text" > {{title}} </span > </div > < div class = "item-list" > < div class = "item" v-for = "(item,index) in list" :key = "index"@ click = "handleClickItem(item)" > <div class = "item-icon" > < img class = "item-icon-image" :src = "item.icon || defaultIcon" /> </div > < div class = "item-title" > {{item .nameZh ||'Default function'}} </div > </div > </div > </div > </template> < script > import {DefaultItemIcon} from '../js/icon' export default { props : { title : { default : 'special area' }, list : { default : [] } }, data () { return { defaultIcon : DefaultItemIcon } }, methods : { handleClickItem ( item ) { this .$router.push({ name : item.name }) } } }; </script > copy code

Indeed, the code

list
Iterate over, and then generate a button for each plug-in, just as guessed.

Source code analysis-function realization

Currently

web
Only realized
Log
with
Application information
Two functions, other functions only
demo
template.

app-info

ToolAppInfo.vue

<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 zoom 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 } } }, } Copy code

This part of the content is relatively simple, all are called

js
in
window
with
document
The built-in function in to get device information, through
vue
The data binding shows up

console

The implementation of the plug-in function is slightly complicated, as can be seen from the directory structure, it is divided into many small modules

console/ -css/ -js/ --- console.js -console-tap.vue -index.js -log-container.vue -log-detail,vue -log-item.vue -main.vue -op-command.vue Copy code

From

index.js
You can see in:

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 code

The main plugin is from

main.vue
Introduced in
Console

main.vue

<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 } }, computed :{ 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 code

Here again, four components are introduced next time, and then continue to analyze in turn

console-tab.vue

<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> Copy code

This part is

console
Above
tab
Column, according to the incoming
tabs
Generate all
tab

log-container.vue

<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> Copy code

The component is

cosole
of
log
Partly according to
logList
Put all
log
display. The display method is
log-item
, Referenced in
log-item.vue

log-item.vue

<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> Copy code

Each

log-item
All follow
Detail
Shows the way,

log-detail.vue

<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> Copy code

Mainly used to display data components, the following

script
Part of the style will change according to different content, so I won t analyze it in detail here.

op-command.vue

<template> < div class = "operation" > < div class = "input-wrapper" > < input class = "input" placeholder = "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 code

here it is

console
In the input/execution part of the command, every command will be passed to
excuteScript
And execute.

The above is control

console
The UI part of the code, the rest are from
js/console
, For the specific logic realization part

js/console.js

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 code

The file first defines a few constants,

LogMap
for
console
Shown in
tab
,
LogNum
with
ConsoleLogMap
Is the mapping between the type and the corresponding subscript,
CONSOLE_METHODS
for
console
Different types of methods in.
LogTabs
Is to generate a
tab
List, generate
console-tab
When used.

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

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

console
console
callback
origConsole
restoreConsole
console

index.js
:

onLoad(){ console.log('Load') overrideConsole(({name, type, value}) => { let state = getGlobalData(); state.logList = state.logList || []; state.logList.push({ type: type, name: name, value: value }); }); },

overrideCOnsole
callback
state
log-container

foolbit

juejin.cn/post/695726