前言
esbuild
是新一代的 JavaScript 打包工具。
他的作者是 Figma
的 CTO – Evan Wallace
。
esbuild
以 速度快
而著称,耗时只有 webpack 的 2% ~3%。
esbuild
我的项目次要指标是: 开拓一个构建工具性能的新时代,创立一个易用的古代打包器
。
它的次要性能:
Extreme speed without needing a cache
ES6 and CommonJS modules
Tree shaking of ES6 modules
An API for JavaScript and Go
TypeScript and JSX syntax
Source maps
Minification
Plugins
当初很多工具都内置了它,比方咱们熟知的:
vite
,snowpack
借助 esbuild 优异的性能,vite 更是锦上添花,快到飞起。
明天咱们就来摸索一下: 为什么 esbuild 这么快。
明天的次要内容:
几组性能数据比照
为什么 esbuild 这么快
esbuild upcoming roadmap
esbuild 在 vite 中的使用
为什么生产环境仍需打包?
为何 vite 不必 esbuild 打包?
总结
注释
先看一组比照:
应用 10 份 threeJS 的生产包,比照不同打包工具在默认配置下的打包速度。
webpack5 垫底,耗时 55.25
秒。
esbuild 仅耗时 0.37
秒。
差别微小。
还有更多比照:
webpack5 示意很受伤: 我还比不过 webpack 4 ?
…
为什么 esbuild 这么快?
有以下几个起因。
(为了保障内容的准确性,以下内容翻译自 esbuild 官网。)
1. 它是用 Go 语言编写的,并能够编译为本地代码。
大多数打包器都是用 JavaScript 编写的,然而对于 JIT 编译
的语言来说,命令行应用程序领有最差的性能体现。
每次运行打包器时,JavaScript VM 都会在没有任何优化提醒的状况下看到打包程序的代码。
在 esbuild 忙于解析 JavaScript 时,node 忙于解析打包程序的 JavaScript。
到节点实现解析打包程序代码的工夫时,esbuild 可能曾经退出,您的打包程序甚至还没有开始打包。
另外,Go 是为 并行性
而设计的,而 JavaScript 不是。
Go 在线程之间 共享内存
,而 JavaScript 必须在线程之间序列化数据。
Go 和 JavaScript 都有 并行的垃圾收集器
,然而 Go 的堆在 所有线程
之间共享,而对于 JavaScript, 每个 JavaScript 线程中都有一个 独自的堆
。
依据测试,这仿佛将 JavaScript worker 线程的并行能力缩小了一半,大略是因为一半 CPU 外围正忙于为另一半收集垃圾。
2. 大量应用了并行操作。
esbuild 中的 算法通过精心设计
,能够充分利用 CPU 资源。
大抵分为三个阶段:
解析
链接
代码生成
解析
和代码生成
是大部分工作,并且能够 齐全并行化
(链接在大多数状况下是固有的串行工作)。
因为所有线程 共享内存
,因而当捆绑导入同一 JavaScript 库的不同入口点时,能够轻松地共享工作。
大多数古代计算机具备 多内核
,因而并行性是一个微小的胜利。
3. 代码都是本人写的,没有应用第三方依赖。
本人编写所有内容, 而不是应用第三方库,能够带来很多性能劣势。
能够从一开始就牢记性能,能够确保所有内容都应用统一的数据结构来防止低廉的转换,并且能够在必要时进行宽泛的体系结构更改。毛病当然是多了很多工作。
例如,许多捆绑程序都应用官网的 TypeScript 编译器作为解析器。
然而,它是为实现 TypeScript 编译器团队的指标而构建的,它们没有将性能作为头等大事。
4. 内存的高效利用。
现实状况下,依据数据数据的长度,编译器的复杂度为 O(n).
如果要解决大量数据,内存访问速度可能会重大影响性能。
对数据进行的遍历次数越少(将数据转换成数据所需的不同示意模式也就越少),编译器就会越快。
例如,esbuild 仅涉及整个 JavaScript AST 3 次:
- 进行词法剖析,解析,作用域设置和申明符号的过程
- 绑定符号,最小化语法。比方:将 JSX / TS 转换为 JS, ES Next 转换为 es5。
- 最小标识符,最小空格,生成代码。
当 AST 数据在 CPU 缓存中依然处于沉闷状态时,会最大化 AST 数据的重用。
其余打包器在独自的过程中执行这些步骤,而不是将它们交错在一起。
它们也能够在数据表示之间进行转换,将多个库组织在一起(例如: 字符串→TS→JS→字符串,而后字符串→JS→旧的 JS→字符串,而后字符串→JS→minified JS→字符串)。
这样会占用更多内存,并且会减慢速度。
Go 的另一个益处是它能够将内容紧凑地存储在内存中,从而使它能够应用更少的内存并在 CPU 缓存中包容更多内容。
所有对象字段的类型和字段都严密地包装在一起,例如几个布尔标记每个仅占用一个字节。
Go 还具备值语义,能够将一个对象间接嵌入到另一个对象中,因而它是 ’ 收费的 ’,无需另外调配。
JavaScript 不具备这些性能,还具备其余毛病,例如 JIT 开销(例如暗藏的类插槽)和低效的示意模式(例如,非整数与指针堆调配)。
以上的每一条因素,都能在肯定水平上进步编译速度。
当它们独特工作时,成果比当今通常应用的其余打包器快几个数量级。
以上内容比拟繁琐,对此,也有一些网友做了简要的总结:
- 它是用
Go
语言编写的,该语言能够编译为本地代码。而且 Go 的执行速度很快。一般来说,JS 的操作是毫秒级
,而 Go 则是纳秒级
。 解析
,生成最终打包文件和生成 source maps 的操作全副齐全并行化无需低廉的数据转换
,只需很少的几步即可实现所有操作- 该库
以进步编译速度为编写代码时的第一准则
,并尽量避免不必要的内存调配。
仅作参考。
Upcoming roadmap
以下这几个 feature 曾经在进行中了, 而且是第一优先级:
Code splitting
(#16, docs)CSS content type
(#20, docs)Plugin API
(#111)
上面这几个 fearure 比拟有后劲, 然而还不确定:
HTML content type
(#31)Lowering to ES5
(#297)Bundling top-level await
(#253)
感兴趣的能够放弃关注。
esbuild 在 vite 中的使用
vite
中大量应用了 esbuild
, 这里简略分享两点。
optimizer
import {build, BuildOptions as EsbuildBuildOptions} from 'esbuild'
// ...
const result = await build({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
})
const meta = result.metafile!
// the paths in `meta.outputs` are relative to `process.cwd()`
const cacheDirOutputPath = path.relative(process.cwd(), cacheDir)
for (const id in deps) {const entry = 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
文件
为什么生产环境仍需打包?
只管原生 ESM
当初失去了 广泛支持
,但因为嵌套导入会导致 额定的网络往返
,在生产环境中公布未打包的 ESM 依然效率低下(即便应用 HTTP/2
)。
为了在生产环境中取得 最佳的加载性能
,最好还是将代码进行 tree-shaking
、 懒加载
和 chunk 宰割
(以取得更好的 缓存
)。
要确保 开发
服务器和 产品构建
之间的 最佳输入
和行为
达到统一,并不容易。
为解决这个问题,Vite 附带了一套 构建优化
的 构建命令
,开箱即用。
为何 vite 不必 esbuild 打包?
尽管 esbuild
快得惊人,并且曾经是一个在构建库方面比拟杰出的工具,但一些针对构建利用的重要性能依然还在继续开发中 —— 特地是 代码宰割
和 CSS 解决
方面。
就目前来说,Rollup
在利用打包方面, 更加成熟和灵便。
尽管如此,当将来这些性能稳固后,也不排除应用 esbuild 作为 生产构建器
的可能。
总结
esbuild 为构建提效带来了曙光,而且 esm 的数量也在疾速减少:
心愿 esm
生态尽快欠缺起来, 造福前端。
—
明天的内容就这么多,心愿对大家有所启发。
满腹经纶,文章若有谬误,欢送斧正,谢谢。
参考链接
- https://esbuild.github.io/get…
- https://esbuild.github.io/faq/
- https://twitter.com/skypackjs…