一、vite中的预编译
1. 预编译概念介绍
Vite
,一个由Vue.js
开发者尤雨溪开发的新型前端构建工具,主要利用了现代浏览器支持的ESM
(ES模块)来进行快速开发。Vite
在法语中意为“快”,其中最大的亮点就是其开发服务器启动的速度,能够在几乎看不到的时间内完成启动,这主要得益于两大关键性的技术:模块热更新(HMR
)和预编译(Pre-Bundling
)。
预编译,也就是预构建(Pre-Bundling
)。在传统的打包过程中(比如使用Webpack
),所有的模块都会被构建到一个文件中去,这个过程就是所谓的“打包(Bundling
)”。在Vite
中,引入了“预编译”的这么一个概念,Vite
会在服务器启动时对所有的依赖进行一次性的编译,并将其缓存起来。在后续的模块导入的时候,就不需要再对这些模块进行编译,提高了加载效率。
在node_modules/.vite
中的文件就是vite
的预编译文件。
Vite
的预编译主要针对那些大型的、复杂的第三方库,对把它们转换成可以直接在浏览器运行的ES
模块。这样做主要有以下优化效果:
- 加快了开发过程中的加载速度。因为在开发模式下,运行
Vite
的应用,会请求大量的小模块,但是对于一些大型的库(比如Vue、React
等),如果按照模块化的方式加载明显不合理,会造成请求过多。通过预编译,Vite
将这些库提前编译到一个文件里,从而减少请求的开销。 - 加快了构建速度。因为预编译阶段已经预先处理了依赖的模块,所以在最终打包构建的时候,只需要处理自身的源代码逻辑,无需再额外处理依赖模块,以此提高构建速度。
- 改善了兼容性问题。由于很多第三方模块可能包含一些现在浏览器所不支持的代码,比如
JSX
,通过预编译,Vite
将这些代码转换成浏览器可以直接运行的ES
模块,解决了兼容性问题。 - 更好地实现了按需加载和代码拆分。预编译将大库拆分成多个小模块,可以按需加载,无需加载整个库,从而优化了性能。
举例来说,假设一个前端项目用到了vue、vant、axios
等多个库,在没有使用预编译的情况下,每次启动项目时都需要去加载和解析这些库,耗时较长。而使用了Vite
的预编译功能后,这些库可以在项目启动前就已经编译完成,后续只需要引用即可,大大提高了前端项目的启动速度和运行效率。
2. 配置预编译选项
在 Vite 项目中,我们在 vite.config.js
文件中配置 optimizeDeps
选项来实现对第三方库预编译的控制。optimizeDeps
选项允许手动设置需要预构建的依赖。exclude
属性用于排除某些不需要预编译的依赖,include
属性用于添加需要预编译的依赖。
以下是一个基本的配置示例:
// vite.config.js
module.exports = {optimizeDeps: {include: ['lodash'], // 将 lodash 加入预编译exclude: ['moment'] // 将 moment 移除预编译}
}
在上面的示例中,lodash
将会被预编译,而 moment
将不会被预编译。
注意:开发依赖和已知不需要编译的依赖会自动被 Vite 排除掉。如果遇到有问题的依赖预编译,可以使用 exclude
来手动排除。而对于 Vite 默认没有预编译,但你需要其预编译的依赖,可以使用 include
来手动添加。
3. vite和webpack目前在预编译阶段的对比
功能/框架 | Vite | Webpack |
---|---|---|
预编译 | Vite 使用 esbuild 进行预编译,因为 esbuild 是用 Go 语言写的,所以预编译的速度比 webpack 快很多。但是 esbuild 的兼容性和插件系统不如 webpack 完善 | Webpack 使用 babel 作为默认的预处理工具,与 esbuild 相比较,速度慢很多。但是 webpack 的社区更加活跃,有很多插件可以使用,并且兼容性更好。 |
Tree Sharking | Vite使用ES模块导入进行tree shaking,可以直接消除无用代码,效率更高。更好的支持动态导入(import())和CSS导入。 | Webpack的tree shaking需要在生产模式下才能执行,而且不支持动态导入和CSS导入的tree shaking,可能会保留无用代码。 |
缓存机制 | Vite在开发模式下没有使用缓存,但在生产模式下使用Rollup打包时,会进行缓存以优化构建速度。Vite的缓存机制更侧重于模块的热更新,并且,支持服务器端的模块缓存。 | Webpack使用硬盘缓存,初次构建慢,但是再次构建会从缓存中加载模块,从而提高构建速度。但在大型项目中,如果不合理配置,缓存可能会导致内存飙升。 |
优点 | 构建速度快,开发体验好,方便快捷。支持Vue 3.0、Hot Module Replacement(HMR)等新特性。 | 提供了丰富的配置和插件系统,适合大型项目。 |
劣势 | 对于大型项目,可能会出现一些隐藏的问题和兼容性问题。 | 配置复杂,学习曲线陡峭。初次构建速度慢。 |
二、热模块替换hmr
1. 什么是hmr
热模块替换(Hot Module Replacement,HMR
)是一种机制,它使得应用在运行时能够更新各种模块,而无需进行完全刷新。例如,某些库可以针对这个API进行优化,以达到接近无刷新更改的效果。这项技术主要针对单页面应用(SPA
)。
举例说明,如果我们在编写一个网页应用,并且同时运行着一个开发服务器,那么当我们修改了代码并保存后,整个页面会自动刷新以显示出新的结果。而如果使用了HMR
,就无需刷新整个页面,我们改动的部分(模块)会被自动替换掉并立即显示出新的效果。
应用HMR
之后的好处有:
- 保持应用状态:传统的整页刷新会导致当前应用的状态被丢失,而
HMR
能够在无需刷新整个页面的情况下替换、添加或删除模块,从而能够保持应用的状态。 - 只更新更改的部分:当修改一个或多个代码模块,整个应用不需要全部更新,只更新被改动的模块。
- 开发速度:由于只替换更改的内容,所以测试新的变更变得更快。
- 样式调整快:如果应用的修改仅仅是
CSS/SCSS
样式,那么HMR
就会变得非常有用。这是因为,当你调整样式的时候,无需刷新页面,调整立马生效,这对于样式调整而言是非常有利的。
注意,HMR 主要在开发环境,生产环境通常不需要开启。
2. vite中hmr的相关配置
在Vite
构建的项目中,默认情况下是启用了热模块替换(HMR
)的。如果你需要修改HMR
相关的配置,你可以在Vite的配置文件(vite.config.js
)中,对server.hmr
进行设置。
例如,如果你需要禁用HMR
,可以在vite.config.js
中这样配置:
export default {server: {hmr: false}
}
如果需要设置连接超时或者跳过检查脏模块,可以如下配置:
export default {server: {hmr: {timeout: 30000,overlay: false}}
}
Vite
的HMR
能力非常强大,它不仅可以处理JavaScript
模块的热替换,还能处理CSS、HTML
等其他类型的模块。只要你的代码里含有HMR
相关的接口,Vite
就能自动完成热替换。
在大部分情况下,你并不需要手动设置HMR,Vite
默认的设置已经可以满足绝大多数应用场景。如果你需要设置HMR
,通常是遇到了一些特殊的情况,比如需要修改连接超时时间、需要禁用HMR
等。
3. vite中hmr的执行过程
热模块替换(Hot Module Replacement,HMR
)是Vite
(以及其他现代前端构建工具,如Webpack
)的一个重要特性,但Vite的实现方式相对更优。
在对JavaScript、Vue、React
等进行HMR
时,Vite
会有些不同的处理方式,总的来说,有以下几个步骤:
-
文件改变后,
Vite
的开发服务器首先会通知在客户端运行的更新代码,文件已经被更新。 -
在客户端,
Vite
有一个运行时处理器,它会找到这个文件对应的模块。 -
查看这个模块的其他依赖或者倚赖(即这个模块被哪些模块引用),建立依赖关系图。
-
如果这个模块可以被更新(即符合
HMR
规则)则直接更新,如果不能则会通知引用它的父模块更新。 -
这个过程是递归的,直至找到可以进行
HMR
的模块,或者已经到达了应用的顶级模块,此时则会进行全量刷新。
相比Webpack
,Vite
不需要额外的代码进行热更新,因为Vite
是基于ES6
模块的,可以依靠浏览器原生的import
语法去请求和缓存模块,使得HMR
更加高效。而Webpack
是基于CommonJS
的,需要通过上下文去理解模块的引用关系,实现热更新则需要额外的编译输出和运行时支持。
此外,Vite
对于CSS
的处理也具有优势,当CSS
文件更新时,Vite
直接通过inject
的方式在客户端更新样式,而不会影响正在运行的JavaScript
代码。
4. vite和webpack中的hmr对比
项目 | Vite 中的 HMR | Webpack 中的 HMR |
---|---|---|
定义 | Vite 中,HMR 是通过 ESM 在浏览器中的原生支持,无需进行额外的浏览器以下代码处理,并且具有极快的速度。 | Webpack 中,HMR 是一种模块热替换的功能,可以在应用程序运行过程中替换、添加、删除模块,无需进行整个页面的刷新。 |
速度 | 速度比较快,因为它只需要更新修改过的部分,而不是整个应用。 | 相对较慢,因为当文件修改时,Webpack 会重新构建并刷新整个页面。 |
热更新范围 | 由于 Vite 利用原生 ESM 进行模块热更新,所以其影响范围较小,只对被修改的模块进行更新。 | 若一个模块的值发生了改变,改变会冒泡,使得依赖这个模块的所有模块全部更新,更新范围相当大。 |
原理 | 利用了 ES6 module 的系统,当文件改变时,直接只请求改变的文件并重新执行。 | 默认会给每个模块添加热更新代码,当文件发生变化后,通过打包工具发出的文件更改信号,触发更新函数,让客户端重新加载文件。 |
配置复杂度 | Vite 的配置相对较简单,只需要简单的几步就能启用 HMR。 | Webpack 的配置要复杂一些,除了需要添加 HMR 插件,还需要对其进行详细的配置。 |
兼容性 | Vite 要求 Node.js 版本 12.0.0 以上,并且只支持现代浏览器,不支持 IE。 | Webpack 对于旧版本的浏览器兼容性更好。 |
三、总结
对于hmr
,在Vite
中,当改动一个文件时,Vite
会立即使用WebSocket
向浏览器推送更新,然后浏览器通过重新获取被改动的模块,替换原有的代码,从而实现了部分更新,而不必要进行页面的整体刷新。这大大提高了开发时的用户体验。
Vite
中的预编译: 在传统的 SPA
打包器中,无论 JavaScript
文件的大小,文件需要被完全解析并转换为浏览器可运行的代码,这个过程可能会比较耗时。而Vite
则采取了另一个策略,即预编译。
Vite
开发服务器在启动时,会预编译所有被 import
的依赖。预编译将ES6+
语法或 TypeScript
编译为可以在浏览器中运行的 ESnext
语法,并尽可能的粗略编译,只做最小化处理(实际上只做语法编译和依赖导入的重写),所以在具有缓存的情况下,预编译是非常快的。
在第一次页面加载时,仅需要加载和解析实际需要的代码。之后的页面导航都会只加载需要的代码,并且因为服务器在内存中保留有编译代码,所以这个过程是非常快的。