1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        10分鐘搞懂 Vite devServer,速來(lái)圍觀!

        共 26462字,需瀏覽 53分鐘

         ·

        2021-05-29 21:19

        梁曉瑩,微醫(yī)前端技術(shù)部前端開(kāi)發(fā)工程師,一只喜歡游泳&&讀書(shū)的豬豬女孩。

        分析 version:2.2.3,和我來(lái)一場(chǎng) vite server 探尋之旅吧~(?ω?)

        一、初始 cli 啟動(dòng)服務(wù)做了什么?

        pacakge.json 的 bin 指定可執(zhí)行文件:

        "bin": {
            "vite""bin/vite.js"
          }

        在安裝帶有 bin 字段的 vite 包,那可執(zhí)行文件會(huì)被鏈接到當(dāng)前項(xiàng)目的./node_modules/.bin 中,所以,npm 會(huì)從 vite.js 文件創(chuàng)建一個(gè)到/usr/local/bin/vite 的符號(hào)鏈接(這使你可以直接在命令行執(zhí)行 vite)如下證明:在本地項(xiàng)目中,也可以很方便地利用 npm 執(zhí)行腳本(package.json 文件中 scripts 可以直接執(zhí)行:'node node_modules/.bin/vite')

        那 vite.js 做了什么?

        cli.ts 才算真正的啟動(dòng)服務(wù),做 cli 命令的相關(guān)配置:

        import { cac } from 'cac' // 是一個(gè)用于構(gòu)建 CLI 應(yīng)用程序的 JavaScript 庫(kù)
        const cli = cac('vite')

        cli
          .option('-c, --config <file>'`[string] use specified config file`// 明確的 config 文件名稱(chēng),默認(rèn) vite.config.js .ts .mjs
          .option('-r, --root <path>'`[string] use specified root directory`// 根路徑,默認(rèn)是當(dāng)前路徑 process.cwd()
          .option('--base <path>'`[string] public base path (default: /)`// 在開(kāi)發(fā)或生產(chǎn)中使用的基本公共路徑,默認(rèn)'/'
          .option('-l, --logLevel <level>'`[string] silent | error | warn | all`// 日志級(jí)別
          .option('--clearScreen'`[boolean] allow/disable clear screen when logging`// 打日志的時(shí)候是否允許清屏
          .option('-d, --debug [feat]'`[string | boolean] show debug logs`// 配置展示 debug 的日志
          .option('-f, --filter <filter>'`[string] filter debug logs`// 篩選 debug 日志

        // dev 的命令[這是我們的討論重點(diǎn) -- devServer]
        cli
          .command('[root]'// default command
          .alias('serve'// 別名,即為 `vite serve`命令 = `vite`命令
          .option('--host [host]'`[string] specify hostname`)
          .option('--port <port>'`[number] specify port`// --host 指定 port (默認(rèn)值:3000)
          .option('--https'`[boolean] use TLS + HTTP/2`// --https 使用 https (默認(rèn)值:false)

          .option('--open [path]'`[boolean | string] open browser on startup`// --open 在服務(wù)器啟動(dòng)時(shí)打開(kāi)瀏覽器
          .option('--cors'`[boolean] enable CORS`// --cors 啟動(dòng)跨域
          .option('--strictPort'`[boolean] exit if specified port is already in use`)
          .option('-m, --mode <mode>'`[string] set env mode`// --mode 指定環(huán)境模式
          .option(
            '--force',
            `[boolean] force the optimizer to ignore the cache and re-bundle` // 優(yōu)化器有緩存,--force true 強(qiáng)制忽略緩存,重新打包
          )
          .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
            const { createServer } = await import('./server')
            try {
              const server = await createServer({ // 創(chuàng)建了 server,接下來(lái)我們重點(diǎn)討論 server 做了什么
                root,
                base: options.base,
                mode: options.mode,
                configFile: options.config,
                logLevel: options.logLevel,
                clearScreen: options.clearScreen,
                server: cleanOptions(options) as ServerOptions
              })
              await server.listen()
            } catch (e) {
              .......
            }
          })

        // build 的命令:生產(chǎn)環(huán)境構(gòu)建
        cli
          .command('build [root]')
         。。。。。。。

        // preview 的命令:預(yù)覽構(gòu)建效果
        cli
          .command('preview [root]')

        // optimize 的命令:預(yù)優(yōu)化
        cli
          .command('optimize [root]')
         。。。。。。。

        簡(jiǎn)單來(lái)講,我們從敲下 npm run dev 執(zhí)行 cli 命令的時(shí)候,會(huì)執(zhí)行/node_modules/vite/dist/node/cli.js,調(diào)用 createServer 方法,傳遞 vite.config.js 或 cli 命令上的自定義 config,創(chuàng)建一個(gè) viteDevServer 實(shí)例。接下來(lái)我們康康打造一個(gè) viteDevServer 的生產(chǎn)流是什么~

        二、devServer 的構(gòu)成

        5 個(gè)主要模塊+15 個(gè)中間件:

        敲重點(diǎn)?。?!在分析這些源碼零件之前,為了方便理解,兄弟們 debug 搞起來(lái)~

        • yarn link 本地代碼(https://cn.vitejs.dev/guide/#command-line-interface)
        • node --inspect-brk 打斷點(diǎn)來(lái) debug 我們 server 端的邏輯, 或者腳本處 debugger,--inspect,然后 yarn inspect 起服務(wù)
        "inspect""node --inspect-brk ./node_modules/.bin/vite --debug lxyDebug"
        • 瀏覽器打開(kāi) chrome://inspect 進(jìn)行 debug:具體操作(http://www.ruanyifeng.com/blog/2018/03/node-debugger.html?spm=a2c6h.12873639.0.0.58f832acIcEUUh)

        三、五大模塊

        首先我們會(huì)簡(jiǎn)單的梳理 5 個(gè)模塊的功能,和各個(gè)模塊之間的協(xié)作聯(lián)系,深入了解請(qǐng)期待后續(xù)文章~

        1. WebSocketServer

        主要就是使用 ws 包,新建了一個(gè) websocket 服務(wù)new WebSocket.Server() 用來(lái)發(fā)送信息,監(jiān)聽(tīng)連接。它主要在 HRM 熱更新里起到發(fā)送各類(lèi)消息的作用,之后 HRM 文章會(huì)著重?cái)⑹鰚

        2. watcher--FSWatcher

        vite 使用 chokidar 這個(gè)跨平臺(tái)文件監(jiān)聽(tīng)?zhēng)?,里面用到的方法也很容易理解,感興趣的去康康~ 它主要是監(jiān)聽(tīng) add unlink change,即監(jiān)聽(tīng)文件新增,刪除,更新,從而更新模塊圖 moduleGraph,同步熱更新?!就?,主要為了熱更新~】

        3. ModuleGraph

        跟蹤導(dǎo)入關(guān)系的模塊圖,url 到文件的映射和 hmr 狀態(tài)。說(shuō)人話就是這個(gè) class 是一個(gè)倉(cāng)庫(kù),可以實(shí)現(xiàn)增刪改查。根據(jù)依賴(lài)關(guān)系新增數(shù)據(jù),進(jìn)行更新,可根據(jù) resolveId,url,file 名稱(chēng)進(jìn)行查找等等。目的就是給你處理模塊的依賴(lài)~

        4. pluginContainer

        基于 Rollup plugin container,提供了一些 hooks:比如下面

        • pluginContainer.watchChange: 每當(dāng)受監(jiān)控的文件發(fā)生更改時(shí),都會(huì)通知插件, 執(zhí)行對(duì)應(yīng)處理
        • pluginContainer.resolveId: 處理 ES6 的 import 語(yǔ)句,最后需要返回一個(gè)模塊的 id
        • pluginContainer.load: 執(zhí)行每個(gè) rollup plugin 的 load 方法,產(chǎn)出 ast 數(shù)據(jù)等,用于 pluginContainer.transform 后續(xù)轉(zhuǎn)換
        • pluginContainer.transform: 每個(gè) rollup plugin 提供 transform 方法,在這個(gè)鉤子里執(zhí)行是為了對(duì)不同文件代碼進(jìn)行轉(zhuǎn)換操作,比如 plugin-vue,經(jīng)過(guò)執(zhí)行就將 vue 文件轉(zhuǎn)換成新的格式代碼。

        總結(jié)一下,拋出這些鉤子都是為了轉(zhuǎn)化 【我們的代碼 =>vite 制定規(guī)則下的新代碼】 ,為其他模塊作為基礎(chǔ)服務(wù)。

        5. httpServer

        原生 node http 服務(wù)器的實(shí)例,根據(jù) http https http2 做了不同情況的處理。使用了selfsigned包生成自簽名的 x509 證書(shū),提供 CA 認(rèn)證保障 https 安全傳輸。

        看完主要模塊,我們來(lái)了解一下中間件都做了哪些細(xì)致加工,有哪些順序的工作流~

        四、15 個(gè)中間件

        每個(gè)中間件結(jié)合下面的注釋看源碼??,數(shù)量有點(diǎn)多,重點(diǎn)中間件如 transformMiddleware,大家可以挑選一些重點(diǎn)來(lái)看~

        1. timeMiddleware

        --debug 命令下,啟動(dòng)打印,時(shí)間中間件能打印出我們整體的啟動(dòng)時(shí)間。

          // 文件: /server/index.ts
          if (process.env.DEBUG) {
            middlewares.use(timeMiddleware(root))
          }
        // 文件:/middleware/time.ts:
        const logTime = createDebugger('vite:time')

        export function timeMiddleware(root: string): Connect.NextHandleFunction {
          return (req, res, next) => {
            const start = Date.now()
            const end = res.end
            res.end = (...args: any[]) => {
              // 打印【時(shí)間 相對(duì)路徑】 -- e.g.: 1ms  /src/App.vue?vue&type=style&index=0&lang.css
              logTime(`${timeFrom(start)} ${prettifyUrl(req.url!, root)}`)
              // @ts-ignore
              return end.call(res, ...args)
            }
            next()
          }
        }

        2. corsMiddleware

        跨域處理的中間件。vite.config.js 傳入 cors 參數(shù)作為 corsOptions 給到 cors 包,實(shí)現(xiàn)各種配置化的跨域場(chǎng)景。

        // 文件: /server/index.ts
        // CORS 用于提供可用于通過(guò)各種選項(xiàng)啟用 CORS 的 Connect / Express 中間件。
        import corsMiddleware from 'cors'

        // cors (默認(rèn)啟用)
          const { cors } = serverConfig
          if (cors !== false) {
            middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
          }

        3. proxyMiddleware

        代理處理。vite.config.js 傳入 proxy 參數(shù),底層用的 http-proxy 包實(shí)現(xiàn)代理功能。

        // vite.config.js
        import { defineConfig } from 'vite'
        import vue from '@vitejs/plugin-vue'

        // https://vitejs.dev/config/
        export default defineConfig({
          plugins: [vue()],
          server: {
            port: 3001,
            host: 'liang.web.com',
            open: true// 自動(dòng)打開(kāi)瀏覽器
            cors: true,
            base: '/mybase',
            proxy: {
              // 字符串簡(jiǎn)寫(xiě)寫(xiě)法
              '/foo1''http://liang.web.com:3001/foo2',
              // 選項(xiàng)寫(xiě)法
              '/api': {
                target: 'http://jsonplaceholder.typicode.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/'')
              },
              // 正則表達(dá)式寫(xiě)法
              '^/fallback/.*': {
                target: 'http://jsonplaceholder.typicode.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/fallback/'')
              },
              '/sunny': {
                bypass: (req, res, options) => {
                  console.log(options)
                  res.end('sunny hhhhhh')
                },
              },
              '^/404/.*': {
                forward: 'http://localhost:3001/',
                bypass: (req, res, options) => {
                  return false // 默認(rèn)服務(wù)器返回是 res.end(404)
                }
              }
            }
          }
        })

        // 文件: /server/index.ts  
        const { proxy } = serverConfig
          if (proxy) { // 啟用代理配置
            middlewares.use(proxyMiddleware(httpServer, config))
          }
        // 文件:/middleware/proxy.ts:
        // node-http-proxy 是一個(gè)支持 websocket 的 HTTP 可編程代理庫(kù)。它適用于實(shí)現(xiàn)諸如反向代理和負(fù)載平衡器之類(lèi)的組件。
        import httpProxy from 'http-proxy'
        export function proxyMiddleware(
          httpServer: http.Server | null,
          config: ResolvedConfig
        ): Connect.NextHandleFunction 
        {
          const options = config.server.proxy!
          ...
          const proxy = httpProxy.createProxyServer(opts) as HttpProxy.Server // 創(chuàng)建代理服務(wù)器
          proxy.on('error'(err) => {...})
          if (opts.configure) { // 執(zhí)行傳遞的 config 方法
            opts.configure(proxy, opts)
          }
          if (httpServer) {
            // 監(jiān)聽(tīng) `upgrade` 事件并且代理 WebSocket 請(qǐng)求
            httpServer.on('upgrade'(req, socket, head) => {
              const url = req.url!
              for (const context in proxies) {
                if (url.startsWith(context)) { // 如果當(dāng)前 URL 匹配上要代理的 url
                  const [proxy, opts] = proxies[context]
                  if (
                    (opts.ws || opts.target?.toString().startsWith('ws:')) &&
                    req.headers['sec-websocket-protocol'] !== HMR_HEADER // 不是 HRM 的 websocket 請(qǐng)求
                  ) {
                    if (opts.rewrite) {
                      req.url = opts.rewrite(url)
                    }
                    proxy.ws(req, socket, head) // 代理 websocket 方法
                  }
                }
              }
            })
          }
          return (req, res, next) => {
            const url = req.url!
            for (const context in proxies) { // 循環(huán)處理傳遞來(lái)的 proxy 對(duì)象配置,context 如【'^/fallback/.*'】
              if (
                (context.startsWith('^') && new RegExp(context).test(url)) ||
                url.startsWith(context)
                ) { // 正則匹配上的 URL 或 字符匹配上的 URL
                const [proxy, opts] = proxies[context]
                const options: HttpProxy.ServerOptions = {}

                if (opts.bypass) { // 執(zhí)行配置傳遞的 bypass 方法 - 記錄 debug
                  const bypassResult = opts.bypass(req, res, opts)
                  ......
                }
                if (opts.rewrite) { // 執(zhí)行傳遞的 rewrite 方法
                  req.url = opts.rewrite(req.url!)
                }
                proxy.web(req, res, options) // 代理 web 請(qǐng)求
                return
              }
            }
            next()
          }
        }

        4. baseMiddleware

        路徑的 base 處理

          // 文件: /server/index.ts  
          if (config.base !== '/') {
            middlewares.use(baseMiddleware(server))
          }
        // 文件 /middlewares/base.ts
        import { parse as parseUrl } from 'url'
        export function baseMiddleware({
          config
        }: ViteDevServer
        ): Connect.NextHandleFunction 
        {
          const base = config.base
          return (req, res, next) => {
            const url = req.url!
            const parsed = parseUrl(url)
            const path = parsed.pathname || '/'

            if (path.startsWith(base)) {
              req.url = url.replace(base, '/'// 刪除 base..這確保其他中間件不需要考慮是否在 base 上加了前綴
            } else if (path === '/' || path === '/index.html') {
              res.writeHead(302, { // 302 重定向到 base 路徑
                Location: base
              })
              res.end()
              return
            } else if (req.headers.accept?.includes('text/html')) {
              // non-based page visit
              res.statusCode = 404
              res.end(xxx)
              return
            }

            next()
          }
        }

        5. launchEditorMiddleware

        在 Node.js 的編輯器中打開(kāi)帶有行號(hào)的某文件。

        import launchEditorMiddleware from 'launch-editor-middleware'  
        middlewares.use('/__open-in-editor', launchEditorMiddleware())

        6. pingPongMiddleware

        hmr 重新連接的心跳檢測(cè)

          middlewares.use('/__vite_ping'(_, res) => res.end('pong'))

        7. decodeURIMiddleware

        sirv 中間件找文件需要解碼的 URL,所以要提前將 parsedUrl 對(duì)象的 key 對(duì)應(yīng) value 進(jìn)行解碼

          // decode 請(qǐng)求 URL
          middlewares.use(decodeURIMiddleware())

        8. servePublicMiddleware

          // 在/ public 下提供靜態(tài)文件
          // 這在轉(zhuǎn)換中間件之前應(yīng)用,以便提供這些文件就像沒(méi)有變換一樣。
          middlewares.use(servePublicMiddleware(config.publicDir))
        // 文件 /server/middleware/static.ts
        import sirv from 'sirv'

        export function servePublicMiddleware(dir: string): Connect.NextHandleFunction {
          const serve = sirv(dir, sirvOptions) // 這個(gè)插件可以處理靜態(tài)服務(wù)

          return (req, res, next) => {
            // 跳過(guò) import 的請(qǐng)求,如 /src/components/HelloWorld.vue?import&t=1620397982037
            if (isImportRequest(req.url!)) { 
              return next()
            }
            serve(req, res, next)
          }
        }

        9. transformMiddleware

        cacheDir: 默認(rèn)為項(xiàng)目路徑下的/node_modules/.vite

          // 核心轉(zhuǎn)換器 middleware
          middlewares.use(transformMiddleware(server))

        核心邏輯:將當(dāng)前請(qǐng)求 url 添加到維護(hù)的 moduleGraph 中,返回處理后的新代碼;主要方法 -- transformRequest:該方法進(jìn)行了緩存,請(qǐng)求資源解析,加載,轉(zhuǎn)換操作。命中緩存的直接返回 transform result,否則進(jìn)行以下操作:

        • pluginContainer.resolveId(url)?.id:獲取新增 resolveId
        • pluginContainer.load(id) :根據(jù)上面獲取的 id,經(jīng)過(guò)該 hook 產(chǎn)出 map【sourceMap 信息】和 code【返回客戶(hù)端的代碼】
        • 把新增的 module 放入 moduleGraph,并且用 watcher 監(jiān)聽(tīng) module.file
        • 處理 map 內(nèi)的 sourceMap 相關(guān)信息,比如注入源代碼內(nèi)容:injectSourceContent
        • 拼接信息成對(duì)象返回
        mod.transformResult = {
          code, // plugin.transform 后返回給客戶(hù)端的代碼
          map, // 處理后的 sourceMap 信息
          etag: getEtag(code, { weak: true }) // etag 插件生成

        源碼有點(diǎn)多,自己搞~1)處理 js 請(qǐng)求:/src/main.js:transform 后的 code 返回結(jié)果查看:

        2)處理?import 請(qǐng)求:場(chǎng)景:更新一行 helloworld.vue 代碼,熱更新打進(jìn)來(lái)的請(qǐng)求3)處理 css 請(qǐng)求

        10. serveRawFsMiddleware

        處理/@fs/的 URL,獲取原有的路徑

        // 文件 /server/middleware/static.ts
        export function serveRawFsMiddleware(): Connect.NextHandleFunction {
          const isWin = os.platform() === 'win32'
          const serveFromRoot = sirv('/', sirvOptions)

          return (req, res, next) => {
            let url = req.url!
              if (url.startsWith(FS_PREFIX)) { // 以`/@fs/`開(kāi)頭的 URL
              url = url.slice(FS_PREFIX.length) // 取原有的路徑
              if (isWin) url = url.replace(/^[A-Z]:/i'')

              req.url = url
              serveFromRoot(req, res, next)
            } else {
              next()
            }
          }
        }

        11. serveStaticMiddleware

        // 文件 /server/middleware/static.ts
        export function serveStaticMiddleware(
          dir: string,
          config: ResolvedConfig
        ): Connect.NextHandleFunction 
        {
          const serve = sirv(dir, sirvOptions) // 傳遞 dir=root, 根路徑下的靜態(tài)服務(wù)

          return (req, res, next) => {
            const url = req.url!

            // 僅在不是 html 請(qǐng)求的情況下處理文件,以便 html 請(qǐng)求可以進(jìn)入我們的 html 中間件特殊處理
            if (path.extname(cleanUrl(url)) === '.html') {
              return next()
            }

            // 也將別名應(yīng)用于靜態(tài)請(qǐng)求
            let redirected: string | undefined
            for (const { find, replacement } of config.resolve.alias) {
              const matches =
                typeof find === 'string' ? url.startsWith(find) : find.test(url)
              if (matches) {
                redirected = url.replace(find, replacement)
                break
              }
            }
            if (redirected) {
              // dir 已預(yù)先標(biāo)準(zhǔn)化為 posix 樣式
              if (redirected.startsWith(dir)) {
                redirected = redirected.slice(dir.length)
              }
              req.url = redirected
            }

            serve(req, res, next)
          }
        }

        12. spaMiddleware

        SPA 處理:提供 URL 對(duì)應(yīng) path 下的 index.html,默認(rèn)為/index.html 文件

        // 該中間件通過(guò)指定的索引頁(yè)代理請(qǐng)求,對(duì)于使用 HTML5 history API 的單頁(yè)應(yīng)用程序非常有用。
        import history from 'connect-history-api-fallback'
          if (!middlewareMode) {
            middlewares.use(
              history({
                logger: createDebugger('vite:spa-fallback'),
                // 支持/ dir /,沒(méi)有明確的 index.html
                rewrites: [
                  {
                    from/\/$/,
                    to({ parsedUrl }: any) {
                      const rewritten = parsedUrl.pathname + 'index.html'
                      if (fs.existsSync(path.join(root, rewritten))) {
                        return rewritten
                      } else {
                        return `/index.html`
                      }
                    }
                  }
                ]
              })
            )
          }

        13. indexHtmlMiddleware

          if (!middlewareMode) {
            // 轉(zhuǎn)換入口文件 index.html
            middlewares.use(indexHtmlMiddleware(server))
          }

        14. 404Middleware

          if (!middlewareMode) {
            // 處理 404
            middlewares.use((_, res) => {
              res.statusCode = 404
              res.end()
            })
          }

        15. errorMiddleware

          // error handler
          middlewares.use(errorMiddleware(server, middlewareMode))
        // 文件 /server/middleware/error.ts
        export function errorMiddleware(
          server: ViteDevServer,
          allowNext = false // 是否允許程序進(jìn)行,否則返回錯(cuò)誤狀態(tài)碼 500
        ): Connect.ErrorHandleFunction 
        {
          // 請(qǐng)注意,必須保留 4 個(gè) arg 才能進(jìn)行 connect,以將其視為錯(cuò)誤中間件
          return (err: RollupError, _req, res, next) => {
            const msg = buildErrorMessage(err, [
              chalk.red(`Internal server error: ${err.message}`)
            ])

            server.config.logger.error(msg, { // 日志記錄錯(cuò)誤
              clear: true,
              timestamp: true
            })

            server.ws.send({ // websocket 發(fā)送錯(cuò)誤
              type'error',
              err: prepareError(err)
            })

            if (allowNext) {
              next()
            } else {
              res.statusCode = 500 // 返回 500 服務(wù)錯(cuò)誤
              res.end()
            }
          }
        }

        五、createServer 總結(jié)

        這就有了 cli 里的創(chuàng)建 server方法啦~ 總結(jié)一下:

        本文從初始第一步的 cli 命令開(kāi)始來(lái)引入,vite 命令做了什么,然后引導(dǎo)大家找到 createServer 的入口。

        其次,我們深入探究 createServer 需要的"五臟十五腑",有 websocketServer,fsWatcher,moduleGraph 等 5 個(gè)模塊來(lái)支撐需要的零件,根據(jù) 15 個(gè)中間件的分工來(lái)串聯(lián)整個(gè)加工流程,最終打磨出我們的 devServer。

        我們可以看到全程么有 bundle 的痕跡,vite 很好的使用了 esmodule 來(lái)做到及時(shí)有效的模塊熱重載,冷啟動(dòng)快速,開(kāi)發(fā)體驗(yàn)杠杠的????

        瀏覽 120
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一边揉着胸一边舌吻拉丝 | 免费成人做爱视频 | 欧美黑人操逼纲站 | 91青青草视频 | 国产熟妇 码视频户外直播 | 影音先锋sv电影资源 | 国产精品69毛片高清亚洲 | 91熟女半推半就对了白在线 | 5个黑人躁我一个爽 | 福利一区二区在线 |