Vite 浅入浅出
文章出自:知乎 https://zhuanlan.zhihu.com/p/400313956
作者:字节跳动ADFE团队
Vite是什么?
Vite
(读音类似于[weɪt],法语,快的意思) 是一个由原生 ES Module 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。
Vite 与 Webpack
webpack 最初是为了解决前端模块化以及使用 Node.Js 生态的问题而出现,随着webpack的大规模使用和社区生态的逐渐繁荣,webpack 的能力越来越强大。webpack 本质上是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,会将所有这些模块打包成一个或多个 bundle。
但因为多了打包构建这一层,随着项目的增长,打包构建速度会越来越慢,冷启动和热更新的速度会越来越长,影响开发效率。然后启动一轮构建优化,随着项目的进一步增大,构建速度又会降低,陷入不断优化的循环。在项目达到一定的规模时,基于 bundle 的构建优化的收益变得越来越有限,无法实现质的提升。
webpack 之所以慢,主要的原因还是在于他将各个资源打包整合在一起形成 bundle,如果我们不需要 bundle 打包的过程,直接让浏览器去加载对应的资源,那么是不是能解决这个问题呢?
那么,Vite来了,Vite基于浏览器特性 ES Module,实现了bundleless架构。真正无打包启动 & 构建。
Vite 特点
-
Lightning fast cold server start – 闪电般的冷启动速度 -
Instant hot module replacement (HMR) – 即时热模块更换(热更新) -
True on-demand compilation – 真正的按需编译
上面三个特点,现在市面上已经有很多解决方案。比较出名的冷启动脚手架:vue-cli, create-react-app 等,热更新也早是已经实现的功能,webpack都已经集成了。也可以通过动态import的方式(import(‘xx.js’))来实现编译时的一次性编译,引入时的按需引入,来达到按需编译的效果。那么 vite 有什么特别的地方呢?用作者在微博上的原话:
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。
ES Module 又是什么呢?
随着我们的应用程序越来越大,我们希望将其拆分为多个文件,即所谓的“Module”, 模块。但是长期以来,JavaScript一直没有语言级别的模块语法。社区中产生了一些方法来解决这个问题,比如:
-
AMD –最古老的模块系统之一,最初由require.js库实现。 -
CommonJS –为Node.js服务器创建的模块系统。 -
UMD –另一种模块系统,建议作为通用模块系统,与AMD和CommonJS兼容。
语言级模块系统于2015年出现在标准中,此后逐渐发展,现在已得到所有主要浏览器和Node.js的支持。也就是 ES Module。
一个简单的 :
// sayHi.js
export function sayHi(userName) {
alert(`Hi, ${userNmae}`)
}
// html
<script type="module">
import { sayHi } from './sayHi.js'
sayHi()
</script>
用一句话来形容ES Module: 就是在浏览器和Node.js中原生增加了模块化的支持。
Vite 基于 bundless 的实践
第一步:让浏览器自主加载模块。
Vite采用了基于web标准的ES Module来实现的浏览器自主加载模块功能,目前基于web标准的ES Module 已经覆盖了超过90%的浏览器。(具体兼容性:https://caniuse.com/?search=javascript%20module)。
第二步:支持bare import(不带相对路径或绝对路径的import)
像我们平常引入位于node_modules 中的依赖会采用下面的 这样引用
import Vue from 'vue'
import _ from 'lodash'
这样的引入其实就是bare import。那么如何解决浏览器 bare import 时返回正确的路径呢。
我们看一个例子(开发环境):
未编译前的main.js
从main.js的返回中可以看到
import { createApp } from '/@modules/vue.js'
在引入的时候前面加了 ‘/@module/’ 这样的字符。并在后面加上了.js 的后缀。在启动 本地server的时候,vite会特殊处理这样依赖。
ES Module 会自动解析 import 语法,然后去请求 import 的模块,那么当 ‘/@module/vue.js’ 这样请求到达 DevServer 的时候,DevServer 可以根据 ‘ /@module/’ 这个特殊的前缀来判断这次请求的文件是外部依赖,需要从node_modules中获取。
第三步:能够支持加载非js的文件资源
如上面截图,这是Vite处理请求时根据请求的后缀不同分配不同的 plugin 去处理。最后都会返回ES module,vite/hmr 会根据返回的内容进行判断而进行不同的处理。
额外的多说一下ES Build:
Vite 针对 TypeScript 做了内置支持,并不需要额外去配置。在编译器方面,Vite 并没有采用官方的 tsc 来编译。而是采用了 ESBuild 这个工具。
ESBuild 是一个用Go语言实现,能够把 TypeScript,Jsx,Tsx 编译到原生JavaScript的一个工具。性能会比 tsc 好上很多。(单线程ES Build 性能大概会好个2,30倍,多线程可能会好到百倍以上 — 基于16GRAM的6核 2019 MacBook Pro)。
ES Build 快在哪里?
首先了解一下 tsc 干了什么,tsc是一个 TypeScript 编译器,可以把TypeScript 编译成JavaScript, tsc 在把目标文件解析成 AST 之后,会进行两步操作,1: 类型检查 2: AST -> JavaScript 代码。
ESBuild 在把目标文件解析成 AST 之后,则只进行了其中一步操作:AST -> JavaSciript 代码。这就是ESBuild 比 tsc 快的原因之一。换句话说, ESBuild 没有类型检查的功能。想要做到类型检查需要配合tsc 命令或者一些IDE插件去解决。了解更多https://www.breword.com/evanw-esbuild/
第四步:浏览器端能够处理包含在ES Module中不同类型的返回
如第三步,所有的文件类型返回都包在ES Module中,那么就需要在浏览器端实现识别返回的类型,并做相对应的处理。
如上图,在本地开发的时候,会在html中插入这么一段代码。/vite/hmr 是一个js文件。只在本地开发时引入,是在浏览器端建立和DevServer的 websocket 链接,注册 websocket 回调,接收不同类型websocket的消息,并处理。(在 DevServer 启动时,会自动监听当前目录下除了node_modules 外的所有文件,根据文件类型不同,文件改动时会触发不同的回调函数,也就会发送不同的 webscoket 消息。)
上图中的代码来自于vite/hmr.js
,有两个主要功能,一个是监听 websocket 的消息的功能,第二个是处理消息的功能。在处理消息的函数 handleMessage
中。可以看出针对不同类型的返回,有不同的处理方法。比如:vue-reload, vue-rerender
这两种类型会通过 动态import的的方法访问本地的devServer
,然后将返回值通过 __VUE_HMR_RUNTIME__
的 对应方法处理。
-
VUE_HMR_RUNTIME 是从 Vue3 暴漏的一个Api,内含 createRecord, rerender, reload 三个方法。暴漏名为:HMRRuntime
比如 Css 类型的返回 style-update, style-remove
,则会通过document.adoptedStyleSheets
来进行css属性的添加与删除。
如果更新的文件会有多个文件依赖它,那么则会返回 multi类型(如下图消息所示),那么则会针对每一项执行一次函数 handleMessage
。
第五步:优化&合并大量网络请求
如图,这是在一个demo中引入一个lodash-es的包,可以看到有600多个请求。对于lodash-es这样自身文件数量较多,或者一些有较多外部依赖的模块。在请求这些模块的时候,会发起大量请求。这会导致页面加载的时间变长,非常影响用户体验。
在这部分呢,Vite也做了相关优化。在Vite中有一个optimize的命令(使用方法: vite optimize)。这个命令会将 package.json 中的 dependencies 依赖借助 @rollup/plugin-commonjs 这个插件将 commonjs 的外部依赖打包为 ESModule 的形式引入。然后将打包后的文件,存放在 /node_modules/.vite_opt_cache 文件夹当中。在下次分析带有 ‘/@module/’ 前缀的这种请求时,会先去cache文件夹中寻找是否有对应的缓存文件。
在vite devServer启动的过程中,也调用了optimize命令对应的方法,将依赖都打包成了ES6 Module的文件。
这是优化之后的请求列表。可以看到关于lodash-es的请求已经全部聚合在lodash-es.js 这一个文件之中。
第六步:支持生产环境构建
在生产环境构建上,Vite 并没有采用纯ES Module来实现,而是基于Rollup将多个模块打包成bundle。这是因为:如果不打包成bundle,大量的ES Module 引入会导致浏览器发出大量请求,这会导致页面加载时间变长。
Vite 的表现如何?
冷启动:
从左到右依次是: vue-cli3 + vue3 的demo, vite 1.0.0-rc + vue 3的demo, vue-cli3 + vue2的demo
从这个gif可以明显感受到vite比其他两个在启动速度上有明显的优势,vue-cli 3 启动Vue2大概需要5s左右,vue-cli3 启动Vue3需要4s左右,而vite 只需要1s 左右的时间。
从一个demo上已经可以很明显的看出Vite的优势,从理论上讲,Vite是基于ES Module实现的,可以真正意义上实现按需引入和不需要打包。那么在越大的项目上,Vite的表现是越好的, 因为一次启动,首屏或者根路由,大部分情况下也就需要那么十几个,最多几十个组件,Vite 很快就可以完成编译,然后启动服务了。
生产环境构建:
Vite是基于Rollup实现的生产环境构建,所以在生产环境构建时,Vite 与 基于webpack实现的vue-cli在构建时间上相比差距不大。
Vite 的生产环境构建可以简单理解为:默认配置了一些rollup的配置(比如:rollup-plugin-vue),然后通过 vite.config.js 来接收一些rollup的相关配置,直接传入到rollup 中参与构建。
Vite 使用:
创建一个 demo并启动
use npm: version >= npm@6.1.0
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
use yarn: version >= yarn@1.0
$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
尽管Vite主要是为与vue3一起工作而设计的,但它也可以支持其他框架。例如,尝试npm init vite-app –template react或–template preact。可以启动一个react | preact 的demo。
命令行支持:
自定义配置:
可以使用 vite.config.js / vite.config.ts
或者自定义文件名配合 vite --config my-config.js
来使用
具体配置项参考:https://github.com/vitejs/vite/blob/main/packages/vite/src/node/config.ts
所以最后,听听Vite 作者是怎么描述 Vite 的?(5:30 – 14:00) https://player.bilibili.com/player.html?bvid=1qC4y18721
原文始发于微信公众号(前端24):Vite 浅入浅出
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/216943.html