精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

小程序依賴分析實(shí)踐

網(wǎng)絡(luò) 通信技術(shù)
在業(yè)務(wù)開發(fā)中,小程序 IDE 每次啟動都需要進(jìn)行全量的編譯,開發(fā)版預(yù)覽的時(shí)候會等待較長的時(shí)間,我們現(xiàn)在有文件依賴關(guān)系后,就可以只選取目前正在開發(fā)的頁面進(jìn)行打包,這樣就能大大提高我們的開發(fā)效率。

[[350074]]

 用過 webpack 的同學(xué)肯定知道 webpack-bundle-analyzer ,可以用來分析當(dāng)前項(xiàng)目 js 文件的依賴關(guān)系。

webpack-bundle-analyzer

 

因?yàn)樽罱恢痹谧鲂〕绦驑I(yè)務(wù),而且小程序?qū)Πw大小特別敏感,所以就想著能不能做一個類似的工具,用來查看當(dāng)前小程序各個主包與分包之間的依賴關(guān)系。經(jīng)過幾天的折騰終于做出來了,效果如下:

[[350075]]

小程序依賴關(guān)系

今天的文章就帶大家來實(shí)現(xiàn)這個工具。

小程序入口

小程序的頁面通過 app.json 的 pages 參數(shù)定義,用于指定小程序由哪些頁面組成,每一項(xiàng)都對應(yīng)一個頁面的路徑(含文件名) 信息。 pages 內(nèi)的每個頁面,小程序都會去尋找對應(yīng)的 json, js, wxml, wxss 四個文件進(jìn)行處理。

如開發(fā)目錄為:

  1. ├── app.js 
  2. ├── app.json 
  3. ├── app.wxss 
  4. ├── pages 
  5. │   │── index 
  6. │   │   ├── index.wxml 
  7. │   │   ├── index.js 
  8. │   │   ├── index.json 
  9. │   │   └── index.wxss 
  10. │   └── logs 
  11. │       ├── logs.wxml 
  12. │       └── logs.js 
  13. └── utils 

則需要在 app.json 中寫:

  1.   "pages": ["pages/index/index""pages/logs/logs"

為了方便演示,我們先 fork 一份小程序的官方demo,然后新建一個文件 depend.js,依賴分析相關(guān)的工作就在這個文件里面實(shí)現(xiàn)。

  1. $ git clone git@github.com:wechat-miniprogram/miniprogram-demo.git 
  2. $ cd miniprogram-demo 
  3. $ touch depend.js 

其大致的目錄結(jié)構(gòu)如下:

目錄結(jié)構(gòu)

以 app.json 為入口,我們可以獲取所有主包下的頁面。

  1. const fs = require('fs-extra'
  2. const path = require('path'
  3.  
  4. const root = process.cwd() 
  5.  
  6. class Depend { 
  7.   constructor() { 
  8.     this.context = path.join(root, 'miniprogram'
  9.   } 
  10.   // 獲取絕對地址 
  11.   getAbsolute(file) { 
  12.     return path.join(this.context, file) 
  13.   } 
  14.   run() { 
  15.     const appPath = this.getAbsolute('app.json'
  16.     const appJson = fs.readJsonSync(appPath) 
  17.     const { pages } = appJson // 主包的所有頁面 
  18.   } 

每個頁面會對應(yīng) json, js, wxml, wxss 四個文件:

  1. const Extends = ['.js''.json''.wxml''.wxss'
  2. class Depend { 
  3.   constructor() { 
  4.     // 存儲文件 
  5.     this.files = new Set() 
  6.     this.context = path.join(root, 'miniprogram'
  7.   } 
  8.   // 修改文件后綴 
  9.   replaceExt(filePath, ext = '') { 
  10.     const dirName = path.dirname(filePath) 
  11.     const extName = path.extname(filePath) 
  12.     const fileName = path.basename(filePath, extName) 
  13.     return path.join(dirName, fileName + ext) 
  14.   } 
  15.   run() { 
  16.     // 省略獲取 pages 過程 
  17.     pages.forEach(page => { 
  18.       // 獲取絕對地址 
  19.       const absPath = this.getAbsolute(page) 
  20.       Extends.forEach(ext => { 
  21.         // 每個頁面都需要判斷 js、json、wxml、wxss 是否存在 
  22.         const filePath = this.replaceExt(absPath, ext) 
  23.         if (fs.existsSync(filePath)) { 
  24.           this.files.add(filePath) 
  25.         } 
  26.       }) 
  27.     }) 
  28.   } 

現(xiàn)在 pages 內(nèi)頁面相關(guān)的文件都放到 files 字段存起來了。

構(gòu)造樹形結(jié)構(gòu)

拿到文件后,我們需要依據(jù)各個文件構(gòu)造一個樹形結(jié)構(gòu)的文件樹,用于后續(xù)展示依賴關(guān)系。 

假設(shè)我們有一個 pages 目錄,pages 目錄下有兩個頁面:detail、index ,這兩個 頁面文件夾下有四個對應(yīng)的文件。

  1. pages 
  2. ├── detail 
  3. │   ├── detail.js 
  4. │   ├── detail.json 
  5. │   ├── detail.wxml 
  6. │   └── detail.wxss 
  7. └── index 
  8.     ├── index.js 
  9.     ├── index.json 
  10.     ├── index.wxml 
  11.     └── index.wxss 

依據(jù)上面的目錄結(jié)構(gòu),我們構(gòu)造一個如下的文件樹結(jié)構(gòu),size 用于表示當(dāng)前文件或文件夾的大小,children 存放文件夾下的文件,如果是文件則沒有 children 屬性。

  1. pages = { 
  2.   "size": 8, 
  3.   "children": { 
  4.     "detail": { 
  5.       "size": 4, 
  6.       "children": { 
  7.         "detail.js": { "size": 1 }, 
  8.         "detail.json": { "size": 1 }, 
  9.         "detail.wxml": { "size": 1 }, 
  10.         "detail.wxss": { "size": 1 } 
  11.       } 
  12.     }, 
  13.     "index": { 
  14.       "size": 4, 
  15.       "children": { 
  16.         "index.js": { "size": 1 }, 
  17.         "index.json": { "size": 1 }, 
  18.         "index.wxml": { "size": 1 }, 
  19.         "index.wxss": { "size": 1 } 
  20.       } 
  21.     } 
  22.   } 

我們先在構(gòu)造函數(shù)構(gòu)造一個 tree 字段用來存儲文件樹的數(shù)據(jù),然后我們將每個文件都傳入 addToTree 方法,將文件添加到樹中 。

  1. class Depend { 
  2.   constructor() { 
  3.     this.tree = { 
  4.       size: 0, 
  5.       children: {} 
  6.     } 
  7.     this.files = new Set() 
  8.     this.context = path.join(root, 'miniprogram'
  9.   } 
  10.    
  11.   run() { 
  12.     // 省略獲取 pages 過程 
  13.     pages.forEach(page => { 
  14.       const absPath = this.getAbsolute(page) 
  15.       Extends.forEach(ext => { 
  16.         const filePath = this.replaceExt(absPath, ext) 
  17.         if (fs.existsSync(filePath)) { 
  18.           // 調(diào)用 addToTree 
  19.           this.addToTree(filePath) 
  20.         } 
  21.       }) 
  22.     }) 
  23.   } 

接下來實(shí)現(xiàn) addToTree 方法:

  1. class Depend { 
  2.   // 省略之前的部分代碼 
  3.  
  4.   // 獲取相對地址 
  5.   getRelative(file) { 
  6.     return path.relative(this.context, file) 
  7.   } 
  8.   // 獲取文件大小,單位 KB 
  9.   getSize(file) { 
  10.     const stats = fs.statSync(file) 
  11.     return stats.size / 1024 
  12.   } 
  13.  
  14.   // 將文件添加到樹中 
  15.   addToTree(filePath) { 
  16.     if (this.files.has(filePath)) { 
  17.       // 如果該文件已經(jīng)添加過,則不再添加到文件樹中 
  18.       return 
  19.     } 
  20.     const size = this.getSize(filePath) 
  21.     const relPath = this.getRelative(filePath) 
  22.     // 將文件路徑轉(zhuǎn)化成數(shù)組 
  23.     // 'pages/index/index.js' => 
  24.     // ['pages''index''index.js'
  25.     const names = relPath.split(path.sep) 
  26.     const lastIdx = names.length - 1 
  27.  
  28.     this.tree.size += size 
  29.     let point = this.tree.children 
  30.     names.forEach((name, idx) => { 
  31.       if (idx === lastIdx) { 
  32.         point[name] = { size } 
  33.         return 
  34.       } 
  35.       if (!point[name]) { 
  36.         point[name] = { 
  37.           size, children: {} 
  38.         } 
  39.       } else { 
  40.         point[name].size += size 
  41.       } 
  42.       point = point[name].children 
  43.     }) 
  44.     // 將文件添加的 files 
  45.     this.files.add(filePath) 
  46.   } 

我們可以在運(yùn)行之后,將文件輸出到 tree.json 看看。

  1. run() { 
  2.   // ... 
  3.   pages.forEach(page => { 
  4.     //... 
  5.   }) 
  6.   fs.writeJSONSync('tree.json', this.tree, { spaces: 2 }) 

tree.json

獲取依賴關(guān)系

上面的步驟看起來沒什么問題,但是我們?nèi)鄙倭酥匾囊画h(huán),那就是我們在構(gòu)造文件樹之前,還需要得到每個文件的依賴項(xiàng),這樣輸出的才是小程序完整的文件樹。文件的依賴關(guān)系需要分成四部分來講,分別是 js, json, wxml, wxss 這四種類型文件獲取依賴的方式。

獲取 .js 文件依賴

小程序支持 CommonJS 的方式進(jìn)行模塊化,如果開啟了 es6,也能支持 ESM 進(jìn)行模塊化。我們?nèi)绻@得一個 js 文件的依賴,首先要明確,js 文件導(dǎo)入模塊的三種寫法,針對下面三種語法,我們可以引入 Babel 來獲取依賴。

  1. import a from './a.js' 
  2. export b from './b.js' 
  3. const c = require('./c.js'

通過 @babel/parser 將代碼轉(zhuǎn)化為 AST,然后通過 @babel/traverse 遍歷 AST 節(jié)點(diǎn),獲取上面三種導(dǎo)入方式的值,放到數(shù)組。

  1. const { parse } = require('@babel/parser'
  2. const { default: traverse } = require('@babel/traverse'
  3.  
  4. class Depend { 
  5.   // ... 
  6.  jsDeps(file) { 
  7.     const deps = [] 
  8.     const dirName = path.dirname(file) 
  9.     // 讀取 js 文件內(nèi)容 
  10.     const content = fs.readFileSync(file, 'utf-8'
  11.     // 將代碼轉(zhuǎn)化為 AST 
  12.     const ast = parse(content, { 
  13.       sourceType: 'module'
  14.       plugins: ['exportDefaultFrom'
  15.     }) 
  16.     // 遍歷 AST 
  17.     traverse(ast, { 
  18.       ImportDeclaration: ({ node }) => { 
  19.         // 獲取 import from 地址 
  20.         const { value } = node.source 
  21.         const jsFile = this.transformScript(dirName, value) 
  22.         if (jsFile) { 
  23.           deps.push(jsFile) 
  24.         } 
  25.       }, 
  26.       ExportNamedDeclaration: ({ node }) => { 
  27.         // 獲取 export from 地址 
  28.         const { value } = node.source 
  29.         const jsFile = this.transformScript(dirName, value) 
  30.         if (jsFile) { 
  31.           deps.push(jsFile) 
  32.         } 
  33.       }, 
  34.       CallExpression: ({ node }) => { 
  35.         if ( 
  36.           (node.callee.name && node.callee.name === 'require') && 
  37.           node.arguments.length >= 1 
  38.         ) { 
  39.           // 獲取 require 地址 
  40.           const [{ value }] = node.arguments 
  41.           const jsFile = this.transformScript(dirName, value) 
  42.           if (jsFile) { 
  43.             deps.push(jsFile) 
  44.           } 
  45.         } 
  46.       } 
  47.     }) 
  48.     return deps 
  49.   } 

在獲取依賴模塊的路徑后,還不能立即將路徑添加到依賴數(shù)組內(nèi),因?yàn)楦鶕?jù)模塊語法 js 后綴是可以省略的,另外 require 的路徑是一個文件夾的時(shí)候,默認(rèn)會導(dǎo)入該文件夾下的 index.js 。

  1. class Depend { 
  2.   // 獲取某個路徑的腳本文件 
  3.   transformScript(url) { 
  4.     const ext = path.extname(url) 
  5.     // 如果存在后綴,表示當(dāng)前已經(jīng)是一個文件 
  6.     if (ext === '.js' && fs.existsSync(url)) { 
  7.       return url 
  8.     } 
  9.     // a/b/c => a/b/c.js 
  10.     const jsFile = url + '.js' 
  11.     if (fs.existsSync(jsFile)) { 
  12.       return jsFile 
  13.     } 
  14.     // a/b/c => a/b/c/index.js 
  15.     const jsIndexFile = path.join(url, 'index.js'
  16.     if (fs.existsSync(jsIndexFile)) { 
  17.       return jsIndexFile 
  18.     } 
  19.     return null 
  20.   } 
  21.  jsDeps(file) {...} 

我們可以創(chuàng)建一個 js,看看輸出的 deps 是否正確:

  1. // 文件路徑:/Users/shenfq/Code/fork/miniprogram-demo/ 
  2. import a from './a.js' 
  3. export b from '../b.js' 
  4. const c = require('../../c.js'

image-20201101134549678

獲取 .json 文件依賴

json 文件本身是不支持模塊化的,但是小程序可以通過 json 文件導(dǎo)入自定義組件,只需要在頁面的 json 文件通過 usingComponents 進(jìn)行引用聲明。usingComponents 為一個對象,鍵為自定義組件的標(biāo)簽名,值為自定義組件文件路徑:

  1.   "usingComponents": { 
  2.     "component-tag-name""path/to/the/custom/component" 
  3.   } 

自定義組件與小程序頁面一樣,也會對應(yīng)四個文件,所以我們需要獲取 json 中 usingComponents 內(nèi)的所有依賴項(xiàng),并判斷每個組件對應(yīng)的那四個文件是否存在,然后添加到依賴項(xiàng)內(nèi)。

  1. class Depend { 
  2.   // ... 
  3.   jsonDeps(file) { 
  4.     const deps = [] 
  5.     const dirName = path.dirname(file) 
  6.     const { usingComponents } = fs.readJsonSync(file) 
  7.     if (usingComponents && typeof usingComponents === 'object') { 
  8.       Object.values(usingComponents).forEach((component) => { 
  9.         component = path.resolve(dirName, component) 
  10.         // 每個組件都需要判斷 js/json/wxml/wxss 文件是否存在 
  11.         Extends.forEach((ext) => { 
  12.           const file = this.replaceExt(component, ext) 
  13.           if (fs.existsSync(file)) { 
  14.             deps.push(file) 
  15.           } 
  16.         }) 
  17.       }) 
  18.     } 
  19.     return deps 
  20.   } 

獲取 .wxml 文件依賴

wxml 提供兩種文件引用方式 import 和 include。

  1. <import src="a.wxml"/> 
  2. <include src="b.wxml"/> 

wxml 文件本質(zhì)上還是一個 html 文件,所以可以通過 html parser 對 wxml 文件進(jìn)行解析,關(guān)于 html parser 相關(guān)的原理可以看我之前寫過的文章 《Vue 模板編譯原理》。

  1. const htmlparser2 = require('htmlparser2'
  2.  
  3. class Depend { 
  4.   // ... 
  5.  wxmlDeps(file) { 
  6.     const deps = [] 
  7.     const dirName = path.dirname(file) 
  8.     const content = fs.readFileSync(file, 'utf-8'
  9.     const htmlParser = new htmlparser2.Parser({ 
  10.       onopentag(name, attribs = {}) { 
  11.         if (name !== 'import' && name !== 'require') { 
  12.           return 
  13.         } 
  14.         const { src } = attribs 
  15.         if (src) { 
  16.           return 
  17.         } 
  18.        const wxmlFile = path.resolve(dirName, src) 
  19.         if (fs.existsSync(wxmlFile)) { 
  20.          deps.push(wxmlFile) 
  21.         } 
  22.       } 
  23.     }) 
  24.     htmlParser.write(content) 
  25.     htmlParser.end() 
  26.     return deps 
  27.   } 

獲取 .wxss 文件依賴

最后 wxss 文件導(dǎo)入樣式和 css 語法一致,使用 @import 語句可以導(dǎo)入外聯(lián)樣式表。

  1. @import "common.wxss"

可以通過 postcss 解析 wxss 文件,然后獲取導(dǎo)入文件的地址,但是這里我們偷個懶,直接通過簡單的正則匹配來做。

  1. class Depend { 
  2.   // ... 
  3.   wxssDeps(file) { 
  4.     const deps = [] 
  5.     const dirName = path.dirname(file) 
  6.     const content = fs.readFileSync(file, 'utf-8'
  7.     const importRegExp = /@import\s*['"](.+)['"];*/g 
  8.     let matched 
  9.     while ((matched = importRegExp.exec(content)) !== null) { 
  10.       if (!matched[1]) { 
  11.         continue 
  12.       } 
  13.       const wxssFile = path.resolve(dirName, matched[1]) 
  14.       if (fs.existsSync(wxmlFile)) { 
  15.         deps.push(wxssFile) 
  16.       } 
  17.     } 
  18.     return deps 
  19.   } 

獲取 .wxss 文件依賴

最后 wxss 文件導(dǎo)入樣式和 css 語法一致,使用 @import 語句可以導(dǎo)入外聯(lián)樣式表。

  1. class Depend { 
  2.   addToTree(filePath) { 
  3.     // 如果該文件已經(jīng)添加過,則不再添加到文件樹中 
  4.     if (this.files.has(filePath)) { 
  5.       return 
  6.     } 
  7.  
  8.     const relPath = this.getRelative(filePath) 
  9.     const names = relPath.split(path.sep) 
  10.     names.forEach((name, idx) => { 
  11.       // ... 添加到樹中 
  12.     }) 
  13.     this.files.add(filePath) 
  14.  
  15.     // ===== 獲取文件依賴,并添加到樹中 ===== 
  16.     const deps = this.getDeps(filePath) 
  17.     deps.forEach(dep => { 
  18.       this.addToTree(dep)       
  19.     }) 
  20.   } 

獲取分包依賴

熟悉小程序的同學(xué)肯定知道,小程序提供了分包機(jī)制。使用分包后,分包內(nèi)的文件會被打包成一個單獨(dú)的包,在用到的時(shí)候才會加載,而其他的文件則會放在主包,小程序打開的時(shí)候就會加載。subpackages 中,每個分包的配置有以下幾項(xiàng):

所以我們在運(yùn)行的時(shí)候,除了要拿到 pages 下的所有頁面,還需拿到 subpackages 中所有的頁面。由于之前只關(guān)心主包的內(nèi)容,this.tree 下面只有一顆文件樹,現(xiàn)在我們需要在 this.tree 下掛載多顆文件樹,我們需要先為主包創(chuàng)建一個單獨(dú)的文件樹,然后為每個分包創(chuàng)建一個文件樹。

  1. class Depend { 
  2.   constructor() { 
  3.     this.tree = {} 
  4.     this.files = new Set() 
  5.     this.context = path.join(root, 'miniprogram'
  6.   } 
  7.   createTree(pkg) { 
  8.     this.tree[pkg] = { 
  9.       size: 0, 
  10.       children: {} 
  11.     } 
  12.   } 
  13.   addPage(page, pkg) { 
  14.     const absPath = this.getAbsolute(page) 
  15.     Extends.forEach(ext => { 
  16.       const filePath = this.replaceExt(absPath, ext) 
  17.       if (fs.existsSync(filePath)) { 
  18.         this.addToTree(filePath, pkg) 
  19.       } 
  20.     }) 
  21.   } 
  22.   run() { 
  23.     const appPath = this.getAbsolute('app.json'
  24.     const appJson = fs.readJsonSync(appPath) 
  25.     const { pages, subPackages, subpackages } = appJson 
  26.      
  27.     this.createTree('main') // 為主包創(chuàng)建文件樹 
  28.     pages.forEach(page => { 
  29.       this.addPage(page, 'main'
  30.     }) 
  31.     // 由于 app.json 中 subPackages、subpackages 都能生效 
  32.     // 所以我們兩個屬性都獲取,哪個存在就用哪個 
  33.     const subPkgs = subPackages || subpackages 
  34.     // 分包存在的時(shí)候才進(jìn)行遍歷 
  35.     subPkgs && subPkgs.forEach(({ root, pages }) => { 
  36.       root = root.split('/').join(path.sep) 
  37.       this.createTree(root) // 為分包創(chuàng)建文件樹 
  38.       pages.forEach(page => { 
  39.         this.addPage(`${root}${path.sep}${page}`, pkg) 
  40.       }) 
  41.     }) 
  42.     // 輸出文件樹 
  43.     fs.writeJSONSync('tree.json', this.tree, { spaces: 2 }) 
  44.   } 

addToTree 方法也需要進(jìn)行修改,根據(jù)傳入的 pkg 來判斷將當(dāng)前文件添加到哪個樹。

  1. class Depend { 
  2.   addToTree(filePath, pkg = 'main') { 
  3.     if (this.files.has(filePath)) { 
  4.       // 如果該文件已經(jīng)添加過,則不再添加到文件樹中 
  5.       return 
  6.     } 
  7.     let relPath = this.getRelative(filePath) 
  8.     if (pkg !== 'main' && relPath.indexOf(pkg) !== 0) { 
  9.       // 如果該文件不是以分包名開頭,證明該文件不在分包內(nèi), 
  10.       // 需要將文件添加到主包的文件樹內(nèi) 
  11.       pkg = 'main' 
  12.     } 
  13.  
  14.     const tree = this.tree[pkg] // 依據(jù) pkg 取到對應(yīng)的樹 
  15.     const size = this.getSize(filePath) 
  16.     const names = relPath.split(path.sep) 
  17.     const lastIdx = names.length - 1 
  18.  
  19.     tree.size += size 
  20.     let point = tree.children 
  21.     names.forEach((name, idx) => { 
  22.       // ... 添加到樹中 
  23.     }) 
  24.     this.files.add(filePath) 
  25.  
  26.     // ===== 獲取文件依賴,并添加到樹中 ===== 
  27.     const deps = this.getDeps(filePath) 
  28.     deps.forEach(dep => { 
  29.       this.addToTree(dep)       
  30.     }) 
  31.   } 

這里有一點(diǎn)需要注意,如果 package/a 分包下的文件依賴的文件不在 package/a 文件夾下,則該文件需要放入主包的文件樹內(nèi)。

通過 EChart 畫圖

經(jīng)過上面的流程后,最終我們可以得到如下的一個 json 文件:

tree.json

 

接下來,我們利用 ECharts 的畫圖能力,將這個 json 數(shù)據(jù)以圖表的形式展現(xiàn)出來。我們可以在 ECharts 提供的實(shí)例中看到一個 Disk Usage 的案例,很符合我們的預(yù)期。

ECharts

 

ECharts 的配置這里就不再贅述,按照官網(wǎng)的 demo 即可,我們需要把 tree. json 的數(shù)據(jù)轉(zhuǎn)化為 ECharts 需要的格式就行了,完整的代碼放到 codesandbod 了,去下面的線上地址就能看到效果了。

線上地址:https://codesandbox.io/s/cold-dawn-kufc9

最后效果

總結(jié)

這篇文章比較偏實(shí)踐,所以貼了很多的代碼,另外本文對各個文件的依賴獲取提供了一個思路,雖然這里只是用文件樹構(gòu)造了一個這樣的依賴圖。

 在業(yè)務(wù)開發(fā)中,小程序 IDE 每次啟動都需要進(jìn)行全量的編譯,開發(fā)版預(yù)覽的時(shí)候會等待較長的時(shí)間,我們現(xiàn)在有文件依賴關(guān)系后,就可以只選取目前正在開發(fā)的頁面進(jìn)行打包,這樣就能大大提高我們的開發(fā)效率。如果有對這部分內(nèi)容感興趣的,可以另外寫一篇文章介紹下如何實(shí)現(xiàn)。

 

責(zé)任編輯:姜華 來源: 更了不起的前端
相關(guān)推薦

2022-05-06 12:01:01

優(yōu)化小程序

2023-04-14 10:29:24

小程序實(shí)踐

2017-06-09 10:40:00

微信小程序架構(gòu)分析

2017-06-09 12:58:20

微信小程序架構(gòu)分析

2017-06-09 10:06:54

微信小程序架構(gòu)分析

2021-09-14 19:01:56

ClickHouse京東小程序

2012-05-18 12:00:27

Fedora 17桌面程序

2016-11-04 10:30:17

微信小程序

2024-02-20 13:08:00

2023-04-28 09:05:20

魔方基礎(chǔ)流程

2019-06-21 10:40:25

微信小程序前端

2025-09-04 11:10:26

2017-01-10 17:38:37

微信小程序

2017-06-16 09:39:32

優(yōu)酷實(shí)踐阿里云

2017-05-08 15:03:07

微信小程序開發(fā)實(shí)戰(zhàn)

2018-06-13 11:36:26

WeexUI渲染魅族

2017-01-18 17:25:46

小程序青雀

2023-12-27 19:12:42

OLAP自助分析

2017-01-12 10:38:04

TalkingData小程序

2018-09-18 23:29:43

小程序云服務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

国产成人精品久久二区二区91| 欧美白人最猛性xxxxx69交| 欧美日韩在线精品| 日本一区二区三区久久| 久久久久久久久久久妇女| 日韩一卡二卡三卡| 日本精品久久电影| 亚洲精品成人av久久| 2019中文亚洲字幕| 欧美日韩国产页| 亚洲在线不卡| 人妻丰满熟妇av无码区| 一区二区在线免费播放| 国产精品久久久久久久岛一牛影视| 国产欧美中文字幕| 摸摸摸bbb毛毛毛片| 青青青国产精品| 亚洲成a人片综合在线| 日韩av免费看| 九九精品在线观看视频| 欧美热在线视频精品999| 午夜精彩视频在线观看不卡| 四虎永久国产精品| 香蕉视频网站在线| 国内成人免费视频| 久久精品国产69国产精品亚洲| 性高潮免费视频| 日本h片久久| 亚洲成av人影院在线观看网| 资源网第一页久久久| 视频一区二区在线播放| 国产精品一区不卡| 国产精品美女网站| 久久亚洲精品国产| 午夜天堂精品久久久久| 尤物九九久久国产精品的特点 | 色婷婷综合久久久| 真实国产乱子伦对白视频| 97在线公开视频| 久久精品日产第一区二区 | 交100部在线观看| 亚洲色图欧美在线| 亚洲永久一区二区三区在线| 韩国三级在线观看久| 亚洲欧美久久| 欧美激情中文字幕乱码免费| 国产乱子轮xxx农村| 欧美人妖在线| 日韩精品在线播放| 99re久久精品国产| theporn国产在线精品| 91精品久久久久久久久99蜜臂| 91香蕉视频网址| sese一区| 欧美激情在线免费观看| 欧美日韩一区在线播放| 欧美在线一卡| 久久亚洲二区三区| 国产精品自拍网| 中文字幕69页| 久久综合九色综合欧美狠狠| 中文字幕亚洲第一| 国产三级在线观看完整版| 九九热线有精品视频99| 亚洲欧美变态国产另类| 无码人妻精品一区二区三区温州| 欧美天堂视频| 日韩欧美在线观看| 一区在线电影| 日本中文字幕视频在线| 成人小视频免费在线观看| 91久久在线播放| 国产黄色av网站| 丁香另类激情小说| 黄色99视频| 毛片免费在线观看| 国产精品不卡在线观看| 国产成人精品免费看在线播放| 九色porny丨首页在线| 亚洲私人黄色宅男| 成年人看的毛片| 亚洲电影观看| 精品视频123区在线观看| 污污的视频免费| 日韩精品久久久久久久软件91| 欧美不卡激情三级在线观看| 亚洲蜜桃精久久久久久久久久久久| 亚洲区小说区图片区qvod按摩| 亚洲石原莉奈一区二区在线观看| 少妇愉情理伦三级| 欧美在线免费| 青青在线视频一区二区三区| 中文字幕有码视频| 国产成人免费在线观看| 欧美福利精品| av网站在线免费| 欧美国产精品一区二区| 精品久久sese| av在线电影免费观看| 亚洲视频综合在线| 国内自拍在线观看| av国产精品| 亚洲激情在线观看| 涩视频在线观看| 伊人久久综合影院| 蜜臀久久99精品久久久久久宅男| 在线看成人av| 久久超碰97人人做人人爱| 成人自拍偷拍| 在线观看h片| 偷窥少妇高潮呻吟av久久免费 | 麻豆高清免费国产一区| 成人免费视频网站入口| 男人的天堂在线| 一区二区成人在线| 日本中文字幕高清| 红杏视频成人| 久久av中文字幕| 无码人妻丰满熟妇区bbbbxxxx| 国产很黄免费观看久久| 四虎影院一区二区三区| 绿色成人影院| 日韩女优毛片在线| 色婷婷粉嫩av| 日韩中文字幕不卡| 久久精品99久久| 天堂a中文在线| 国产精品国产自产拍在线| 欧美二区在线视频| 在线精品自拍| 日韩国产高清视频在线| 日韩一区二区三区久久| 成人一区视频| 亚洲精品福利在线观看| 欧美丰满艳妇bbwbbw| 另类小说一区二区三区| 久久综合中文色婷婷| 黄污视频在线观看| 日韩欧美国产综合| 欧美激情精品久久久久久免费| 久久字幕精品一区| 免费成人av网站| 女海盗2成人h版中文字幕| 精品欧美乱码久久久久久1区2区| 中日韩一级黄色片| 久久精品72免费观看| 视频二区一区| 久久91视频| 最好看的2019年中文视频| 久久精品久久久久久久| 久久久综合视频| 丝袜老师办公室里做好紧好爽| 激情视频极品美女日韩| 韩国欧美亚洲国产| 男人的天堂a在线| 久久午夜老司机| 国产一区二区网| 欧美黄色影院| 5278欧美一区二区三区| 神马电影在线观看| 欧美性猛交xxxx| 摸摸摸bbb毛毛毛片| 欧美bbbbb| 伊人久久99| 国产精品亚洲四区在线观看| 欧美成年人网站| 亚洲av无码国产综合专区| 亚洲午夜视频在线观看| av网页在线观看| 久久三级福利| 亚洲欧洲精品在线| 国产一区二区三区精品在线观看| 久久影院在线观看| 亚洲美女综合网| 福利视频导航一区| 色欲狠狠躁天天躁无码中文字幕| 蜜臀久久久99精品久久久久久| 在线观看精品视频| 伊人久久亚洲| 国产xxx69麻豆国语对白| 91露出在线| 日韩你懂的在线播放| 亚洲精品视频在线观看免费视频| 久久综合久色欧美综合狠狠| 国产三级三级看三级| 综合国产在线| 久久av免费一区| 久久天堂影院| 久久久久久久久综合| 你懂的视频在线| 91精品国产综合久久久蜜臀粉嫩| 精品无码人妻一区二区三区品 | 91福利区一区二区三区| 亚洲欧美精品aaaaaa片| 成人午夜视频网站| 精品久久久噜噜噜噜久久图片| 99久久夜色精品国产亚洲96| 国产91精品青草社区| 番号集在线观看| 欧美成人三级电影在线| 波多野结衣影片| 一级特黄大欧美久久久| 在哪里可以看毛片| 国产精品香蕉一区二区三区| 欧美视频第一区| 欧美激情aⅴ一区二区三区| 欧美精品在线一区| 亚洲精品一区二区三区中文字幕 | 成人午夜一级| 81精品国产乱码久久久久久| 久久77777| 亚洲欧美日韩中文视频| 亚洲精品久久久久久无码色欲四季 | 国产鲁鲁视频在线观看特色| 亚洲精品色婷婷福利天堂| 99久久久无码国产精品免费| 色国产综合视频| 日本亚洲色大成网站www久久| 国产精品天干天干在线综合| 黄色污在线观看| 国产成人在线视频网站| 超碰超碰在线观看| 免费一区视频| 人妻少妇精品久久| 欧美韩日精品| 中国 免费 av| 久久五月天小说| 日产精品久久久一区二区| 红杏一区二区三区| 国产福利久久| 亚洲国产欧美在线观看| 国产欧美一区二区| 不卡亚洲精品| 国产激情视频一区| 新版的欧美在线视频| 久久久久久久久中文字幕| 羞羞网站在线免费观看| 久久久国产精彩视频美女艺术照福利| 国产福利在线视频| 国产视频久久网| 日本电影一区二区在线观看| 亚洲丁香久久久| 欧美特黄一级视频| 日韩精品一区国产麻豆| 精品二区在线观看| 日韩欧美一卡二卡| 超碰免费在线97| 日韩精品专区在线影院观看| a网站在线观看| 日韩欧美中文字幕一区| 精品国产av 无码一区二区三区 | 欧美激情中文字幕| 国产毛片欧美毛片久久久| 国产亚洲欧美日韩俺去了| 中文字幕在线1| 美日韩一级片在线观看| 免费观看精品视频| 日韩制服丝袜av| 亚洲黄色a v| 久久成人av少妇免费| 欧美一级特黄aaa| 国产美女精品人人做人人爽| 免费高清视频在线观看| 国产99精品在线观看| 波多野结衣办公室双飞| 97久久超碰国产精品| 性欧美精品中出| 国产精品久线观看视频| 在线看的片片片免费| 亚洲午夜电影在线观看| 久久夜靖品2区| 色婷婷综合在线| 亚洲中文一区二区三区| 欧美一区二区三区视频在线| 免费看日批视频| 欧美午夜精品久久久久久孕妇| 亚洲自拍偷拍另类| 日韩欧美一区二区三区在线| 欧美一级片免费| 亚洲人成绝费网站色www| 求av网址在线观看| 欧美精品videos另类日本| 成人直播视频| 国产精品综合不卡av| 2020最新国产精品| 久久综合婷婷综合| 亚洲欧美偷拍自拍| 国产视频九色蝌蚪| 蜜桃av一区二区| 免费黄色a级片| 国产精品丝袜久久久久久app| 免费在线观看一级片| 色综合夜色一区| 国产乱色精品成人免费视频| 亚洲国产欧美一区| 永久免费在线观看视频| 久久久久久这里只有精品| 精品三区视频| 国产美女精品久久久| 成人在线视频免费观看| 日韩一级性生活片| 麻豆精品视频在线| 加勒比精品视频| 日韩美女视频一区二区| 亚洲欧美精品一区二区三区| 91精品黄色片免费大全| 欧美性孕妇孕交| 久久视频精品在线| 日韩性xxx| 国产欧美日韩在线播放| 99久久久久国产精品| av动漫在线观看| 国产成人在线视频网站| 91视频免费看片| 欧美性生交大片免费| 性一交一乱一色一视频麻豆| 一区二区欧美日韩视频| 国产在线天堂www网在线观看| 91免费精品视频| 精品理论电影| 国产免费毛卡片| 成人免费黄色在线| 91视频综合网| 夜夜精品浪潮av一区二区三区| 天堂网一区二区| 日韩成人在线视频观看| 午夜激情在线| 国产精品自产拍在线观| 国产亚洲第一伦理第一区| 日韩日韩日韩日韩日韩| 国产精品影视天天线| 成人黄色短视频| 欧美中文字幕一二三区视频| 亚洲av电影一区| 午夜精品www| 99a精品视频在线观看| 四虎4hu永久免费入口| 精品一二线国产| 极品尤物一区二区| 欧美午夜一区二区三区免费大片| 你懂的免费在线观看视频网站| 欧美夜福利tv在线| 色老板在线视频一区二区| 欧美亚洲日本一区二区三区 | 久久久无码人妻精品无码| 亚洲少妇30p| av加勒比在线| 欧美成人免费在线观看| 高清一区二区| 永久免费网站视频在线观看| 韩国一区二区三区| 老妇女50岁三级| 日韩欧美一级二级| 在线电影福利片| 高清不卡日本v二区在线| 国产精品hd| 日本三级免费观看| 久久免费的精品国产v∧| 潘金莲一级淫片aaaaaa播放| 亚洲欧美一区二区三区久久| 久久野战av| 一区二区精品国产| 国产一区二区在线视频| 国产亚洲欧美精品久久久www| 精品久久一区二区| 九色porny视频在线观看| 久久综合九色综合久99| 日本v片在线高清不卡在线观看| 1024在线看片| 欧美一区二区在线免费观看| 欧美人与牲禽动交com| 国产免费一区二区| 久久美女性网| 美女三级黄色片| 亚洲精品一区二区三区蜜桃下载| 日本黄色免费在线| 欧美亚洲另类久久综合| 精品综合久久久久久8888| 久久久久久久久久久97| 亚洲伦理中文字幕| 日韩欧美三区| 欧美一级免费播放| 国产午夜亚洲精品午夜鲁丝片| 亚洲一级视频在线观看| 久久久久久91| 精品久久美女| 久久久久亚洲av无码网站| 欧美性高潮在线| 超碰在线免费公开| 免费99视频| 国内精品久久久久影院色| 国产成人无码一区二区三区在线| 国产午夜精品免费一区二区三区| 亚洲三级电影| 无码人妻h动漫| 亚洲欧美区自拍先锋| 每日更新在线观看av| 亚洲综合av影视| 日本视频一区二区三区| 久久久久成人精品无码|