2024-10-13 08:18:49
在前面我们提到过开发模式下使用Vite会有首屏性能下降的负面效果。之所以会造成首屏性能下降,一方面是devserver需要完成预构建才可以响应首屏请求;另一方面是需要对请求文件做实时转换。
也许有的同学会问,是不是针对这两个方面做优化,就可以提升首屏性能呢?原则上这样是没有问题的,而且Vite也是这么做的。为了能提升性能,Vite另辟蹊径的借助了Esbuild能快速完成项目打包、文件转换的能力来进行预构建、内容转换,效果非常好。
今天小编就通过本文和大家一起聊一聊Vite是怎样利用Esbuild来提升性能的。
首先,小编先带大家简单了解一下Esbuild,其官方地址是:Esbuild。
什么是EsbuildEsbuild是一款基于Go语言开发的javascript打包工具,最大的一个特征就是快。
通过官网提供的一张图,我们可以清晰的看到Esbuild的表现是多么优秀:
同样规模的项目,使用Esbuild可以将打包速度提升10-100倍,这对广大一直饱受Webpack缓慢打包速度折磨的开发人员来说,简直就是福音。
而Esbuild之所以能这么快,主要原因有两个:
Go语言开发,可以多线程打包,代码直接编译成机器码;
Webpack一直被人诟病构建速度慢,主要原因是在打包构建过程中,存在大量的resolve、load、transform、parse操作,而这些操作通常是通过javascript代码来执行的。要知道,javascript并不是什么高效的语言,在执行过程中要先编译后执行,还是单线程并且不能利用多核cpu优势,和Go语言相比,效率很低。
可充分利用多核cpu优势;
关键API-transfrom&buildEsbuild并不复杂。它对外提供了两个API-transform和build,使用起来非常简单。
transfrom,转换的意思。通过这个api,我们可以将ts、jsx、tsx等格式的内容转化为js。transfrom只负责文件内容转换,并不会生成一个新的文件。
build,构建的意思,根据指定的单个或者多个入口,分析依赖,并使用loader将不同格式的内容转化为js内容,生成一个或多个bundle文件。
这两个API的使用方式:
constres=awaitesbuild.transform(code,options)//将code转换为指定格式的内容esbuild.build(options)//打包构建关于使用transform、build需要传入的具体配置项,本文就不详细说明了,官网对这一块儿有很详细的说明,感兴趣的同学可以去官网-simple-options、Advancedoptions看看,也可以自己动手试试。
plugin和Webpack、Rollup等构建工具一样,Esbuild也提供了供外部使用的plugin,使得我们可以介入构建打包过程。
在这里要说明一点,只有build这个API的入参中可以配置plugin,transform不可以。
一个标准的plugin的标准格式如下:
letcustomerPlugin={name:'xxx',setup:(build)=>{build.onResolve({filter:'',namespace:''},args=>{...});build.onLoad({filter:'',namespace:''},args=>{...});build.onStart(()=>{...});build.onEnd((result)=>{...});}}其中,setup可以帮助我们在build的各个过程中注册hook。
Esbuild对外提供的hook比较简单,总共4个:
onResolve,解析url时触发,可自定义url如何解析。如果callback有返回path,后面的同类型callback将不会执行。所有的onResolvecallback将按照对应的plugin注册的顺序执行。
onLoad,加载模块时触发,可自定义模块如何加载。如果callback有返回contents,后面的同类型callback将不会执行。所有的onLoadcallback将按照对应的plugin注册的顺序执行。
onStart,每次build开始时都会触发,没有入参,因此不具有改变build的能力。多个plugin的onStart并行执行。
onEnd,每次build结束时会触发,入参为build的结果,可对result做修改。所有的的onEnd将按照对应的plugin注册的顺序执行。
正是有了onResolve、onLoad、onStart、onEnd,我们可以在build过程中的解析url、加载模块内容、构建开始、构建结束阶段介入,做自定义操作。
Esbuild在Vite中的巧妙使用了解了Esbuild的基本用法以后,小编就带大家一起来看看Vite是怎么利用Esbuild来做预构建和内容转换的。
预构建先来回顾一下为什么要做预构建。
原因有两点:
将非ESM规范的代码转换为符合ESM规范的代码;
将第三方依赖内部的多个文件合并为一个,减少http请求数量;
要完成预构建,最关键的两点是找到项目中所有的第三份依赖和对第三方依赖做合并、转换。借助Esbuild,Vite很轻松的实现了这两个诉求。
寻找第三方依赖
寻找第三方依赖的过程非常简单,分为两步:
和Webpack、Rollup、Parcel等构建工具一样,Esbuild在做打包构建时也要构建模块依赖图-modulegraph。
在构建modulegraph时,第一步就是解析模块的绝对路径,这个时候就会触发onResolvehook。在onResolvehook触发时,会传入模块的路径。根据模块的路径,我们就可以判断出这个模块是第三方依赖还是业务代码。
举个例子,
//main.tsximportreactfrom'react';importCustomeComponentfrom'./components/CustomeComponent';...在对main.tsx的内容做parser操作时,能知道main.tsx依赖react和CustomeComponent,然后开始解析react和CustomeComponent。
解析react、CustomeComponent时,会触发onResolvehook,入参分别为'react'和'./components/CustomeComponent'。根据入参,我们可以很清楚的区分'react'是第三方依赖,'./components/CustomeComponet'是业务代码。
这样,esbuild完成构建,项目中的第三方依赖也就收集完毕了。所有的第三方依赖会收集到一个deps列表中。
定义一个带onResolvehook和onLoadhook的esbuildplugin;
执行esbuild的build方法做打包构建;
合并、转换第三方依赖
知道了项目中的第三方依赖以后,再做合并、转换操作就非常简单了。
这一步,Vite直接通过esbuild提供的build方法,指定entryPoints为收集到的第三方依赖,format为esm,再做一次打包构建。
这一次,会对第三方依赖做合并、转换操作。打包构建完成以后,再把构建内容输出到/node_modules/.vite/deps下。
这样,通过两次esbuild.build,预构建就完成了。
middlewares中内容转换Vite中源文件的转换是在devserver启动以后通过middlewares实现的。
当浏览器发起请求以后,devsever会通过相应的middlewares对请求做处理,然后将处理以后的内容返回给浏览器。
middlewares对源文件的处理,分为resolve、load、transform、parser四个过程:
resolve-解析url,找到源文件的绝对路径;
load-加载源文件。如果是第三方依赖,直接将预构建内容返回给浏览器;如果是业务代码,继续transform、parser。
transfrom-对源文件内容做转换,即ts->js,less->css等。转换完成的内容可以直接返回给浏览器了。
parser-对转换以后的内容做分析,找到依赖模块,对依赖模块做预转换-pretransform操作,即重复1-4。
pretransform是Vite做的一个优化点。预转换的内容会先做缓存,等浏览器发起请求以后,如果已经完成转换,直接将缓存的内容返回给浏览器。
Vite在处理步骤3时,是通过esbuild.transform实现的,对比Webpack使用各个loader处理源文件,那是非常简单、快捷的。
结语有一说一,Vite通过Esbuild来优化预构建和内容转换的思路非常棒,这给我们以后处理同类问题提供了解决方案,真心给尤大点赞。
另外除了使用Esbuild,Vite内部还有很多可以拿出来单独讲的优化技巧,这个以后有机会小编可以再给大家详细讲讲。
最后说一句,如果本文对大家有帮助,那就给小编点个赞吧。大家的支持,是小编前进的动力。
作者:百应前端团队