【源码】vite dev server原理(http模块)

101 阅读2分钟

写在前面

个人总结了两条读源码的经验方法⬇️

  • 读源码的方法:新旧知识联系
  • 读源码第一步:找入口 重点关注package.json中的bin,main,scripts字段⏰ 以下一一实践一下:

🚪看入口

首先我们要看的是程序/应用/框架的入口。我们可以找到vite dev的入口

寻找路径:package.json中的“bin”字段->bin/index.js->cli.js ⬇️

// src/node/cli.ts
// vite dev命令执行了什么?
const server = await createServer({
        root,
        base: options.base,
        mode: options.mode,
        configFile: options.config,
        logLevel: options.logLevel,
        clearScreen: options.clearScreen,
        optimizeDeps: { force: options.force },
        server: cleanOptions(options)
      })
      
await server.listen()

🕸️ 新旧知识联系

🔍 搜索已有旧知识

说到http server,根据已有的经验知识,我们知道有node的http/https模块,其简单的用法是⬇️

const http = require('http') 
const hostname = '127.0.0.1' 
const port = 3000 

const server = http.createServer((req, res) => { 
res.statusCode = 200 
res.setHeader('Content-Type', 'text/plain') //writeHead,200表示页面正常,text/plain表示是文字。 
res.end('Hello World\n') // end 完成写入 
}) 

server.listen(port, hostname, () => { console.log(`服务器运行在 http://${hostname}:${port}`) })

联系起来

很自然地,我们可以推断出,最上面的server.listen()与http模块的server.listen(port...)有必然的联系。 那么它们的关系是什么呢? 前者是ViteDevServer实例的方法,后者是http模块Server实例的方法。

一般来说,优秀的源码会对比较底层的代码进行封装。

为了证明这个假设,我们就要设法找到他们两之间的联系。接着我们往里层看,server.listen()的代码封装是⬇️

async listen(port?: number, isRestart?: boolean) {
      await startServer(server, port, isRestart) // ⬅️关键代码看这里
      if (httpServer) {
        server.resolvedUrls = await resolveServerUrls(
          httpServer,
          config.server,
          config
        )
      }
      return server
  }

可见listen方法调用到了startServer方法,那么回到startServer方法可以看到⬇️

async function startServer(
  server: ViteDevServer,
  inlinePort?: number,
  isRestart: boolean = false
): Promise<void> {
  const httpServer = server.httpServer // ⬅️关键代码看这里
  // ...
  const serverPort = await httpServerStart(httpServer, {
    port,
    strictPort: options.strictPort,
    host: hostname.host,
    logger: server.config.logger
  }) // ⬆️关键代码看这里

到这里我们已经可以确定,ViteDevServerhttp模块Server之间的关系了! ViteDevServer实例的属性就是Server实例。

💡 进一步验证猜想

我们可以继续回溯,看看httpServerStart的内容:

export async function httpServerStart(
  httpServer: HttpServer,
  serverOptions: {
    port: number
    strictPort: boolean | undefined
    host: string | undefined
    logger: Logger
  }
): Promise<number> {
  let { port, strictPort, host, logger } = serverOptions

  return new Promise((resolve, reject) => {
    const onError = (e: Error & { code?: string }) => {
      if (e.code === 'EADDRINUSE') {
        if (strictPort) {
          httpServer.removeListener('error', onError)
          reject(new Error(`Port ${port} is already in use`))
        } else {
          logger.info(`Port ${port} is in use, trying another one...`)
          httpServer.listen(++port, host)
        }
      } else {
        httpServer.removeListener('error', onError)
        reject(e)
      }
    }

    httpServer.on('error', onError)
    
    // ⬇️找到了我们想要的代码!!!
    httpServer.listen(port, host, () => {
      httpServer.removeListener('error', onError)
      resolve(port)
    })
  })
}

由此可见我们的猜想是正确的! 那么我们来理清一下前后ViteDevServer实例和http模块Server实例的关系。

屏幕快照 2022-11-14 下午8.44.50.png

📖 总结

运用第一步找入口,新旧知识联系的方法可以很清楚地理清楚源码的运行逻辑与结构。