😀 管吻关注,先期上新极好过~
责任编辑为 Vue Conf 2021 撷取文本。
超链接:LOVELESS,Vue Core Team 核心成员,目前供职于 二进制颤动 Web Infra 项目组
1. 大背景
在 ESM 出现以后,由于应用领域程序缺乏 JS 组件化的监督机制和网页读取操控性的难题,合作开发人员单厢装箱来构筑 Web App。前夕 Webpack 等装箱辅助工具快速盛行在街道社区,被广为采用在工程项目中。但,随着工程项目的保护工程项目外部的 JS 组件愈来愈多,这些装箱辅助工具在合作开发时碰到了操控性困局。坚信我们多多少少的都有新体验,在 Webpack 的大型工程项目中须要等极短天数就能开启 Dev Server,预览文档之后也须要历经许多天数网页就能展示出新一代的更动,这十分减少了合作开发人员的合作开发工作效率和新体验。Vite 便是为了提高合作开发人员的合作开发新体验而合作开发的辅助工具,保有全速的服务工程项目开启和高性能加速的热预览,2.0 正式发布以来愈来愈多的使用者已经开始采用Vite[1]。本文会对 Vite 进行探究,让我们对于 Vite 更为介绍,在合作开发采用时更为游刃有余。
2. 如前所述 ESM 的 Dev Server + HMR
具体来说,我们对照呵呵 vite 与 webpack 的开启一个 vue hello world 天数。
服务工程项目开启费时 (ms)网页读取费时 (ms)Webpack934203Vite368231从下面的范例中能看见 Webpack 开启服务工程项目的天数极短许多,但网页读取操控性是好于 vite 的。
2.1 Webpack(Bundle-Based Dev Server) 如何工作的呢?
从图中能看见 Webpack dev server 的开启方式:
从 entry 已经开始预测倚赖,bundle 倚赖 (操控性困局),同时将出口处文档转化成到 index.html 中开启 Webpack-dev-server,等候应用领域程序出访 难题很明显:Dev Server 必须等候所有组件构筑顺利完成,应用领域越大,开启天数越长新溪洲的组件也要构筑2.2 那 Vite dev server 是如何提高操控性的呢?
ESM 是 ES6 引入的组件化能力,现已经被主流应用领域程序支持,当 import 组件时,应用领域程序就会下载被导入的组件。
Vite 的 Dev server 如前所述应用领域程序原生支持 ESM 的能力实现的,因此不须要通过 Bundler 即可读取 JS 组件,但要求使用者的代码必须是 ESM 组件,而且须要在 index.html 中采用 <script type=”module” src=”./main.js”/> 来引入组件
从图中能看见 Vite 的开启方式:
不历经 Bundle,直接开启 Dev Server等候应用领域程序出访文档,当请求文档时进行对文档那边进行转换返回给应用领域程序 (操控性困局)2.3 Vite dev server 避免了 Bundle 的操控性难题,但也有许多新的难题:
文档 transform 操控性
组件转换时尽可能采用操控性高的辅助工具缓存 transform 结果非 ESM 组件兼容 (TS/JSX …)
将非 ESM 组件转换成 ESM,依靠文档类型来辨别组件类型用 esbuild 转换 TS/JSX,代替 TSC/BabelBroswer ESM 不能读取 Node 组件
采用 es-module-lexer 扫描 import 语法magic-string 重写 Node 组件的引入路径,如下图Node 组件其他难题
Node CJS 组件兼容Node 组件一般文档数量较多,如果直接读取,一个文档会产生一个请求,导致网页读取操控性减少2.4 为介绍决 Node 组件的难题,Vite 引入 Pre-Bundle Node 组件方案:
在工程项目开启前,扫描工程项目内的所采用的 node 组件,将 node 组件装箱成单个文档,这个操作是费时的,但由于 Node 组件有自己的版本,能将其写入硬盘,下次开启时如果版本匹配能跳过 Pre-Bundle 采用硬盘缓存的结果。另外,Pre-Bunde 会生成组件的元信息,通过识别引入的组件并对其进行转换,支持了 CJS 组件的 Named Import。如下图
采用辅助工具:
v1: Rollup + @rollup/plugin-commonjs @rollup/plugin-commonjs 的方案是将 cjs 代码直接转为 esm,cjs 组件的导入导出方式过于动态 而且 cjs 循环引用难题(19.0.0 版本修复)导致装箱成 ESM 失败v2: esbuild 其支持 cjs 的方案是生成 helper 函数,兼容性好那应该怎么识别 Node 组件进行 Pre-Bundle 呢,vite 支持了使用者自己配置和自动倚赖扫描的功能。自动倚赖扫描是扫描使用者全部代码并识别其中引入的 node 组件,由于 esbuild 的装箱操控性是 rollup 的 10-100 倍,操控性也没有造成下降。
2.5 ESM HMR
Vite ESM HMR API 借鉴于 Webpack HMR API,当某个组件发生变化时,不用刷新网页就能预览对应的组件。
具体来说看个 Vite HMR 采用的范例
import foo from ./foo.jsfoo()
if (import.meta.hot) {
import.meta.hot.accept(./foo.js,(newFoo) => {
newFoo.foo()
})
}
转换后的结果
import { createHotContext as__vite__createHotContext }from “/@vite/client”;
import.meta.hot = __vite__createHotContext(“/hmrDep.js”);
import foo form /foo.jsfoo()
if (import.meta.hot) {
import.meta.hot.accept(“/foo.js”,(newFoo) => {
newFoo.foo()
})
}
具体来说介绍一个概念 boundary 代表接受预览的组件,如范例中引入 ./foo.js 的组件
import.meta[2] 是一个给 JavaScript 组件暴露特定上下文的元数据属性的对象元信息的方式。
能看见通过转化成 helper 函数,给组件引入了 import.meta.hot API,这个 API 会在应用领域程序运行时记录 boundary 与组件直接的映射关系(包含预览执行的回调函数),当 ws 接收到某个组件预览信息(boundary 和 发生预览的组件)时,会发起对预览组件的读取,并且会根据组件的预览信息从映射关系中查找到预览须要执行的回调函数,执行并传入预览后的组件。这样就能无需刷新网页就能预览 JS 组件了。
那 vite 的 HMR 怎么工作的呢?
构筑组件倚赖图 当一个文档请求时,Vite 会扫描其中的 import 语法,记录组件之间的倚赖关系同时如果发现文档引用了 import.meta.hot 时会转化成 helper 函数,并且组件中含有 import.meta.hot.accept 的调用则将组件标记成 boundary当文档变更时,依据模块倚赖图寻找 boundaries发送 websocket 消息到应用领域程序端,应用领域程序会重新读取变更组件并执行预览如果没有查找到 boundaries, 网页则会重新读取支持了 ESM Dev Server,但并不能直接用于生产环境,为了在生产环境获得更好的读取操控性,还须要 生产构筑,对代码进行体积优化(tree-shaking,minify)、chunk 合并分割等等。
3. 如前所述 Rollup 的 Bundle 和 Plugins
由于已有的 Bundler 很成熟而且有良好的生态, vite 选择在他们的基础上进行使用者代码的 Bundle。那如何选择呢?Rollup 同样如前所述 ESM ,而且其灵活的 Plugin API 和体积更小、运行速度更快的构筑产物显然更为合适。但由于其对于 Web App 的支持度较低,而且配置复杂,十分不利于使用者的采用。为此,Vite 内置了合作开发 Web App 常用的 Plugins,尽可能让使用者能零配置的采用。
TS/JSXPostCSS / CSS Modules / CSS Pre-processorsAssetsJSONWeb WorkerModule Resolver / Module Alias / Module Glob Import…对于 Framwork 的支持,Vite 官方集成了 Vue3(@vitejs/plugin-vue[3]) 、React (@vitejs/plugin-react-refresh[4]),而且提供了开箱即用的模版供使用者选择。
另外,Vite Plugins 继承了 Rollup Plugins API,并进行了许多拓展(ssr、hmr 支持等),这样使用者能利用街道社区内已有的 Rollup Plugins,或者合作开发 Plugins 满足自己不同场景的需求。由于 Dev 是以文档为单位单独 transfrom,Bundle 是以工程项目为单位构筑,造成了 dev 和 Bundle 有一定的差异。为了减少这种差异性,Vite 受 WMR 启发,通过 PluginContainer 模拟了 Bundle 时 Plugns 的行为,支持了在 Dev 环境中运行 Plugins。
对跑在应用领域程序内的 Web App 进行了新体验优化,那能不能更进一步对跑在 Node 中的 Web App 进行优化呢?
4. SSR
SSR 是支持在 Node.js 中运行相同应用领域程序的前端框架(例如 React、Vue 等),将其预渲染成 HTML 返回客户端,用于解决 SPA 的 SEO 和 首屏操控性难题。
和合作开发 Web App 一样,SSR 的合作开发环境也须要 Bundle,同样也有相同的操控性难题。那如何解决呢?ESM Based Dev Server 依靠了应用领域程序原生支持了 ESM,那 Node.js 是否能支持原生读取 ESM 呢?答案是肯定的,Node.js 在 12.22.0 版本支持了标准的 ESM 实现,提供了Loaders API[5],但仍是实验性质的而且须要 –experimental-loader 开启。为此,Vite 通过转换 ESM 组件导入导出相关的语法,并提供相应了 API,实现了自己的 Node ESM 组件读取器,而且复用了 Dev Server PluginContainer 支持了 Plugins + HMR。以下是许多实现/采用细节:
由于 Vite 实现 Node ESM 组件读取器 ,使用者须要通过 ssrLoadModule API 来导入出口处组件,此方法会递归读取子组件并执行。采用 estree + magic-string 转换 ESM 组件,如下图 import 语法会转换成vite_ssr_import API。另外,由于运行在 Node.js 环境中,Node CJS 组件的读取能不历经转换,直接 require 执行。参考资料
[1]Vite: https://www.npmtrends.com/vite
[2]import.meta: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import.meta
[3]@vitejs/plugin-vue: https://github.com/vitejs/vite/tree/main/packages/plugin-vue
[4]@vitejs/plugin-react-refresh: https://github.com/vitejs/vite/tree/main/packages/plugin-react-refresh
[5]Loaders API: https://nodejs.org/api/esm.html#esm_loaders