一,vite启动的时候都做了什么?

最近,虽然在项目中用到了vite,但是对它的特性还不是很了解,所以想扒一扒它的源码,看下为什么它突然异军突起,好多大佬都说用了它之后,再也不想用webpack了。

我们都知道,当执行npm run dev的时候,npm会去项目的package.json文件里找scripts 里找对应的dev指令执行。

package.json

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
复制代码

执行vite的时候,npm会找到 ./node_modules/.bin 对应的vite脚本(.bin中的文件在npm安装对应依赖的时候创建)。下面是vite脚本。可以看到文件顶部写着 #!/user/bin/env node ,表示这是一个通过使用 Node 执行的脚本。

vite.js

#!/usr/bin/env node
const { performance } = require('perf_hooks')

if (!__dirname.includes('node_modules')) {
  try {
    // only available as dev dependency
    require('source-map-support').install()
  } catch (e) {}
}

global.__vite_start_time = performance.now()

// check debug mode first before requiring the CLI.
const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg))
const filterIndex = process.argv.findIndex((arg) =>
  /^(?:-f|--filter)$/.test(arg)
)
const profileIndex = process.argv.indexOf('--profile')

if (debugIndex > 0) {
  let value = process.argv[debugIndex + 1]
  if (!value || value.startsWith('-')) {
    value = 'vite:*'
  } else {
    // support debugging multiple flags with comma-separated list
    value = value
      .split(',')
      .map((v) => `vite:${v}`)
      .join(',')
  }
  process.env.DEBUG = `${
    process.env.DEBUG ? process.env.DEBUG + ',' : ''
  }${value}`

  if (filterIndex > 0) {
    const filter = process.argv[filterIndex + 1]
    if (filter && !filter.startsWith('-')) {
      process.env.VITE_DEBUG_FILTER = filter
    }
  }
}

function start() {
  require('../dist/node/cli')
}

if (profileIndex > 0) {
  process.argv.splice(profileIndex, 1)
  const next = process.argv[profileIndex]
  if (next && !next.startsWith('-')) {
    process.argv.splice(profileIndex, 1)
  }
  const inspector = require('inspector')
  const session = (global.__vite_profile_session = new inspector.Session())
  session.connect()
  session.post('Profiler.enable', () => {
    session.post('Profiler.start', start)
  })
} else {
  start()
}

复制代码

那么为什么,npm在安装依赖的时候会自动创建该文件呢?我们可以找到vite的依赖包,打开来看一下,可以看到,在vitepackage.json里面有如下配置:

"bin": {
    "vite": "bin/vite.js"
  },
复制代码

npm读取到该配置时,会将对应的文件链接到.bin文件目录下

image.png

接下来,我们回到vite.js文件,我们找到下面这个方法,找到对应的执行文件。

function start() {
  require('../dist/node/cli')
}
复制代码

来看一下它的核心代码:

cli.js

const cac = (name = "") => new CAC(name);
const cli = cac('vite');
复制代码

在这个文件执行的时候,会先new 一个CAC对象,这个对象定义了很多方法,在这里不一一赘述了,有兴趣的可以直接查看vite的源码。创建了cli对象后,可以看下如下代码,这些是vite内置的一些指令。我们着重看一下action这个方法。

// dev
cli
    .command('[root]', 'start dev server') // default command
    .alias('serve') // the command is called 'serve' in Vite's API
    .alias('dev') // alias to align with the script name
    .option('--host [host]', `[string] specify hostname`)
    .option('--port <port>', `[number] specify port`)
    .option('--https', `[boolean] use TLS + HTTP/2`)
    .option('--open [path]', `[boolean | string] open browser on startup`)
    .option('--cors', `[boolean] enable CORS`)
    .option('--strictPort', `[boolean] exit if specified port is already in use`)
    .option('--force', `[boolean] force the optimizer to ignore the cache and re-bundle`)
    .action(async (root, options) => {
    // output structure is preserved even after bundling so require()
    // is ok here
    const { createServer } = await Promise.resolve().then(function () { return require('./chunks/dep-8f5c9290.js'); }).then(function (n) { return n.index$1; });
    try {
        const server = await createServer({
            root,
            base: options.base,
            mode: options.mode,
            configFile: options.config,
            logLevel: options.logLevel,
            clearScreen: options.clearScreen,
            server: cleanOptions(options)
        });
        if (!server.httpServer) {
            throw new Error('HTTP server not available');
        }
        await server.listen();
        const info = server.config.logger.info;
        info(index.colors.cyan(`\n  vite v${require('vite/package.json').version}`) +
            index.colors.green(` dev server running at:\n`), {
            clear: !server.config.logger.hasWarned
        });
        server.printUrls();
        // @ts-ignore
        if (global.__vite_start_time) {
            // @ts-ignore
            const startupDuration = perf_hooks.performance.now() - global.__vite_start_time;
            info(`\n  ${index.colors.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n`);
        }
    }
    catch (e) {
        index.createLogger(options.logLevel).error(index.colors.red(`error when starting dev server:\n${e.stack}`), { error: e });
        process.exit(1);
    }
});
复制代码

当执行命令的时候,会调用action的回调函数。可以看到有创建一个server对象。这个创建方法代码有点多,这里只展示一些主要的代码以及主体流程,在后续的文章里面再一一分开阐述。

async function createServer(inlineConfig = {}) {
    // 获取本地项目根路径和本地服务器相关配置
    const config = await resolveConfig(inlineConfig, 'serve', 'development');
    const { root, server: serverConfig } = config;
    
    const httpsOptions = await resolveHttpsConfig(config.server.https, config.cacheDir);
    let { middlewareMode } = serverConfig;
    if (middlewareMode === true) {
        middlewareMode = 'ssr';
    }
    const middlewares = connect();
    // 创建http服务
    const httpServer = middlewareMode
        ? null
        : await resolveHttpServer(serverConfig, middlewares, httpsOptions);
    // 创建 WebSocket 服务器
    const ws = createWebSocketServer(httpServer, config, httpsOptions);
    
    const { ignored = [], ...watchOptions } = serverConfig.watch || {};
    // 通过 chokidar 监听文件
    const watcher = chokidar.watch(path__default.resolve(root), {
        ignored: [
            '**/node_modules/**',
            '**/.git/**',
            ...(Array.isArray(ignored) ? ignored : [ignored])
        ],
        ignoreInitial: true,
        ignorePermissionErrors: true,
        disableGlobbing: true,
        ...watchOptions
    });
    // 创建Vite 的 ModuleGraph 实例
    const moduleGraph = new ModuleGraph((url, ssr) => container.resolveId(url, undefined, { ssr }));
    // 创建插件容器,是一个对象,对象的属性是 vite 支持的 rollup 的钩子函数
    // 比如 options、resolveId、load、transform
    const container = await createPluginContainer(config, moduleGraph, watcher);
    const closeHttpServer = createServerCloseFn(httpServer);
    // eslint-disable-next-line prefer-const
    let exitProcess;
    // 声明 server 对象
    const server = {
        config,// 包含命令行传入的配置 和 配置文件的配置
        middlewares,
        get app() {
            config.logger.warn(`ViteDevServer.app is deprecated. Use ViteDevServer.middlewares instead.`);
            return middlewares;
        },
        httpServer,
        watcher,
        pluginContainer: container,
        ws,
        moduleGraph,
        ssrTransform,
        transformWithEsbuild,
        transformRequest(url, options) {
            return transformRequest(url, server, options);
        },
        transformIndexHtml: null,
        async ssrLoadModule(url, opts) {
            if (!server._ssrExternals) {
                let knownImports = [];
                const optimizedDeps = server._optimizedDeps;
                if (optimizedDeps) {
                    await optimizedDeps.scanProcessing;
                    knownImports = [
                        ...Object.keys(optimizedDeps.metadata.optimized),
                        ...Object.keys(optimizedDeps.metadata.discovered)
                    ];
                }
                server._ssrExternals = resolveSSRExternal(config, knownImports);
            }
            return ssrLoadModule(url, server, undefined, undefined, opts === null || opts === void 0 ? void 0 : opts.fixStacktrace);
        },
        ssrFixStacktrace(e) {
            if (e.stack) {
                const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph);
                rebindErrorStacktrace(e, stacktrace);
            }
        },
        ssrRewriteStacktrace(stack) {
            return ssrRewriteStacktrace(stack, moduleGraph);
        },
        listen(port, isRestart) {
            return startServer(server, port, isRestart);
        },
        async close() {
            process.off('SIGTERM', exitProcess);
            if (!middlewareMode && process.env.CI !== 'true') {
                process.stdin.off('end', exitProcess);
            }
            await Promise.all([
                watcher.close(),
                ws.close(),
                container.close(),
                closeHttpServer()
            ]);
        },
        printUrls() {
            if (httpServer) {
                printCommonServerUrls(httpServer, config.server, config);
            }
            else {
                throw new Error('cannot print server URLs in middleware mode.');
            }
        },
        async restart(forceOptimize) {
            if (!server._restartPromise) {
                server._forceOptimizeOnRestart = !!forceOptimize;
                server._restartPromise = restartServer(server).finally(() => {
                    server._restartPromise = null;
                    server._forceOptimizeOnRestart = false;
                });
            }
            return server._restartPromise;
        },
        _optimizedDeps: null,
        _ssrExternals: null,
        _globImporters: Object.create(null),
        _restartPromise: null,
        _forceOptimizeOnRestart: false,
        _pendingRequests: new Map()
    };
    server.transformIndexHtml = createDevHtmlTransformFn(server);
    exitProcess = async () => {
        try {
            await server.close();
        }
        finally {
            process.exit(0);
        }
    };
    process.once('SIGTERM', exitProcess);
    if (!middlewareMode && 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, true);
    });
    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
    // 执行插件中的 configureServer 钩子函数 
    // configureServer:https://vitejs.cn/guide/api-plugin.html#configureserver
    const postHooks = [];
    for (const plugin of config.plugins) {
        if (plugin.configureServer) {
        // configureServer 可以注册前置中间件,就是在内部中间件之前执行;也可以注册后置中间件 
        // 如果configureServer 返回一个函数,这个函数内部就是注册后置中间件,并将这些函数收集到 postHooks 中
            postHooks.push(await plugin.configureServer(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(corsMiddleware(typeof cors === 'boolean' ? {} : cors));
    }
    // proxy
    const { proxy } = serverConfig;
    if (proxy) {
        middlewares.use(proxyMiddleware(httpServer, proxy, config));
    }
    // base
    // 注册中间件
    if (config.base !== '/') {
        middlewares.use(baseMiddleware(server));
    }
    // open in editor support
    middlewares.use('/__open-in-editor', launchEditorMiddleware());
    // hmr reconnect ping
    // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
    middlewares.use('/__vite_ping', function viteHMRPingMiddleware(_, res) {
        res.end('pong');
    });
    // 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));
    }
    // main transform middleware
    // 主要转换中间件
    middlewares.use(transformMiddleware(server));
    // serve static files
    
    middlewares.use(serveRawFsMiddleware(server));
    middlewares.use(serveStaticMiddleware(root, server));
    // spa fallback
    // 如果请求路径是 /结尾,则将路径修改为 /index.html ??
    if (!middlewareMode || middlewareMode === 'html') {
        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 (!middlewareMode || middlewareMode === 'html') {
    // 如果请求的url是 html 则调用插件中所有的 transformIndexHtml 钩子函数,转换html,并将转换后的 html 代码发送给客户端
        // 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));
    const initOptimizer = () => {
        if (!config.optimizeDeps.disabled) {
            server._optimizedDeps = createOptimizedDeps(server);
        }
    };
    if (!middlewareMode && httpServer) {
        let isOptimized = false;
        // overwrite listen to init optimizer before server start
        // 重写 httpServer.listen,在服务器启动前预构建
        const listen = httpServer.listen.bind(httpServer);
        httpServer.listen = (async (port, ...args) => {
            if (!isOptimized) {
                try {
                    await container.buildStart({});
                    initOptimizer();
                    isOptimized = true;
                }
                catch (e) {
                    httpServer.emit('error', e);
                    return;
                }
            }
            return listen(port, ...args);
        });
    }
    else {
        await container.buildStart({});
        initOptimizer();
    }
    return server;
}
复制代码

总体来说,createServer函数的大体流程如下

  • 获取config配置
  • 创建 http 服务器httpServer ??
  • 创建 WebSocket 服务器ws ??
  • 通过 chokidar 创建监听器watcher
  • 创建一个兼容rollup钩子函数的对象container
  • 创建实例moduleGraph ??
  • 声明server
  • 注册watcher
  • 执行插件中的configureServer钩子函数(注册用户定义的前置中间件),并收集用户定义的后置中间件
  • 注册中间件
  • 注册用户定义的后置中间件
  • 注册转换html文件的中间件和未找到文件的404中间件
  • 重写 httpServer.listen

大致内容就是这些啦,还有超多不懂的地方,欢迎大佬来指导哦!!

最后,给自己留个作业吧!欢迎大家和我一起来探讨呀!

问答

Vite 冷启动为什么快?相对于 Webpack,vite的优势在哪里?

vite是如何获取配置文件的?

猜你喜欢

转载自juejin.im/post/7110036696116396045
今日推荐