Vite的createServer启动源码解析

目录
  • 启动Vite的createServer
  • 通过vite3安装一个vue的工程
  • 添加断点并开启调试
  • 边调试边理解代码

启动Vite的createServer

为了能够了解vite里面运行了什么,通过执行单步调试能够更加直观的知道Vite具体内容。所以这次我们来试着启动Vite的createServer,并进行调试。

通过vite3安装一个vue的工程

进入工作目录,运行下面的代码,项目名称随意,语言用Vue

npm create vite

进入工程目录安装依赖

添加断点并开启调试

通过vscode打开刚刚生成的项目,通过上一章的内容,可以知道程序的入口是cli.js。所以首先打开node_modules里面vite文件夹下面找到cli.js。找到运行dev时候的主要代码createServer,并打上断点。

开启vsCode的调试功能,JavaScript Debug Terminal

执行Dev命令,便可以运行到断点位置。

yarn run dev

边调试边理解代码

进入CreateServer内部代码,稍微加了一点注释,代码如下:

async function createServer(inlineConfig = {}) {
    // 获取config,用户自定义的vite.config.js/ts,还有vite的默认配置
    const config = await resolveConfig(inlineConfig, 'serve', 'development');
    const { root, server: serverConfig } = config;
    // http相关的配置
    const httpsOptions = await resolveHttpsConfig(config.server.https);
    // serverConfig就是vite.config里面的server对象,参照上main的代码,middlewareMode包含html和ssr 2个选项
    const { middlewareMode } = serverConfig;
    const resolvedWatchOptions = resolveChokidarOptions({
        disableGlobbing: true,
        ...serverConfig.watch
    });
    // connect是一个中间件的类库,https://www.jianshu.com/p/d606c1059c51
    const middlewares = connect();
    const httpServer = middlewareMode
        ? null
        : await resolveHttpServer(serverConfig, middlewares, httpsOptions);
    const ws = createWebSocketServer(httpServer, config, httpsOptions);
    if (httpServer) {
        setClientErrorHandler(httpServer, config.logger);
    }
    // 监听文件变化
    const watcher = chokidar.watch(path$n.resolve(root), resolvedWatchOptions);
    // 创建模块图纸,记录每个模块之间的依赖关系
    const moduleGraph = new ModuleGraph((url, ssr) => container.resolveId(url, undefined, { ssr }));
    // 创建PluginContainer
    const container = await createPluginContainer(config, moduleGraph, watcher);
    // 关系HttpServer的时候的处理
    const closeHttpServer = createServerCloseFn(httpServer);
    let exitProcess;
    // 创建Server对象
    const server = {
        config,
        middlewares,
        httpServer,
        watcher,
        pluginContainer: container,
        ws,
        moduleGraph,
        resolvedUrls: null,
        ssrTransform(code, inMap, url, originalCode = code) {
            return ssrTransform(code, inMap, url, originalCode, server.config);
        },
        transformRequest(url, options) {
            return transformRequest(url, server, options);
        },
        transformIndexHtml: null,
        async ssrLoadModule(url, opts) {
            if (isDepsOptimizerEnabled(config, true)) {
                await initDevSsrDepsOptimizer(config, server);
            }
            await updateCjsSsrExternals(server);
            return ssrLoadModule(url, server, undefined, undefined, opts?.fixStacktrace);
        },
        ssrFixStacktrace(e) {
            if (e.stack) {
                const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph);
                rebindErrorStacktrace(e, stacktrace);
            }
        },
        ssrRewriteStacktrace(stack) {
            return ssrRewriteStacktrace(stack, moduleGraph);
        },
        async listen(port, isRestart) {
            await startServer(server, port, isRestart);
            if (httpServer) {
                server.resolvedUrls = await resolveServerUrls(httpServer, config.server, config);
            }
            return server;
        },
        // 关闭server时调用的函数
        async close() {
            if (!middlewareMode) {
                process.off('SIGTERM', exitProcess);
                if (process.env.CI !== 'true') {
                    process.stdin.off('end', exitProcess);
                }
            }
            await Promise.all([
                watcher.close(),
                ws.close(),
                container.close(),
                closeHttpServer()
            ]);
            server.resolvedUrls = null;
        },
        printUrls() {
            if (server.resolvedUrls) {
                printServerUrls(server.resolvedUrls, serverConfig.host, config.logger.info);
            }
            else if (middlewareMode) {
                throw new Error('cannot print server URLs in middleware mode.');
            }
            else {
                throw new Error('cannot print server URLs before server.listen is called.');
            }
        },
        async restart(forceOptimize) {
            if (!server._restartPromise) {
                server._forceOptimizeOnRestart = !!forceOptimize;
                server._restartPromise = restartServer(server).finally(() => {
                    server._restartPromise = null;
                    server._forceOptimizeOnRestart = false;
                });
            }
            return server._restartPromise;
        },
        _ssrExternals: null,
        _restartPromise: null,
        _importGlobMap: new Map(),
        _forceOptimizeOnRestart: false,
        _pendingRequests: new Map()
    };
    // vite.config里面申明了transformIndexHtml,在会被调用。
    server.transformIndexHtml = createDevHtmlTransformFn(server);
    if (!middlewareMode) {
        exitProcess = async () => {
            try {
                await server.close();
            }
            finally {
                process.exit();
            }
        };
        // 进程收到这个信号时候,会调用exitProcess方法
        process.once('SIGTERM', exitProcess);
        if (process.env.CI !== 'true') {
            process.stdin.on('end', exitProcess);
        }
    }
    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) => {
        file = normalizePath$3(file);
        if (file.endsWith('/package.json')) {
            return invalidatePackageData(packageCache, file);
        }
        // invalidate module graph cache on file change
        // 模型关系图变化时处理
        moduleGraph.onFileChange(file);
        if (serverConfig.hmr !== false) {
            try {
                await handleHMRUpdate(file, server);
            }
            catch (err) {
                ws.send({
                    type: 'error',
                    err: prepareError(err)
                });
            }
        }
    });
    watcher.on('add', (file) => {
        handleFileAddUnlink(normalizePath$3(file), server);
    });
    watcher.on('unlink', (file) => {
        handleFileAddUnlink(normalizePath$3(file), server);
    });
    if (!middlewareMode && httpServer) {
        httpServer.once('listening', () => {
            // update actual port since this may be different from initial value
            serverConfig.port = httpServer.address().port;
        });
    }
    // apply server configuration hooks from plugins
    // 给plugin配置hook钩子
    const postHooks = [];
    for (const hook of config.getSortedPluginHooks('configureServer')) {
        postHooks.push(await hook(server));
    }
    // Internal middlewares ------------------------------------------------------
    // request timer
    if (process.env.DEBUG) {
        middlewares.use(timeMiddleware(root));
    }
    // cors (enabled by default)
    const { cors } = serverConfig;
    if (cors !== false) {
        middlewares.use(lib$1.exports(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.
    // 最外层public目录的配置
    if (config.publicDir) {
        middlewares.use(servePublicMiddleware(config.publicDir, config.server.headers));
    }
    // main transform middleware
    // ️最核心的中间件--重点--javaScript的编译
    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));
    }
    // run post config hooks
    // This is applied before the html middleware so that user middleware can
    // serve custom content instead of index.html.
    // 自定义的中间件,这里运行
    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) {
        // overwrite listen to init optimizer before server start
        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;
}

以上就是Vite的createServer启动源码解析的详细内容,更多关于Vite createServer启动的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vite使用Esbuild提升性能详解

    目录 前言 初探 Esbuild 关键 API - transfrom & build plugin Esbuild 在 Vite 中的巧妙使用 预构建 middlewares 中内容转换 结束语 前言 在上一篇 为什么有人说 vite 快,有人却说 vite 慢? 中,我们提到过开发模式下使用 Vite 会有首屏性能下降的负面效果.之所以会造成首屏性能下降,一方面是 dev server 需要完成预构建才可以响应首屏请求:另一方面是需要对请求文件做实时转换. 也许有的同学会问,是不是针对这两个

  • 详解vite如何支持cjs方案示例

    目录 一.问题 二.解决方案 三.如何处理commonJS 一.问题 vite运行时使用esbuild,基于esm 大部分三方包为UMD规范,输出的是CommonJS的包(比如react.lodash) // react 入口文件 // 只有 CommonJS 格式 if (process.env.NODE_ENV === "production") { module.exports = require("./cjs/react.production.min.js"

  • 深入剖析vite到底是快还是慢原理详解

    目录 前言 Vite 的快 快速的冷启动 快速的热更新 Vite 的慢 首屏性能 懒加载性能 结束语 前言 谈到 Vite,给人的第一印象就是 dev server 启动速度快.同样规模的项目,相比 Webpack 动辄十几秒甚至几十秒的的启动速度,Vite 简直是快到没朋友,往往数秒之内即可完成启动(PS: 都没有时间去喝一杯 ️ 啦). 正好小编最近在做一些关于开发体验的性能优化,就想着把手上一些项目的开发模式更新为 Vite.经过一番操作,终于改造成功,而效果也不负众望,项目启动速度由原来

  • Vite配置优雅的code spliiting代码分割详解

    目录 Vite如何配置分割代码 1.什么是代码分割/code spliiting 2.Vite 中 rollup code spliiting分割默认方法原理 (1)按照动态导入语句分割打包测试. (2)按照资源导入入口点分割打包测试. (3)manualChunks函数 手动自定义分割.(下面的案例) 3.如何在Vite中配置(vite.config.ts)代码分割/code spliiting (核心关键) Vite代码分割方法1 Vite代码分割方法2 Vite如何配置分割代码 1.什么是

  • vue3 Vite 进阶rollup命令行使用详解

    目录 rollup介绍 以命令行方式打包 Tree Shaking Rollup 的命令行使用 命令行 format 格式 rollup.config.js 设置/获取环境变量 插件 plugins rollup介绍 开源类库优先选择 以 ESM 标准为目标的构建工具 Tree Shaking 以命令行方式打包 安装 rollup npm install -g rollup 创建 index.js 文件 import path from "path"; console.log(&quo

  • Vite的createServer启动源码解析

    目录 启动Vite的createServer 通过vite3安装一个vue的工程 添加断点并开启调试 边调试边理解代码 启动Vite的createServer 为了能够了解vite里面运行了什么,通过执行单步调试能够更加直观的知道Vite具体内容.所以这次我们来试着启动Vite的createServer,并进行调试. 通过vite3安装一个vue的工程 进入工作目录,运行下面的代码,项目名称随意,语言用Vue. npm create vite 进入工程目录安装依赖 添加断点并开启调试 通过vsc

  • .NET Core源码解析配置文件及依赖注入

    写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表.不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦. 这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原理.配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等. 俗话说,授人以鱼不如授人以渔,所以文章旨在带着大

  • create vite 实例源码解析

    目录 代码结构 init() projectName:项目名称 overwrite:是否覆盖已存在的目录 overwriteChecker:检测覆盖的目录是否为空 framework:框架 variant:语言 获取用户输入 清空目录 生成项目 确定项目模板 确定包管理器 正式生成项目 创建package.json 完成 总结 代码结构 create-vite的源码很简单,只有一个文件,代码总行数400左右,但是实际需要阅读的代码大约只有200行左右,废话不多说,直接开始吧. create-vi

  • Spring SpringMVC在启动完成后执行方法源码解析

    关键字:spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件) 应用场景:很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下: 1.写一个类继承spring的ApplicationListener监听,并监控ContextRefresh

  • Android okhttp的启动流程及源码解析

    前言 这篇文章主要讲解了okhttp的主要工作流程以及源码的解析. 什么是OKhttp 简单来说 OkHttp 就是一个客户端用来发送 HTTP 消息并对服务器的响应做出处理的应用层框架. 那么它有什么优点呢? 易使用.易扩展. 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接. 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟. 支持 GZIP,减小了下载大小. 支持缓存处理,可以避免重复请求. 如果你的服务有多个 IP 地址,当第一次连接失败,OkHt

  • Android10 App 启动分析进程创建源码解析

    目录 正文 RootActivityContainer ActivityStartController 调用startActivityUnchecked方法 ActivityStackSupervisor 启动进程 RuntimeInit.applicationInit这个方法 正文 从前文# Android 10 启动分析之SystemServer篇 (四)中可以得知,系统在完成所有的初始化工作后,会通过 mAtmInternal.startHomeOnAllDisplays(currentU

  • Android10 启动Zygote源码解析

    目录 app_main ZygoteInit preload preloadClasses preloadResources preloadSharedLibraries forkSystemServer app_main 上一篇文章: # Android 10 启动分析之servicemanager篇 (二) 在init篇中有提到,init进程会在在Trigger 为late-init的Action中,启动Zygote服务,这篇文章我们就来具体分析一下Zygote服务,去挖掘一下Zygote负

  • java.lang.Void类源码解析

    在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code null} * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. */ private static Void checkParentAcc

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • Laravel源码解析之路由的使用和示例详解

    前言 我的解析文章并非深层次多领域的解析攻略.但是参考着开发文档看此类文章会让你在日常开发中更上一层楼. 废话不多说,我们开始本章的讲解. 入口 Laravel启动后,会先加载服务提供者.中间件等组件,在查找路由之前因为我们使用的是门面,所以先要查到Route的实体类. 注册 第一步当然还是通过服务提供者,因为这是laravel启动的关键,在 RouteServiceProvider 内加载路由文件. protected function mapApiRoutes() { Route::pref

随机推荐