Vite 双引擎架构 —— Esbuild 概念篇

Vite 底层采用 双引擎架构 ,核心构建引擎是 EsbuildRollup ,二者在开发和生产环境中分工协作,共同实现高性能构建。不可否认,作为 Vite 的双引擎之一,Esbuild 在很多关键的构建阶段(如依赖预编译TS 语法转译代码压缩)让 Vite 获得了相当优异的性能,是 Vite 高性能的得力助手。无论是在 Vite 的配置项还是源码实现中,都包含了不少 Esbuild 本身的基本概念和高阶用法。因此,要深入掌握 Vite,学习 Esbuild 必不可少。

本篇文章我将由 Esbuild 开始,讲解 Vite 强大的双引擎结构。强烈建议上手操作,读两三遍,不如上手写一遍 。《esbuild 中文文档

🔍 一、为什么 Esbuild 的性能极高?

极速构建:

  • 性能碾压传统工具:基于 Go 语言编写,多进程并行处理,比 Webpack/Rollup 快 10-100 倍(10个 three.js 副本打包仅需 0.39秒 vs Webpack 的 41秒) 。
  • 无缓存仍高效 :内置优化算法,无需依赖缓存即可实现秒级编译 。

开箱即用的支持:

  • 语言支持:原生处理 JS、TS、JSX、CSS(含 CSS Modules),无需额外配置 。
  • 模块化 :无缝捆绑 ESM 和 CommonJS 模块,自动树摇(Tree Shaking) 。

多场景适配:

  • 浏览器环境 :默认输出浏览器兼容代码,支持 --minify 压缩、--sourcemap 源码映射 。
  • Node 环境 :通过 --platform=node 打包,剥离 TS 类型、转换 ESM→CommonJS 。

🛠️ 二、 Esbuild 安装与使用

复制代码
npm install esbuild  # 或 yarn add esbuild

1. 命令行调用

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

复制代码
import Server from "react-dom/server";

let Greet = () => <h1>祝所有高三的同学,金榜题名!</h1>;
console.log(Server.renderToString(<Greet />));

注意安装一下所需的依赖,在终端执行如下的命令:

复制代码
npm install react react-dom

接着到package.json中添加build脚本:

复制代码
 "scripts": {
    "build": "esbuild src/index.jsx --bundle --outfile=dist/out.js",
 },

现在,你可以在终端执行npm run build,可以发现如下的日志信息:

接着我们就可以看到dish目录中的打包产物

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

2. 代码调用

Esbuild 对外暴露了一系列的 API,主要包括两类: Build APITransform API,我们可以在 Nodejs 代码中通过调用这些 API 来使用 Esbuild 的各种功能。想要更全面的了解的,可以去访问文章开头的文档地址。

项目打包------Build API

Build API主要用来进行项目打包,包括**build** 、buildSync 和 **serve**三个方法。

A、build 方法:异步打包

功能:执行异步构建任务,返回 Promise 对象,支持插件和并行操作。

适用场景:生产环境打包、复杂构建流程(如代码分割、压缩)。

首先我们来试着在 Node.js 中使用build 方法。你可以在项目根目录新建build.js文件,内容如下:

复制代码
import { build } from '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,就能在控制台发现如下日志信息:

接着我们就可以看到dish目录中的打包产物和相应的 SourceMap 文件

B、buildSync 方法:同步打包 (不推荐)

功能 :同步执行构建任务,立即返回结果,但阻塞主线程

适用场景:小型项目、简单脚本或 CLI 工具。

一个简单的例子:

复制代码
const result = esbuild.buildSync({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
  platform: 'node' // 指定 Node 环境
});

if (result.errors.length > 0) {
  throw new Error('Build failed');
}

局限性

  • 性能影响:阻塞主线程,可能导致界面卡顿
  • 插件限制 :Rollup 等工具的 buildSync 不支持插件
  • 适用性:仅推荐在轻量任务中使用

**难道就不能使用同步打包了吗??**如果说有,其实也是有的

使用 build + await 实现伪同步:

复制代码
async function runBuild() {
  await esbuild.build({ /* 配置 */ });
}
C、serve 方法:开发服务器

这个 API 有 3 个特点。

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

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

  3. 每次请求 到来时,都会进行重新构建(rebuild),永远返回新的产物。

下面,我们通过一个具体例子来感受一下。

复制代码
// build.js
import { serve } from 'esbuild';

function runBuild() {
    serve(
        {
            port: 8000,
            servedir: './dist',
            onRequest: (args) => {
                if (args.path === '/') {
                    args.path = '/index.html'
                }
            }
        },
        {
            absWorkingDir: process.cwd(),
            entryPoints: ["./src/index.jsx"],
            bundle: true,
            format: "esm",
            splitting: true,
            sourcemap: true,
            outdir: "dist",
            loader: {
                '.js': 'jsx',
                '.png': 'file',
                '.jpg': 'file'
            }
        }
    ).then((server) => {
        console.log("HTTP Server starts at port", server.port);
    });
}

runBuild();

1.运行构建命令

复制代码
npm run build

2.启动服务器

复制代码
node build.js

我们在浏览器访问http://localhost:8000/dist/index.js可以看到 Esbuild 服务器返回的编译产物如下所示:

后续每次在浏览器请求都会触发 Esbuild 重新构建,而每次重新构建都是一个增量构建的过程,耗时也会比首次构建少很多(一般能减少 70% 左右)。

Serve API 只适合在开发阶段使用,不适用于生产环境。

单文件转译------Transform API

功能 :对单个字符串内容进行转换(如转译 TS/JSX),不访问文件系统,适用于非文件环境(如浏览器内联处理)或作为工具链一环。

Build API 类似,它也包含了同步和异步的两个方法,分别是transformSynctransform

举例栗子:在项目根目录新建transform.js

复制代码
// transform.js
import { transform, transformSync } from 'esbuild';

async function runTransform() {
    // 第一个参数是代码字符串,第二个参数为编译配置
    const content = await transform(
        "const isNull = (str: string): boolean => str.length > 0;",
        {
            sourcemap: true,
            loader: "tsx",
        }
    );
    console.log(content);
}

runTransform();

终端输入:

复制代码
node transform.js

接着你就会看见:

同样的步骤,传参:

复制代码
// transform.js
import { transform, transformSync } from 'esbuild';

async function runTransform(code = "const isNull = (str: string): boolean => str.length > 0;") {
    // 第一个参数是代码字符串,第二个参数为编译配置
    const content = await transform(
        code,
        {
            sourcemap: true,
            loader: "tsx",
        }
    );
    console.log(content);
}

const inputCode = process.argv[2];
runTransform(inputCode).catch(console.error);

终端输入:

复制代码
node transform.js "const add = (a: number, b: number): number => a + b;"

打印出:

由于同步的 API 会使 Esbuild 丧失并发任务处理的优势(Build API的部分已经分析过),我同样也不推荐大家使用**transformSync** 。出于性能考虑,Vite 的底层实现也是采用 **transform**这个异步的 API 进行 TS 及 JSX 的单文件转译的。

📊 三、总结

Esbuild的优势在于编译速度非常快,且拥有Go语言的优势,Go语言编写的程序比JavaScript少了一个动态解释的过程;在代码实现上,Esbuild使用比较克制,很多在Webpack上使用插件实现的功能如loader、minify等均使用Go实现。;劣势在于支持不完善,提供的功能很基础,对代码分割和css处理等支持较弱。

优势
  • 速度为王 :Go 语言 + 并行处理 + 内置功能(减少 AST 转换链) 。
  • 轻量 API :提供 CLI、JS、Go 三种接口,配置简洁 。
  • 生产优化:默认支持 Tree Shaking、代码压缩、Source Map 。
局限性

1.生态插件较弱

  • 不支持 Vue/Sass/Less 等语法,需 JS 插件(性能下降) 。
  • 无热更新(HMR),依赖 --watch 或手动重启 。

2.高级功能缺失

  • 无 AST 操作接口,无法实现类 Babel 按需引入 。
  • 代码分割(Code Splitting)对非 ESM 包支持差 。

但是不可否认,它的作用和潜力,我相信 Esbuild未来在持续迭代中, 生态完善后或颠覆前端构建范式。

相关推荐
SimonKing1 小时前
拯救大文件上传:一文彻底彻底搞懂秒传、断点续传以及分片上传
java·后端·架构
数据智能老司机1 小时前
Linux内核编程——网络驱动程序
linux·架构·操作系统
vivo互联网技术1 小时前
号码生成系统的创新实践:游戏周周乐幸运码设计
redis·后端·架构
Java技术小馆2 小时前
POST为什么发送两次请求
java·面试·架构
摸鱼仙人~2 小时前
重塑智能体决策路径:深入理解 ReAct 框架
前端·react.js·前端框架
百度Geek说2 小时前
搜索数据建设系列之数据架构重构
数据仓库·重构·架构·spark·dubbo
DemonAvenger2 小时前
Go内存压力测试:模拟与应对高负载的技术文章
性能优化·架构·go
DemonAvenger2 小时前
从C/C++迁移到Go:内存管理思维转变
性能优化·架构·go
大只鹅2 小时前
两级缓存 Caffeine + Redis 架构:原理、实现与实践
redis·缓存·架构
数据智能老司机2 小时前
Linux内核编程——字符设备驱动程序
linux·架构·操作系统