前言esbuild是新一代的JavaScript打包工具。他的作者是Figma的CTO——EvanWallace。(这个卡子兰大大的眼睛,尴尬的发际线,一看就知道很给力!)esbuild以速度快着称,耗时仅为webpack的2%~3%。esbuild项目的主要目标是:开创构建工具性能的新时代,并创建易于使用的现代捆绑器。它的主要功能:无需缓存的极速ES6和CommonJS模块ES6模块的TreeshakingJavaScript和Go的APITypeScript和JSX语法SourcemapsMinificationPlugins现在很多工具都内置了它,比如我们熟悉的:vite,snowpack凭借esbuild的优秀性能,vite如虎添翼,飞速发展。今天我们将探讨:为什么esbuild如此之快?主要内容如下:几组性能数据对比为什么esbuild这么快esbuild即将发布的roadmapesbuild在vite中的应用为什么生产环境还需要打包?为什么vite不用esbuild打包?总结文先看一组对比:使用10份三个JS生产包,对比默认配置下不同打包工具的打包速度。webpack5排在最后,耗时55.25秒。esbuild只用了0.37秒。差别很大。还有比较多的:https://twitter.com/evanwallace/status/1314121407903617025webpack5表示很受伤:我比不过webpack4?...为什么esbuild这么快?有几个原因。(为保证内容的准确性,以下内容翻译自esbuild官网。)1.Go语言编写,可编译成本地代码。大多数打包器都是用JavaScript编写的,但命令行应用程序对于JIT编译语言的性能最差。每次运行捆绑器时,JavaScriptVM都会看到没有任何优化提示的捆绑器代码。当esbuild忙于解析JavaScript时,node正忙于解析bundler的JavaScript。当节点完成解析加壳代码时,esbuild可能已经退出并且您的加壳器甚至还没有开始加壳。此外,Go是为并行而设计的,而JavaScript不是。Go在线程之间共享内存,而JavaScript必须在线程之间序列化数据。Go和JavaScript都有并行垃圾收集器,但Go的堆是所有线程共享的,而对于JavaScript,每个JavaScript线程中都有一个单独的堆。根据测试,这似乎将JavaScript工作线程的并行度减少了一半,大概是因为一半的CPU内核忙于为另一半收集垃圾。2.大量使用并行操作。esbuild中的算法经过精心设计,可以充分利用CPU资源。大致分为三个阶段:解析链接代码生成解析和代码生成是大部分工作,可以完全并行化(在大多数情况下,链接本质上是一项串行任务)。由于所有线程共享内存,因此在捆绑导入相同JavaScript库的不同入口点时可以轻松共享工作。大多数现代计算机都有多个内核,因此并行性是一个巨大的胜利。3.代码是自己写的,没有使用第三方依赖。自己编写一切,而不是使用第三方库,可以带来很多性能优势。您可以从一开始就牢记性能,您可以确保一切都使用一致的数据结构以避免昂贵的转换,并且您可以在必要时进行大量的架构更改。当然,缺点是需要做更多的工作。例如,许多打包器使用官方的TypeScript编译器作为解析器。然而,它是为了满足TypeScript编译器团队的目标而构建的,并没有将性能放在首位。4.高效利用内存。理想情况下,编译器的复杂度为O(n),具体取决于数据的长度。如果您正在处理大量数据,内存访问速度会严重影响性能。转换数据所需的遍数越少(以及将数据转换成的不同表示形式越少),编译器就会越快。例如,esbuild只触及整个JavaScriptAST3次:词法分析、解析、范围界定和声明符号绑定符号以及缩小语法的过程。例如:将JSX/TS转换为JS,将ES转换为es5。最少的标识符、最少的空间、生成的代码。当AST数据在CPU缓存中仍处于活动状态时,AST数据重用最大化。其他包装商在单独的通道中执行这些步骤,而不是将它们交织在一起。它们还可以在数据表示之间进行转换,将多个库组合在一起(例如:字符串→TS→JS→字符串,然后是字符串→JS→旧JS→字符串,然后是字符串→JS→缩小JS→字符串)。这会使用更多内存并且会减慢速度。Go的另一个好处是它可以将东西紧凑地存储在内存中,从而允许它使用更少的内存并在CPU缓存中容纳更多内容。所有对象字段的类型和字段都紧密地打包在一起,例如几个布尔标志每个只占用一个字节。Go还具有值语义,其中一个对象可以直接嵌入到另一个对象中,因此它是“免费的”,无需额外分配。JavaScript没有这些特性,并且还有其他缺点,例如JIT开销(例如隐藏的类槽)和低效的表示(例如非整数与指针堆分配)。以上每一个因素都可以在一定程度上提高编译速度。当它们一起工作时,效果比当今常用的其他捆绑器快几个数量级。上面的内容比较繁琐,有网友对此做了一个简单的总结:用Go语言编写,可以编译成本地代码。Go很快。一般来说,JS的操作是毫秒级的,而Go是纳秒级的。解析、生成最终包文件、生成sourcemaps等操作都完全并行化,无需昂贵的数据转换,所有操作只需几步即可完成。该库以提高编译速度为编写代码的首要原则,并尽量避免不必要的内存分配。仅供参考。即将到来的路线图的以下功能已经在进行中,并且是首要任务:代码拆分(#16,文档)CSS内容类型(#20,文档)插件API(#111)以下几个功能更有用潜力,但还不确定:HTML内容类型(#31)降低到ES5(#297)捆绑顶级await(#253)有兴趣的可以继续关注。esbuild在vite中的使用esbuild在vite中应用广泛,这里简单分享两个点。优化器https://github.com/vitejs/vite/blob/main/packages/vite/src/node/optimizer/index.ts#L262import{build,BuildOptionsasEsbuildBuildOptions}from'esbuild'//...constresult=awaitbuild({entryPoints:Object.keys(flatIdDeps),bundle:true,format:'esm',external:config.optimizeDeps?.exclude,logLevel:'error',splitting:true,sourcemap:true,outdir:cacheDir,treeShaking:'ignore-annotations',metafile:true,define,plugins:[...plugins,esbuildDepPlugin(flatIdDeps,flatIdToExports,config)],...esbuildOptions})constmeta=result.metafile!//thepathsin`meta.outputs`arerelativeto`process.cwd()`constcacheDirOutputPath=path.relative(process.cwd(),cacheDir)for(constidindeps){constentry=deps[id]data.optimized[id]={file:normalizePath(path.resolve(cacheDir,flattenId)(id)+'.js')),src:entry,needsInterop:needsInterop(id,idToExports[id],meta.outputs,cacheDirOutputPath)}}writeFile(dataPath,JSON.stringify(data,null,2))处理.ts文件https://github.com/vitejs/vite/commit/59035546db7ff4b7020242ba994a5395aac92802为什么生产还需要包装?尽管原生ESM现在得到广泛支持,但由于嵌套导入导致的额外网络往返,在生产环境中发布未打包的ESM仍然效率低下(即使使用HTTP/2)为了在生产环境中获得最佳加载性能,最好是tree-shaking、延迟加载和分块你的代码(为了更好的缓存)。确保开发服务器和生产构建之间的最佳输出和行为并不容易。为了解决这个问题,Vite自带了一套开箱即用的构建优化构建命令。为什么不使用esbuild打包vite?虽然esbuild的速度出奇地快,并且已经是构建库的出色工具,但构建应用程序的一些重要功能仍在开发中,尤其是代码拆分和CSS处理。目前来看,Rollup在应用打包方面更加成熟和灵活。不过,等未来这些功能稳定下来后,不排除使用esbuild作为productionbuilder。综上所述,esbuild给构建效率的提升带来了曙光,esm的数量也在快速增加:https://twitter.com/skypackjs/status/1113838647487287296希望esm生态尽快完善使前端受益。本文转载自微信公众号“前端皮小丹”,可通过以下二维码关注。转载本文请联系前端皮小丹公众号。
