「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
前言
esbuild是面向未来的前端打包工具,让我们从官方文档开始一起学习。 esbuild.github.io。
简介
访问首页,会显示使用esbuild打包three.js这个库所使用的时间与当下流行打包工具parcel2、rollup + terser、webpack 5的对比。从统计图可以轻易看出esbuild有很明显的速度优势(10-100倍)。
为什么会这么快?
esbuild使用go语言来编写,运行时AOT对比JIT的JavaScript,有着无需编译的速度优势。并且go有着运行时占用内存少,能充分利用宿主CPU、多线程、内存共享等特征优势。
开始使用
根据官网,先从安装开始 esbuild.github.io/getting-sta…
安装
在控制台支持node命令的前提下执行:
npm install esbuild
上述命令是针对控制台当前路径项目安装,而非全局安装。
安装完成后尝试执行esbuild:
./node_modules/.bin/esbuild --version
输出:
0.14.20
是目前esbuild发布在npm最新的稳定版本号。
打包试验
继续在当前项目做尝试,安装react和react-dom:
npm install react react-dom
并在当前项目根路径创建app.jsx内容如下:
import * as React from 'react'
import * as Server from 'react-dom/server'
let Greet = () => <h1>Hello, world!</h1>
console.log(Server.renderToString(<Greet />))
然后执行esbuild命令打包
./node_modules/.bin/esbuild app.jsx --bundle --outfile=out.js
输出:
当前项目文件内容:
out.js为什么这么大?
out.js内容就不贴出来了,主要是因为esbuild把react和react-dom两个依赖的代码都打包进去了,所以才这么大。
也正因为这样,out.js是可以单独拿出来执行的,无需额外安装依赖。
可以把out.js拷贝出来放到任意目录,然后对应控制台执行:
node out.js
输出:
<h1 data-reactroot="">Hello, world!</h1>
🤔,这不就是ssr吗!
npm script 配置
每次都用./node_modules/.bin/esbuild使用esbuild太麻烦了,优先使用node_modules/bin目录作为环境变量,本来就是npm script该干的事情啊。
在项目package.json加入以下脚本:
{
"scripts": {
"build": "esbuild app.jsx --bundle --outfile=out.js"
}
}
然后控制台执行:
npm run build
😊一样能生成出打包后的产物out.js
除了使用命令行外,esbuild还提供build等其他api方法,供在nodejs运行时中打包使用。
项目根路径创建esbuild.js,内容如下:
require('esbuild').build({
entryPoints: ['app.jsx'],
bundle: true,
outfile: 'out.js',
}).catch(() => process.exit(1))
然后控制台执行:
node esbuild.js
🥸一样能生成出打包后的产物out.js
定制浏览器运行产物
说在前面,esbuild目前官方是尚不支持将 ES6+ 语法转换为 ES5,有兼容ie11、Android4.4.4、Opera Mini的同学,可能需要额外用babel或者swc等工具先对源码做一下目标浏览器的polyfill。
--minify
生产环境使用,用于压缩混淆产物代码。我们基于package.json基础上新增npm script:
"build:min": "esbuild app.jsx --bundle --outfile=out.js --minify"
然后,控制台执行
npm run build:min
输出:
--sourcemap
一般开发时需要,生成sourcemap。
打包时候额外生成out.js.map,并在out.js末尾追加:
//# sourceMappingURL=out.js.map
--watch
一般开发时需要,输入文件变化自动重新执行命令行。
--target
esbuild 支持所有现代 JavaScript 语法。但是,较旧的浏览器可能不支持较新的语法,因此您可能需要配置target选项来告诉 esbuild 将较新的语法适当地转换为较旧的语法。
- 这些语法功能总是针对旧版浏览器进行转换:
| 语法转换 | 语言版本 | 例子 |
|---|---|---|
| 函数参数列表和调用中的尾随逗号 | es2017 | foo(a, b, ) |
| 数字分隔符 | esnext | 1_000_000 |
- 根据配置的语言目标,这些语法功能会针对旧版浏览器进行有条件的转换:
| 语法转换 | 转换时--target低于 | 例子 |
|---|---|---|
| 幂运算符 | es2016 | a ** b |
| 异步函数 | es2017 | async () => {} |
| 传播属性 | es2018 | let x = {...y} |
| 休息属性 | es2018 | let {...x} = y |
| 可选的捕获绑定 | es2019 | try {} catch {} |
| 可选链接 | es2020 | a?.b |
| 无效合并 | es2020 | a ?? b |
import.meta | es2020 | import.meta |
| 逻辑赋值运算符 | es2021 | a ??= b |
| 类实例字段 | esnext | class { x } |
| 静态类字段 | esnext | class { static x } |
| 私有实例方法 | esnext | class { #x() {} } |
| 私有实例字段 | esnext | class { #x } |
| 私有静态方法 | esnext | class { static #x() {} } |
| 私有静态字段 | esnext | class { static #x } |
| 符合人体工程学的品牌检查 | esnext | #x in y |
| 导入断言 | esnext | import "x" assert {} |
| 类静态块 | esnext | class { static {} } |
- 这些语法特征目前总是通过未转换的:
| 语法转换 | --target低于时不支持 | 例子 |
|---|---|---|
| 异步迭代 | es2018 | for await (let x of y) {} |
| 异步生成器 | es2018 | async function* foo() {} |
| 大整数 | es2020 | 123n |
| Hashbang 语法 | esnext | #!/usr/bin/env node |
| 顶级等待 | esnext | await import(x) |
| 任意模块命名空间标识符 | esnext | export {foo as 'f o o'} |
API
esbuild的API可以通过三种方式调用:命令行、JavaScript 和 Go。
esbuild有两个主要的API:transform和build。了解应使用哪一个很重要,因为它们的工作方式不同。
Transform API
transform API是纯字符串操作的API,它的输入输出都是字符串。无需文件系统支持,适合用在工具链之中。
如 使用node命令行交互输入:
require('esbuild').transformSync('let x: number = 1', { loader: 'ts', })
输出:
Build API
build API是文件操作的API,传入单个或多个文件路径,输出单个或多个产物文件。
如 创建in.ts内容如下:
let x: number = 1
然后控制台执行以下命令行:
./node_modules/.bin/esbuild in.ts --outfile=out.js
生成out.js内容如下:
let x = 1;
同样,也可以使用node命令行交互:
require('esbuild').buildSync({ entryPoints: ['in.ts'], outfile: 'out.js', })
也是会生成一样内容的out.js。
支持内容
目前esbuild 默认loader支持的文件内容格式有:
- JavaScript:
.js .cjs .mjs - JSX:
.jsx - TypeScript:
.ts .tsx .cts .mts - JSON:
.json - CSS:
.css - TEXT:
.txt
插件
最后是插件部分,目前官方解释该部分还是试验阶段,有可能发生重大变化,就不一一细说了,HHH😂。有兴趣的伙伴请见 esbuild.github.io/plugins/
总结
前端工具链内卷越发激烈,最近esbuild swc热起,使用go rust对传统编译工具(babel)发起了降维打击。且越发成熟,大有取代趋势。搬砖之余需要多多观望与了解。期待后续深度内容,😚感谢阅读。