vite 为什么快

一、ES module 减少服务启动时间

import { foo } from './other-module'

由于大多数现代浏览器都支持上面的 ES module 语法,所以在开发阶段,我们就不必对其进行打包,这节省了大量的服务启动时间。另外,vite 按需加载当前页面所需文件,一个文件一个http请求,进一步减少启动时间。

二、缓存减少页面更新时间

每个文件通过 http 头缓存在浏览器端,当编辑完一个文件,只需让此文件缓存失效。当基于 ES module 进行热更新时,仅需更新失效的模块,这使得更新时间不随包的增大而增大。

vite 怎么做到的?

当我们执行yarn dev时,vite-cli 会在本地启动一个 koa 服务:

export function createServer(config) {
  ...
  const app = new Koa()
  const server = resolveServer(config, app.callback())
  ...
  // 加载插件
  ...

  const listen = server.listen.bind(server)
  server.listen = (async (port, ...args) => {
    if (optimizeDeps.auto !== false) {
      await require('../optimizer').optimizeDeps(config)
    }
    return listen(port, ...args)
  })
  ...

  return server;
}

监听端口,执行其他服务之前,会执行optimizeDeps方法,即优化依赖。vite 文档将这部分优化叫做依赖预打包Dependency Pre-Bundling,这么做的理由有两个:一是将非 ES module转化为可被浏览器导入的 ESM;二是将 ESM 依赖的多个内部模块转化为一个模块,以减少浏览器请求从而提升页面加载速度。

按需加载

上面我们提到,vite通过按需加载减少等待时间,这是如何做到的?所谓一生二,二生三,三生万物。一切的根源就是服务启动后,我们访问的初始地址http://localhost:3000/。首页,浏览器根据这个 url 发出第一个请求,通过serverPluginServeStatic.ts插件获取到位于项目根目录的index.html文件,再通过serverPluginHtml.ts向其插入<script type="module">import "/vite/client"</script>,最终我们看到第一个请求返回的内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <script type="module">import "/vite/client"</script>

  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vite App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

我们先看<script type="module" src="/src/main.js"></script>,浏览器向我们的本地服务器请求这个文件,koa 插件serverPluginModuleRewrite.ts会重写main.js源文件的 import 的解析方式:

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

上面的内容经过插件处理后,将以下内容返回给浏览器:

import { createApp } from '/@modules/vue.js'
import App from '/src/App.vue'
import '/src/index.css?import'

这里文档npm-dependency-resolving也有部分提及。浏览器解析此文件,依次发送三个请求:

当浏览器发送第二个请求,koa 插件serverPluginVue.ts会编译单文件组件 App.vue,并打上 etag(用于缓存)后再返回给浏览器(A流程)。浏览器再解析此文件并更新页面,重复这个过程(发送请求、服务端响应、浏览器解析)直到不再发送请求。

组件热更新

vue 组件热更新,由serverPluginVue.tsserverPluginHmr.tsclient.ts三个部分共同完成。其中client.ts会被发送到浏览器端,位于服务端的 serverPluginHmr.ts 通过 websocket 和 client.ts 进行通信。

我们以vite-cli默认模板项目举例,假设更改了App.vue文件内容,此时:

  • 1)serverPluginVue.ts 使用 serverPluginHmr.ts 提供的 send 方法向 client.ts 所在的浏览器端发送数据,即数据通过 websockt 从服务端推送到浏览器,数据内容如下:

    {
      "type": "vue-reload",
      "path": "/src/App.vue",
      "changeSrcPath": "/src/App.vue",
      "timestamp": 1609815863830
    }
    
  • 2) client.ts 收到数据后,执行import('/src/App.vue?t=1609815863830')

  • 3)接着浏览器发起请求:http://localhost:3000/src/App.vue?t=1609815863830,时间戳的作用是使url缓存失效

  • 4)最后,执行重复上面的A流程。
    经过上述步骤,页面得以更新。当然,更新的类型还有full-reloadjs-update以及样式更新等,但总体流程都框定在以上步骤,这里就不展开了。

p.s.以上内容搭配vite源码再看更易理解

posted @ 2021-01-05 11:40  Liaofy  阅读(2243)  评论(0编辑  收藏  举报