写在前面
个人总结了两条读源码的经验方法⬇️
- 读源码的方法:
新旧知识联系 - 读源码第一步:
找入口重点关注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
}) // ⬆️关键代码看这里
到这里我们已经可以确定,ViteDevServer与http模块的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实例的关系。
📖 总结
运用第一步找入口,新旧知识联系的方法可以很清楚地理清楚源码的运行逻辑与结构。