vite createServer 探索

相信体验过 vite 的小伙伴都知道,vite 带给我们的开发体验真的是非常的流畅,甚至你感觉不到他的存在。

vite 解决了什么问题

从表面上看,vite 解决的是速度的问题,一个字就是 “快”。而大背景是:浏览器开始原生支持 ES 模块,意味着网站可以分模块加载。
我们都知道 vite 只是在开发阶段速度非常快,生产打包阶段是使用的 rollup 进行打包,生产打包和我们平时的打包速度是差不多的。
vite 的官网上可以知道,vite 是通过在一开始将应用中的模块区分为 依赖源码 两类,改进了开发服务器启动时间。

快的原因

  1. esbuildbabel

    相信很多人都知道 esbuild 的这个打包工具,使用 Go 语言编写的,他们官网有个图,展示了他的构建速度,大概是 webpack5 的100倍左右。那咱就说这么好的构建工具,为啥代替不了 babel 呢 ? 他还是有一些缺点的。总的来说就是这个工具还在不断的完善中,目前在代码分割和CSS 处理方面的处理还是比较差。vite 的官网也说了:“当未来这些功能稳定后,我们也不排除使用 esbuild 作为生产构建器的可能”。总的来说还是很值得期待的。

  2. 代码模块拆分

    代码模块拆分其实大致就是类似基于路由懒加载的这种感觉,我们开发的时候,并不是对所有的源代码进行修改,正常就是显示那个页面,改哪个页面,也就是说,如果浏览器不显示的页面,即使你改了这部分的源代码,我也可以不用加载他。我只需要更新加载目前打开这个页面,所需要的模块就行了。在 vite 的官网上有这样一个图:
    在这里插入图片描述

  3. 缓存

    在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活[1](大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
    Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。(31536000 = 24 * 365 * 3600)

源码学习

vite 源码中 packages/vite/src/node/cli.ts 这个文件里头,可以看到
在这里插入图片描述
我们就转到 packages/vite/src/node/server/index.ts

在这里插入图片描述

export async function createServer(inlineConfig = {}) {
    const config = await resolveConfig(inlineConfig, 'serve', 'development')
    const { root, server } = config
    const httpsOptions = await resolveHttpsConfig(
      config.server.https,
      config.cacheDir
    )
    const { middlewareMode } = serverConfig
  
    const middlewares = connect() as Connect.Server
    // 1. 创建 httpServer
    const httpServer = middlewareMode
      ? null
      : await resolveHttpServer(serverConfig, middlewares, httpsOptions)
    // 2. 创建 WebSocketServer
    const ws = createWebSocketServer(httpServer, config, httpsOptions)
  
    const { ignored = [], ...watchOptions } = serverConfig.watch || {}
    // 3. chokidar:监听文件变化的库
    const watcher = chokidar.watch(path.resolve(root), {
      ignored: [
        '**/node_modules/**',
        '**/.git/**',
        ...(Array.isArray(ignored) ? ignored : [ignored])
      ],
      ignoreInitial: true,
      ignorePermissionErrors: true,
      disableGlobbing: true,
      ...watchOptions
    }) as FSWatcher
  
    // ModuleGraph: 记录每个模块之间的关系
    const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
      container.resolveId(url, undefined, { ssr })
    )
    // PluginContainer
    const container = await createPluginContainer(config, moduleGraph, watcher)
  
    const server = {
      config,
      middlewares,
      httpServer,
      watcher,
      pluginContainer: container,
      ws,
      moduleGraph,
      ssrTransform() {},
      transformRequest() {},
      transformIndexHtml: null!, // to be immediately set
      async ssrLoadModule() {},
      ssrFixStacktrace(e) {},
      ssrRewriteStacktrace(stack: string) {},
      listen() {},
      async close() {},
      async printUrls() {},
      async restart() {},
  
      _ssrExternals: null,
      _restartPromise: null,
      _importGlobMap: new Map(),
      _forceOptimizeOnRestart: false,
      _pendingRequests: new Map()
    }
  
    // transformIndexHtml 插件
    server.transformIndexHtml = createDevHtmlTransformFn(server)
  
    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(file)
      // invalidate module graph cache on file change
      moduleGraph.onFileChange(file)
      if (serverConfig.hmr !== false) {
		await handleHMRUpdate(file, server)
      }
    })
  
	// 新建文件
    watcher.on('add', (file) => {
      handleFileAddUnlink(normalizePath(file), server)
    })
		// 卸载依赖
    watcher.on('unlink', (file) => {
      handleFileAddUnlink(normalizePath(file), server)
    })
  
    if (!middlewareMode && httpServer) {
      httpServer.once('listening', () => {
        // update actual port since this may be different from initial value
        serverConfig.port = (httpServer.address() as net.AddressInfo).port
      })
    }
  
    // apply server configuration hooks from plugins
    const postHooks: ((() => void) | void)[] = []
    for (const plugin of config.plugins) {
      if (plugin.configureServer) {
        postHooks.push(await plugin.configureServer(server))
      }
    }
  
    // 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())
  
    const initOptimizer = async () => {
      if (isDepsOptimizerEnabled(config)) {
        await initDepsOptimizer(config, server)
      }
    }
  
    if (!middlewareMode && httpServer) {
      let isOptimized = false
      // overwrite listen to init optimizer before server start
      const listen = httpServer.listen.bind(httpServer)
      httpServer.listen = (async (port: number, ...args: any[]) => {
        if (!isOptimized) {
          await container.buildStart({})
          await initOptimizer()
          isOptimized = true
        }
        return listen(port, ...args)
      }) as any
    } else {
      await container.buildStart({})
      await initOptimizer()
    }
    return server
  }

去掉了一些实现的细节,是不是看着就清晰很多了呢?然后再梳理一下这个 createServer 的流程

  1. 创建 httpServerwebSocketServer 都是 用于后面构建 server,属于server 中的配置
  2. 创建 watcher 用于监听文件模块与依赖的变化
  3. 创建 moduleGraph 记录每个模块之间的关系
  4. 创建 container 用于处理 plugin
  5. postHooks 处理模块中的 hook
  6. 最后就返回一个完成的 server 对象

不知道大家会不会有这么一种感觉,就是看了和没看差不多,都说处理处理,那道理怎么处理呢?我对新的事物的态度,最开始都是了解,应该先去理解他的大致流程,具体实现细节,如果需要的话,再去研究。如果一开始就一头扎进细节里头去,很容易给自己整蒙圈了,也容易给自己带来挫败感,让自己放弃。如果后面还有继续学习里头的细节的话,x再来做记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值