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

一次基于AST的大規模代碼遷移實踐

開發
本文簡單闡述了基于 AST 的代碼遷移概念和大致流程,并通過代碼案例帶大家了解到了其中的處理細節。

在研發項目過程中,我們經常會遇到技術架構迭代更新的需求,通過技術的迭代更新,讓項目從新的技術特性中受益,但由于很多新的技術迭代版本并不能完全向下兼容,包含了很多非兼容性的改變(Breaking Changes),因此我們需要設計一款工具,幫助我們完成大規模代碼自動遷移問題。本文簡單闡述了基于 AST 的代碼遷移概念和大致流程,并通過代碼案例帶大家了解到了其中的處理細節。

一、背景介紹

在研發項目過程中,我們經常會遇到技術架構迭代更新的需求,通過技術的迭代更新,讓項目從新的技術特性中受益。例如將 Vue 2 遷移至 Vue 3、Webpack 4 升級 Webpack 5、構建工具遷移至 Vite 等,這些技術架構的升級能讓項目持續受益,獲得諸如可維護性、性能、擴展性、編譯速度、可讀性等等方面的提升,適時的對項目進行技術架構更新是很有必要的。

那既然新特性這么好,有人會說那當然要與時俱進,隨時更新了。

但問題在于很多新的技術迭代版本并不能完全向下兼容,包含了很多非兼容性的改變(Breaking Changes),并不是簡單升個版本就行了,通常還需要投入不少的人力和學習成本。例如 Vue 3 只能兼容80%的 Vue 2 代碼,對于一些新特性、新語法糖,開發者只能參考官方提供的遷移文檔,手動完成遷移。

1.1 Vue 3 代碼遷移案例

來看一個 Vue 3 的代碼遷移案例,在 Vue 2 和 Vue 3 中聲明一個全局指令(Directive)的差異:

(1)Vue 2:允許直接在 Vue 原型上注冊全局指令。而在 Vue 3 中,為了避免多個 Vue 實例產生指令混淆,已經不再支持該寫法。

import Vue from 'vue'
 
Vue.directive('focus', {
  inserted: (el) => el.focus()
})

(2)Vue 3:建議通過 createApp 創建 Vue 實例,并直接在實例上注冊全局指令。就像這樣:

import { createApp } from 'vue'
 
const app = createApp({})
 
app.directive('focus', {
  inserted: (el) => el.focus() 
})

以上是一個大家熟知的 Vue 3 遷移案例,看似簡單,動幾行代碼即可。但當我們的項目規模足夠大,或者有大量項目都需要類似代碼遷移時,工作量會變得巨大,并且很難規避手動遷移的帶來的風險。

因此,一般針對大規模的項目遷移,最好的方式還是寫個腳手架工具,協助我們完成自動化遷移。既能提高效率,又能降低人工遷移的風險。

1.2 本文的代碼遷移背景

同樣地,我在項目中也遇到了相同的技術架構升級難題。簡單來說,我需要將基于 Vue 2 的項目遷移到一個我司內部自研的技術棧,這個技術棧的語法結構和 Vue 2 相似,但由于底層的技術原因,有一部分語法上的差異,需要手動去遷移改造兼容(類似 Vue 2 升級至 Vue 3 的過程)。

除了和遷移 Vue 3 一樣需要針對 JavaScript、Template 模板做遷移處理之外,我還需要額外去單獨處理 CSS、Less、SCSS 等樣式文件。

所以,我實現了一個自動化遷移腳手架工具,來協助完成代碼的遷移工作,減少人工遷移帶來的低效和風險問題。

二、代碼遷移思路

剛剛提到我們需要設計一個腳手架來幫助我們完成自動化的代碼遷移,那腳手架該如何設計呢?

首先,代碼遷移思路可以簡單概括為:對原代碼做靜態代碼分析,并按一定規則替換為新代碼。那最直觀的辦法就是利用正則表達式來匹配和替換代碼,所以我也做了這樣的嘗試。

2.1 思路一:利用正則表達式匹配規則和替換代碼

例如,將下述代碼:

import { toast } from '@vivo/v-jsbridge'

按規則替換為:

import { toast } from '@webf/webf-vue-render'

這看起來很簡單,似乎用正則匹配即可完成,像這樣:

const regx = /\@vivo\/v\-jsbridge/gi
 
const target = '@webf/webf-vue-render'
 
sourceCode.replace(regx, target)

但在實操過程中,發現正則表達式實在太局限,有幾個核心問題:

  • 正則表達式完全基于字符串匹配,對原代碼格式的統一性要求很高。空格、換行、單雙引號等格式差異都可能引起正則匹配錯誤;
  • 面對復雜的匹配場景,正則表達式很難寫、很晦澀,容易誤匹配、誤處理;
  • 處理樣式文件時,需要兼容 CSS / Less / SCSS / Sass 等語法差異,工作量倍增。

簡單舉個例子,當我需要匹配 import { toast } from '@vivo/v-jsbridge'  字符串時。針對單雙引號、空格、分號等細節處理上需要更仔細,稍有不慎就會忽略一些特殊場景,結果就是匹配失敗,造成隱蔽的遷移問題。

import { toast } from '@vivo/v-jsbridge'  // 單引號


import { toast } from "@vivo/v-jsbridge"  // 雙引號


import { toast } from "@vivo/v-jsbridge";  // 雙引號 + 分號


import {toast} from "@vivo/v-jsbridge";  // 無空格

所以,用簡單的正則匹配規則是無法幫助我們完成大規模的代碼遷移和重構的,我們需要更好的方法:基于 AST 的代碼遷移。

2.2 思路二:基于 AST(抽象語法樹)的代碼遷移

在了解到正則匹配規則的局限性后,我把目光鎖定到了基于 AST 的代碼遷移上。

那么什么是基于 AST 的代碼遷移呢?

2.2.1 Babel 的編譯過程

如果你了解過 Babel 的代碼編譯原理,應該對 AST 代碼遷移不陌生。我們知道 Babel 的編譯過程大致分為三個步驟:

  • 解析:將源代碼解析為 AST(抽象語法樹);
  • 變換:對 AST 進行變換;
  • 再建:根據變換后的 AST 重新構建生成新的代碼。

圖片

(圖片來源:Luminosity Blog )

舉個例子,Babel 將一個 ES6 語法轉換為 ES5 語法的過程如下:

(1)輸入一個簡單的 sayHello 箭頭函數方法源碼:

const sayHello = () => {
    console.log('hello')
}

(2)經過 Babel 解析為 AST(可以看到 AST 是一串由 JSON 描述的語法樹),并對 AST 進行規則變換:

  • 將 type 字段由 ArrowFunctionExpression 轉換為 FunctionExpression
  • 將 kind 字段由 const 轉換為 var
{
  "type": "Program",
  "start": 0,
  "end": 228,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 179,
      "end": 227,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 185,
          "end": 227,
          "id": {
            "type": "Identifier",
            "start": 185,
            "end": 193,
            "name": "sayHello"
          },
          "init": {
-            "type": "ArrowFunctionExpression",
+            "type": "FunctionExpression",
            "start": 196,
            "end": 227,
-            "id": null,
+            "id": {
+               "type": "Identifier",
+               "start": 203,
+               "end": 211,
+               "name": "sayHello"
+            },
            "expression": false,
            "generator": false,
            "async": false,
            "params": [],
            "body": {
              "type": "BlockStatement",
              "start": 202,
              "end": 227,
              "body": [
                {
                  "type": "ExpressionStatement",
                  "start": 205,
                  "end": 225,
                  "expression": {
                    "type": "CallExpression",
                    "start": 205,
                    "end": 225,
                    "callee": {
                      "type": "MemberExpression",
                      "start": 205,
                      "end": 216,
                      "object": {
                        "type": "Identifier",
                        "start": 205,
                        "end": 212,
                        "name": "console"
                      },
                      "property": {
                        "type": "Identifier",
                        "start": 213,
                        "end": 216,
                        "name": "log"
                      },
                      "computed": false,
                      "optional": false
                    },
                    "arguments": [
                      {
                        "type": "Literal",
                        "start": 217,
                        "end": 224,
                        "value": "hello",
                        "raw": "'hello'"
                      }
                    ],
                    "optional": false
                  }
                }
              ]
            }
          }
        }
      ],
-      "kind": "const"
+      "kind": "var"
    }
  ],
  "sourceType": "module"
}

(3)從 AST 重新構建為 ES5 語法:

var sayHello = function sayHello() {
   console.log('hello');
 };

這樣就完成了一個簡單的 ES6 到 ES5 的語法轉換。我們的腳手架自動代碼遷移思路也是如此。

對比正則表達式匹配,基于 AST 代碼遷移,有幾點好處:

  • 比字符串匹配更靈活、涵蓋更多復雜場景。
  • 通常 AST 代碼遷移工具都提供了方便的解析、查詢、匹配、替換的 API,能輕易寫出高效的代碼轉換規則。
  • 方便統一轉換后的代碼風格。

2.2.2 代碼遷移流程設計

了解了 AST 的基本原理和可行性后,我們需要找到合適的工具庫來完成代碼的 AST 解析、重構、生成。考慮到項目中至少包含了這幾種內容(腳本、樣式、HTML):

  • 單獨的 JS 文件;
  • 單獨的樣式文件:CSS / Less / SCSS / Sass;
  • Vue 文件:包含 Template、Script、Style 三部分。

我們需要分別找到各類文件內容對應的解析和處理工具。

首先,是 JS 文件的解析處理工具的選擇。在市面上比較流行的 JS AST 工具有很多種選擇,例如最常見的 Babel、jscodeshift 以及 Esprima、Recast、Acorn、estraverse 等。做了一些簡單調研后,發現這些工具都有一些共通的缺陷:

  • 上手難度大,有較大的學習成本,要求開發者充分了解 AST 的語法規范;
  • 語法復雜,代碼量大;
  • 代碼可讀性差,不利于維護。

以 jscodeshift 為例,如果我們需要匹配一個簡單語句:item.update('price')(this, '50'),它的實現代碼如下:

const callExpressions = root.find(j.CallExpression, {
  callee: {
    callee: {
      object: {
        name: 'item'
      },
      property: {
        name: 'update'
      }
    },
    arguments: [{
      value: 'price'
    }]
  },
  arguments: [{
    type: 'ThisExpression'
  }, {
    value: '50'
  }]
})

其實相比于原始的 Babel 語法,上述的 jscodeshift 語法已經相對簡潔,但可以看出依然有較大的代碼量,并且要求開發者熟練掌握 AST 的語法結構。

因此我找到了一個更簡潔、更高效的 AST 工具:GoGoCode,它是一款阿里開源的 AST 工具,封裝了類似 jQuery 的語法,簡單易用。一個直觀的對比就是,如果用 GoGoCode 同樣實現上述的語句匹配,只需要一行代碼:

$(code).find(`item.update('price')(this, '50')`)

它直觀的語義以及簡潔的代碼,讓我選擇了它作為 JS 的 AST 解析工具。

其次,是單獨的 CSS 樣式文件解析工具選擇。這個選擇很輕易,直接使用通用的 PostCSS 來解析和處理樣式即可。

最后,是 Vue 文件的解析工具選擇。因為 Vue 文件是由 Template、Script、Style 三部分組成,因此需要更復雜的工具進行組合處理。很慶幸的是 GoGoCode 除了能夠對單獨的 JS 文件進行解析處理,它還封裝了對 Vue 文件中的 Template 和 Script 部分的處理能力,因此 Vue 文件中除了 Style 樣式部分,我們也可以交由 GoGo Code 來處理。那 Style 樣式的部分該如何處理呢?這里我大致看了官方的 vue-loader 源碼,發現源碼中使用的是 @vue/component-compiler-utils 來解析 Vue 的 SFC 文件,它可以將文件中的 Style 樣式內容單獨抽離出來。因此思路很簡單,我們利用 @vue/component-compiler-utils 將 Vue 文件中的 Style 樣式內容抽離出來,再交由 PostCSS 來處理即可。

那么,簡單總結下找到的幾款適合的工具庫:

  • GoGoCode:阿里開源的一款抽象語法樹處理工具,可用于解析 JS / HTML / Vue 文件并生成抽象語法樹(AST),進行代碼的規則替換、重構等。封裝了類似 jQuery 的語法,簡單易用。
  • PostCSS:大家熟悉的開源 CSS 代碼遷移工具,可用于解析 Less / CSS / SCSS / Sass 等樣式文件并生成語法樹(AST),進行代碼的規則替換、重構等。
  • @vue/component-compiler-utils:Vue 的開源工具庫,可用于解析 Vue 的 SFC 文件,我用它將 SFC 中的 Style 內容單獨抽出,并配合 PostCSS 來處理樣式代碼的規則替換、重構。

有了這三個工具,我們就可以梳理出針對不同文件內容的處理思路:

  • JS 文件:交給 GoGoCode 處理。
  • CSS / Less / SCSS / Sass 文件:交給 PostCSS 處理。
  • Vue文件:
  • Template / Script 部分:交給 GoGoCode 處理。
  • Style 部分:先用 @vue/component-compiler-utils 解析出 Style 部分,再交給 PostCSS 處理。

圖片

有了處理思路后,接下來進入正文,深入代碼細節,詳細了解代碼遷移流程。

三、代碼遷移流程詳解

整個代碼遷移流程分為幾個步驟,分別是:

3.1 遍歷和讀取文件內容

遍歷項目文件內容,根據文件類型交由不同的 transform 函數來處理:

  • transformVue:處理 Vue 文件
  • transformScript:處理 JS 文件
  • transformStyle:處理 CSS 等樣式文件
const path = require('path')
const fs = require('fs')
 
const transformFiles = path => {
    const traverse = path => {
        try {
            fs.readdir(path, (err, files) => {
                files.forEach(file => {
                    const filePath = `${path}/${file}`
                    fs.stat(filePath, async function (err, stats) {
                        if (err) {
                            console.error(chalk.red(`  \n?? ~ ${o} Transform File Error:${err}`))
                        } else {
                            // 如果是文件則開始執行替換規則
                            if (stats.isFile()) {
                                const language = file.split('.').pop()
                                if (language === 'vue') {
                                    // 處理vue文件內容
                                    await transformVue(file, filePath, language)
                                } else if (jsSuffix.includes(language)) {
                                    // 處理JS文件內容
                                    await transformScript(file, filePath, language)
                                } else if (styleSuffix.includes(language)) {
                                    // 處理樣式文件內容
                                    await transformStyle(file, filePath, language)
                                }
                            } else {
                                // 如果是目錄,則繼續遍歷
                                traverse(`${path}/${file}`)
                            }
                        }
                    })
                })
            })
        } catch (err) {
            console.error(err)
            reject(err)
        }
    }
    traverse(path)
}

3.2 Vue 文件的代碼遷移

由于單獨的 JS、樣式文件處理流程和 Vue 文件相似,唯一的差別在于 Vue 文件多了一層解析。所以這里以 Vue 文件為例,闡述下具體的代碼遷移流程:

const $ = require('gogocode')
const path = require('path')
const fs = require('fs')
 
// 處理vue文件
const transformVue = async (file, path, language = 'vue') => {
    return new Promise((resolve, reject) => {
        fs.readFile(path, function read(err, code) {
            const sourceCode = code.toString()
            // 1. 利用gogocode提供的$方法,將源碼轉換為ast語法樹
            const ast = $(sourceCode, { parseOptions: { language: 'vue' } })
 
            // 2. 處理script
            transformScript(ast)
 
            // 3. 處理template
            transformTemplate(ast)
           
            // 4. 處理styles
            transformStyles(ast)
 
            // 5. 對處理過的ast重新生成代碼
           const outputCode = ast.root().generate()
 
            // 6. 重新寫入文件
            fs.writeFile(path, outputCode, function (err) {
                if (err) {
                    reject(err)
                    throw err
                }
                resolve()
           })
        })
    })
}

可以看到,代碼中的 Vue 文件處理流程主要如下:

  1. 生成 AST 語法樹:利用 GoGoCode 提供的 $ 方法,將源碼轉換為 Ast 語法樹,然后將 Ast 語法樹交由不同的處理器來完成語法的匹配和轉換。
  2. 處理 JavaScript:調用 transformScript 處理 JavaScript 部分。
  3. 處理 template:調用 transformTemplate 處理 template(HTML)部分。
  4. 處理 Styles:調用 transformStyles 處理樣式部分。
  5. 對處理過的 Ast 重新生成代碼:調用 GoGoCode 提供的 ast.root().generate() 方法,可以由 Ast 重新生成目標代碼。
  6. 重新寫入文件:將生成的目標代碼重新寫入文件。

這樣一來,一個 Vue 文件的代碼遷移就結束了。接下來看看針對不同的內容 JavaScript、HTML、Style,需要如何處理。

3.3 處理 JavaScript 腳本

處理 JavaScript 腳本時,主要是依賴 GoGoCode 提供的一些語法進行代碼遷移:

  • 首先,利用 ast.find('<script></script>') 解析并找到 Vue 文件中的 JavaScript 腳本部分。
  • 其次,利用 replace 等方法對原代碼進行一個匹配和替換。
  • 最后,返回處理后的 Ast。

下面是一個簡單的代碼替換案例,主要目的是將一個 import 語句替換引用包的來源。將

@vivo/v-jsbridge 替換為@webf/webf-vue-render/modules/jsb。

// 轉換前
import { call } from '@vivo/v-jsbridge'
 
// 轉換后
import { call } from '@webf/webf-vue-render/modules/jsb'

const transformScript = (ast) => {
    const script = ast.find('<script></script>')
    // 利用replace方法替換代碼
    script.replace(
        `import {$$$} from "@vivo/v-jsbridge"`,
        `import {$$$} from "@webf/webf-vue-render/modules/jsb"`
    )
    return ast
}

除了 replace 之外,還有一些別的語法也經常用到,例如:

  • find(查找代碼)
  • has(判斷是否包含代碼)
  • append(在后面插入代碼)
  • prepend(在前面插入代碼)
  • remove(刪除代碼)等。

只要簡單熟悉下 GoGoCode 的語法,就能很快寫出一些轉換規則(相比于正則表達式,效率高很多)。

3.4 處理 Template 模板

處理 Template 模板時也是類似,主要依賴 GoGoCode 提供的 API:

  • 首先,利用 ast.find('<template></template>') 解析并找到 Vue 文件中的 Template(HTML)部分。
  • 其次,利用 replace 等方法對原代碼進行一個匹配和替換。
  • 最后,返回處理后的 Ast 。

下面是簡單的 Template 標簽替換案例,將帶有 @change 屬性的 div 標簽,替換為帶有 :onChange 屬性的 span 標簽。

// 轉換前
<div @change="onChange"></div>
 
// 轉換后
<span :notallow="onChange"></div>

const transformTemplate = (ast) => {
    const template = ast.find('<template></template>')
    const tagName = 'div'
    const targetTagName = 'span'
    // 利用replace方法替換代碼,將div標簽替換為span標簽
    template
        .replace(
            `<${tagName} @change="$_$" $$$1>$$$0</${tagName}>`,
            `<${targetTagName} :notallow="$_$" $$$1>$$$0</${targetTagName}>`
        )
    return ast
}

值得一提的是,GoGoCode 提供了 $_$、$$$1 這類通配符,讓我們能對 DOM 結構進行更好的規則匹配,高效地寫出匹配和轉換規則。

3.5 處理 Style 樣式

最后是處理 Style 樣式部分,這部分和處理 JavaScript、Template 有些不同,由于 GoGoCode 暫時沒有提供針對 Style 樣式的處理方法。所以我們需要額外借用兩個工具,分別是:

  • @vue/component-compiler-utils:解析樣式代碼,轉成 Ast 。
  • PostCSS:按規則處理樣式,轉換成目標代碼。

整個 Style 樣式的處理流程如下(寫過 PostCSS 插件的同學對這樣式處理這部分應該很熟悉):

  • 獲取 Styles 節點:利用 ast.rootNode.value.styles 獲取 Styles 節點,一個 Vue 文件中可能包含多個 Style 代碼塊,對應多個 Styles 節點。
  • 遍歷 Styles 節點:
  • 利用 @vue/component-compiler-utils 提供的 compileStyle 方法解析 Style 節點內容。
  •  利用 postcss.process 方法,根據規則處理樣式內容,生成目標代碼。
  • 返回轉換后的 Ast 。


下面是一個簡單的案例,將樣式中所有的 color 屬性值統一替換為 red。

// 轉換前
<style>
  .button {
    color: blue;
  }
</style>
 
// 轉換后
<style>
  .button {
    color: red;
  }
</style>


const compiler = require('@vue/component-compiler-utils')
const { parse, compileStyle } = compiler
const postcss = require('postcss')
 
// 一個簡單的替換所有color屬性為'red'的postcss插件
const colorPlugin = (opts = {}) => {
    return {
        postcssPlugin: 'postcss-color',
        Once(root, { result }) {
            root.walkDecls(node => {
                // 找到所有prop為color的節點,將節點的值設置為red
                if (node.prop === 'color') {
                    node.value = 'red'
                }
            })
        }
    }
}
colorPlugin.postcss = true
 
const transformStyles = (ast) => {
    // 獲取styles節點(一個vue文件中可能包含多個style代碼塊,對應多個styles節點)
    const styles = ast.rootNode.value.styles
 
    // 遍歷所有styles節點
    styles.forEach((style, index) => {
        let content = style.content
        // 獲取文件的后綴:less / sass / scss / css等
        const lang = style.lang || 'css'
 
        // 利用@vue/component-compiler-utils提供的compileStyle方法解析style內容
        const result = compileStyle({
            source: content,
            scoped: false
        })
 
        // 交由postcss處理樣式,傳入剛剛聲明的colorPlugin插件
        const res = postcss([colorPlugin]).process(result.code, { from: path, syntax: parsers[lang] })
 
        style.content = res.css
    })
    return ast
}

到此,整個代碼遷移流程就完成了。

【源碼 DEMO 可參考】https://github.com/vivo/BlueSnippets/tree/main/demos/ast-migration

四、總結

本文簡單闡述了基于 AST 的代碼遷移概念和大致流程,并通過代碼案例帶大家了解到了其中的處理細節。梳理下整個代碼遷移處理流程:

  • 遍歷和讀取文件內容。
  • 將內容分類,針對不同文件內容,采用不同的處理器。
  • JavaScript 腳本直接交由 GoGoCode 處理。
  • 樣式文件直接交由 PostCSS 處理。
  • 針對 Vue 文件,通過解析,拆分成 Template、JavaScript、Style 樣式三部分,再進行分別處理。
  • 處理完成后,基于轉換后的 Ast 生成目標代碼并重新寫入文件,完成代碼遷移。

整個處理流程還是相對簡單的,只需要掌握 AST 代碼轉換的基本概念,對 GoGoCode、PostCSS、

@vue/component-compiler-utils 這些工具的基本使用有一定的了解,就可以進行一個自動化遷移工具的開發了。

最后,需要額外提醒大家的一點是,在設計代碼匹配和轉換規則時,需要注意邊界場景,避免產生錯誤的代碼轉換,從而造成潛在 bug。為了規避代碼轉換異常,建議大家針對每個轉換規則編寫充分的測試用例,以保障轉換規則的正確性。

如果大家有類似的需求,也可以參照本文進行工具設計和實現。

責任編輯:龐桂玉 來源: vivo互聯網技術
相關推薦

2015-04-28 15:31:09

2012-07-24 08:54:15

2025-06-10 08:15:00

LLM大語言模測試

2020-02-10 08:00:38

AI 數據人工智能

2024-01-12 21:22:10

Scribus開源大規模升級

2023-04-04 07:32:35

TorchRec模型訓練

2021-04-22 13:38:21

前端開發技術

2023-06-30 17:59:27

Ray離線推理

2018-02-09 05:30:19

5G運營商物聯網

2024-07-03 08:19:56

2020-11-30 11:06:30

云計算云遷移IT

2025-10-16 09:14:48

2015-07-14 10:34:42

ViewModel代碼高效

2023-06-28 08:23:41

搜索語義模型

2023-05-10 10:54:37

項目ts代碼

2013-03-22 14:44:52

大規模分布式系統飛天開放平臺

2016-01-31 16:52:53

2023-11-06 07:45:42

單據圖片處理

2010-04-01 22:16:21

2023-05-26 08:39:44

深度學習Alluxio
點贊
收藏

51CTO技術棧公眾號

中国特级黄色片| 91嫩草国产在线观看| 国产精品揄拍100视频| 丝袜美腿一区| 亚洲免费av高清| 国产日韩二区| 天堂网中文字幕| 91精品91| 精品亚洲国产成av人片传媒| 性生活免费在线观看| 日本伦理一区二区| 久久免费电影网| 成人日韩av在线| 91精品国产乱码久久久张津瑜| 欧美伦理在线视频| 欧美mv日韩mv国产网站| 成人免费视频久久| 伦理av在线| 国产精品每日更新在线播放网址| 国产精品sss| 在线免费看91| 国产亚洲网站| 久久99国产综合精品女同| 欧美做受高潮6| 波多野结衣一区二区三区免费视频| 91国偷自产一区二区使用方法| 国产天堂视频在线观看| 欧美人xxx| 久久精品亚洲精品国产欧美 | 青青草精品视频| 久久久久久久成人| 在线看的片片片免费| 国产麻豆一区二区三区精品视频| 精品少妇一区二区三区视频免付费| 五月婷婷狠狠操| 欧美a级在线观看| 亚洲一区二区三区免费视频| 亚洲欧美电影在线观看| 男人的天堂av高清在线| www.欧美日韩| 国产不卡一区二区三区在线观看 | 91精品久久久久久久99蜜桃| 韩国一区二区av| 国产美女高潮在线| 亚洲一区日韩精品中文字幕| 中文字幕一区二区三区四区五区| 精品福利视频导航大全| 国产亚洲欧美在线| 欧美日韩精品免费在线观看视频| 刘亦菲久久免费一区二区| 国内久久婷婷综合| 国产日韩欧美成人| 在线观看毛片视频| 毛片基地黄久久久久久天堂| 国产精品久久久久久久久久久不卡| 国产精品xxxx喷水欧美| 91久久黄色| 午夜免费久久久久| 精品成人久久久| 亚洲三级色网| 69国产精品成人在线播放| 日韩字幕在线观看| 亚洲资源av| 人人爽久久涩噜噜噜网站| 免费av网站在线| 噜噜噜91成人网| 日韩av片免费在线观看| 色屁屁影院www国产高清麻豆| 国产一区二区三区久久| 国产99视频精品免视看7| 久久久精品毛片| 久久精品国产第一区二区三区| 国产欧美精品在线播放| 国产视频aaa| 懂色av一区二区三区免费看| 国产伦精品一区二区三区视频黑人| 性一交一乱一伧老太| 成人97人人超碰人人99| 久久精品五月婷婷| 国产对白叫床清晰在线播放| 国产精品久久久爽爽爽麻豆色哟哟 | 中文字幕免费一区| 青青草免费在线视频观看| 国产第一页在线| 色诱视频网站一区| www.com污| 成人18夜夜网深夜福利网| 日韩电影第一页| 四季av中文字幕| 午夜视频一区| 日韩av电影院| 国产日韩欧美一区二区东京热| 国产成人在线色| 欧美精品v日韩精品v国产精品| 成年人在线免费观看| 亚洲人成网站色在线观看| 毛片在线视频播放| 精品自拍视频| 精品国产麻豆免费人成网站| 久久久久久久久久久久| 国产精品99一区二区| 国产999精品久久久| 精品黑人一区二区三区国语馆| 99久久er热在这里只有精品15| 亚洲精品在线视频观看| 国产网红在线观看| 欧美日韩亚洲另类| 久久久久成人精品无码中文字幕| 日韩av密桃| 91福利视频网| 国产熟女一区二区丰满| 国产午夜精品一区二区| 99在线免费视频观看| 男人天堂久久| 精品国产乱码久久久久久牛牛| 亚洲欧美va天堂人熟伦| 99精品视频网| av一区二区在线看| 欧美jizz18性欧美| 色综合久久九月婷婷色综合| 国产精品欧美性爱| 久久国产精品亚洲人一区二区三区 | av中文字幕在线播放| 在线视频国内一区二区| 国产精品无码在线| 国内精品久久久久久久97牛牛| 国产精品一二三视频| 桃花色综合影院| 亚洲一区二区五区| 丰满少妇一区二区三区专区| 日韩欧美二区| 国产成人av在线播放| 蜜桃av中文字幕| 亚洲激情欧美激情| 亚洲午夜精品一区| 成人在线免费观看网站| 欧洲成人在线视频| 免费在线视频一级不卡| 激情久久av一区av二区av三区| 五月天激情播播| 日韩精品久久| 国产精品久久久久久超碰| 欧美精品a∨在线观看不卡| 午夜欧美2019年伦理| 久久精品无码专区| 国产精品a久久久久| 成人久久精品视频| 麻豆影视在线观看_| 884aa四虎影成人精品一区| 一级片黄色录像| 免费在线观看精品| 亚洲在线观看一区| 亚洲日韩中文字幕一区| 久久精品欧美视频| 国产成年妇视频| 亚洲精选一二三| 精品人妻人人做人人爽夜夜爽| 综合激情婷婷| 99精品国产一区二区| а_天堂中文在线| 亚洲国产精品网站| 欧美黑人一区二区| 91农村精品一区二区在线| 国产又大又硬又粗| 欧美日韩xxxx| 国产视频福利一区| 午夜小视频福利在线观看| 精品国产乱码久久久久久闺蜜| jizz国产免费| 久久亚洲一区二区三区明星换脸| 最近免费中文字幕中文高清百度| 成人看的羞羞网站| 亚洲一区制服诱惑| 国产啊啊啊视频在线观看| 日韩精品免费一线在线观看| 久久精品国产亚洲av麻豆蜜芽| 中文字幕色av一区二区三区| 少妇献身老头系列| 久久久久中文| 在线播放 亚洲| 国产精品白丝av嫩草影院| 欧美专区日韩视频| 免费观看成人高潮| 亚洲风情亚aⅴ在线发布| 日韩一级在线视频| 亚洲欧美日韩电影| www.自拍偷拍| 狠狠v欧美v日韩v亚洲ⅴ| 免费看欧美黑人毛片| 精品久久国产| 国产91aaa| 国模私拍国内精品国内av| 欧美激情视频播放| 国产小视频在线| 欧美电影精品一区二区| 日韩精品在线一区二区三区| 亚洲精选一二三| japanese中文字幕| 成人做爰69片免费看网站| 亚洲 欧美 日韩系列| 精品福利电影| 中文字幕精品—区二区日日骚| av成人app永久免费| 国产精品久久久久久av福利| 草草视频在线| 久久国产精品网站| 国产h视频在线观看| 精品国偷自产国产一区| 伊人久久一区二区| 疯狂欧美牲乱大交777| wwwav国产| 国产精品视频一二三| 国产精品无码一区二区三区免费 | 欧美一级片黄色| 国内精品在线播放| 国产免费视频传媒| 国产精品毛片一区二区三区| 国产成人一二三区| 91亚洲一区| 日本在线免费观看一区| 欧美日韩夜夜| 成人免费视频观看视频| 色综合一区二区日本韩国亚洲| 日本久久久久久久久久久| missav|免费高清av在线看| 久久视频在线免费观看| 91社区在线观看| 亚洲人成亚洲人成在线观看| 天堂中文在线资源| 精品日韩一区二区| av中文字幕播放| 欧美男男青年gay1069videost| 亚洲精品男人的天堂| 午夜久久电影网| 国产精选第一页| 亚洲一区二区三区自拍| 朝桐光av在线| 亚洲人成网站精品片在线观看| 一级片久久久久| 国产三区在线成人av| 成年人网站免费在线观看| www久久久久| 美国黄色a级片| 91网站在线观看视频| chinese麻豆新拍video| www..com久久爱| 国产二级一片内射视频播放| 99久久久久久99| 五月开心播播网| 2023国产精品| 91成人破解版| 国产精品午夜在线| 国产精品精品软件男同| 亚洲三级小视频| 91aaa在线观看| 亚洲在线视频一区| 国产大片aaa| 欧美日韩亚洲成人| 日本妇乱大交xxxxx| 欧美日韩国产在线播放网站| 亚洲视频在线观看免费视频| 欧美日本视频在线| 精品国产av一区二区| 欧美成人r级一区二区三区| 色呦呦中文字幕| 亚洲欧美视频在线| av男人的天堂在线| 欧美成人激情视频免费观看| 欧美xxxx性xxxxx高清| 97视频在线观看免费高清完整版在线观看| 国产夫妻在线| 国产精彩精品视频| www.91精品| 国产美女精品久久久| 自拍欧美一区| 国产免费色视频| 狠狠综合久久| 国产黄色特级片| 精品在线亚洲视频| 国产女人18毛片水真多18| 国产亚洲午夜高清国产拍精品 | 4438全国成人免费| 粉嫩一区二区| 川上优av一区二区线观看| 国产美女撒尿一区二区| 日本亚洲自拍| 欧美日韩爆操| 国产精品69页| 国产精品自产自拍| a级大片在线观看| 亚洲精品视频在线| 亚洲欧美综合另类| 91精品国产综合久久精品| 午夜视频在线播放| 久久激情视频久久| 特黄毛片在线观看| 91在线播放国产| 欧美极品在线观看| av一区二区三区免费观看| 日韩精品成人一区二区三区| 日本女人性视频| 国产日产欧美一区二区三区 | 一区二区电影| 凹凸日日摸日日碰夜夜爽1| 国产剧情一区在线| 一级黄色片网址| 性做久久久久久免费观看| 影音先锋国产资源| 日韩精品有码在线观看| av片在线观看网站| 国产精品丝袜久久久久久高清 | 欧美亚洲一级二级| 欧美成人一品| 三上悠亚av一区二区三区| 91污片在线观看| 日韩精品一区三区| 欧美一区二区三区免费大片| youjizz亚洲女人| 久久不见久久见中文字幕免费| 日本福利一区二区三区| 好吊一区二区三区| 亚洲第一色av| 欧美激情一区二区三区四区| 亚洲综合一二三| 日韩亚洲欧美一区二区三区| www亚洲人| 日本成人免费在线| 老司机aⅴ在线精品导航| www.在线观看av| 国产精品一卡二卡| 国精产品久拍自产在线网站| 日本精品一级二级| 视频一区二区在线播放| 久久理论片午夜琪琪电影网| 久久影院一区二区三区| 中文字幕综合在线观看| 精品制服美女久久| av片在线免费看| 欧美三区在线观看| 成人动漫在线播放| 国产精品国语对白| 欧美日韩性在线观看| 黄色高清无遮挡| 国产夜色精品一区二区av| 日韩手机在线视频| 亚洲女人初尝黑人巨大| 亚洲一区站长工具| 欧美午夜精品久久久久免费视 | 午夜精品久久久99热福利| 在线精品自拍| 草b视频在线观看| 成人91在线观看| www.日本精品| 亚洲女人天堂成人av在线| 美女日韩欧美| 天天爽天天狠久久久| 美女脱光内衣内裤视频久久影院| 国产毛片欧美毛片久久久| 欧美日韩小视频| 中文字幕在线播放网址| 97久草视频| 男人天堂久久久| 亚洲精品v天堂中文字幕| 97天天综合网| 免费成人在线观看av| 视频一区二区国产| 国产一区第一页| 日韩一区二区免费视频| rebdb初裸写真在线观看| 久久综合福利| 秋霞影院一区二区| 性欧美videos| 亚洲国产精品va在线| 刘亦菲一区二区三区免费看| 一本一道久久a久久综合精品| 国产在线麻豆精品观看| 一区二区免费在线观看| 免费观看久久久4p| 国产一区二区播放| 亚洲精品videossex少妇| 成人做爰视频www网站小优视频| 亚洲高清视频一区| 风间由美性色一区二区三区| 久久久久久久久久久久久久av| 亚洲小视频在线| 精品国产乱码一区二区三区 | 国产精品一卡二卡三卡| 不卡一区二区三区四区五区| 一区二区国产精品| 精品一区二区6| 精品国产91九色蝌蚪| 小明成人免费视频一区| 日韩精品手机在线观看| 97久久超碰国产精品| 91无套直看片红桃| 91精品国产高清久久久久久久久| 欧美日韩有码| 涩视频在线观看| 欧美日韩成人一区二区| 理论片午夜视频在线观看|