跳到主要内容

语法

2023年12月19日
柏拉文
越努力,越幸运

一、认识


使用 Esbuild2 种方式,分别是命令行调用和代码调用。

二、代码调用


Esbuild 对外暴露了一系列的 API,主要包括两类: Build APITransform API,我们可以在 Nodejs 代码中通过调用这些 API 来使用 Esbuild 的各种功能。

2.1 Build API

Build API 主要用来进行项目打包,包括buildbuildSyncserve三个方法。

首先我们来试着在 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,就能在控制台发现如下日志信息:

Preview

以上就是 Esbuild 打包的元信息,这对我们编写插件扩展 Esbuild 能力非常有用。

接着,我们再观察一下 dist 目录,发现打包产物和相应的 SourceMap 文件也已经成功写入磁盘:

Preview

其实 buildSync 方法的使用几乎相同,如下代码所示:

function runBuild() {
// 同步方法
const result = buildSync({
// 省略一系列的配置
});
console.log(result);
}

runBuild();

但我并不推荐大家使用 buildSync 这种同步的 API,它们会导致两方面不良后果。一方面容易使 Esbuild 在当前线程阻塞,丧失并发任务处理的优势。另一方面,Esbuild 所有插件中都不能使用任何异步操作,这给插件开发增加了限制。

因此我更推荐大家使用 build 这个异步 API,它可以很好地避免上述问题。

在项目打包方面,除了 buildbuildSyncEsbuild 还提供了另外一个比较强大的 API——serve。这个 API3 个特点。

  1. 开启 serve 模式后,将在指定的端口和目录上搭建一个静态文件服务,这个服务器用原生 Go 语言实现,性能比 Nodejs 更高

  2. 类似 webpack-dev-server,所有的产物文件都默认不会写到磁盘,而是放在内存中,通过请求服务来访问。

  3. 每次请求到来时,都会进行重新构建(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 服务器返回的编译产物如下所示:

Preview

后续每次在浏览器请求都会触发 Esbuild 重新构建,而每次重新构建都是一个增量构建的过程,耗时也会比首次构建少很多(一般能减少 70% 左右)。Serve API 只适合在开发阶段使用,不适用于生产环境。

2.2 Transform API

Esbuild 还专门提供了单文件编译的能力,即Transform API,与 Build API 类似,它也包含了同步和异步的两个方法,分别是 transformSynctransform

首先,在项目根目录新建 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 进行 TSJSX 的单文件转译的。

三、命令行调用


命令行方式调用也是最简单的使用方式。我们先来写一些示例代码,新建 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,可以发现如下的日志信息:

Preview

说明我们已经成功通过命令行完成了 Esbuild 打包!但命令行的使用方式不够灵活,只能传入一些简单的命令行参数,稍微复杂的场景就不适用了,所以一般情况下我们还是会用代码调用的方式。