前端构建新世代,Esbuild 原来还能这么玩!

大家好,我是若川。持续组织了5个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。

今天分享一篇esbuild的文章~

Hello,我是三元同学。之前停更了一段时间,因为得了流感,一直在家养病,没来得及更新文章,跟读者朋友们先说声抱歉~今天给大家带来的是我最近写的原创文章,由于近段时间一直在研究前端构建相关的领域,像 Esbuild、Vite 这些都接触得比较多了,而且这些工具现在在前端圈也比较热门,备受业界关注,因此我想我有必要把我研究过的一些东西分享给大家,希望能对你有所帮助。

什么是 Esbuild?

Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以快 10~100 倍。

a6dcdc7972563dd04d00d41f3a73bc96.png

架构优势

1. Golang 开发

采用 Go 语言开发,相比于 单线程 + JIT 性质的解释型语言 ,使用 Go 的优势在于 :

  • 一方面可以充分利用多线程打包,并且线程之间共享内容,而 JS 如果使用多线程还需要有线程通信(postMessage)的开销;

  • 另一方面直接编译成机器码,而不用像 Node 一样先将 JS 代码解析为字节码,然后转换为机器码,大大节省了程序运行时间。

2. 多核并行

内部打包算法充分利用多核 CPU 优势。Esbuild 内部算法设计是经过精心设计的,尽可能充分利用所有的 CPU 内核。所有的步骤尽可能并行,这也是得益于 Go 当中多线程共享内存的优势,而在 JS 中所有的步骤只能是串行的。

3. 从零造轮子

从零开始造轮子,没有任何第三方库的黑盒逻辑,保证极致的代码性能。

4. 高效利用内存

一般而言,在 JS 开发的传统打包工具当中一般会频繁地解析和传递 AST 数据,比如 string -> TS -> JS -> string,这其中会涉及复杂的编译工具链,比如 webpack -> babel -> terser,每次接触到新的工具链,都得重新解析 AST,导致大量的内存占用。而 Esbuild 中从头到尾尽可能地复用一份 AST 节点数据,从而大大提高了内存的利用效率,提升编译性能。

与 SWC 对比

速度

下面拿纯 Esbuild 和 SWC 来编译代码,作为 Transformer 来转换 800+ 个 tsx 文件,不写任何的 JS 胶水代码(如 esbuild-register、esbuild-loader、swc-loader 本身为了适配相应的宿主工具,会写一堆 JS 胶水代码,影响判断)。


EsbuildSWCTSC
第一次138 ms217 ms8640 ms
第二次154 ms206 ms8400 ms
第三次142 ms258 ms8480 ms
平均144.7 ms227 ms8507 ms
耗时倍率x1x 1.58x 58.8

从这个例子可以看出,Esbuild 与 SWC 在性能上是在一个量级的,这里通过仓库的例子 Esbuild 略快,但不排除其他例子里面 SWC 比 Esbuild 略快的场景。

兼容性

Esbuild 本身的限制,包括如下:

  • 没有 TS 类型检查

  • 不能操作 AST

  • 不支持装饰器语法

  • 产物 target 无法降级到 ES5 及以下

意味着需要 ES5 产物的场景只用 Esbuild 无法胜任。

相比之下,SWC 的兼容性更好:

  • 产物支持 ES5 格式

  • 支持装饰器语法

  • 可以通过写 JS 插件操作 AST

应用场景

对于 Esbuild 和 SWC,很多时候我们都在对比两者的性能而忽略了应用场景。对于前端的构建工具来说主要有这样几个垂直的功能:

  • Bundler

  • Transformer

  • Minimizer

从上面的速度和兼容性对比可以看出,Esbuild 和 SWC 作为 transformer 性能是差不多的,但 Esbuild 兼容性远远不及 SWC。因此,SWC 作为 Transformer 更胜一筹。

但作为 Bundler 以及 Minimizer,SWC 就显得捉襟见肘了,首先官方的 swcpack 目前基本处于不可用状态,Minimizer 方面也非常不成熟,很容易碰到兼容性问题。

而 Esbuild 作为 Bundler 已经被 Vite 作为开发阶段的依赖预打包工具,同时也被大量用作线上 esm CDN 服务,比如esm.sh等等;作为 Minimizer ,Esbuild 也已足够成熟,目前已经被 Vite 作为 JS 和 CSS 代码的压缩工具用上了生产环境。

综合来看,SWC 与 Esbuild 的关系类似于当下的 Babel 和 Webpack,前者更适合做兼容性自定义要求高的 Transformer(比如移动端业务场景),而后者适合做 Bundler 和 Minimizer,以及兼容性自定义要求均不高的 Transformer。

插件机制

esbuild 插件就是一个对象,里面有namesetup两个属性,name是插件的名称,setup是一个函数,其中入参是一个 build 对象,这个对象上挂载了一些钩子可供我们自定义一些构建逻辑。以下是一个简单的esbuild插件示例:

let envPlugin = {name: 'env',setup(build) {// 文件解析时触发// 将插件作用域限定于env文件,并为其标识命名空间"env-ns"build.onResolve({ filter: /^env$/ }, args => ({path: args.path,namespace: 'env-ns',}))// 加载文件时触发// 只有命名空间为"env-ns"的文件才会被处理// 将process.env对象反序列化为字符串并交由json-loader处理build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({contents: JSON.stringify(process.env),loader: 'json',}))},
}require('esbuild').build({entryPoints: ['app.js'],bundle: true,outfile: 'out.js',// 应用插件plugins: [envPlugin],
}).catch(() => process.exit(1))

使用如下:

*// 应用了env插件后,构建时将会被替换成process.env对象*import { PATH } from 'env'console.log(`PATH is ${PATH}`)

不过在编写插件的时候有一些需要注意的地方:

  1. Esbuild 插件机制只可作用于 build API,而不适用于 transformAPI,这意味着 webpack 当中的 esbuild-loader 这种只使用 Esbuild transform 功能的地方无法利用 Esbuild 的插件机制。

  2. 插件中的 filter 正则是使用 go 原生的正则实现的,用来过滤文件,为了不使性能过于劣化,规则应该尽可能严格。同时它本身和 JS 的正则也有所区别,比如前瞻(?<=)、后顾(?=)和反向引用(\1)就不支持。

  3. 实际的插件应该考虑到自定义缓存(减少 load 的重复开销)、sourcemap 合并(源代码正确映射)和错误处理。可以参考 Svelte plugin。

虚拟模块支持

与 Rollup 对比

作为打包器,一般需要两种形式的模块,一种存在于真实的磁盘文件系统中,另一种并不在磁盘而在内存当中,也就是虚拟模块。Rollup 本身就天然支持虚拟模块,Vite 基于它的插件机制,也重度使用了虚拟模块的功能,以 wasm 文件的处理为例:

const wasmHelperId = '/__vite-wasm-helper'
// helper 函数实现
const wasmHelper = async (opts = {}, url: string) => {// 省略具体实现
}
export const wasmPlugin = (config: ResolvedConfig): Plugin => {return {name: 'vite:wasm',resolveId(id) {if (id === wasmHelperId) {return id}},async load(id) {if (id === wasmHelperId) {return `export default ${wasmHelperCode}`}if (!id.endsWith('.wasm')) {return}const url = await fileToUrl(id, config, this)// 虚拟模块return `
import initWasm from "${wasmHelperId}"
export default opts => initWasm(opts, ${JSON.stringify(url)})
`}}
}

但 Rollup 的虚拟模块也有一些限制,为了与真实模块区分开,默认约定要在路径前面拼上一个'\0'。这样会对路径产生一定的入侵性,直接放到浏览器进行 import 会出问题(Vite 内部也将 \0 替换成 __xx 这种形式,以免直接将 带\0 路径放到浏览器中 import):

a6f61ae84b6568035fdd417f71214657.png

image.png

Esbuild 中对于虚拟模块的支持更加友好一些,直接通过 namespace 来区分真实模块和虚拟模块,这样也不会有 \0 这样 hack 操作。

编译能力

使用 Esbuild 的虚拟模块,可以完成很丰富的功能,除了上述插件实例中在内存中计算出 env 的值作为模块内容,还可以模块名当做一个函数来进行编译,甚至可以在编译阶段实现函数递归的过程。比如这个 Esbuild 插件:

{name: 'fibo',setup(build) {build.onResolve({ filter: /^fib\(\d+\)/ }, args => {return { path: args.path, namespace: 'fib' }})build.onLoad({ filter: /^fib\(\d+\)/, namespace: 'fib' }, args => {const match = /^fib\((\d+)\)/.exec(args.path);n = Number(match[1]);console.log(n);let contents = n < 2 ? `export default ${n+1}` : `import n1 from 'fib(${n - 1})'import n2 from 'fib(${n - 2})'export default n1 + n2`return { contents }})}
}

引入这个插件,可以解析如下的 import 语句:

import fib5 from 'fib(5)'console.log(fib5)// 13

所有的模块都是虚拟模块,在真实文件系统中并不存在

另外,还能借助虚拟模块来进行 URL Import,支持如下的 import 代码:

import React from 'https://esm.sh/react@17'

这也可以在插件当中实现,可参考示例。

落地场景

1. 代码压缩工具

Esbuild 的代码压缩功能非常优秀,可以甩开传统的压缩工具一个量级以上的性能差距。Vite 在 2.6 版本也官宣在生产环境中直接使用 Esbuild 来压缩 JS 和 CSS 代码。

427d143fc8d5b72de05b76d2db48ab35.png

2. 代替 ts-node

社区已经有了相应的方案 esno: https://github.com/antfu/esno

ts-node index.ts
// 替换为
esno hello.ts

3. 代替 ts-jest

使用 esbuild-jest 代替ts-jest,我曾经尝试在某些大型包中使用 esbuild-jest 来作为 transformer,相比 ts-jest,整体大概提升 3 倍测试效率。

Github 地址:https://github.com/aelbore/esbuild-jest

4. 第三方库 Bundler

Vite 中在开发阶段使用 Esbuild 来进行依赖的预打包,将所有用到的第三方依赖转成 ESM 格式 Bundle 产物,并且未来有用到生产环境的打算。

3823cf6a727a40425db37c118829f3a5.png

同时业界也有一些平台基于纯 Esbuild 来做线上 cjs -> esm 的 CDN 服务,比如 esm.sh  和 skypack:

4869f81926dbd78f6ddcad62d2f3afe1.png27ba0df55afab775a31d940ac1ab206c.png

5. 打包 Node 库

为什么要打包 Node 库:

  • 减少 node_modules 代码,避免业务安装一大堆 node_modules 的代码,减少安装体积

  • 提高启动速度,所有代码打到一个文件,减少了大量的文件 io 操作

  • 更安全。所有代码打包也是锁定依赖版本的一种方式,可以避免之前出现的 coa 包导致的大面积 CI 挂掉的问题,可参考云谦的这篇文章。

这方面 Esbuild 的作用跟现在 vercel 团队出品的 ncc 差不多,但会对代码的写法有一些限制,无法分析动态 require 或者 import 语句含有变量的情况:

2ea35e794b2499265edb8a4396a9efbe.png

6. 小程序编译

对于小程序的场景,也可以使用 Esbuild 来代替 Webpack,大大提升编译速度,对于 AST 的转换则通过 Esbuild 插件嵌入 SWC 来实现,实现快速编译。详见 132 的分享 esbuild 上生产。

7. Web 构建

Web 场景就显得比较复杂了,对于兼容性和周边工具生态的要求比较高,比如低浏览器语法降级、CSS 预编译器、HMR 等等,如果要用纯 Esbuild 来做,还需要补充很多能力。

之前三元同学基于 Esbuild 实现了一套 Web 开发脚手架 ewas,已经在 Github 开源,并且已成功落地到我之前的小册项目当中,相比 create-react-app 启动速度提升了 100 倍以上(30s -> 0.3s)。仓库地址: https://github.com/sanyuan0704/ewas。

如今 Remix 1.0 正式发布,底层使用 Esbuild 构建,带来了极致的性能体验,成为 Next.js 强有力的竞争对手。

但总体来说,目前 Esbuild 对于真实的 Web 场景还有很多能力不支持,还有一些硬伤,包括语法不支持降级到ES5,拆包不灵活、不支持 HMR,对于真正能作为 Webpack 一样的构建工具来讲还有很长的路要走。

bebca41683adbb8261673c366b76630e.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

d87985be83193cf88e60781fbb65b7cd.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

平面设计师和ui设计师_平面设计师为什么要享受所有乐趣?

平面设计师和ui设计师Graphic designers are pretty cool. We have to admit that. Be it their dressing style, their attitude and most importantly their enviable gadgets. Large Mac monitor, wacom tablet, drawing sets, swatchbooks , iPad pro with pencil, humungo…

web表单设计:点石成金_设计复杂的用户表单:12个UX最佳实践

web表单设计:点石成金It’s been a few years that I’ve been taking interest in designing complex user forms, where a lot of information is requested from users. Here are a few industries where you regularly find such flows:几年来&#xff0c;我一直对设计复杂…

跨平台开发框架到底哪家强?5款主流框架横向对比!

跨平台开发框架到底哪家强&#xff1f;目前市场上有多个专业做跨平台开发的框架&#xff0c;那么对开发者来说究竟哪一个框架更符合自己的需求呢&#xff1f;笔者特地总结对比了一下不同框架的特性。国内外笔者选择了一共5个主流的测评对象&#xff0c;分别是RN&#xff0c;Flu…

c#创建web应用程序_创建Web应用程序图标集的6个步骤

c#创建web应用程序I am not great at creating logos or icons, mainly because of the lack of practice. So when I was tasked to create an unique icon set for our web app, I wasn’t confident that things will turn out right. After researching effective and rele…

基于pnpm + lerna + typescript的最佳项目实践 - 理论篇

本文来自作者金虹桥程序员 投稿原文链接&#xff1a;https://juejin.cn/post/7043998041786810398本系列文章分为两篇&#xff1a;理论篇和实践篇 理论篇&#xff1a;介绍pnpm&#xff08;pnpm的特点、解决的问题等&#xff09;、lerna&#xff08;lerna的常用命令&#xff09;…

nginx 多进程 + io多路复用 实现高并发

一、nginx 高并发原理 简单介绍&#xff1a;nginx 采用的是多进程&#xff08;单线程&#xff09; io多路复用(epoll)模型 实现高并发 二、nginx 多进程 启动nginx解析初始化配置文件后会 创建&#xff08;fork&#xff09;一个master进程 之后 这个进程会退出 master 进程会…

ux设计工具_UX设计中的工具和实用主义

ux设计工具There’s a zillion tools for User Experience and User Interface Design. Don’t take my word for it: a simple Google search for “what are the best tools for wireframing” (to take just one aspect of UX) leads you to endless pages of “The 20 best…

幕后常驻嘉宾配音小姐姐的2021年度总结

大家好&#xff0c;我是若川。这是公众号幕后常驻嘉宾配音小姐姐&#xff0c;看完了上一个阿源小姐姐的年度总结《一张图看程序媛阿源的2021个人年度流水账》&#xff0c;写的年度总结投稿。点击以下音频可以查看收听往期更多音频。以下是正文~Hi&#xff0c;大家好呀~我是若川…

结果规格化_结果

结果规格化If you’ve seen an Instagram story involving a question and people tilting their heads, you probably were looking at the “Who Is More” Instagram filter. In this article, I will share the creative process and decision making behind this filter.如…

2021 年 JavaScript 大事记

大家好&#xff0c;我是 ConardLi&#xff0c;不知不觉中&#xff0c;2021 年已经接近尾声了&#xff0c;不知道在 2021 这一年&#xff0c;你收获了什么&#xff1f;又失去了什么呢&#xff1f;又到了开始做年终总结的时候了&#xff0c;今天&#xff0c;我来给 JavaScript 做…

动画 制作_您希望制作的10个醒目的徽标动画

动画 制作重点 (Top highlight)标志设计 (Logo Design) Have you ever watched paint dry? No? I didn’t think so. How about watched a turtle crossing the road? Probably not. Maybe spent an hour standing in line at the post office? Well that’s pretty likely…

使用 CSS 用户选择控制选择

IE10 平台预览 4 包括一个新的 CSS 属性的支持-ms-user-select&#xff0c;这使得 Web 开发者控制完全可以选择什么的文本&#xff0c;在其网站上更容易。如果你是看我一整天都在我的工作站&#xff0c;您会注意到我读计算机上时&#xff0c;我选择的文本。我不是只有一个人读起…

一个在校的普通前端小姐姐的2021

大家好&#xff0c;我是若川。这是我的源码共读群里一个大三的前端小姐姐&#xff08;小曹同学&#xff09;的年度总结。她写了5篇源码笔记。同时做了很多项目&#xff0c;获得了很多奖。而且策划和建立了学校工作室的前端训练营&#xff0c;40人报名参加。总之就是现在的大学生…

按钮 交互_SwiftUI中的微交互—菜单按钮动画

按钮 交互Microinteractions have become increasingly important in a world with a dizzying number of digital platforms and an ocean of content. While microinteractions used to be considered an interesting resource in the early days of digital design, in toda…

选择控件— UI组件系列

重点 (Top highlight)The word “toggle” is a reference to a switch with a short handle that alternates between two states each time it is activated. You encounter it every time you “switch” on the lights.单词“ toggle”是指带有短手柄的开关&#xff0c;该开…

SEE Conf: Umi 4 设计思路文字稿

大家好&#xff0c;我是若川。持续组织了5个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。复制此链接 https:…

用户体验改善案例_改善用户体验研究的5种习惯

用户体验改善案例There’s plenty of misunderstanding around user research, whether it’s the concept of validation or one-off anecdotes being thrown around as concrete evidence for a product decision.用户研究存在很多误解&#xff0c;无论是验证的概念还是一次性…

巴克莱对冲_“巴克莱的财政预算案”:使金钱管理对心理健康有效—用户体验案例研究

巴克莱对冲Disclaimer: all official Barclays assets used for this project are purely for educational/project purposes only and do not reflect the intentions of Barclays or any of its affiliates.免责声明&#xff1a;用于此项目的所有官方巴克莱资产纯粹是出于教育…

6 个对所有 Web 开发者都有用的 GitHub 仓库

作者&#xff1a;Mehdi Aoussiad原文&#xff1a;https://javascript.plainenglish.io/6-useful-github-repositories-for-all-web-developers-44f26912fd66大家好&#xff0c;我是若川。持续组织了5个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&…

openfiler的iSCSI配置(二)

为什么80%的码农都做不了架构师&#xff1f;>>> 一.openfiler iSCSI配置 1.启动iSCSI target server服务。在Services列表下。 2.设置访问列表。在System---Network Access Configuration下设置。 3.创建卷设备 二.ISCSI客户端配置 1.安装open-iscsi # apt-get ins…