esbuild是最近比较流行的编译工具。它已经开始在某些领域取代webpack或babel。让我们来看看这个工具的细节。1.速度优势对比下面是一个压测数据。从图中我们可以看出esbuild的表现非常出色。至于为什么它的性能如此优越,官方解释有以下几点:1.它是用Go语言编写的,编译成可执行代码JavaScript必须基于解释器的nod??e环境执行,所以当工具如webpack已经解释了自己的代码,esbuild可能已经完成了编译工作,然后webpack会开始执行编译。此外,Go的设计核心是并行,而JavaScript不是。Go在线程之间共享内存,而JavaScript必须在线程之间序列化数据。Go和JavaScript都有并行垃圾收集器,但Go的堆在所有线程之间共享,而JavaScript每个线程有一个独立的堆。JavaScript工作线程并行度减少了一半,因为一半的CPU内核忙于为另一半收集垃圾。2.大量使用并行性esbuild内部的算法经过精心设计,以尽可能地完全饱和所有可用的CPU内核。大致分为三个阶段:解析、链接和代码生成。解析和代码生成是大部分工作,并且可以完全并行化。由于所有线程共享内存,因此在编译导入相同JavaScript库的不同入口点时可以轻松共享工作。大多数现代计算机都有很多内核,因此并行化是一个很大的性能提升。3.esbuild中的所有内容都是从头编写的与使用第三方库相比,自己编写有很多性能优势。可以从一开始就考虑性能,以确保一切都使用一致的数据结构,避免代价高昂的转换,并在必要时允许进行广泛的架构更改。当然,缺点是工作量很大。4.内存得到有效利用在理想情况下,编译器的输入长度大多为O(n)复杂度。因此,如果您正在处理大量数据,内存访问速度会严重影响性能。您需要做的数据操作越少,编译器就会越快。2.加载器(loader)esbuild加载器的作用类似于webpack中的加载器。它编译某些类型的文件。具体功能如下:1、.js默认使用js-loader,.cjs和.mjs文件。.cjs扩展名由node用于CommonJS模块,.mjs扩展名由node用于ECMAScript模块,尽管esbuild不区分两者。esbuild支持所有现代JavaScript语法。但是,较旧的浏览器可能不支持较新的语法,因此您可能需要配置目标选项以告知esbuild将较新的语法转换为适当的旧语法。但请注意,ES5支持不是很好,目前不支持将ES6+语法转换为ES5。2.ts-loaderortsx-loader这个加载器默认为.ts和.tsx文件启用,这意味着esbuild内置了对TypeScript语法的解析和丢弃类型注释的支持。但是,esbuild不做任何类型检查,因此您仍然需要在esbuild中并行运行tsc-noEmit来检查类型。需要注意的是,esbuild在编译时不会进行类型检查,编译前需要先用ide检查3.jsx-loader会将xml代码转成js代码4.json-loader对于.json文件,默认开启此加载.它在构建时将JSON文件解析为JavaScript对象,并将该对象作为默认对象导出。5.css-loader对于.css文件,这个加载器默认是启用的。它以CSS语法加载文件。CSS是esbuild中的一级内容类型,这意味着esbuild可以直接编译CSS文件,而无需从JavaScript代码中导入你的CSS。您可以@import其他CSS文件,使用url()来引用图像和字体文件,esbuild会将所有内容编译在一起。请注意,您必须为图像和字体文件配置加载器,因为esbuild没有任何预配置。通常这是一个数据URL加载器或外部文件加载器。请注意,esbuild尚不支持CSS模块,因此CSS文件的导出名称集目前始终为空。未来计划支持CSSModule。6.text-loader对于.txt文件,默认启用此加载程序。它在构建时将文件加载为字符串,并将字符串导出为默认导出。使用它看起来像这样。7.binary-loader此加载器将在构建时将文件加载为二进制缓冲区,并使用Base64编码将其嵌入到包中。文件的原始字节在运行时从Base64解码,并使用默认导出方法导出为Uint8Array。8.Base64-loader此加载器将在构建时将文件加载为二进制缓冲区,并使用Base64编码将它们嵌入到已编译的字符串中。该字符串将使用默认导出方法导出。9.dataurl-loader此加载器将在构建时将文件加载为二进制缓冲区,并将其作为Base64编码数据URL嵌入到编译中。此字符串使用默认导出方法导出。10.file-loader该加载器会将文件复制到输出目录,并将文件名作为字符串嵌入到编译中。此字符串使用默认导出方法导出。3.API调用为了更方便的使用,esbuild提供了API调用的方法,在调用API时传入option来设置相应的功能。在esbuild的API中,主要有两种API调用方式:transform和build。两者的区别在于文件最终是否生成。1.TransformAPITransformAPI调用对单个字符串进行操作,不需要访问文件系统。非常适合在没有文件系统的环境中使用或作为另一个工具链的一部分。这是一个简单的例子:require('esbuild').transformSync('letx:number=1',{loader:'ts',})=>{code:'letx=1;\n',map:'',警告:[]}2。构建API构建API调用对文件系统中的一个或多个文件进行操作。这允许文件相互引用并一起编译。这是一个简单的例子:require('fs').writeFileSync('in.ts','letx:number=1')require('esbuild').buildSync({entryPoints:['in.ts'],outfile:'out.js',})4.插件API1.简介插件API是上述API调用的一部分。插件API允许您将代码注入构建过程的各个部分。与API的其余部分不同,它不能从命令行使用。您必须编写JavaScript或Go代码才能使用插件API。插件API只能与BuildAPI一起使用,不能与TransformAPI一起使用。如果您正在寻找现有的esbuild插件,您应该查看现有的esbuild插件列表。此列表中的插件是作者有意添加的,供esbuild社区中的其他人使用。2.编写插件esbuild插件是一个包含名称和设置函数的对象。它们作为数组传递给构建API调用。每次调用BUILDAPI时都会运行设置函数。让我们尝试自定义一个插件importfsfrom'fs'exportdefault{name:"env",setup(build){build.onLoad({filter:/\.tsx$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,"utf8");constcontents=source.toString();console.log('文件内容:',contents)return{contents:contents,loader:"tsx",};});},};2.1namename一般表示本插件的名称2.2setupfunction2.2.1.命名空间每个模块都有一个关联的命名空间。默认情况下,esbuild在文件命名空间中运行,它对应于文件系统中的文件。但esbuild也可以处理在文件系统上没有相应位置的“虚拟”模块。虚拟模块通常使用文件以外的名称空间来区别于文件系统模块。2.2.2.Filters每个回调必须提供一个正则表达式作为过滤器。当路径与过滤器不匹配时,esbuild会跳过调用回调,这样做是为了提高性能。从esbuild的高度并行内部调用单线程JavaScript代码是昂贵的,应该尽可能避免以获得最大速度。您应该尝试使用过滤正则表达式而不是JavaScript代码进行过滤。这更快,因为正则表达式是在esbuild内部计算的,根本不需要调用JavaScript。2.2.3.Resolve回调使用onResolve添加的回调将在esbuild构建的每个模块的每个导入路径上运行。此回调可以自定义esbuild如何执行路径解析。2.2.4.加载回调使用onLoad添加的回调可以处理并返回文件内容。3、plugins和loader的执行顺序问题如何解决?esbuild中的加载器直接处理并返回某种格式的文件,插件API也有机会访问文件的内容。文件中并没有具体说明两者的执行时机。提及。importfsfrom"fs";exportdefault{name:"env",setup(build){build.onLoad({filter:/\.tsx$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,"utf8");constcontents=source.toString();//astHandle只能处理js内容,不知道ts或jsx,编译错误constresult=astHandle(contents)return{contents:结果,装载机:“tsx”,};});},};从上面的代码示例可以看出,插件api在接收到文件内容后是不能直接处理tsx的内容的,因为我们可能不具备处理tsx的能力,这个时候,是不可能的显示和定义将tsx转换为js后要执行的插件。针对这种情况,只能借助esbuild的transformapi能力。从“fs”导入fs;从“esbuild”导入esbuild;导出默认值constsource=awaitfs.promises.readFile(args.path,"utf8");constcontents=source.toString();constresult=astHandle(esbuild.transformSync(contents,{loader:'tsx',}))返回{内容:result.code,装载机:“tsx”,};});},};4。Babel迁移由于Babel社区插件众多,这给原本使用Babel的项目迁移到esbuild造成了障碍。可以使用社区提供的esbuild-plugin-babel一键迁移。例如使用antd组件时,需要使用antd-plugin-import插件,如下:importbabelfrom"esbuild-plugin-babel";importesbuildfrom"esbuild";importpath,{dirname}from"path";从"url"导入{fileURLToPath};constbabelJSON=babel({filter:/\.tsx?/,config:{presets:["@babel/preset-react","@babel/preset-typescript"],插件:[["import",{libraryName:"antd",style:"css"}]],},});const__dirname=dirname(fileURLToPath(import.meta.url));esbuild.build({entryPoints:[path.join(__dirname+"/app.tsx")],outdir:"out",plugins:[babelJSON],}).catch(()=>进程.exit(1));5.插件使用限制插件API并非旨在涵盖所有用例,也不可能关联编译过程的每个部分。例如,目前无法直接修改AST。存在此限制是为了保留esbuild的出色性能特征,同时也是为了避免暴露过多的API表面,这将成为维护负担并会阻止涉及AST更改的改进。将esbuild视为网络的“链接器”的一种方式。与本机代码的链接器一样,esbuild的工作是获取一组文件,解析并绑定它们之间的引用,并生成包含所有代码链接的单个文件。插件的工作是生成最终链接的单个文件。esbuild中的插件在相对范围内效果最好,并且仅自定义构建的一小部分。例如,自定义格式(如YAML)的特殊配置文件的插件非常适合。您使用的插件越多,您的构建速度就越慢,尤其是如果您的插件是用JavaScript编写的。如果一个插件适用于您构建中的每个文件,那么您的构建很可能会非常慢。如果缓存适用,则必须由插件本身完成。
