Vite: Esbuild的使用与其插件开发

概述

  • 作为 Vite 的双引擎之一,Esbuild 在很多关键的构建阶段(如 依赖预编译 、 TS 语法转译 、 代码压缩 ) 让 Vite 获得了相当优异的性能,是 Vite 高性能的得力助手
  • 无论是在 Vite 的配置项还是源码实现中,都包含了不少 Esbuild 本身的基本概念和高阶用法

高性能的Esbuild

  • Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以比传统工具快 10~100 倍。获得这样超高的构建性能,主要原因可以概括为 4 点

1 )使用 Golang 开发

  • 构建逻辑代码直接被编译为原生机器码,而不用像 JS 一样先代码解析为字节码,然后转换为机器码,大大节省了程序运行时间。

2 ) 多核并行

  • 内部打包算法充分利用多核 CPU 优势,所有的步骤尽可能并行,这也是
    得益于 Go 当中多线程共享内存的优势。

3 )从零造轮子

  • 几乎没有使用任何第三方库,所有逻辑自己编写,大到 AST 解析,小
    到字符串的操作,保证极致的代码性能

4 )高效的内存利用

  • Esbuild 中从头到尾尽可能地复用一份 AST 节点数据,而不用像 JS 打包工具中频繁地解析和传递 AST 数据(如 string -> TS -> JS -> string),造成内存的大量浪费

使用

  • 执行 pnpm init -y 新建一个项目, 然后通过如下的命令完成 Esbuild 的安装:
    • $ pnpm i esbuild
  • 使用 Esbuild 有 2 种方式,分别是 命令行调用和代码调用

1 )命令行调用

  • 命令行方式调用也是最简单的使用方式。我们先来写一些示例代码,新建 src/index.jsx 文件,内容如下

    // src/index.jsx
    import Server from "react-dom/server";
    
    let Greet = () => <h1>Hello, juejin!</h1>;
    console.log(Server.renderToString(<Greet />));
    
  • 注意安装一下所需的依赖,在终端执行如下的命令:

    • $ pnpm install react react-dom
  • 接着到 package.json 中添加 build 脚本:

    "scripts": {
         
         
    	 "build": "./node_modules/.bin/esbuild src/index.jsx --bundle --outfile=dist/out.js"
    },
    
  • 现在,你可以在终端执行 $ pnpm run build,可以发现如下的日志信息

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

2 ) 代码调用

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

项目打包——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
 	// 针对一些特殊的文件,调用不同的 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 只适合在开发阶段使用,不适用于生产环境。

单文件转译——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 >
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值