vite 的实现浅析
vite 的实现思想
让我们来考虑下,浏览器已经普遍支持 esm 的情况下,为什么还需要 webpack、rollup、vite 这些打包工具。原因是 esm 的功能不满足我们的需求,比如说裸导入、导入 css、tree sharking、typescript、es678 等等。拿 webpack 处理导入css 举例来说,webpack 识别到`import 'raw.css';`这段代码时,会读取 raw.css 样式文件并转换为对应 js 代码。这个 js 文件在浏览器中执行,将 css 样式创建为 style 标签并插入到文档中。如果是 esm 执行到这段代码,会报错说我只想要一个 js 模块脚本,你个的 css 文件我执行不了。
和 webpack、rollup 相比,vite有什么优势呢?在 vite 官网上给的答案是,为了解决开发服务器启动缓慢和热更新缓慢的问题,提升开发效率和开发幸福感!拿 webpack 来说,开发服务器启动时,webpack 会通过入口文件,解析出所有依赖的资源,并执行这些资源的转义操作,构建出整个应用后才能提供服务。当一个项目包含上千个模块时,一次构建出整个应用的模式就遇到性能瓶颈了。vite 的做法是抛弃打包过程,按需构建,前端请求到哪个模块,再去构建这个模块。并且 vite 还使用了 esbuild 和预构建第三方库的优化手段,进一步提升按需构建的速度。
vite 的实现
开发服务器的启动流程
vite 的开发服务器使用 http + connect 实现,主要代码如下
import connect from 'connect';
import http from 'http';
// 请求中间件
const middlewares = connect();
// 开发服务器
const httpServer = http.createServer(middlewares);
// 添加各种中间件
// middlewares.use(xxx);
// 启动服务器
httpServer.listen(port, () => {
console.log('http://localhost:' + port);
connect 是一个 node 中间件(middleware)框架,起源与 express。具体用法可以看 https:// github.com/senchalabs/c onnec 。vite 中有几个比较重要的中间件,可以在源码的`src/node/server/middlewares/`目录下找到
baseMiddleware
baseMiddleware 用来处理配置项中的 base 选项(开发或生产环境服务的公共基础路径)。我们的代码在部署到生产环境时,一般会部署到某一个特定的目录下,这时访问请求的 url 会带上这个特定目录的前缀,也就是这里的 base 选项。baseMiddleware 会删除 base 前缀。
servePublicMiddleware
响应 public 文件夹下的请求。vite 默认会将 public 文件夹下的文件当作项目根目录下的文件,且不做任何转义处理。
transformMiddleware
transformMiddleware 用来转义处理 js、ts、css、png 等资源文件,这是 vite 最核心的代码实现。在这个中间件中会执行 resolveId、load、transform 这三个钩子函数。如果我们想对代码做些什么,可以通过写插件的方式实现这三个钩子。
serveStaticMiddleware
serveStaticMiddleware 用来响应不需要转义的的资源文件请求,比如页面中的 img 标签发出的图片请求。
indexHtmlMiddleware
indexHtmlMiddleware 用来处理 html 文件请求。其内部会调用所有插件的 transformIndexHtml 钩子。vite 内部实现了一个叫 devHtmlHook 的 transformIndexHtml 钩子,用来遍历 html 节点,将元素的 src 属性添加上 base 前缀,将内联脚本转换为网络请求。会被重写的节点属性如下
{
link: ['href'],
video: ['src', 'poster'],