前言
本文是vite源码的第一章,看过vite仓库之后发现是一个monorepo,一共有一百三十多个项目,大部分其实都是playground(类似于vite的实操),package目录是最主要的项目代码,之前已经对create-vite做了解析
- create-vite
- plugin-legacy
- plugin-react
- plugin-vue
- plugin-vue-jsx
- vite
按照惯例打开vite目录安装依赖,这里就遇到问题了,有一个叫做playwright-chromium的依赖在安装的时候一直报错,为此专门在vite github上提了一个讨论,不过好像也一直没人鸟的样子
执行npm run dev后vite做了什么
项目执行npm run dev后,其实就是执行了npx vite,通过阅读packages\vite\bin\vite.js我们可以知道如果vite后不加参数就是执行默认命令(这部分直接跳过了,主要就是cac的使用),也就是createServer方法,内部的逻辑以流程图的形式展现的话就像这个样子
方法代码主要在packages\vite\src\node\server目录,源码分析如下
// inlineConfig:命令行配置
async function createServer(inlineConfig = {}) {
// 解析vite配置 https://juejin.cn/post/7140925216993050661
const config = await resolveConfig(inlineConfig, 'serve', 'development');
const { root, server: serverConfig } = config;
const httpsOptions = await resolveHttpsConfig(config.server.https);
const { middlewareMode } = serverConfig;
// 解析传递给Chokidar的参数,Chokidar是一个监听文件变化的库
const resolvedWatchOptions = resolveChokidarOptions({
disableGlobbing: true,
...serverConfig.watch
});
// connect:作为中间件使用的服务端框架
const middlewares = connect();
// 创建http和websocket服务
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions);
const ws = createWebSocketServer(httpServer, config, httpsOptions);
if (httpServer) {
setClientErrorHandler(httpServer, config.logger);
}
// 创建chokidar监听实例
const watcher = chokidar.watch(path.resolve(root), resolvedWatchOptions);
// 创建依赖关系实例 https://juejin.cn/post/7082922754164391972
const moduleGraph = new ModuleGraph((url, ssr) => container.resolveId(url, undefined, { ssr }));
// 创建插件容器 https://juejin.cn/post/7083352352601669669
const container = await createPluginContainer(config, moduleGraph, watcher);
const closeHttpServer = createServerCloseFn(httpServer);
let exitProcess;
// 字面量的形式创建server
const server = {
...
};
// 插件的 transformIndexHtml 钩子在这里就执行啦,用于转换 index.html
server.transformIndexHtml = createDevHtmlTransformFn(server);
// 监听vite进程关闭
if (!middlewareMode) {
exitProcess = async () => {
try {
await server.close();
}
finally {
process.exit();
}
};
process.once('SIGTERM', exitProcess);
if (process.env.CI !== 'true') {
process.stdin.on('end', exitProcess);
}
}
// packageCache是resolveConfig时new Map(),这个map的目的还不清楚
// 这块主要是对这个map实例的set方法做了劫持
const { packageCache } = config;
const setPackageData = packageCache.set.bind(packageCache);
packageCache.set = (id, pkg) => {
if (id.endsWith('.json')) {
watcher.add(id);
}
return setPackageData(id, pkg);
};
// 监听当前项目所有文件的改变 热更新也在其中
watcher.on('change', async (file) => {
...
});
// 添加新文件时触发
watcher.on('add', (file) => {
...
});
// 文件删除时触发
watcher.on('unlink', (file) => {
...
});
if (!middlewareMode && httpServer) {
httpServer.once('listening', () => {
// 更新端口号,配置中指定的端口可能会被占用
serverConfig.port = httpServer.address().port;
});
}
// 插件的configureServer钩子可以选择在前置或后置执行
// 使用postHooks收集后置的configureServer钩子
const postHooks = [];
for (const plugin of config.plugins) {
if (plugin.configureServer) {
postHooks.push(await plugin.configureServer(server));
}
}
// 内部中间件 ------------------------------------------------------
// request timer
if (process.env.DEBUG) {
middlewares.use(timeMiddleware(root));
}
// cors (enabled by default)
const { cors } = serverConfig;
if (cors !== false) {
middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors));
}
// proxy
const { proxy } = serverConfig;
if (proxy) {
middlewares.use(proxyMiddleware(httpServer, proxy, config));
}
// base
const devBase = config.base;
if (devBase !== '/') {
middlewares.use(baseMiddleware(server));
}
// open in editor support
middlewares.use('/__open-in-editor', launchEditorMiddleware());
// serve static files under /public
// this applies before the transform middleware so that these files are served
// as-is without transforms.
if (config.publicDir) {
middlewares.use(servePublicMiddleware(config.publicDir, config.server.headers));
}
// 核心!转换中间件
middlewares.use(transformMiddleware(server));
// serve static files
middlewares.use(serveRawFsMiddleware(server));
middlewares.use(serveStaticMiddleware(root, server));
// spa fallback
if (config.appType === 'spa') {
middlewares.use(spaFallbackMiddleware(root));
}
// This is applied before the html middleware so that user middleware can
// serve custom content instead of index.html.
// 调用后置configureServer钩子
postHooks.forEach((fn) => fn && fn());
if (config.appType === 'spa' || config.appType === 'mpa') {
// transform index.html
middlewares.use(indexHtmlMiddleware(server));
// handle 404s
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
middlewares.use(function vite404Middleware(_, res) {
res.statusCode = 404;
res.end();
});
}
// error handler
middlewares.use(errorMiddleware(server, middlewareMode));
let initingServer;
let serverInited = false;
// 预构建
const initServer = async () => {
if (serverInited) {
return;
}
if (initingServer) {
return initingServer;
}
initingServer = (async function () {
await container.buildStart({});
if (isDepsOptimizerEnabled(config, false)) {
// non-ssr
await initDepsOptimizer(config, server);
}
initingServer = undefined;
serverInited = true;
})();
return initingServer;
};
if (!middlewareMode && httpServer) {
// 重写监听函数 目的是在服务启动前执行预构建
const listen = httpServer.listen.bind(httpServer);
httpServer.listen = (async (port, ...args) => {
try {
await initServer();
}
catch (e) {
httpServer.emit('error', e);
return;
}
return listen(port, ...args);
});
}
else {
await initServer();
}
return server;
}
总结
如果用一张图来梳理createServer函数的整个流程,大概就是下面这个
当我们敲下 vite 命令后,cac插件识别命令并执行createServer,之后 vite 在短短时间内做了解析配置、创建 http server、创建 ws、创建文件监听器、初始化模块依赖图、创建插件、预构建、启动服务等任务,最后返回server对象。 接收到server对象之后,执行server.listen()启动开发服务,并在控制台打印出我们经常看到的服务地址