相信随着尤雨溪卖力的吆呼,不少前端的小伙伴已经对 vite 有所耳熟了,这是一个伴随着着 Vue3.0 发布的下一代构建工具,其名字就是出自法语单 fast。可以看到尤雨溪近期的主要工作全部是在 vite 上,足以说明其重要之处
在这篇文章中,我们就来看看 vite 到底有何独到之处。
天下苦 webpack 久矣
在前端这块混,肯定不能没有听说过 webpack,现代的大型前端应用一般都是用这玩意构建的,但是随着项目的增大,大家发现了 webpack 几个「难言之隐」
- 随着项目大小增长,项目冷启动时间指数增长
- 热更新时间也会随着项目大小增大而增长
所以才有了我们常戏称的:npm run dev
,然后去上个厕所,喝杯咖啡,回来可能还没跑完,极大的影响了开发效率。虽然 webpack 提供了很多方法去做构建优化,在日益庞大的前端项目中仍然不太够用,能不能有一种方式一劳永逸的解决这个问题呢?答案就是 Vite !
Vite 横空出世
vite 是如何解决这个世纪难题的呢,答案是 native ES modules。在浏览器没有原生模块化支持的时代,我们往往需要通过 webpack 等构建工具将整个项目打包成一个 js 文件,方便浏览器进行调用。但是随着浏览器厂商的不断努力,现代浏览器基本已经全部支持了 import/ export 语法,下图可以看到具体的浏览器兼容情况。
大部分浏览器都已经可以解析 js 文件中的 import 语句,webpack 的打包过程是不是有点脱了裤子放屁的感觉了。
如果要一个字概括 vite 的话,那就是快,这是尤大在 vite PPT 中列出来的数据
- 服务启动时间 < 300ms
- 模块热替换时间(HMR) < 100ms
从数据上来看确实是吊打 Webpack,我们来看看它是如何做的
为什么 Webpack 这么慢
前文也提到,在之前的浏览器中没有模块化的设计,所以期望把所有源代码编译进一个 js 文件中提供给浏览器使用,所以在开发中当我们运行启动命令的时候,webpack 总是需要从入口文件去索引整个项目的文件,编译成一个或多个单独的 js 文件,即使采用了代码拆分,也需要一次生成所有路由下的编译后文件(这也是为什么代码拆分对开发模式性能没有帮助)。这也导致了服务启动时间随着项目复杂度而指数增长
Vite 是如何解决问题的
vite 是如何通过使用 native ES modules 优化服务启动时间的呢,使用 Vite 启动开发服务器的时候并不需要提前编译文件(其实是有一个类似过程的,下文详述),而是在浏览器请求对应 URL 的时候,再提供对应的文件,这就实现了在使用了路由懒加载的项目中,仅提供对应路由下的模块的编译文件,而没有索引全部代码的这一过程,项目启动时间始终为常量级。并不会随着项目复杂度变高而一直增长,我们来看看具体是怎么做的
Vite 是如何实现的
依赖预构建
对 native ES modules 了解的同学可能会想到,它是不支持如下的裸模块导入的,那咋办?
import { someMethod } from 'my-dep'
我们来看看 Vite 是如何实现的,可以直接打开使用 yarn create @vitejs/app
创建的基于 react+ts 的项目这是src/main.tsx 的代码
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode> <App /> </React.StrictMode>,
document.getElementById('root')
)
然后把项目跑起来,这是经过Vite 编译后的 src/main.tsx 文件
可以看到之前的 from 'react'
被重写为了 from "/node_modules/.vite/react.js?v=432aac16"
看起来 Vite 会将预构建的依赖缓存到 node_modules/.vite
路径下,可以看到文件名后跟着一串随机字符串,很容易就可以想到是用来控制浏览器缓存相关的,让我们打开 react.js?v=432aac16
这个文件的请求头看一看:
果不其然,Cache-Control 属性被写为了: max-age=31536000,immutable
,将这个文件设置为了永久的强制缓存,也就是永远从本地取文件,然后通过向文件名中添加 hash 值控制版本更新。这样一来将依赖文件的缓存判断交给了浏览器老大哥,减少了 Vite 端的工作量,实在是妙啊。
你以为这就完了吗,其实并没有,打开 react.js?v=432aac16
文件一看
这是什么玩意? 为什么不是熟悉的 react 好兄弟,Vite 还偷偷的干了什么? 没错,在这一步 Vite 并不是只是简单的重写了一下路径。在服务启动的时候,vite 将会在所有源代码中检查类似 import { someMethod } from ‘my-dep’ 的裸模块导入语句,并执行以下操作
- 预构建
- 重写为合法的 URL
其中「预构建」就是上文提到过的「提前编译文件」,在项目启动之初,Vite 会使用 esbuild 进行「依赖预构建」,有两个目的
1.CommonJS 和 UMD 兼容性 在开发阶段中, Vite 的开发服务器将所有的代码都视为原生 ES 模块,所以需要在预构建阶段先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
2.优化加载性能 Vite将带有许多内部模块的ESM依赖转换为单个模块,以提高后续页面加载性能(降低请求数量),比如 lodash-es 有超过 600 个内置模块,一次性发送 600 多个 http 请求,就算是采用了 HTTP2 也是不可接受的,大量的网络请求在浏览器端会造成网络拥塞,导致页面的加载速度相当慢,通过预构建 lodash-es 成为一个模块,就只需要一个 HTTP 请求了!
关于「依赖预构建」更多详细信息可以参考官方文档。
预构建完依赖项之后,再使用 es-module-lexer + magic-string 进行轻量级裸模块导入语句的重写。因为并没有进行完整的 AST 遍历,所以速度非常快,对于大多数文件来说这个时间都小于 1ms !
文件编译
解决完裸模块导入的问题,就能愉快的开发了吗,其实并没有。注意到我们的项目是使用的 TypeScript+React 构建的,浏览器是没办法直接解析 tsx 的,老办法,直接看看编译后的文件 没错看到了熟悉的 React.createElement,熟悉 webpack 的同学应该已经马上想到了,这不是 babel-loader 的工作吗,在 Vite 中是并没有 loader 功能,是如何实现的呢,答案就是 esbuild,Vite 使用 esbuild 作为部分文件类型的解析器(如 TSX & TypeScript),Vite 并不会与 webpack 一样,提前将所有文件编译为浏览器可以接受的类型,而是在接收到浏览器发起的 http 请求之后再去编译对应文件,提供给浏览器。这样会有一个非常合理的疑问,速度够么?
每一次页面加载都需要编译一次文件,看起来会对加载速度有影响啊?这就不得不再次提到 Vite 所使用的构建工具 esbuild,是一个使用 Go 编写的构建工具,按照官网描述,速度比现有的构建工具(webpack 不是在说你)快了10-100倍。其官网有更多详细性能测试数据
为什么 esbuild 这么快?
为什么同样是构建工具,esbuild 这么优秀?具体有以下几点(来自esbuild 官网)
1. 使用 Go 编写,并且编译成了机器码
现在的构建工具一般都是用 JavaScript 进行编写的,对于这种解释型语言(动态语言)来说,在命令行下的性能非常糟糕。因为每次运行编译的时候 V8 引擎都是第一次遇见代码,无法进行任何优化措施。而 esbuild 使用 Go 这种编译型语言(静态语言)编写而成,已经编译成了机器可以直接执行的机器码。当 esbuild 在编译你的 javaScript 代码的时候,可能 Node 还在忙着解析你的构建工具的代码。
除此之外,Go 语言是为并发而设计的语言,而 JavaScript 明显不是(老单线程了)。
- Go 在线程之间共享使用内存空间,而 JS 想要在线程间传递数据还需要把数据序列化之后再传送。
- Go 和 JS 的并发都有相应的垃圾回收机制,Go 会在所有线程之间共享堆,对于 JS 而言,每一个线程都有一个独立的堆。
根据 esbuild 的作者的测试,这似乎将 JavaScript 的工作线程的并行处理能力减少了一半,可能是因为你的一半 CPU 核心忙于为另一半收集垃圾
2. 大量使用并行算法
除了 Go 语言天生对于并发的优势,使得其处理并发任务性能远远优于 JavaScript, Esbuild 的内部算法也是经过精心设计的,尽可能的压榨所有的 CPU 核心。
3. esbuild 的所有内容都是从零编写的
自己编写一切而不是使用第三方库有很多性能上的好处。可以从一开始就考虑到性能,可以确保所有的东西都使用一致的数据结构以避免昂贵的转换,当然,缺点就是这工作量非常大。
4. 更有效利用内存
- esbuild 通过减少 AST 遍历次数(三次),来减小内存访问速度对于打包速度的影响
- Go 语言还有一个好处就是可以把数据更加紧凑的储存在内存中,从而使得高速 CPU 缓存可以存下更多的内容
Vite2.0 刚刚发布稳定版,其中就把预构建的工具从 Rollup 换成了 esbuild,性能加快了几十倍。
Vite 功能
了解完一些原理,来看看 Vite 到底能干啥?
模块热重载
Vite 支持 Vue 和 React 的模块热重载,可以准确的更新页面而无需重新加载页面或者删除应用程序状态
TypeScript
Vite 支持开箱即用的引入 .ts 文件,但是值得注意的是 Vite 仅执行 ts 文件的翻译工作,并不执行任何类型检查,这是因为 Vite 使用 esbuild 进行转译工作,而并不含类型信息,所以不支持 TypeScript 的特定功能如「常量枚举」「隐式 type-only 导入」。你必须在你的 tsconfig.json
中的 compilerOptions
里设置 "isolatedModules": true
,这样 TS 才会警告你哪些功能无法与独立编译模式一同工作。
Vue
作为亲兄弟,Vite 自然会为 Vue 提供第一优先级的支持
- Vue 3 单文件组件支持:@vitejs/plugin-vue
- Vue 3 JSX 支持:@vitejs/plugin-vue-jsx
- Vue 2 支持:underfin/vite-plugin-vue2
JSX
前端另一巨头 React 同样不用担心,.jsx 与 .tsx 也是开箱即用。其翻译是通过 ESBuild 进行的,默认为 React16 形式,React17 形式的 JSX 在 esbuild 中的支持请看这里
CSS
导入 .css 文件会把内容插入到 标签中,同样支持热重载。也能够以字符串的形式检索处理后的、作为其模块默认导出的 CSS。 也支持 「@import 内联」「PostCSS」「CSS Modules」「CSS 预处理器」,详细内容可以看文档
限于篇幅只是简单的列举了几种常用的文件类型的支持,其实 Vite 还支持很多文件类型如「JSON」「Web Assembly」「Web Worker」,可以去官方文档查看更多
写在最后
关于 Vite 其实还有很多想写的,比如 生产环境下的优化,服务端渲染等,但是限于时间与篇幅与能力,只能遗憾的留给下一篇文章了。不会鸽太久的!
参考资料
今天的文章Vite2.0 正式发布,凭什么这么快 ?分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/13842.html