语法
一、认识
使用 Esbuild
有 2
种方式,分别是命令行调用和代码调用。
二、代码调用
Esbuild
对外暴露了一系列的 API
,主要包括两类: Build API
和Transform API
,我们可以在 Nodejs
代码中通过调用这些 API
来使用 Esbuild
的各种功能。
2.1 Build API
Build API
主要用来进行项目打包,包括build
、buildSync
和serve
三个方法。
首先我们来试着在 Node.js
中使用 build
方法。你可以在项目根目录新建 build.js
文件,内容如下:
const { build, buildSync, serve } = require("esbuild");
async function runBuild() {
// 异步方法,返回一个 Promise
const result = await build({
// ---- 如下是一些常见的配置 ---
// 当前项目根目录
absWorkingDir: process.cwd(),
// 入口文件列表,为一个数组
entryPoints: ["./src/index.jsx"],
// 打包产物目录
outdir: "dist",
// 是否需要打包,一般设为 true
bundle: true,
// 模块格式,包括`esm`、`commonjs`和`iife`
format: "esm",
// 需要排除打包的依赖列表
external: [],
// 是否开启自动拆包
splitting: true,
// 是否生成 SourceMap 文件
sourcemap: true,
// 是否生成打包的元信息文件
metafile: true,
// 是否进行代码压缩
minify: false,
// 是否开启 watch 模式,在 watch 模式下代码变动则会触发重新打包
watch: false,
// 是否将产物写入磁盘
write: true,
// Esbuild 内置了一系列的 loader,包括 base64、binary、css、dataurl、file、js(x)、ts(x)、text、json
// 针对一些特殊的文件,调用不同的 loader 进行加载
loader: {
'.png': 'base64',
}
});
console.log(result);
}
runBuild();
随后,你在命令行执行 node build.js
,就能在控制台发现如下日志信息:
以上就是 Esbuild
打包的元信息,这对我们编写插件扩展 Esbuild
能力非常有用。
接着,我们再观察一下 dist
目录,发现打包产物和相应的 SourceMap
文件也已经成功写入磁盘:
其实 buildSync
方法的使用几乎相同,如下代码所示:
function runBuild() {
// 同步方法
const result = buildSync({
// 省略一系列的配置
});
console.log(result);
}
runBuild();
但我并不推荐大家使用 buildSync
这种同步的 API
,它们会导致两方面不良后果。一方面容易使 Esbuild
在当前线程阻塞,丧失并发任务处理的优势。另一方面,Esbuild
所有插件中都不能使用任何异步操作,这给插件开发增加了限制。
因此我更推荐大家使用 build
这个异步 API
,它可以很好地避免上述问题。
在项目打包方面,除了 build
和 buildSync
,Esbuild
还提供了另外一个比较强大的 API——serve
。这个 API
有 3
个特点。
-
开启
serve
模式后,将在指定的端口和目录上搭建一个静态文件服务,这个服务器用原生Go
语言实现,性能比Nodejs
更高 -
类似
webpack-dev-server
,所有的产物文件都默认不会写到磁盘,而是放在内存中,通过请求服务来访问。 -
每次请求到来时,都会进行重新构建(
rebuild
),永远返回新的产物。值得注意的是,触发rebuild
的条件并不是代码改动,而是新的请求到来。
下面,我们通过一个具体例子来感受一下。
// build.js
const { build, buildSync, serve } = require("esbuild");
function runBuild() {
serve(
{
port: 8000,
// 静态资源目录
servedir: './dist'
},
{
absWorkingDir: process.cwd(),
entryPoints: ["./src/index.jsx"],
bundle: true,
format: "esm",
splitting: true,
sourcemap: true,
ignoreAnnotations: true,
metafile: true,
}
).then((server) => {
console.log("HTTP Server starts at port", server.port);
});
}
runBuild();
我们在浏览器访问 localhost:8000
可以看到 Esbuild
服务器返回的编译产物如下所示:
后续每次在浏览器请求都会触发 Esbuild
重新构建,而每次重新构建都是一个增量构建的过程,耗时也会比首次构建少很多(一般能减少 70%
左右)。Serve API
只适合在开发阶段使用,不适用于生产环境。
2.2 Transform API
Esbuild
还专门提供了单文件编译的能力,即Transform API
,与 Build API
类似,它也包含了同步和异步的两个方法,分别是 transformSync
和 transform
。
首先,在项目根目录新建 transform.js
,内容如下:
// transform.js
const { transform, transformSync } = require("esbuild");
async function runTransform() {
// 第一个参数是代码字符串,第二个参数为编译配置
const content = await transform(
"const isNull = (str: string): boolean => str.length > 0;",
{
sourcemap: true,
loader: "tsx",
}
);
console.log(content);
}
runTransform();
transformSync
的用法类似,换成同步的调用方式即可。
function runTransform {
const content = await transformSync(/* 参数和 transform 相同 */)
console.log(content);
}
不过由于同步的 API
会使 Esbuild
丧失并发任务处理的优势(Build API
的部分已经分析过),我同样也不推荐大家使用transformSync
。出于性能考虑,Vite
的底层实现也是采用 transform
这个异步的 API
进行 TS
及 JSX
的单文件转译的。
三、命令行调用
命令行方式调用也是最简单的使用方式。我们先来写一些示例代码,新建 src/index.jsx
文件,内容如下:
// src/index.jsx
import Server from "react-dom/server";
let Greet = () => <h1>Hello, juejin!</h1>;
console.log(Server.renderToString(<Greet />));
接着到 package.json
中添加 build
脚本:
"scripts": {
"build": "./node_modules/.bin/esbuild src/index.jsx --bundle --outfile=dist/out.js"
}
现在,你可以在终端执行 pnpm run build
,可以发现如下的日志信息:
说明我们已经成功通过命令行完成了 Esbuild
打包!但命令行的使用方式不够灵活,只能传入一些简单的命令行参数,稍微复杂的场景就不适用了,所以一般情况下我们还是会用代码调用的方式。