API

API 可以通过三种语言访问:命令行、JavaScript 和 Go。三种语言的概念和参数基本相同,因此它们将在此处一起介绍,而不是为每种语言提供单独的文档。你可以使用每个代码示例右上角的 CLIJSGo 选项卡在语言之间切换。每种语言的一些具体细节

概述

esbuild API 中最常用的两个 API 是 buildtransform。下面将对每个 API 进行高级别的描述,然后是每个 API 选项的文档。

构建

这是 esbuild 的主要接口。你通常会传递一个或多个 入口点 文件进行处理,以及各种选项,然后 esbuild 将结果写回文件系统。以下是一个简单的示例,它启用了 打包输出目录

CLI JS Go
esbuild app.ts --bundle --outdir=dist
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})
console.log(result)
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "dist",
  })
  if len(result.Errors) != 0 {
    os.Exit(1)
  }
}

构建 API 的高级用法涉及设置一个长期运行的构建上下文。此上下文在 JS 和 Go 中是一个显式对象,但在 CLI 中是隐式的。使用给定上下文完成的所有构建都共享相同的构建选项,并且后续构建是增量完成的(即,它们重用先前构建中的一些工作以提高性能)。这对于开发很有用,因为 esbuild 可以在你工作时在后台为你重新构建应用程序。

有三种不同的增量构建 API

CLI JS Go
esbuild app.ts --bundle --outdir=dist --watch
[watch] build finished, watching for changes...
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

await ctx.watch()
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

err2 := ctx.Watch(api.WatchOptions{})
CLI JS Go
esbuild app.ts --bundle --outdir=dist --serve

 > Local:   http://127.0.0.1:8000/
 > Network: http://192.168.0.1:8000/

127.0.0.1:61302 - "GET /" 200 [1ms]
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

let { host, port } = await ctx.serve()
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

server, err2 := ctx.Serve(api.ServeOptions{})
CLI JS Go
# The CLI does not have an API for "rebuild"
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

for (let i = 0; i < 5; i++) {
  let result = await ctx.rebuild()
}
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

for i := 0; i < 5; i++ {
  result := ctx.Rebuild()
}

这三种增量构建 API 可以组合使用。要启用 实时重载(在你编辑和保存文件时自动重新加载页面),你需要在同一个上下文中同时启用 观察服务

当你完成上下文对象后,可以调用上下文上的 dispose() 来等待现有构建完成,停止观察和/或服务模式,并释放资源。

构建和上下文 API 都接受以下选项

转换

这是 build 的一个有限的特殊情况,它转换表示内存中文件的代码字符串,在一个与任何其他文件完全断开的隔离环境中。常见用途包括压缩代码和将 TypeScript 转换为 JavaScript。以下是一个示例

CLI JS Go
echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'

let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
  loader: 'ts',
})
console.log(result)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "let x: number = 1"
  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

使用字符串而不是文件作为输入对于某些用例来说更符合人体工程学。文件系统隔离具有一定的优势(例如,在浏览器中工作,不受附近 package.json 文件的影响)和一些劣势(例如,不能与 打包插件 一起使用)。如果你的用例不适合转换 API,那么你应该使用更通用的 build API。

转换 API 接受以下选项

JS 特定细节

esbuild 的 JS API 有异步和同步两种形式。建议使用 异步 API,因为它在所有环境中都能正常工作,并且更快、更强大。 同步 API 仅在 node 中工作,并且只能执行某些操作,但在某些特定于 node 的情况下有时是必要的。详细来说

异步 API

异步 API 调用使用 promise 返回其结果。请注意,你可能需要在 node 中使用 .mjs 文件扩展名,因为使用了 import 和顶层 await 关键字

import * as esbuild from 'esbuild'

let result1 = await esbuild.transform(code, options)
let result2 = await esbuild.build(options)

优点

缺点

同步 API

同步 API 调用会内联返回其结果

let esbuild = require('esbuild')

let result1 = esbuild.transformSync(code, options)
let result2 = esbuild.buildSync(options)

优点

缺点

在浏览器中

esbuild API 也可以在浏览器中使用 WebAssembly 在 Web Worker 中运行。要利用此功能,你需要安装 esbuild-wasm 包,而不是 esbuild

npm install esbuild-wasm

浏览器的 API 与 node 的 API 类似,除了你需要先调用 initialize(),并且你需要传递 WebAssembly 二进制文件的 URL。同步版本的 API 也不可用。假设你正在使用打包器,这将类似于以下内容

import * as esbuild from 'esbuild-wasm'

await esbuild.initialize({
  wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
})

let result1 = await esbuild.transform(code, options)
let result2 = esbuild.build(options)

如果你已经从 worker 中运行此代码,并且不希望 initialize 创建另一个 worker,你可以向它传递 worker: false。然后它将在调用 initialize 的线程的同一线程中创建一个 WebAssembly 模块。

你也可以在 HTML 文件中使用 esbuild 的 API 作为脚本标签,而无需使用打包器,方法是使用 <script> 标签加载 lib/browser.min.js 文件。在这种情况下,API 会创建一个名为 esbuild 的全局变量,其中包含 API 对象

<script src="./node_modules/esbuild-wasm/lib/browser.min.js"></script>
<script>
  esbuild.initialize({
    wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
  }).then(() => {
    ...
  })
</script>

如果你想将此 API 与 ECMAScript 模块一起使用,你应该导入 esm/browser.min.js 文件

<script type="module">
  import * as esbuild from './node_modules/esbuild-wasm/esm/browser.min.js'

  await esbuild.initialize({
    wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
  })

  ...
</script>

通用选项

打包

支持:构建

打包文件意味着将任何导入的依赖项内联到文件本身。此过程是递归的,因此依赖项的依赖项(等等)也将被内联。默认情况下,esbuild 不会 打包输入文件。打包必须像这样显式启用

CLI JS Go
esbuild in.js --bundle
import * as esbuild from 'esbuild'

console.log(await esbuild.build({
  entryPoints: ['in.js'],
  bundle: true,
  outfile: 'out.js',
}))
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"in.js"},
    Bundle:      true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

有关使用真实代码打包的示例,请参阅 入门指南

请注意,打包与文件连接不同。将多个输入文件传递给 esbuild 并启用打包将创建多个独立的包,而不是将输入文件连接在一起。要使用 esbuild 连接一组文件,请将它们全部导入到单个入口点文件中,然后仅使用 esbuild 打包该文件。

不可分析的导入

导入路径当前仅在它们是字符串文字或 glob 模式 时才会被打包。其他形式的导入路径不会被打包,而是会按原样保留在生成的输出中。这是因为打包是一个编译时操作,esbuild 不支持所有形式的运行时路径解析。以下是一些示例

// Analyzable imports (will be bundled by esbuild)
import 'pkg';
import('pkg');
require('pkg');
import(`./locale-${foo}.json`);
require(`./locale-${foo}.json`);

// Non-analyzable imports (will not be bundled by esbuild)
import(`pkg/${foo}`);
require(`pkg/${foo}`);
['pkg'].map(require);

解决不可分析导入的方法是将包含此问题代码的包标记为 外部,以便它不会包含在包中。然后,你需要确保在运行时,你的打包代码可以访问外部包的副本。

一些打包器,例如 Webpack,试图通过将所有可能访问的文件包含在包中,并在运行时模拟文件系统来支持所有形式的运行时路径解析。但是,运行时文件系统模拟超出了 esbuild 的范围,不会在 esbuild 中实现。如果您确实需要打包执行此操作的代码,您可能需要使用其他打包器而不是 esbuild。

Glob 风格的导入

现在可以在某些有限的情况下打包在运行时评估的导入路径。导入路径表达式必须是字符串连接的形式,并且必须以 ./../ 开头。字符串连接链中的每个非字符串表达式都成为 glob 模式中的通配符。一些例子

// These two forms are equivalent
const json1 = require('./data/' + kind + '.json')
const json2 = require(`./data/${kind}.json`)

当您执行此操作时,esbuild 将搜索文件系统以查找与模式匹配的所有文件,并将它们全部包含在包中,以及一个将匹配的导入路径映射到打包模块的映射。导入表达式将被替换为对该映射的查找。如果导入路径不在映射中,则会在运行时抛出错误。生成的代码将类似于以下内容(为了简洁起见,省略了不重要的部分)

// data/bar.json
var require_bar = ...;

// data/foo.json
var require_foo = ...;

// require("./data/**/*.json") in example.js
var globRequire_data_json = __glob({
  "./data/bar.json": () => require_bar(),
  "./data/foo.json": () => require_foo()
});

// example.js
var json1 = globRequire_data_json("./data/" + kind + ".json");
var json2 = globRequire_data_json(`./data/${kind}.json`);

此功能适用于 require(...)import(...),因为它们都可以接受运行时表达式。它不适用于 importexport 语句,因为它们不能接受运行时表达式。如果您想阻止 esbuild 尝试打包这些导入,您应该将字符串连接表达式移出 require(...)import(...)。例如

// This will be bundled
const json1 = require('./data/' + kind + '.json')

// This will not be bundled
const path = './data/' + kind + '.json'
const json2 = require(path)

请注意,使用此功能意味着 esbuild 可能需要执行大量文件系统 I/O 来查找可能与模式匹配的所有文件。这是设计使然,不是错误。如果这是一个问题,有两种方法可以减少 esbuild 执行的文件系统 I/O 量

  1. 最简单的方法是将您想为给定运行时导入表达式导入的所有文件放在一个子目录中,然后将该子目录包含在模式中。这将 esbuild 限制在该子目录内搜索,因为 esbuild 在模式匹配期间不考虑 .. 路径元素。

  2. 另一种方法是阻止 esbuild 搜索任何子目录。esbuild 使用的模式匹配算法只允许通配符匹配包含 / 路径分隔符的内容,如果该通配符在模式中之前有 /。因此,例如 './data/' + x + '.json' 将匹配任何子目录中的 x,而 './data-' + x + '.json' 将只匹配顶层目录中的 x(但不在任何子目录中)。

取消

支持:构建

如果您使用 rebuild 手动调用增量构建,您可能希望使用此取消 API 提早结束当前构建,以便您可以开始新的构建。您可以像这样操作

CLI JS Go
# The CLI does not have an API for "cancel"
import * as esbuild from 'esbuild'
import process from 'node:process'

let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'www',
  logLevel: 'info',
})

// Whenever we get some data over stdin
process.stdin.on('data', async () => {
  try {
    // Cancel the already-running build
    await ctx.cancel()

    // Then start a new build
    console.log('build:', await ctx.rebuild())
  } catch (err) {
    console.error(err)
  }
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "www",
    LogLevel:    api.LogLevelInfo,
  })
  if err != nil {
    os.Exit(1)
  }

  // Whenever we get some data over stdin
  buf := make([]byte, 100)
  for {
    if n, err := os.Stdin.Read(buf); err != nil || n == 0 {
      break
    }
    go func() {
      // Cancel the already-running build
      ctx.Cancel()

      // Then start a new build
      result := ctx.Rebuild()
      fmt.Fprintf(os.Stderr, "build: %v\n", result)
    }()
  }
}

确保在开始新的构建之前等待取消操作完成(即,在使用 JavaScript 时 await 返回的 promise),否则下一个 rebuild 将为您提供刚刚取消的构建,该构建尚未结束。请注意,插件 on-end 回调 将在构建是否被取消的情况下仍然运行。

实时重载

支持:构建

实时重载是一种开发方法,您可以在其中同时打开并查看浏览器和代码编辑器。当您编辑并保存源代码时,浏览器会自动重新加载,重新加载的应用程序版本包含您的更改。这意味着您可以更快地迭代,因为您不必在每次更改后手动切换到浏览器、重新加载,然后切换回代码编辑器。例如,在更改 CSS 时非常有用。

esbuild 没有直接用于实时重载的 API。相反,您可以通过结合 监视模式(在您编辑并保存文件时自动启动构建)和 服务模式(服务最新构建,但阻塞直到完成)以及一小段仅在开发期间添加到应用程序中的客户端 JavaScript 代码来构建实时重载。

第一步是同时启用 监视服务

CLI JS Go
esbuild app.ts --bundle --outdir=www --watch --servedir=www
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'www',
})

await ctx.watch()

let { host, port } = await ctx.serve({
  servedir: 'www',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "www",
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }

  result, err3 := ctx.Serve(api.ServeOptions{
    Servedir: "www",
  })
  if err3 != nil {
    os.Exit(1)
  }
}

第二步是在您的 JavaScript 中添加一些代码,这些代码订阅 /esbuild 服务器发送事件 源。当您收到 change 事件时,您可以重新加载页面以获取应用程序的最新版本。您可以在一行代码中执行此操作

new EventSource('/esbuild').addEventListener('change', () => location.reload())

就是这样!如果您在浏览器中加载应用程序,现在当您编辑并保存文件时,页面应该会自动重新加载(假设没有构建错误)。

这应该只在开发期间包含,并且不应包含在生产中。在生产中删除此代码的一种方法是使用 if 语句(例如 if (!window.IS_PRODUCTION))对其进行保护,然后使用 define 在生产中将 window.IS_PRODUCTION 设置为 true

实时重载注意事项

以这种方式实现实时重载有一些已知的注意事项

CSS 的热重载

change 事件还包含其他信息以启用更高级的使用案例。它目前包含 addedremovedupdated 数组,其中包含自上次构建以来已更改的文件的路径,这些路径可以用以下 TypeScript 接口描述

interface ChangeEvent {
  added: string[]
  removed: string[]
  updated: string[]
}

下面的代码示例启用了 CSS 的“热重载”,即在不重新加载页面的情况下自动更新 CSS。如果到达的事件与 CSS 无关,则整个页面将作为回退重新加载

new EventSource('/esbuild').addEventListener('change', e => {
  const { added, removed, updated } = JSON.parse(e.data)

  if (!added.length && !removed.length && updated.length === 1) {
    for (const link of document.getElementsByTagName("link")) {
      const url = new URL(link.href)

      if (url.host === location.host && url.pathname === updated[0]) {
        const next = link.cloneNode()
        next.href = updated[0] + '?' + Math.random().toString(36).slice(2)
        next.onload = () => link.remove()
        link.parentNode.insertBefore(next, link.nextSibling)
        return
      }
    }
  }

  location.reload()
})

JavaScript 的热重载

esbuild 目前没有实现 JavaScript 的热重载。可以透明地实现 CSS 的热重载,因为 CSS 是无状态的,但 JavaScript 是有状态的,因此您无法像 CSS 那样透明地实现 JavaScript 的热重载。

一些其他开发服务器仍然为 JavaScript 实现了热重载,但这需要额外的 API,有时需要特定于框架的黑客,有时会在编辑会话期间引入瞬态状态相关的错误。这样做超出了 esbuild 的范围。如果您需要 JavaScript 的热重载,欢迎您使用其他工具而不是 esbuild。

但是,使用 esbuild 的实时重载,您可以将应用程序的当前 JavaScript 状态持久化到 sessionStorage 中,以便在页面重新加载后更容易恢复应用程序的 JavaScript 状态。如果您的应用程序加载速度很快(对于用户的利益,它应该已经很快),使用 JavaScript 的实时重载几乎可以与使用 JavaScript 的热重载一样快。

平台

受支持:构建转换

默认情况下,esbuild 的打包器配置为生成针对浏览器的代码。如果您的打包代码旨在在 node 中运行,您应该将平台设置为 node

CLI JS Go
esbuild app.js --bundle --platform=node
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  platform: 'node',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Platform:    api.PlatformNode,
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

当平台设置为 browser(默认值)时

当平台设置为 node

当平台设置为 neutral

另请参阅 为浏览器捆绑为 Node.js 捆绑

重新构建

支持:构建

如果您需要重复使用相同的选项调用 esbuild 的 build API,您可能需要使用此 API。例如,如果您正在实现自己的文件监视器服务,这将很有用。重新构建比再次构建更有效,因为来自先前构建的一些数据被缓存,如果原始文件自上次构建以来没有更改,则可以重复使用这些数据。目前,重建 API 使用两种形式的缓存

以下是如何进行重新构建

CLI JS Go
# The CLI does not have an API for "rebuild"
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})

// Call "rebuild" as many times as you want
for (let i = 0; i < 5; i++) {
  let result = await ctx.rebuild()
}

// Call "dispose" when you're done to free up resources
ctx.dispose()
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outfile:     "out.js",
  })
  if err != nil {
    os.Exit(1)
  }

  // Call "Rebuild" as many times as you want
  for i := 0; i < 5; i++ {
    result := ctx.Rebuild()
    if len(result.Errors) > 0 {
      os.Exit(1)
    }
  }

  // Call "Dispose" when you're done to free up resources
  ctx.Dispose()
}

服务

支持:构建

如果您希望您的应用程序在您编辑时自动重新加载,您应该阅读有关 实时重新加载 的内容。它将服务模式与 监视模式 相结合,以监听文件系统的更改。

服务模式启动一个 Web 服务器,该服务器将您的代码提供给您设备上的浏览器。以下是一个将 src/app.ts 捆绑到 www/js/app.js,然后还通过 https://127.0.0.1:8000/ 提供 www 目录的示例

CLI JS Go
esbuild src/app.ts --outdir=www/js --bundle --servedir=www
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['src/app.ts'],
  outdir: 'www/js',
  bundle: true,
})

let { host, port } = await ctx.serve({
  servedir: 'www',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"src/app.ts"},
    Outdir:     "www/js",
    Bundle:      true,
  })
  if err != nil {
    os.Exit(1)
  }

  server, err2 := ctx.Serve(api.ServeOptions{
    Servedir: "www",
  })
  if err2 != nil {
    os.Exit(1)
  }

  // Returning from main() exits immediately in Go.
  // Block forever so we keep serving and don't exit.
  <-make(chan struct{})
}

如果您创建了包含以下内容的文件 www/index.html,那么当您导航到 https://127.0.0.1:8000/ 时,src/app.ts 中包含的代码将加载。

<script src="js/app.js"></script>

使用 esbuild 内置的 Web 服务器而不是其他 Web 服务器的一个好处是,无论何时您重新加载,esbuild 提供的文件始终是最新的。其他开发设置不一定如此。一种常见的设置是运行一个本地文件监视器,该监视器在输入文件发生更改时重建输出文件,然后单独运行一个本地文件服务器来提供这些输出文件。但这意味着在编辑后重新加载可能会重新加载旧的输出文件,如果重建尚未完成。使用 esbuild 的 Web 服务器,每个传入请求都会启动一个重建(如果还没有进行重建),然后等待当前重建完成,然后再提供文件。这意味着 esbuild 永远不会提供过时的构建结果。

请注意,此 Web 服务器仅用于开发。请勿在生产环境中使用它。

参数

serve API 的参数如下

CLI JS Go
# Enable serve mode
--serve

# Set the port
--serve=9000

# Set the host and port (IPv4)
--serve=127.0.0.1:9000

# Set the host and port (IPv6)
--serve=[::1]:9000

# Set the directory to serve
--servedir=www

# Enable HTTPS
--keyfile=your.key --certfile=your.cert

# Specify a fallback HTML file
--serve-fallback=some-file.html
interface ServeOptions {
  port?: number
  host?: string
  servedir?: string
  keyfile?: string
  certfile?: string
  fallback?: string
  onRequest?: (args: ServeOnRequestArgs) => void
}

interface ServeOnRequestArgs {
  remoteAddress: string
  method: string
  path: string
  status: number
  timeInMS: number
}
type ServeOptions struct {
  Port      uint16
  Host      string
  Servedir  string
  Keyfile   string
  Certfile  string
  Fallback  string
  OnRequest func(ServeOnRequestArgs)
}

type ServeOnRequestArgs struct {
  RemoteAddress string
  Method        string
  Path          string
  Status        int
  TimeInMS      int
}

返回值

CLI JS Go
# The CLI will print the host and port like this:

 > Local: http://127.0.0.1:8000/
interface ServeResult {
  host: string
  port: number
}
type ServeResult struct {
  Host string
  Port uint16
}

启用 HTTPS

默认情况下,esbuild 的 Web 服务器使用 http:// 协议。但是,某些现代 Web 功能对 HTTP 网站不可用。如果您想使用这些功能,那么您需要告诉 esbuild 使用 https:// 协议。

要使用 esbuild 启用 HTTPS

  1. 生成一个自签名证书。有很多方法可以做到这一点。以下是一种方法,假设您已安装 openssl 命令

     openssl req -x509 -newkey rsa:4096 -keyout your.key -out your.cert -days 9999 -nodes -subj /CN=127.0.0.1 
  2. 使用 keyfilecertfile serve 参数your.keyyour.cert 传递给 esbuild。

  3. 当您加载页面时,单击浏览器中的可怕警告(自签名证书不安全,但这并不重要,因为我们只是在进行本地开发)。

如果您有比这更复杂的需求,您仍然可以 在 esbuild 前面放置一个代理,并使用它来代替 HTTPS。请注意,如果您在加载页面时看到消息 Client sent an HTTP request to an HTTPS server,那么您正在使用错误的协议。将浏览器 URL 栏中的 http:// 替换为 https://

请记住,esbuild 的 HTTPS 支持与安全性无关。在 esbuild 中启用 HTTPS 的唯一原因是,浏览器已经使在没有经过这些额外步骤的情况下,使用某些现代 Web 功能进行本地开发变得不可能。请勿将 esbuild 的开发服务器用于任何需要安全性的内容。它仅用于本地开发,并且没有考虑生产环境。

自定义服务器行为

无法挂钩到 esbuild 的本地服务器以自定义服务器本身的行为。相反,应通过在 esbuild 前面放置一个代理来自定义行为。

以下是一个简单的代理服务器示例,可帮助您入门,使用 Node.js 内置的 http 模块。它添加了一个自定义的 404 页面,而不是 esbuild 的默认 404 页面

import * as esbuild from 'esbuild'
import http from 'node:http'

// Start esbuild's server on a random local port
let ctx = await esbuild.context({
  // ... your build options go here ...
})

// The return value tells us where esbuild's local server is
let { host, port } = await ctx.serve({ servedir: '.' })

// Then start a proxy server on port 3000
http.createServer((req, res) => {
  const options = {
    hostname: host,
    port: port,
    path: req.url,
    method: req.method,
    headers: req.headers,
  }

  // Forward each incoming request to esbuild
  const proxyReq = http.request(options, proxyRes => {
    // If esbuild returns "not found", send a custom 404 page
    if (proxyRes.statusCode === 404) {
      res.writeHead(404, { 'Content-Type': 'text/html' })
      res.end('<h1>A custom 404 page</h1>')
      return
    }

    // Otherwise, forward the response from esbuild to the client
    res.writeHead(proxyRes.statusCode, proxyRes.headers)
    proxyRes.pipe(res, { end: true })
  })

  // Forward the body of the request to esbuild
  req.pipe(proxyReq, { end: true })
}).listen(3000)

此代码在随机本地端口上启动 esbuild 的服务器,然后在端口 3000 上启动一个代理服务器。在开发过程中,您将在浏览器中加载 https://127.0.0.1:3000,它会与代理通信。此示例演示了在 esbuild 处理请求后修改响应,但您也可以在 esbuild 处理请求之前修改或替换请求。

您可以使用这样的代理做很多事情,包括

如果您有更高级的需求,您也可以使用真正的代理,例如 nginx

Tsconfig

支持:构建

通常,build API 会自动发现 tsconfig.json 文件并在构建期间读取其内容。但是,您也可以配置一个自定义的 tsconfig.json 文件来代替。如果您需要使用不同的设置对同一代码进行多次构建,这将很有用

CLI JS Go
esbuild app.ts --bundle --tsconfig=custom-tsconfig.json
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  bundle: true,
  tsconfig: 'custom-tsconfig.json',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Tsconfig:    "custom-tsconfig.json",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Tsconfig raw

受支持:构建转换

此选项可用于将您的 tsconfig.json 文件传递给 transform API,该 API 不会访问文件系统。它也可以用于将您的 tsconfig.json 文件的内容内联传递给 build API,而无需将其写入文件。使用它看起来像这样

CLI JS Go
echo 'class Foo { foo }' | esbuild --loader=ts --tsconfig-raw='{"compilerOptions":{"useDefineForClassFields":false}}'
import * as esbuild from 'esbuild'

let ts = 'class Foo { foo }'
let result = await esbuild.transform(ts, {
  loader: 'ts',
  tsconfigRaw: `{
    "compilerOptions": {
      "useDefineForClassFields": false,
    },
  }`,
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "class Foo { foo }"

  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
    TsconfigRaw: `{
      "compilerOptions": {
        "useDefineForClassFields": false,
      },
    }`,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

监视

支持:构建

启用监视模式会告诉 esbuild 监听文件系统的更改,并在发生可能使构建无效的文件更改时自动重建。使用它看起来像这样

CLI JS Go
esbuild app.js --outfile=out.js --bundle --watch
[watch] build finished, watching for changes...
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
})

await ctx.watch()
console.log('watching...')
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }
  fmt.Printf("watching...\n")

  // Returning from main() exits immediately in Go.
  // Block forever so we keep watching and don't exit.
  <-make(chan struct{})
}

如果您想在将来的某个时间停止监视模式,您可以调用上下文对象的 dispose 来终止文件监视器

CLI JS Go
# Use Ctrl+C to stop the CLI in watch mode
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
})

await ctx.watch()
console.log('watching...')

await new Promise(r => setTimeout(r, 10 * 1000))
await ctx.dispose()
console.log('stopped watching')
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
import "time"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }
  fmt.Printf("watching...\n")

  time.Sleep(10 * time.Second)
  ctx.Dispose()
  fmt.Printf("stopped watching\n")
}

esbuild 中的监视模式是使用轮询而不是特定于操作系统的文件系统 API 来实现的,以确保可移植性。轮询系统旨在使用相对较少的 CPU,与更传统的轮询系统相比,后者会一次扫描整个目录树。文件系统仍然会定期扫描,但每次扫描只检查文件的随机子集,这意味着对文件的更改将在更改后不久被发现,但不一定立即被发现。

使用当前的启发式方法,大型项目应该大约每 2 秒完全扫描一次,因此在最坏的情况下,可能需要长达 2 秒才能发现更改。但是,在发现更改后,更改的路径将进入最近更改的路径的简短列表,这些路径将在每次扫描时进行检查,因此对最近更改的文件的进一步更改应该几乎立即被发现。

请注意,如果您不想使用基于轮询的方法,仍然可以使用 esbuild 的 rebuild API 和您选择的任何文件监视库来自己实现监视模式。

如果您使用的是 CLI,请记住,当 esbuild 的 stdin 关闭时,监视模式将终止。这可以防止 esbuild 意外地比父进程存活更长时间,并意外地继续消耗系统资源。如果您有需要 esbuild 即使在父进程完成之后也永远监视的用例,可以使用 --watch=forever 而不是 --watch

输入

入口点

支持:构建

这是一个文件数组,每个文件都作为捆绑算法的输入。它们被称为“入口点”,因为每个入口点都应该作为最初被评估的脚本,然后加载它所代表的所有其他代码方面。与其在您的页面中使用 <script> 标签加载许多库,不如使用 import 语句将它们导入到您的入口点(或导入到另一个随后被导入到您的入口点的文件中)。

简单的应用程序只需要一个入口点,但是如果存在多个逻辑上独立的代码组(例如主线程和工作线程),或者应用程序具有独立的相对无关的区域(例如登录页面、编辑器页面和设置页面),则额外的入口点可能会有用。单独的入口点有助于引入关注点分离,并有助于减少浏览器需要下载的不必要代码量。如果适用,启用 代码拆分 可以进一步减少浏览到第二个页面的下载大小,该页面的入口点与已访问的第一个页面共享一些已下载的代码。

指定入口点的简单方法是只传递一个文件路径数组

CLI JS Go
esbuild home.ts settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['home.ts', 'settings.ts'],
  bundle: true,
  write: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"home.ts", "settings.ts"},
    Bundle:      true,
    Write:       true,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这将生成两个输出文件,out/home.jsout/settings.js,分别对应于两个入口点 home.tssettings.ts

为了进一步控制如何从相应的输入入口点派生输出文件的路径,您应该查看这些选项

此外,您还可以使用另一种入口点语法为每个单独的入口点指定一个完全自定义的输出路径

CLI JS Go
esbuild out1=home.ts out2=settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: [
    { out: 'out1', in: 'home.ts'},
    { out: 'out2', in: 'settings.ts'},
  ],
  bundle: true,
  write: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPointsAdvanced: []api.EntryPoint{{
      OutputPath: "out1",
      InputPath:  "home.ts",
    }, {
      OutputPath: "out2",
      InputPath:  "settings.ts",
    }},
    Bundle: true,
    Write:  true,
    Outdir: "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这将生成两个输出文件,out/out1.jsout/out2.js,分别对应于两个入口点 home.tssettings.ts

加载器

受支持:构建转换

此选项更改如何解释给定的输入文件。例如,js 加载器将文件解释为 JavaScript,而 css 加载器将文件解释为 CSS。有关所有内置加载器的完整列表,请参见 内容类型 页面。

为给定文件类型配置加载器可以让您使用 import 语句或 require 调用加载该文件类型。例如,将 .png 文件扩展名配置为使用 数据 URL 加载器意味着导入 .png 文件会为您提供包含该图像内容的数据 URL

import url from './example.png'
let image = new Image
image.src = url
document.body.appendChild(image)

import svg from './example.svg'
let doc = new DOMParser().parseFromString(svg, 'application/xml')
let node = document.importNode(doc.documentElement, true)
document.body.appendChild(node)

上面的代码可以使用 build API 调用像这样捆绑

CLI JS Go
esbuild app.js --bundle --loader:.png=dataurl --loader:.svg=text
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  loader: {
    '.png': 'dataurl',
    '.svg': 'text',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Loader: map[string]api.Loader{
      ".png": api.LoaderDataURL,
      ".svg": api.LoaderText,
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

如果您使用的是带有来自 stdin 的输入的 build API,则此选项的指定方式不同,因为 stdin 没有文件扩展名。使用 build API 为 stdin 配置加载器如下所示

CLI JS Go
echo 'import pkg = require("./pkg")' | esbuild --loader=ts --bundle
import * as esbuild from 'esbuild'

await esbuild.build({
  stdin: {
    contents: 'import pkg = require("./pkg")',
    loader: 'ts',
    resolveDir: '.',
  },
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Stdin: &api.StdinOptions{
      Contents:   "import pkg = require('./pkg')",
      Loader:     api.LoaderTS,
      ResolveDir: ".",
    },
    Bundle: true,
  })
  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

transform API 调用只接受一个加载器,因为它不涉及与文件系统交互,因此不处理文件扩展名。为 transform API 配置加载器(在本例中为 ts 加载器)如下所示

CLI JS Go
echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'

let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
  loader: 'ts',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "let x: number = 1"
  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
  })
  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

标准输入

支持:构建

通常,build API 调用将一个或多个文件名作为输入。但是,此选项可用于在文件系统上根本不存在模块的情况下运行构建。它被称为“stdin”,因为它对应于将文件管道到命令行上的 stdin。

除了指定 stdin 文件的内容之外,您还可以选择指定解析目录(用于确定相对导入的位置)、源文件(用于错误消息和源映射的文件名)以及 加载器(用于确定如何解释文件内容)。CLI 没有指定解析目录的方法。相反,它会自动设置为当前工作目录。

以下是使用此功能的方法

CLI JS Go
echo 'export * from "./another-file"' | esbuild --bundle --sourcefile=imaginary-file.js --loader=ts --format=cjs
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  stdin: {
    contents: `export * from "./another-file"`,

    // These are all optional:
    resolveDir: './src',
    sourcefile: 'imaginary-file.js',
    loader: 'ts',
  },
  format: 'cjs',
  write: false,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Stdin: &api.StdinOptions{
      Contents: "export * from './another-file'",

      // These are all optional:
      ResolveDir: "./src",
      Sourcefile: "imaginary-file.js",
      Loader:     api.LoaderTS,
    },
    Format: api.FormatCommonJS,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

输出内容

受支持:构建转换

使用此选项在生成的 JavaScript 和 CSS 文件的开头插入任意字符串。这通常用于插入注释

CLI JS Go
esbuild app.js --banner:js=//comment --banner:css=/*comment*/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  banner: {
    js: '//comment',
    css: '/*comment*/',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Banner: map[string]string{
      "js":  "//comment",
      "css": "/*comment*/",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这类似于 页脚,它在结尾处插入而不是开头。

请注意,如果您将非注释代码插入 CSS 文件,请注意 CSS 会忽略所有在非 @import 规则(除了 @charset 规则)之后的 @import 规则,因此使用横幅注入 CSS 规则可能会意外地禁用外部样式表的导入。

字符集

受支持:构建转换

默认情况下,esbuild 的输出是 ASCII 唯一的。任何非 ASCII 字符都使用反斜杠转义序列进行转义。一个原因是浏览器默认情况下会错误地解释非 ASCII 字符,这会导致混淆。您必须在您的 HTML 中显式添加 <meta charset="utf-8"> 或使用正确的 Content-Type 标头提供它,以便浏览器不会篡改您的代码。另一个原因是非 ASCII 字符会显著 减慢浏览器的解析器速度。但是,使用转义序列会使生成的输出稍微变大,并且也会使它更难阅读。

如果您希望 esbuild 在不使用转义序列的情况下打印原始字符,并且您已确保浏览器将您的代码解释为 UTF-8,则可以通过设置字符集来禁用字符转义

CLI JS Go
echo 'let π = Math.PI' | esbuild
let \u03C0 = Math.PI;
echo 'let π = Math.PI' | esbuild --charset=utf8
let π = Math.PI;
import * as esbuild from 'esbuild'
let js = 'let π = Math.PI'
(await esbuild.transform(js)).code
'let \\u03C0 = Math.PI;\n'
(await esbuild.transform(js, {
  charset: 'utf8',
})).code
'let π = Math.PI;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "let π = Math.PI"

  result1 := api.Transform(js, api.TransformOptions{})

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Charset: api.CharsetUTF8,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

一些注意事项

受支持:构建转换

使用此选项在生成的 JavaScript 和 CSS 文件的末尾插入任意字符串。这通常用于插入注释

CLI JS Go
esbuild app.js --footer:js=//comment --footer:css=/*comment*/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  footer: {
    js: '//comment',
    css: '/*comment*/',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Footer: map[string]string{
      "js":  "//comment",
      "css": "/*comment*/",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这类似于 横幅,它在开头处插入而不是结尾。

格式

受支持:构建转换

这设置了生成的 JavaScript 文件的输出格式。目前可以配置三个可能的值:iifecjsesm。当没有指定输出格式时,esbuild 会在启用 捆绑 时为您选择一个输出格式(如下所述),或者在禁用 捆绑 时不进行任何格式转换。

IIFE

iife 格式代表“立即调用函数表达式”,旨在在浏览器中运行。将您的代码包装在函数表达式中可确保您的代码中的任何变量不会意外地与全局范围内的变量冲突。如果您的入口点具有您想在浏览器中作为全局变量公开的导出,则可以使用 全局名称 设置配置该全局变量的名称。当未指定输出格式、启用 捆绑 以及 平台 设置为 browser(默认情况下为 browser)时,将自动启用 iife 格式。指定 iife 格式如下所示

CLI JS Go
echo 'alert("test")' | esbuild --format=iife
(() => {
  alert("test");
})();
import * as esbuild from 'esbuild'

let js = 'alert("test")'
let result = await esbuild.transform(js, {
  format: 'iife',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "alert(\"test\")"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatIIFE,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

CommonJS

cjs 格式代表“CommonJS”,旨在在 node 中运行。它假设环境包含 exportsrequiremodule。具有 ECMAScript 模块语法导出的入口点将被转换为一个模块,该模块对每个导出名称在 exports 上都有一个 getter。当未指定输出格式、启用 捆绑 以及 平台 设置为 node 时,将自动启用 cjs 格式。指定 cjs 格式如下所示

CLI JS Go
echo 'export default "test"' | esbuild --format=cjs
...
var stdin_exports = {};
__export(stdin_exports, {
  default: () => stdin_default
});
module.exports = __toCommonJS(stdin_exports);
var stdin_default = "test";
import * as esbuild from 'esbuild'

let js = 'export default "test"'
let result = await esbuild.transform(js, {
  format: 'cjs',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "export default 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatCommonJS,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

ESM

esm 格式代表“ECMAScript 模块”。它假设环境支持 importexport 语法。具有 CommonJS 模块语法导出的入口点将被转换为 module.exports 值的单个 default 导出。当未指定输出格式、启用 捆绑 以及 平台 设置为 neutral 时,将自动启用 esm 格式。指定 esm 格式如下所示

CLI JS Go
echo 'module.exports = "test"' | esbuild --format=esm
...
var require_stdin = __commonJS({
  "<stdin>"(exports, module) {
    module.exports = "test";
  }
});
export default require_stdin();
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'esm',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatESModule,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

esm 格式可以在浏览器或 node 中使用,但您必须显式地将其加载为模块。如果您从另一个模块导入它,这将自动发生。否则

全局名称

受支持:构建转换

此选项仅在 格式 设置为 iife(代表立即调用函数表达式)时才重要。它设置用于存储入口点导出的全局变量的名称

CLI JS Go
echo 'module.exports = "test"' | esbuild --format=iife --global-name=xyz
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'iife',
  globalName: 'xyz',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format:     api.FormatIIFE,
    GlobalName: "xyz",
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

使用 iife 格式指定全局名称将生成类似于以下代码的代码

var xyz = (() => {
  ...
  var require_stdin = __commonJS((exports, module) => {
    module.exports = "test";
  });
  return require_stdin();
})();

全局名称也可以是复合属性表达式,在这种情况下,esbuild 将生成具有该属性的全局变量。与现有全局变量冲突的变量将不会被覆盖。这可用于实现“命名空间”,其中多个独立脚本将其导出添加到同一个全局对象上。例如

CLI JS Go
echo 'module.exports = "test"' | esbuild --format=iife --global-name='example.versions["1.0"]'
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'iife',
  globalName: 'example.versions["1.0"]',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format:     api.FormatIIFE,
    GlobalName: `example.versions["1.0"]`,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

上面使用的复合全局名称生成的代码如下所示

var example = example || {};
example.versions = example.versions || {};
example.versions["1.0"] = (() => {
  ...
  var require_stdin = __commonJS((exports, module) => {
    module.exports = "test";
  });
  return require_stdin();
})();

受支持:构建转换

“法律注释”被认为是 JS 中的任何语句级注释或 CSS 中的任何规则级注释,其中包含 @license@preserve,或者以 //!/*! 开头。这些注释默认情况下会保留在输出文件中,因为这遵循了代码原始作者的意图。但是,可以使用以下选项之一配置此行为

当启用 捆绑 时,默认行为为 eof,否则为 inline。设置法律注释模式如下所示

CLI JS Go
esbuild app.js --legal-comments=eof
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  legalComments: 'eof',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:   []string{"app.js"},
    LegalComments: api.LegalCommentsEndOfFile,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

请注意,对于 JS 而言,“语句级” 和对于 CSS 而言的“规则级” 意味着注释必须出现在允许多个语句或规则的上下文中,例如在顶层作用域或语句或规则块中。因此,表达式内部或声明级别的注释不被视为合法注释。

行限制

受支持:构建转换

此设置是防止 esbuild 生成具有非常长行的输出文件的一种方法,这有助于在实现不佳的文本编辑器中提高编辑性能。将其设置为正整数以告诉 esbuild 在超过该字节数后不久结束给定行。例如,这会在行超过约 80 个字符后不久将其换行。

CLI JS Go
esbuild app.ts --line-limit=80
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  lineLimit: 80,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    LineLimit:   80,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

行在超过限制后被截断,而不是在之前被截断,因为在超过限制时进行检查比预测何时将超过限制更简单,并且因为在生成输出文件时避免回退和重写内容更快。因此,限制只是近似的。

此设置适用于 JavaScript 和 CSS,即使在禁用缩小的情况下也能正常工作。请注意,启用此设置会使您的文件更大,因为额外的换行符在文件中占用更多空间(即使在 gzip 压缩后)。

拆分

支持:构建

代码拆分仍在进行中。它目前仅适用于 esm 输出 格式。还有一个已知的 排序问题 与跨代码拆分块的 import 语句有关。您可以关注 跟踪问题 以获取有关此功能的更新。

这将启用“代码拆分”,它有两个目的。

启用代码拆分后,您还必须使用 outdir 设置配置输出目录。

CLI JS Go
esbuild home.ts about.ts --bundle --splitting --outdir=out --format=esm
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['home.ts', 'about.ts'],
  bundle: true,
  splitting: true,
  outdir: 'out',
  format: 'esm',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"home.ts", "about.ts"},
    Bundle:      true,
    Splitting:   true,
    Outdir:      "out",
    Format:      api.FormatESModule,
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

输出位置

允许覆盖

支持:构建

启用此设置允许输出文件覆盖输入文件。它默认情况下未启用,因为这样做意味着覆盖您的源代码,如果您的代码未签入,这会导致数据丢失。但是,支持这一点使某些工作流程更容易,因为无需使用临时目录。因此,当您想要故意覆盖您的源代码时,可以启用它。

CLI JS Go
esbuild app.js --outdir=. --allow-overwrite
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  outdir: '.',
  allowOverwrite: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:    []string{"app.js"},
    Outdir:         ".",
    AllowOverwrite: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

资产名称

支持:构建

此选项控制当 加载器 设置为 file 时生成的附加输出文件的名称。它使用带有占位符的模板配置输出路径,这些占位符将在生成输出路径时用特定于文件的值替换。例如,指定 assets/[name]-[hash] 的资产名称模板会将所有资产放入输出目录内的名为 assets 的子目录中,并在文件名中包含资产的内容哈希。这样看起来像这样

CLI JS Go
esbuild app.js --asset-names=assets/[name]-[hash] --loader:.png=file --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  assetNames: 'assets/[name]-[hash]',
  loader: { '.png': 'file' },
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    AssetNames:  "assets/[name]-[hash]",
    Loader: map[string]api.Loader{
      ".png": api.LoaderFile,
    },
    Bundle: true,
    Outdir: "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

在资产路径模板中可以使用四个占位符。

资产路径模板不需要包含文件扩展名。资产的原始文件扩展名将在模板替换后自动添加到输出路径的末尾。

此选项类似于 块名称入口点名称 选项。

块名称

支持:构建

此选项控制当启用 代码拆分 时自动生成的共享代码块的文件名。它使用带有占位符的模板配置输出路径,这些占位符将在生成输出路径时用特定于块的值替换。例如,指定 chunks/[name]-[hash] 的块名称模板会将所有生成的块放入输出目录内的名为 chunks 的子目录中,并在文件名中包含块的内容哈希。这样看起来像这样

CLI JS Go
esbuild app.js --chunk-names=chunks/[name]-[hash] --bundle --outdir=out --splitting --format=esm
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  chunkNames: 'chunks/[name]-[hash]',
  bundle: true,
  outdir: 'out',
  splitting: true,
  format: 'esm',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    ChunkNames:  "chunks/[name]-[hash]",
    Bundle:      true,
    Outdir:      "out",
    Splitting:   true,
    Format:      api.FormatESModule,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

在块路径模板中可以使用三个占位符。

块路径模板不需要包含文件扩展名。将自动将相应内容类型的配置的 输出扩展名 添加到模板替换后的输出路径的末尾。

请注意,此选项仅控制自动生成的共享代码块的名称。它控制与入口点相关的输出文件的名称。这些的名称目前由原始入口点文件相对于 outbase 目录的路径决定,并且此行为无法更改。将来会添加一个额外的 API 选项,让您更改入口点输出文件的名称。

此选项类似于 资产名称入口点名称 选项。

入口点名称

支持:构建

此选项控制与每个输入入口点文件对应的输出文件的文件名。它使用带有占位符的模板配置输出路径,这些占位符将在生成输出路径时用特定于文件的值替换。例如,指定 [dir]/[name]-[hash] 的入口点名称模板会在文件名中包含输出文件的内容哈希,并将文件放入输出目录,可能在子目录下(请参阅下面有关 [dir] 的详细信息)。这样看起来像这样

CLI JS Go
esbuild src/main-app/app.js --entry-names=[dir]/[name]-[hash] --outbase=src --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/main-app/app.js'],
  entryNames: '[dir]/[name]-[hash]',
  outbase: 'src',
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"src/main-app/app.js"},
    EntryNames:  "[dir]/[name]-[hash]",
    Outbase:     "src",
    Bundle:      true,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

在入口点路径模板中可以使用四个占位符。

入口点路径模板不需要包含文件扩展名。将根据文件类型自动将适当的 输出扩展名 添加到模板替换后的输出路径的末尾。

此选项类似于 资产名称块名称 选项。

输出扩展名

支持:构建

此选项允许您将 esbuild 生成的文件的文件扩展名自定义为除 .js.css 之外的其他内容。特别是,.mjs.cjs 文件扩展名在节点中具有特殊含义(它们分别表示 ESM 和 CommonJS 格式的文件)。如果您使用 esbuild 生成多个文件,并且必须使用 outdir 选项而不是 outfile 选项,则此选项很有用。您可以像这样使用它

CLI JS Go
esbuild app.js --bundle --outdir=dist --out-extension:.js=.mjs
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outdir: 'dist',
  outExtension: { '.js': '.mjs' },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outdir:      "dist",
    OutExtension: map[string]string{
      ".js": ".mjs",
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Outbase

支持:构建

如果您的构建包含多个位于不同目录中的入口点,则目录结构将相对于 outbase 目录复制到 输出目录 中。例如,如果存在两个入口点 src/pages/home/index.tssrc/pages/about/index.ts,并且 outbase 目录是 src,则输出目录将包含 pages/home/index.jspages/about/index.js。以下是使用方法

CLI JS Go
esbuild src/pages/home/index.ts src/pages/about/index.ts --bundle --outdir=out --outbase=src
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: [
    'src/pages/home/index.ts',
    'src/pages/about/index.ts',
  ],
  bundle: true,
  outdir: 'out',
  outbase: 'src',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{
      "src/pages/home/index.ts",
      "src/pages/about/index.ts",
    },
    Bundle:  true,
    Outdir:  "out",
    Outbase: "src",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

如果没有指定 outbase 目录,它将默认为所有输入入口点路径的 最近公共祖先 目录。在上面的示例中,它是 src/pages,这意味着默认情况下,输出目录将包含 home/index.jsabout/index.js

Outdir

支持:构建

此选项设置构建操作的输出目录。例如,此命令将生成一个名为 out 的目录。

CLI JS Go
esbuild app.js --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

如果输出目录不存在,它将被创建,但如果它已经包含一些文件,它不会被清空。任何生成的将静默覆盖同名文件。如果你想让输出目录只包含 esbuild 当前运行产生的文件,你应该在运行 esbuild 之前手动清空输出目录。

如果你的构建包含多个位于不同目录的入口点,目录结构将从所有输入入口点路径的最近公共祖先目录开始复制到输出目录。例如,如果存在两个入口点 src/home/index.tssrc/about/index.ts,输出目录将包含 home/index.jsabout/index.js。如果你想自定义此行为,你应该更改outbase 目录

输出文件

支持:构建

此选项设置构建操作的输出文件名。这仅适用于只有一个入口点的情况。如果有多个入口点,你必须使用outdir 选项来指定输出目录。使用 outfile 的方式如下

CLI JS Go
esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outdir:      "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

公共路径

支持:构建

这在与外部文件 加载器结合使用时很有用。默认情况下,该加载器使用 default 导出将导入文件的名称作为字符串导出。公共路径选项允许你在此加载器加载的每个文件的导出字符串前面添加一个基本路径

CLI JS Go
esbuild app.js --bundle --loader:.png=file --public-path=https://www.example.com/v1 --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  loader: { '.png': 'file' },
  publicPath: 'https://www.example.com/v1',
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Loader: map[string]api.Loader{
      ".png": api.LoaderFile,
    },
    Outdir:     "out",
    PublicPath: "https://www.example.com/v1",
    Write:      true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

写入

支持:构建

构建 API 调用可以将文件直接写入文件系统,也可以将本应写入的文件作为内存缓冲区返回。默认情况下,CLI 和 JavaScript API 写入文件系统,而 Go API 则不写入。要使用内存缓冲区

JS Go
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  sourcemap: 'external',
  write: false,
  outdir: 'out',
})

for (let out of result.outputFiles) {
  console.log(out.path, out.contents, out.hash, out.text)
}
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Sourcemap:   api.SourceMapExternal,
    Write:       false,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  for _, out := range result.OutputFiles {
    fmt.Printf("%v %v %s\n", out.Path, out.Contents, out.Hash)
  }
}

hash 属性是 contents 字段的哈希值,为了方便起见提供。哈希算法(目前为XXH64)依赖于实现,并且可能在 esbuild 版本之间随时更改。

路径解析

别名

支持:构建

此功能允许你在捆绑时用一个包替换另一个包。下面的示例用包 newpkg 替换包 oldpkg

CLI JS Go
esbuild app.js --bundle --alias:oldpkg=newpkg
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  write: true,
  alias: {
    'oldpkg': 'newpkg',
  },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Write:       true,
    Alias: map[string]string{
      "oldpkg": "newpkg",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这些新的替换在 esbuild 的所有其他路径解析逻辑之前发生。此功能的一个用例是在你无法控制的第三方代码中用浏览器友好的包替换仅限节点的包。

请注意,当使用别名替换导入路径时,生成的导入路径将在工作目录中解析,而不是在包含具有导入路径的源文件的目录中解析。如果需要,可以使用工作目录 功能设置 esbuild 使用的工作目录。

条件

支持:构建

此功能控制如何解释 package.json 中的 exports 字段。可以使用 conditions 设置添加自定义条件。你可以指定任意数量的这些条件,它们的含义完全由包作者决定。Node 目前只认可 developmentproduction 自定义条件用于推荐使用。以下是如何添加自定义条件 custom1custom2 的示例

CLI JS Go
esbuild src/app.js --bundle --conditions=custom1,custom2
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/app.js'],
  bundle: true,
  conditions: ['custom1', 'custom2'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"src/app.js"},
    Bundle:      true,
    Conditions:  []string{"custom1", "custom2"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

条件的工作原理

条件允许你在不同情况下将相同的导入路径重定向到不同的文件位置。包含条件和路径的重定向映射存储在包的 package.json 文件中的 exports 字段中。例如,这将使用 importrequire 条件将 require('pkg/foo') 重映射到 pkg/required.cjs,并将 import 'pkg/foo' 重映射到 pkg/imported.mjs

{
  "name": "pkg",
  "exports": {
    "./foo": {
      "import": "./imported.mjs",
      "require": "./required.cjs",
      "default": "./fallback.js"
    }
  }
}

条件按它们在 JSON 文件中出现的顺序进行检查。因此上面的示例的行为类似于

if (importPath === './foo') {
  if (conditions.has('import')) return './imported.mjs'
  if (conditions.has('require')) return './required.cjs'
  return './fallback.js'
}

默认情况下,有五个具有特殊行为的条件内置于 esbuild,并且无法禁用

platform 设置为 browsernode 且未配置任何自定义条件时,以下条件也会自动包含在内。如果配置了任何自定义条件(即使是空列表),则此条件将不再自动包含在内

请注意,当你使用 requireimport 条件时,你的包可能会在捆绑包中出现多次! 这是一个微妙的问题,可能会导致错误,因为你的代码状态的重复副本会使生成的捆绑包膨胀。这通常被称为双包危害

避免双包危害的一种方法是将所有代码放在 require 条件中作为 CommonJS,并让 import 条件只是一个轻量级的 ESM 包装器,它在你的包上调用 require 并使用 ESM 语法重新导出包。但是,这种方法不会提供良好的树摇动,因为 esbuild 不会对 CommonJS 模块进行树摇动。

避免双包危害的另一种方法是使用特定于捆绑器的 module 条件来指示捆绑器始终加载你的包的 ESM 版本,同时让 node 始终回退到你的包的 CommonJS 版本。importmodule 都旨在与 ESM 一起使用,但与 import 不同,module 条件始终处于活动状态,即使导入路径是使用 require 调用加载的。这在捆绑器中效果很好,因为捆绑器支持使用 require 加载 ESM,但这不是 node 可以做的事情,因为 node 故意没有实现使用 require 加载 ESM。

外部

支持:构建

你可以将文件或包标记为外部,以将其从构建中排除。导入将被保留(对于 iifecjs 格式使用 require,对于 esm 格式使用 import),而不是被捆绑,并且将在运行时进行评估。

这有几个用途。首先,它可以用来从你的捆绑包中修剪掉你已知永远不会执行的代码路径的无用代码。例如,一个包可能包含仅在 node 中运行的代码,但你只会在浏览器中使用该包。它也可以用来在 node 中从无法捆绑的包中导入代码。例如,fsevents 包包含一个原生扩展,esbuild 不支持。将某些东西标记为外部的方式如下

CLI JS Go
echo 'require("fsevents")' > app.js
esbuild app.js --bundle --external:fsevents --platform=node
// app.js
require("fsevents");
import * as esbuild from 'esbuild'
import fs from 'node:fs'

fs.writeFileSync('app.js', 'require("fsevents")')

await esbuild.build({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
  platform: 'node',
  external: ['fsevents'],
})
package main

import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ioutil.WriteFile("app.js", []byte("require(\"fsevents\")"), 0644)

  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
    Platform:    api.PlatformNode,
    External:    []string{"fsevents"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

你也可以在外部路径中使用 * 通配符来将与该模式匹配的所有文件标记为外部。例如,你可以使用 *.png 来删除所有 .png 文件,或者使用 /images/* 来删除所有以 /images/ 开头的路径

CLI JS Go
esbuild app.js --bundle "--external:*.png" "--external:/images/*"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
  external: ['*.png', '/images/*'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
    External:    []string{"*.png", "/images/*"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

外部路径在路径解析之前和之后都应用,这使你能够匹配源代码中的导入路径和绝对文件系统路径。如果外部路径在任一情况下都匹配,则该路径被认为是外部路径。具体行为如下

主字段

支持:构建

当你导入 node 中的包时,该包的 package.json 文件中的 main 字段决定导入哪个文件(以及许多其他规则)。包括 esbuild 在内的主要 JavaScript 捆绑器允许你指定在解析包时尝试的额外 package.json 字段。至少有三个这样的字段在使用中

默认主字段取决于当前的platform 设置。这些默认值应该与现有的包生态系统最广泛地兼容。但如果你想自定义它们,可以这样做

CLI JS Go
esbuild app.js --bundle --main-fields=module,main
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  mainFields: ['module', 'main'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    MainFields:  []string{"module", "main"},
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

对于包作者

如果您想编写一个使用browser字段和module字段组合的包,那么您可能需要填写所有四个条目在完整的 CommonJS-vs-ESM 和浏览器-vs-节点兼容性矩阵中。为此,您需要使用browser字段的扩展形式,它是一个映射,而不仅仅是一个字符串。

{
  "main": "./node-cjs.js",
  "module": "./node-esm.js",
  "browser": {
    "./node-cjs.js": "./browser-cjs.js",
    "./node-esm.js": "./browser-esm.js"
  }
}

main字段预期为 CommonJS,而module字段预期为 ESM。关于使用哪种模块格式的决定与是否使用浏览器特定或节点特定变体的决定无关。如果您省略了这四个条目中的一个,那么您就有可能选择错误的变体。例如,如果您省略了 CommonJS 浏览器构建的条目,那么可能会选择 CommonJS 节点构建。

请注意,使用mainmodulebrowser是旧的实现方式。还有一种更新的实现方式,您可能更喜欢使用它:package.json中的exports字段。它提供了一组不同的权衡。例如,它可以让您更精确地控制包中所有子路径的导入(而main字段只让您控制入口点),但它可能会导致您的包根据您的配置方式被多次导入。

节点路径

支持:构建

节点的模块解析算法支持一个名为NODE_PATH的环境变量,其中包含一个用于解析导入路径的全局目录列表。除了所有父目录中的node_modules目录外,还会在这些路径中搜索包。您可以使用 CLI 中的环境变量以及 JS 和 Go API 中的数组将此目录列表传递给 esbuild。

CLI JS Go
NODE_PATH=someDir esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  nodePaths: ['someDir'],
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    NodePaths:   []string{"someDir"},
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

如果您使用的是 CLI 并希望使用NODE_PATH传递多个目录,则必须在 Unix 上使用:,在 Windows 上使用;分隔它们。这与 Node 本身使用的格式相同。

支持:构建

使用此设置将您包的所有依赖项从捆绑包中排除。当为节点捆绑时,这很有用,因为许多 npm 包使用 esbuild 在捆绑时不支持的节点特定功能(例如__dirnameimport.meta.urlfs.readFileSync*.node本机二进制模块)。使用它看起来像这样

CLI JS Go
esbuild app.js --bundle --packages=external
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  packages: 'external',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Packages:    api.PackagesExternal,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

启用此功能会自动将所有看起来像 npm 包的导入路径(即,不以...路径组件开头且不是绝对路径的路径)标记为外部。它与手动将每个依赖项传递给external具有相同的效果,但更简洁。如果您想自定义哪些依赖项是外部的,哪些不是,那么您应该使用external而不是此设置。

请注意,此设置仅在启用捆绑时才有效。还要注意,将导入路径标记为外部是在导入路径被任何配置的别名重写之后发生的,因此别名功能在使用此设置时仍然有效。

支持:构建

此设置反映了节点中的--preserve-symlinks设置。如果您使用该设置(或 Webpack 中类似的resolve.symlinks设置),您可能需要在 esbuild 中也启用此设置。它可以像这样启用

CLI JS Go
esbuild app.js --bundle --preserve-symlinks --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  preserveSymlinks: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:      []string{"app.js"},
    Bundle:           true,
    PreserveSymlinks: true,
    Outfile:          "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

启用此设置会导致 esbuild 通过原始文件路径(即不跟随符号链接的路径)而不是实际文件路径(即跟随符号链接后的路径)来确定文件标识。这对于某些目录结构可能是有益的。请记住,这意味着如果有多个符号链接指向一个文件,则该文件可能会被赋予多个标识,这会导致它在生成的输出文件中多次出现。

注意:术语“符号链接”是指符号链接,指的是文件系统功能,其中路径可以重定向到另一个路径。

解析扩展名

支持:构建

节点使用的解析算法支持隐式文件扩展名。您可以require('./file'),它将按顺序检查./file./file.js./file.json./file.node。包括 esbuild 在内的现代捆绑器将此概念扩展到其他文件类型。esbuild 中隐式文件扩展名的完整顺序可以使用解析扩展名设置进行自定义,该设置默认为.tsx,.ts,.jsx,.js,.css,.json

CLI JS Go
esbuild app.js --bundle --resolve-extensions=.ts,.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  resolveExtensions: ['.ts', '.js'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    Bundle:            true,
    ResolveExtensions: []string{".ts", ".js"},
    Write:             true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

请注意,esbuild 故意不将新的.mjs.cjs扩展名包含在此列表中。节点的解析算法不将这些视为隐式文件扩展名,因此 esbuild 也不将它们视为隐式文件扩展名。如果您想导入具有这些扩展名的文件,您应该在导入路径中显式添加扩展名,或者更改此设置以包含您希望隐式的其他扩展名。

工作目录

支持:构建

此 API 选项允许您指定用于构建的工作目录。它通常默认为您用于调用 esbuild API 的进程的当前工作目录。esbuild 使用工作目录来执行一些不同的操作,包括将作为 API 选项给出的相对路径解析为绝对路径,以及在日志消息中将绝对路径漂亮地打印为相对路径。以下是自定义 esbuild 工作目录的方法

CLI JS Go
cd "/var/tmp/custom/working/directory"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['file.js'],
  absWorkingDir: '/var/tmp/custom/working/directory',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:   []string{"file.js"},
    AbsWorkingDir: "/var/tmp/custom/working/directory",
    Outfile:       "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

注意:如果您使用的是Yarn Plug'n'Play,请记住,此工作目录用于搜索 Yarn 的清单文件。如果您从无关的目录运行 esbuild,则必须将此工作目录设置为包含清单文件的目录(或其子目录之一),以便 esbuild 能够找到清单文件。

转换

JSX

受支持:构建转换

此选项告诉 esbuild 如何处理 JSX 语法。以下是可用的选项

以下是如何将 JSX 转换设置为preserve的示例

CLI JS Go
echo '<div/>' | esbuild --jsx=preserve --loader=jsx
<div />;
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<div/>', {
  jsx: 'preserve',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<div/>", api.TransformOptions{
    JSX:    api.JSXPreserve,
    Loader: api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

JSX dev

受支持:构建转换

如果JSX转换已设置为automatic,则启用此设置会导致 esbuild 自动将文件名和源位置注入到每个 JSX 元素中。您的 JSX 库然后可以使用此信息来帮助调试。如果 JSX 转换已设置为除automatic以外的其他内容,则此设置将不起作用。以下是如何启用此设置的示例

CLI JS Go
echo '<a/>' | esbuild --loader=jsx --jsx=automatic
import { jsx } from "react/jsx-runtime";
/* @__PURE__ */ jsx("a", {});
echo '<a/>' | esbuild --loader=jsx --jsx=automatic --jsx-dev
import { jsxDEV } from "react/jsx-dev-runtime";
/* @__PURE__ */ jsxDEV("a", {}, void 0, false, {
  fileName: "<stdin>",
  lineNumber: 1,
  columnNumber: 1
}, this);
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  jsxDev: true,
  jsx: 'automatic',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.jsx"},
    JSXDev:      true,
    JSX:         api.JSXAutomatic,
    Outfile:     "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

JSX 工厂

受支持:构建转换

这将设置对每个 JSX 元素调用的函数。通常,像这样的 JSX 表达式

<div>Example text</div>

被编译成对React.createElement的函数调用,如下所示

React.createElement("div", null, "Example text");

您可以通过更改 JSX 工厂来调用除React.createElement以外的其他内容。例如,要调用函数h(其他库(如Preact)使用)

CLI JS Go
echo '<div/>' | esbuild --jsx-factory=h --loader=jsx
/* @__PURE__ */ h("div", null);
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<div/>', {
  jsxFactory: 'h',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<div/>", api.TransformOptions{
    JSXFactory: "h",
    Loader:     api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

或者,如果您使用的是 TypeScript,您只需通过将以下内容添加到您的tsconfig.json文件中来配置 TypeScript 的 JSX,esbuild 应该会自动将其拾取,而无需进行配置

{
  "compilerOptions": {
    "jsxFactory": "h"
  }
}

如果您想在每个文件的基础上配置此设置,您可以使用// @jsx h注释来实现。请注意,当JSX转换已设置为automatic时,此设置不适用。

JSX 片段

受支持:构建转换

这将设置对每个 JSX 片段调用的函数。通常,像这样的 JSX 片段表达式

<>Stuff</>

被编译成使用React.Fragment组件,如下所示

React.createElement(React.Fragment, null, "Stuff");

您可以通过更改 JSX 片段来使用除React.Fragment以外的组件。例如,要使用组件Fragment(其他库(如Preact)使用)

CLI JS Go
echo '<>x</>' | esbuild --jsx-fragment=Fragment --loader=jsx
/* @__PURE__ */ React.createElement(Fragment, null, "x");
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<>x</>', {
  jsxFragment: 'Fragment',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<>x</>", api.TransformOptions{
    JSXFragment: "Fragment",
    Loader:      api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

或者,如果您使用的是 TypeScript,您只需通过将以下内容添加到您的tsconfig.json文件中来配置 TypeScript 的 JSX,esbuild 应该会自动将其拾取,而无需进行配置

{
  "compilerOptions": {
    "jsxFragmentFactory": "Fragment"
  }
}

如果您想在每个文件的基础上配置此设置,您可以使用// @jsxFrag Fragment注释来实现。请注意,当JSX转换已设置为automatic时,此设置不适用。

JSX 导入源

受支持:构建转换

如果JSX转换已设置为automatic,则设置此选项可以让您更改 esbuild 用于自动从其 JSX 辅助函数导入的库。请注意,这仅适用于特定于 React 17+的 JSX 转换。如果您将 JSX 导入源设置为your-pkg,则该包必须至少公开以下导出

import { createElement } from "your-pkg"
import { Fragment, jsx, jsxs } from "your-pkg/jsx-runtime"
import { Fragment, jsxDEV } from "your-pkg/jsx-dev-runtime"

/jsx-runtime/jsx-dev-runtime子路径是硬编码的,不能更改。当JSX dev 模式关闭时,jsxjsxs导入被使用,而当 JSX dev 模式打开时,jsxDEV导入被使用。这些的含义在React 关于其新的 JSX 转换的文档中进行了描述。当元素具有道具扩展后跟key道具时,无论 JSX dev 模式如何,createElement导入都会被使用,它看起来像这样

return <div {...props} key={key} />

以下是如何将 JSX 导入源设置为preact的示例

CLI JS Go
esbuild app.jsx --jsx-import-source=preact --jsx=automatic
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  jsxImportSource: 'preact',
  jsx: 'automatic',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:     []string{"app.jsx"},
    JSXImportSource: "preact",
    JSX:             api.JSXAutomatic,
    Outfile:         "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

或者,如果您使用的是 TypeScript,您只需通过将以下内容添加到您的tsconfig.json文件中来配置 TypeScript 的 JSX 导入源,esbuild 应该会自动将其拾取,而无需进行配置

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

如果您想在每个文件的基础上控制此设置,您可以在每个文件中使用// @jsxImportSource your-pkg注释来实现。您可能还需要添加// @jsxRuntime automatic注释,如果JSX转换尚未通过其他方式设置,或者如果您希望在每个文件的基础上设置它。

JSX 副作用

受支持:构建转换

默认情况下,esbuild 假设 JSX 表达式没有副作用,这意味着它们被标注为 /* @__PURE__ */ 注释,并在捆绑过程中被移除,因为它们没有被使用。这遵循了 JSX 用于虚拟 DOM 的常见用法,并适用于绝大多数 JSX 库。但是,有些人编写了没有此属性的 JSX 库(特别是 JSX 表达式可以具有任意副作用,并且在未使用时无法删除)。如果您正在使用这样的库,您可以使用此设置告诉 esbuild JSX 表达式具有副作用。

CLI JS Go
esbuild app.jsx --jsx-side-effects
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  outfile: 'out.js',
  jsxSideEffects: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:    []string{"app.jsx"},
    Outfile:        "out.js",
    JSXSideEffects: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

支持

受支持:构建转换

此设置允许您在单个语法功能级别自定义 esbuild 的不支持语法功能集。例如,您可以使用它告诉 esbuild BigInts 不受支持,以便 esbuild 在您尝试使用它时生成错误。通常,当您使用 target 设置时,会为您配置此设置,您通常应该使用此设置而不是此设置。如果除了此设置之外还指定了目标,则此设置将覆盖目标指定的任何内容。

以下是一些您可能希望使用此设置而不是或除了设置目标之外的示例。

如果您希望 esbuild 将某个语法功能视为不受支持,您可以像这样指定它。

CLI JS Go
esbuild app.js --supported:bigint=false
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  supported: {
    'bigint': false,
  },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Supported: map[string]bool{
      "bigint": false,
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

语法功能使用 esbuild 特定的功能名称指定。完整的特征名称集如下所示。

JavaScript

CSS

目标

受支持:构建转换

这将为生成的 JavaScript 和/或 CSS 代码设置目标环境。它告诉 esbuild 将对这些环境来说太新的 JavaScript 语法转换为将在这

请注意,这仅与语法功能有关,而不是 API。它不会自动添加 polyfills 用于这些环境不使用的

每个目标环境都是一个环境名称,后跟一个版本号。目前支持以下环境名称。

此外,您还可以指定 JavaScript 语言版本,例如 es2020。默认目标是 esnext,这意味着默认情况下,esbuild 将假

CLI JS Go
esbuild app.js --target=es2020,chrome58,edge16,firefox57,node12,safari11
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  target: [
    'es2020',
    'chrome58',
    'edge16',
    'firefox57',
    'node12',
    'safari11',
  ],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Target:      api.ES2020,
    Engines: []api.Engine{
      {Name: api.EngineChrome, Version: "58"},
      {Name: api.EngineEdge, Version: "16"},
      {Name: api.EngineFirefox, Version: "57"},
      {Name: api.EngineNode, Version: "12"},
      {Name: api.EngineSafari, Version: "11"},
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

您可以参考 JavaScript 加载器 以了解有关哪些语法功能是在哪些语言版本中引入的详细信息。请记住,虽然 JavaScript 语言版本(例如 es2020)是按年份标识的,但那是规范被批准的年份。它与所有主要浏览器实现该规范的年份无关,该年份通常早于或晚于该年份。

如果您使用 esbuild 尚未支持转换为当前语言目标的语法功能,esbuild 将在使用不支持的语法的地方生成错误。例如,当针对 es5 语言版本时,这种情况经常发生,因为 esbuild 仅支持将大多数较新的 JavaScript 语法功能转换为 es6

如果您需要除了或代替 target 提供的功能之外,在单个功能级别自定义支持的语法功能集,您可以使用 supported 设置。

优化

定义

受支持:构建转换

此功能提供了一种方法,可以将全局标识符替换为常量表达式。它可以成为一种在构建之间更改某些代码的行为而无需更改代码本身的方法。

CLI JS Go
echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=true
hooks = require("hooks");
echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=false
hooks = false;
import * as esbuild from 'esbuild'let js = 'hooks = DEBUG && require("hooks")'(await esbuild.transform(js, {
  define: { DEBUG: 'true' },
})).code
'hooks = require("hooks");\n'
(await esbuild.transform(js, {
  define: { DEBUG: 'false' },
})).code
'hooks = false;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "hooks = DEBUG && require('hooks')"

  result1 := api.Transform(js, api.TransformOptions{
    Define: map[string]string{"DEBUG": "true"},
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Define: map[string]string{"DEBUG": "false"},
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

每个 define 条目将一个标识符映射到一个包含表达式的代码字符串。字符串中的表达式必须是 JSON 对象(null、布尔值、数字、字符串、数组或对象)或单个标识符。除了数组和对象之外的替换表达式将被内联替换,这意味着它们可以参与常量折叠。数组和对象替换表达式存储在变量中,然后使用标识符引用,而不是内联替换,这避免了替换值的重复副本,但意味着这些值不参与常量折叠。

如果您想用字符串文字替换某些内容,请记住,传递给 esbuild 的替换值本身必须包含引号,因为每个 define 条目都映射到包含代码的字符串。省略引号意味着替换值是一个标识符而不是字符串。这在下面的示例中得到了证明。

CLI JS Go
echo 'id, str' | esbuild --define:id=text --define:str=\"text\"
text, "text";
import * as esbuild from 'esbuild'(await esbuild.transform('id, str', {
  define: { id: 'text', str: '"text"' },
})).code
'text, "text";\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("id, text", api.TransformOptions{
    Define: map[string]string{
      "id":  "text",
      "str": "\"text\"",
    },
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

如果您使用的是 CLI,请记住,不同的 shell 对如何转义双引号字符(在替换值为字符串时是必需的)有不同的规则。使用 \" 反斜杠转义,因为它在 bash 和 Windows 命令提示符中都有效。在 bash 中有效的其他转义双引号方法(例如用单引号将它们括起来)在 Windows 上将不起作用,因为 Windows 命令提示符不会删除单引号。这与从 package.json 文件中的 npm 脚本使用 CLI 时相关,人们期望它在所有平台上都能正常工作。

{
  "scripts": {
    "build": "esbuild --define:process.env.NODE_ENV=\\\"production\\\" app.js"
  }
}

如果您仍然在不同的 shell 中遇到跨平台引号转义问题,您可能需要切换到使用 JavaScript API。在那里,您可以使用常规 JavaScript 语法来消除跨平台差异。

如果您正在寻找更高级形式的 define 功能,该功能可以将表达式替换为非常量内容(例如,将全局变量替换为 shim),您可能可以使用类似的 inject 功能来做到这一点。

删除

受支持:构建转换

这告诉 esbuild 在构建之前编辑您的源代码以删除某些结构。目前,可以删除两种可能的东西。

CLI JS Go
esbuild app.js --drop:debugger
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  drop: ['debugger'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Drop:        api.DropDebugger,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
CLI JS Go
esbuild app.js --drop:console
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  drop: ['console'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Drop:        api.DropConsole,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

删除标签

受支持:构建转换

这告诉 esbuild 在构建之前编辑您的源代码以删除具有特定标签名称的 带标签的语句。例如,考虑以下代码。

function example() {
  DEV: doAnExpensiveCheck()
  return normalCodePath()
}

如果您使用此选项删除所有名为 DEV 的标签,那么 esbuild 将为您提供以下内容。

function example() {
  return normalCodePath();
}

您可以像这样配置此功能(这将删除 DEVTEST 标签)。

CLI JS Go
esbuild app.js --drop-labels=DEV,TEST
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  dropLabels: ['DEV', 'TEST'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    DropLabels:  []string{"DEV", "TEST"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

请注意,这不是唯一一种有条件地删除代码的方法。另一种更常见的方法是使用 define 功能将特定全局变量替换为布尔值。例如,考虑以下代码。

function example() {
  DEV && doAnExpensiveCheck()
  return normalCodePath()
}

如果您将 DEV 定义为 false,那么 esbuild 将为您提供以下内容。

function example() {
  return normalCodePath();
}

这与使用标签几乎相同。但是,与使用全局变量相比,使用标签来有条件地删除代码的优势在于,您不必担心全局变量未定义,因为有人忘记配置 esbuild 将其替换为其他内容。使用标签方法的一些缺点是,当标签被删除时,它会使有条件地删除代码变得稍微难以阅读,并且它不适用于嵌套表达式中的嵌入代码。对于给定项目使用哪种方法取决于个人喜好。

忽略注释

受支持:构建转换

由于 JavaScript 是一种动态语言,因此编译器有时很难识别未使用的代码,因此社区开发了一些注释来帮助告诉编译器哪些代码应该被视为无副作用且可供删除。目前,esbuild 支持两种形式的副作用注释

这些注释可能存在问题,因为编译器完全依赖于开发人员的准确性,而开发人员偶尔会发布带有错误注释的包。sideEffects 字段对于开发人员来说尤其容易出错,因为默认情况下,如果未使用任何导入,它会导致您的包中的所有文件都被视为死代码。如果您添加了一个包含副作用的新文件并忘记更新该字段,那么当人们尝试捆绑它时,您的包可能会出现故障。

这就是 esbuild 包含一种忽略副作用注释的方法的原因。只有在遇到由于意外从捆绑包中删除了必要的代码而导致捆绑包损坏的问题时,您才应该启用此功能

CLI JS Go
esbuild app.js --bundle --ignore-annotations
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  ignoreAnnotations: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    Bundle:            true,
    IgnoreAnnotations: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

启用此功能意味着 esbuild 将不再尊重 /* @__PURE__ */ 注释或 sideEffects 字段。但是,它仍然会对未使用的导入进行自动 树摇,因为这并不依赖于开发人员的注释。理想情况下,此标志只是一个临时的解决方法。您应该将这些问题报告给包的维护者以进行修复,因为它们表明包存在问题,并且它们也可能会让其他人绊倒。

注入

支持:构建

此选项允许您自动将全局变量替换为来自另一个文件的导入。这对于将您无法控制的代码适应新环境来说是一个有用的工具。例如,假设您有一个名为 process-cwd-shim.js 的文件,它使用导出名称 process.cwd 导出一个垫片

// process-cwd-shim.js
let processCwdShim = () => ''
export { processCwdShim as 'process.cwd' }
// entry.js
console.log(process.cwd())

这旨在替换对 node 的 process.cwd() 函数的使用,以防止调用它的包在浏览器中运行时崩溃。您可以使用注入功能将对全局属性 process.cwd 的所有引用替换为来自该文件的导入

CLI JS Go
esbuild entry.js --inject:./process-cwd-shim.js --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['entry.js'],
  inject: ['./process-cwd-shim.js'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"entry.js"},
    Inject:      []string{"./process-cwd-shim.js"},
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

这将产生类似于以下内容的结果

// out.js
var processCwdShim = () => "";
console.log(processCwdShim());

您可以将注入功能视为类似于 define 功能,只是它将表达式替换为对文件的导入,而不是替换为常量,并且要替换的表达式是使用文件中的导出名称指定的,而不是使用 esbuild API 中的内联字符串。

JSX 的自动导入

React(最初创建 JSX 语法的库)有一个名为 automatic 的模式,您不必 import 任何内容即可使用 JSX 语法。相反,JSX 到 JS 转换器将自动为您导入正确的 JSX 工厂函数。您可以使用 esbuild 的 jsx 设置启用 automatic JSX 模式。如果您想要 JSX 的自动导入并且您使用的是足够新的版本的 React,那么您应该使用 automatic JSX 模式。

但是,将 jsx 设置为 automatic 不幸地也意味着您正在使用高度特定于 React 的 JSX 转换,而不是默认的通用 JSX 转换。这意味着编写 JSX 工厂函数更加复杂,这也意味着 automatic 模式不适用于期望与标准 JSX 转换一起使用的库(包括旧版本的 React)。

您可以使用 esbuild 的注入功能在 JSX 转换未设置为 automatic 时自动导入 JSX 表达式的 工厂片段。以下是一个可以注入以执行此操作的示例文件

const { createElement, Fragment } = require('react')
export {
  createElement as 'React.createElement',
  Fragment as 'React.Fragment',
}

此代码使用 React 库作为示例,但您也可以通过适当的更改将此方法与任何其他 JSX 库一起使用。

注入没有导入的文件

您也可以将此功能与没有导出的文件一起使用。在这种情况下,注入的文件只是在输出的其余部分之前出现,就好像每个输入文件都包含 import "./file.js" 一样。由于 ECMAScript 模块的工作方式,这种注入仍然是“卫生的”,因为不同文件中的同名符号会被重命名,以避免它们相互冲突。

有条件地注入文件

如果您想有条件地仅在实际使用导出时导入文件,则应将注入文件标记为没有副作用,方法是将其放在包中并在该包的 package.json 文件中添加 "sideEffects": false。此设置是来自 Webpack 的约定,esbuild 会尊重任何导入的文件,而不仅仅是使用 inject 的文件。

保留名称

受支持:构建转换

在 JavaScript 中,函数和类的 name 属性默认为源代码中的附近标识符。这些语法形式都将函数的 name 属性设置为 "fn"

function fn() {}
let fn = function() {};
fn = function() {};
let [fn = function() {}] = [];
let {fn = function() {}} = {};
[fn = function() {}] = [];
({fn = function() {}} = {});

但是,缩小 会重命名符号以减少代码大小,并且 捆绑 有时需要重命名符号以避免冲突。这会更改许多这些情况下的 name 属性的值。这通常没问题,因为 name 属性通常仅用于调试。但是,一些框架依赖于 name 属性进行注册和绑定。如果是这种情况,您可以启用此选项以保留原始的 name 值,即使在缩小的代码中也是如此

CLI JS Go
esbuild app.js --minify --keep-names
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  minify: true,
  keepNames: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    KeepNames:         true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

请注意,如果 target 已设置为不支持 esbuild 更改函数和类上的 name 属性的旧环境,则此功能不可用。对于不支持 ES6 的环境,情况就是这样。

混淆属性

受支持:构建转换

使用此功能可能会以微妙的方式破坏您的代码。除非您知道自己在做什么,并且确切地知道它将如何影响您的代码和所有依赖项,否则请勿使用此功能。

此设置允许您将正则表达式传递给 esbuild,以告诉 esbuild 自动重命名与该正则表达式匹配的所有属性。当您想要缩小代码中的某些属性名称以使生成的代码更小或以某种方式混淆代码的意图时,这很有用。

以下是一个使用正则表达式 _$ 来混淆所有以下划线结尾的属性(例如 foo_)的示例。这会将 print({ foo_: 0 }.foo_) 混淆为 print({ a: 0 }.a)

CLI JS Go
esbuild app.js --mangle-props=_$
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    MangleProps: "_$",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

仅混淆以下划线结尾的属性是一个合理的启发式方法,因为正常的 JS 代码通常不包含这样的标识符。浏览器 API 也不使用这种命名约定,因此这也避免了与浏览器 API 的冲突。如果您想避免混淆诸如 __defineGetter__ 之类的名称,您可以考虑使用更复杂的正则表达式,例如 [^_]_$(即必须以非下划线结尾,后跟下划线)。

这是一个单独的设置,而不是 minify 设置的一部分,因为它是一种不安全的转换,不适用于任意 JavaScript 代码。它仅在提供的正则表达式匹配您想要混淆的所有属性并且不匹配您不想混淆的任何属性时才有效。它也仅在您在任何情况下都不间接引用混淆的属性时才有效。例如,这意味着您不能使用 obj[prop] 来引用一个属性,其中 prop 是一个包含属性名称的字符串。具体来说,以下语法结构是唯一有资格进行属性混淆的结构

语法 示例
点属性访问 x.foo_
点可选链 x?.foo_
对象属性 x = { foo_: y }
对象方法 x = { foo_() {} }
类字段 class x { foo_ = y }
类方法 class x { foo_() {} }
对象解构绑定 let { foo_: x } = y
对象解构赋值 ({ foo_: x } = y)
JSX 元素成员表达式 <X.foo_></X.foo_>
JSX 属性名称 <X foo_={y} />
TypeScript 命名空间导出 namespace x { export let foo_ = y }
TypeScript 参数属性 class x { constructor(public foo_) {} }

使用此功能时,请记住,属性名称仅在单个 esbuild API 调用中始终如一地混淆,而不是跨 esbuild API 调用。每个 esbuild API 调用都会执行独立的属性混淆操作,因此由两个不同的 API 调用生成的输出文件可能会将相同的属性混淆为两个不同的名称,这会导致生成的代码行为不正确。

带引号的属性

默认情况下,esbuild 不会修改字符串文字的内容。这意味着您可以通过将属性作为字符串引用来避免对单个属性进行属性混淆。但是,您必须始终对给定属性使用引号或不使用引号,才能使此方法起作用。例如,print({ foo_: 0 }.foo_) 将被混淆为 print({ a: 0 }.a),而 print({ 'foo_': 0 }['foo_']) 不会被混淆。

如果您希望 esbuild 也混淆字符串文字的内容,您可以像这样显式启用该行为

CLI JS Go
esbuild app.js --mangle-props=_$ --mangle-quoted
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  mangleQuoted: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:  []string{"app.js"},
    MangleProps:  "_$",
    MangleQuoted: api.MangleQuotedTrue,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

启用此功能将使以下语法结构也有资格进行属性混淆

语法 示例
带引号的属性访问 x['foo_']
带引号的可选链 x?.['foo_']
带引号的对象属性 x = { 'foo_': y }
带引号的对象方法 x = { 'foo_'() {} }
带引号的类字段 class x { 'foo_' = y }
带引号的类方法 class x { 'foo_'() {} }
带引号的对象解构绑定 let { 'foo_': x } = y
带引号的对象解构赋值 ({ 'foo_': x } = y)
in 左侧的字符串文字 'foo_' in x

混淆其他字符串

混淆 带引号的属性 仍然只混淆属性名称位置的字符串。有时您可能还需要混淆代码中任意其他位置的字符串中的属性名称。为此,您可以使用 /* @__KEY__ */ 注释作为字符串的前缀,以告诉 esbuild 将字符串的内容视为可以混淆的属性名称。例如

let obj = {}
Object.defineProperty(
  obj,
  /* @__KEY__ */ 'foo_',
  { get: () => 123 },
)
console.log(obj.foo_)

这将导致字符串 'foo_' 的内容被混淆为属性名称(假设 属性混淆 已启用并且 foo_ 有资格重命名)。/* @__KEY__ */ 注释是来自 Terser 的约定,Terser 是一种流行的 JavaScript 缩小器,具有类似的属性混淆功能。

防止重命名

如果您想排除某些属性不被混淆,您可以使用额外的设置来保留它们。例如,这使用正则表达式^__.*__$来保留所有以两个下划线开头和结尾的属性,例如__foo__

CLI JS Go
esbuild app.js --mangle-props=_$ "--reserve-props=^__.*__$"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  reserveProps: /^__.*__$/,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:  []string{"app.js"},
    MangleProps:  "_$",
    ReserveProps: "^__.*__$",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

持久化重命名决策

属性混淆功能的高级用法涉及将原始名称到混淆名称的映射存储在持久缓存中。启用后,所有混淆的属性重命名将在初始构建期间记录在缓存中。后续构建将重用存储在缓存中的重命名,并为任何新添加的属性添加额外的重命名。这有一些后果

例如,考虑以下输入文件

console.log({
  someProp_: 1,
  customRenaming_: 2,
  disabledRenaming_: 3
});

如果我们希望customRenaming_被重命名为cR_,并且我们不希望disabledRenaming_被重命名,我们可以将以下混淆缓存 JSON 传递给 esbuild

{
  "customRenaming_": "cR_",
  "disabledRenaming_": false
}

混淆缓存 JSON 可以像这样传递给 esbuild

CLI JS Go
esbuild app.js --mangle-props=_$ --mangle-cache=cache.json
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  mangleCache: {
    customRenaming_: "cR_",
    disabledRenaming_: false
  },
})

console.log('updated mangle cache:', result.mangleCache)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    MangleProps: "_$",
    MangleCache: map[string]interface{}{
      "customRenaming_":   "cR_",
      "disabledRenaming_": false,
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Println("updated mangle cache:", result.MangleCache)
}

启用属性命名后,将生成以下输出文件

console.log({
  a: 1,
  cR_: 2,
  disabledRenaming_: 3
});

以及以下更新的混淆缓存

{
  "customRenaming_": "cR_",
  "disabledRenaming_": false,
  "someProp_": "a"
}

压缩

受支持:构建转换

启用后,生成的代码将被压缩而不是漂亮打印。压缩后的代码通常等同于未压缩的代码,但体积更小,这意味着它下载速度更快,但更难调试。通常您在生产中压缩代码,但在开发中不压缩。

在 esbuild 中启用压缩如下所示

CLI JS Go
echo 'fn = obj => { return obj.x }' | esbuild --minify
fn=n=>n.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
  minify: true,
})).code
'fn=n=>n.x;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "fn = obj => { return obj.x }"

  result := api.Transform(js, api.TransformOptions{
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

此选项组合执行三项独立操作:它删除空白,它将您的语法重写为更紧凑的形式,并且它将局部变量重命名为更短的名称。通常您希望执行所有这些操作,但如果需要,也可以单独启用这些选项。

CLI JS Go
echo 'fn = obj => { return obj.x }' | esbuild --minify-whitespace
fn=obj=>{return obj.x};
echo 'fn = obj => { return obj.x }' | esbuild --minify-identifiers
fn = (n) => {
  return n.x;
};
echo 'fn = obj => { return obj.x }' | esbuild --minify-syntax
fn = (obj) => obj.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
  minifyWhitespace: true,
})).code
'fn=obj=>{return obj.x};\n'
(await esbuild.transform(js, {
  minifyIdentifiers: true,
})).code
'fn = (n) => {\n  return n.x;\n};\n'
(await esbuild.transform(js, {
  minifySyntax: true,
})).code
'fn = (obj) => obj.x;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  css := "div { color: yellow }"

  result1 := api.Transform(css, api.TransformOptions{
    Loader:           api.LoaderCSS,
    MinifyWhitespace: true,
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(css, api.TransformOptions{
    Loader:            api.LoaderCSS,
    MinifyIdentifiers: true,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }

  result3 := api.Transform(css, api.TransformOptions{
    Loader:       api.LoaderCSS,
    MinifySyntax: true,
  })

  if len(result3.Errors) == 0 {
    fmt.Printf("%s", result3.Code)
  }
}

这些相同的概念也适用于 CSS,而不仅仅是 JavaScript

CLI JS Go
echo 'div { color: yellow }' | esbuild --loader=css --minify
div{color:#ff0}
import * as esbuild from 'esbuild'var css = 'div { color: yellow }'
(await esbuild.transform(css, {
  loader: 'css',
  minify: true,
})).code
'div{color:#ff0}\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  css := "div { color: yellow }"

  result := api.Transform(css, api.TransformOptions{
    Loader:            api.LoaderCSS,
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

esbuild 中的 JavaScript 压缩算法通常生成的输出非常接近行业标准 JavaScript 压缩工具的压缩输出大小。 此基准 提供了不同压缩器之间输出大小比较的示例。虽然 esbuild 并非在所有情况下都是最佳的 JavaScript 压缩器(并且没有尝试成为最佳),但它努力为大多数代码生成与专用压缩工具大小相差不到几个百分点的压缩输出,当然,比其他工具快得多。

注意事项

在使用 esbuild 作为压缩器时,请记住以下几点

受支持:构建转换

各种 JavaScript 工具使用一种约定,其中包含 /* @__PURE__ *//* #__PURE__ */ 的特殊注释在新的或调用表达式之前表示该表达式可以被删除,如果结果值未被使用。它看起来像这样

let button = /* @__PURE__ */ React.createElement(Button, null);

此信息在捆绑器(如 esbuild)在树摇动(也称为死代码删除)期间使用,以在捆绑器无法自行证明删除是安全的(由于 JavaScript 代码的动态特性)的情况下,跨模块边界执行对未使用的导入的细粒度删除。

请注意,虽然注释说“纯”,但它令人困惑地没有表示被调用的函数是纯的。例如,它不表示可以缓存对该函数的重复调用。这个名称本质上只是“如果未使用则可以删除”的抽象简写。

esbuild 中会自动将某些表达式(如 JSX 和某些内置全局变量)注释为 /* @__PURE__ */。您还可以配置其他全局变量以标记为 /* @__PURE__ */。例如,您可以将全局 document.createElement 函数标记为这样,以便在捆绑包被压缩时,只要结果没有被使用,它就会自动从捆绑包中删除。

值得一提的是,注释的效果只扩展到调用本身,而不扩展到参数。即使启用压缩,具有副作用的参数也会被保留

CLI JS Go
echo 'document.createElement(elemName())' | esbuild --pure:document.createElement
/* @__PURE__ */ document.createElement(elemName());
echo 'document.createElement(elemName())' | esbuild --pure:document.createElement --minify
elemName();
import * as esbuild from 'esbuild'let js = 'document.createElement(elemName())'
(await esbuild.transform(js, {
  pure: ['document.createElement'],
})).code
'/* @__PURE__ */ document.createElement(elemName());\n'
(await esbuild.transform(js, {
  pure: ['document.createElement'],
  minify: true,
})).code
'elemName();\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "document.createElement(elemName())"

  result1 := api.Transform(js, api.TransformOptions{
    Pure: []string{"document.createElement"},
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Pure:         []string{"document.createElement"},
    MinifySyntax: true,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

请注意,如果您尝试删除对 console API 方法(如 console.log)的所有调用,并且还希望删除具有副作用的参数的求值,则为此提供了一个特殊情况:您可以使用drop 功能,而不是将 console API 调用标记为纯的。但是,此机制特定于 console API,不适用于其他调用表达式。

树摇动

受支持:构建转换

树摇动是 JavaScript 社区用来表示死代码消除的术语,这是一种常见的编译器优化,它会自动删除无法访问的代码。在 esbuild 中,此术语专门指声明级死代码删除。

树摇动最容易用一个例子来解释。考虑以下文件。有一个被使用的函数和一个未被使用的函数

// input.js
function one() {
  console.log('one')
}
function two() {
  console.log('two')
}
one()

如果您使用 esbuild --bundle input.js --outfile=output.js 捆绑此文件,未使用的函数将自动被丢弃,留下以下输出

// input.js
function one() {
  console.log("one");
}
one();

即使我们将函数拆分到一个单独的库文件中,并使用 import 语句导入它们,这也适用

// lib.js
export function one() {
  console.log('one')
}
export function two() {
  console.log('two')
}
// input.js
import * as lib from './lib.js'
lib.one()

如果您使用 esbuild --bundle input.js --outfile=output.js 捆绑此文件,未使用的函数和未使用的导入仍然会自动被丢弃,留下以下输出

// lib.js
function one() {
  console.log("one");
}

// input.js
one();

这样,esbuild 只会捆绑您实际使用的包的部分,这有时可以节省大量空间。请注意,esbuild 的树摇动实现依赖于使用 ECMAScript 模块 importexport 语句。它不适用于 CommonJS 模块。npm 上的许多包都包含这两种格式,esbuild 尝试默认选择与树摇动一起使用的格式。您可以使用主字段 和/或条件 选项来自定义 esbuild 选择的格式,具体取决于包。

默认情况下,树摇动仅在启用捆绑或输出格式 设置为 iife 时启用,否则树摇动将被禁用。您可以通过将其设置为 true 来强制启用树摇动

CLI JS Go
esbuild app.js --tree-shaking=true
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  treeShaking: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    TreeShaking: api.TreeShakingTrue,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

您也可以通过将其设置为 false 来强制禁用树摇动

CLI JS Go
esbuild app.js --tree-shaking=false
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  treeShaking: false,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    TreeShaking: api.TreeShakingFalse,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

树摇动和副作用

用于树摇动的副作用检测是保守的,这意味着 esbuild 仅将代码视为可删除的死代码,如果它可以确定没有隐藏的副作用。例如,原始字面量(如 12.34"abcd")是无副作用的,可以被删除,而表达式(如 "ab" + cdfoo.bar)不是无副作用的(连接字符串会调用 toString(),它可能具有副作用,成员访问可能会调用 getter,它也可能具有副作用)。即使引用全局标识符也被认为是副作用,因为它会在没有该名称的全局变量的情况下抛出 ReferenceError。以下是一个示例

// These are considered side-effect free
let a = 12.34;
let b = "abcd";
let c = { a: a };

// These are not considered side-effect free
// since they could cause some code to run
let x = "ab" + cd;
let y = foo.bar;
let z = { [x]: x };

有时,即使无法自动确定代码没有副作用,也希望允许对某些代码进行树状抖动。这可以通过 纯注释 来实现,它告诉 esbuild 信任代码作者,注释的代码中没有副作用。注释是 /* @__PURE__ */,只能放在新的或调用表达式之前。您可以注释一个立即调用的函数表达式,并在函数体中放置任意副作用。

// This is considered side-effect free due to
// the annotation, and will be removed if unused
let gammaTable = /* @__PURE__ */ (() => {
  // Side-effect detection is skipped in here
  let table = new Uint8Array(256);
  for (let i = 0; i < 256; i++)
    table[i] = Math.pow(i / 255, 2.2) * 255;
  return table;
})();

虽然 /* @__PURE__ */ 只能用于调用表达式这一事实有时会使代码更冗长,但这种语法的一个主要优点是它可以在 JavaScript 生态系统中的许多其他工具中移植,包括流行的 UglifyJSTerser JavaScript 压缩器(它们被其他主要工具使用,包括 WebpackParcel)。

请注意,注释会导致 esbuild 假设注释的代码没有副作用。如果注释错误,并且代码实际上确实具有重要的副作用,则这些注释会导致代码出错。如果您正在捆绑带有错误注释的第三方代码,您可能需要启用 忽略注释 以确保捆绑的代码正确。

源映射

源根目录

受支持:构建转换

此功能仅在启用 源映射 时才相关。它允许您设置源映射中 sourceRoot 字段的值,该字段指定源映射中所有其他路径的相对路径。如果此字段不存在,则源映射中的所有路径都将被解释为相对于包含源映射的目录。

您可以像这样配置 sourceRoot

CLI JS Go
esbuild app.js --sourcemap --source-root=https://raw.githubusercontent.com/some/repo/v1.2.3/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  sourcemap: true,
  sourceRoot: 'https://raw.githubusercontent.com/some/repo/v1.2.3/',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Sourcemap:   api.SourceMapInline,
    SourceRoot:  "https://raw.githubusercontent.com/some/repo/v1.2.3/",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

源文件

受支持:构建转换

此选项在使用没有文件名的输入时设置文件名。这发生在使用 transform API 时,以及在使用 build API 和 stdin 时。配置的文件名会反映在错误消息和源映射中。如果未配置,则文件名默认为 <stdin>。它可以像这样配置

CLI JS Go
cat app.js | esbuild --sourcefile=example.js --sourcemap
import * as esbuild from 'esbuild'
import fs from 'node:fs'

let js = fs.readFileSync('app.js', 'utf8')
let result = await esbuild.transform(js, {
  sourcefile: 'example.js',
  sourcemap: 'inline',
})

console.log(result.code)
package main

import "fmt"
import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js, err := ioutil.ReadFile("app.js")
  if err != nil {
    panic(err)
  }

  result := api.Transform(string(js),
    api.TransformOptions{
      Sourcefile: "example.js",
      Sourcemap:  api.SourceMapInline,
    })

  if len(result.Errors) == 0 {
    fmt.Printf("%s %s", result.Code)
  }
}

源映射

受支持:构建转换

源映射可以使调试代码更容易。它们对必要的信息进行编码,以便从生成的输出文件中的行/列偏移量转换回相应的原始输入文件中的行/列偏移量。如果您生成的代码与原始代码有很大不同(例如,您的原始代码是 TypeScript 或您启用了 压缩),这将非常有用。如果您更喜欢在浏览器的开发者工具中查看单个文件而不是一个大型捆绑文件,这也很有用。

请注意,源映射输出同时支持 JavaScript 和 CSS,并且相同的选项适用于两者。下面所有关于 .js 文件的内容也同样适用于 .css 文件。

有四种不同的源映射生成模式

  1. linked

    此模式意味着源映射将生成到一个单独的 .js.map 输出文件中,该文件与 .js 输出文件并排,并且 .js 输出文件包含一个特殊的 //# sourceMappingURL= 注释,指向 .js.map 输出文件。这样,浏览器就知道在您打开调试器时,在哪里可以找到给定文件的源映射。像这样使用 linked 源映射模式

CLI JS Go
esbuild app.ts --sourcemap --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapLinked,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. external

    此模式意味着源映射将生成到一个单独的 .js.map 输出文件中,该文件与 .js 输出文件并排,但与 linked 模式不同,.js 输出文件不包含 //# sourceMappingURL= 注释。像这样使用 external 源映射模式

CLI JS Go
esbuild app.ts --sourcemap=external --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'external',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapExternal,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. inline

    此模式意味着源映射将作为 //# sourceMappingURL= 注释中的 base64 负载附加到 .js 输出文件的末尾。不会生成额外的 .js.map 输出文件。请记住,源映射通常非常大,因为它们包含所有原始源代码,因此您通常不希望发送包含 inline 源映射的代码。要从源映射中删除源代码(仅保留文件名和行/列映射),请使用 源代码内容 选项。像这样使用 inline 源映射模式

CLI JS Go
esbuild app.ts --sourcemap=inline --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'inline',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapInline,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. 两者

    此模式是 inlineexternal 的组合。源映射将内联附加到 .js 输出文件的末尾,并且同一源映射的另一个副本将写入一个单独的 .js.map 输出文件中,该文件与 .js 输出文件并排。像这样使用 both 源映射模式

CLI JS Go
esbuild app.ts --sourcemap=both --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'both',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapInlineAndExternal,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

build API 支持上面列出的所有四种源映射模式,但 transform API 不支持 linked 模式。这是因为从 transform API 返回的输出没有关联的文件名。如果您希望 transform API 的输出包含源映射注释,您可以自己附加一个。此外,transform API 的 CLI 形式仅支持 inline 模式,因为输出写入 stdout,因此无法生成多个输出文件。

如果您想“窥视内部”以了解源映射的作用(或调试源映射问题),您可以将相关输出文件和关联的源映射上传到此处:源映射可视化

使用源映射

在浏览器中,只要启用了源映射设置,浏览器开发者工具应该会自动获取源映射。请注意,浏览器仅使用源映射来更改堆栈跟踪的显示方式,当它们被记录到控制台中时。堆栈跟踪本身不会被修改,因此在您的代码中检查 error.stack 仍然会给出包含已编译代码的未映射堆栈跟踪。以下是在浏览器开发者工具中启用此设置的方法

在 node 中,从 版本 v12.12.0 开始,源映射得到原生支持。此功能默认情况下处于禁用状态,但可以使用标志启用。与浏览器不同,node 中的实际堆栈跟踪也会被修改,因此在您的代码中检查 error.stack 会给出包含原始源代码的映射堆栈跟踪。以下是在 node 中启用此设置的方法(--enable-source-maps 标志必须位于脚本文件名之前)

node --enable-source-maps app.js

源代码内容

受支持:构建转换

源映射 是使用 版本 3 的源映射格式生成的,该格式是迄今为止最广泛支持的变体。每个源映射看起来都像这样

{
  "version": 3,
  "sources": ["bar.js", "foo.js"],
  "sourcesContent": ["bar()", "foo()\nimport './bar'"],
  "mappings": ";AAAA;;;ACAA;",
  "names": []
}

sourcesContent 字段是一个可选字段,它包含所有原始源代码。这对于调试很有帮助,因为它意味着原始源代码将在调试器中可用。

但是,在某些情况下不需要它。例如,如果您只是在生产环境中使用源映射来生成包含原始文件名的堆栈跟踪,则不需要原始源代码,因为没有涉及调试器。在这种情况下,省略 sourcesContent 字段以使源映射更小可能是可取的

CLI JS Go
esbuild --bundle app.js --sourcemap --sources-content=false
import * as esbuild from 'esbuild'

await esbuild.build({
  bundle: true,
  entryPoints: ['app.js'],
  sourcemap: true,
  sourcesContent: false,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Bundle:         true,
    EntryPoints:    []string{"app.js"},
    Sourcemap:      api.SourceMapInline,
    SourcesContent: api.SourcesContentExclude,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

构建元数据

分析

支持:构建

如果您正在寻找交互式可视化,请尝试 esbuild 的 捆绑大小分析器。您可以上传您的 esbuild 元文件 以查看捆绑大小细分。

使用分析功能会生成一个关于捆绑包内容的易于阅读的报告

CLI JS Go
esbuild --bundle example.jsx --outfile=out.js --minify --analyze

  out.js                                                                    27.6kb  100.0%
   ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js  19.2kb   69.8%
   ├ node_modules/react/cjs/react.production.min.js                          5.9kb   21.4%
   ├ node_modules/object-assign/index.js                                     962b     3.4%
   ├ example.jsx                                                             137b     0.5%
   ├ node_modules/react-dom/server.browser.js                                 50b     0.2%
   └ node_modules/react/index.js                                              50b     0.2%

...
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['example.jsx'],
  outfile: 'out.js',
  minify: true,
  metafile: true,
})

console.log(await esbuild.analyzeMetafile(result.metafile))
package main

import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"example.jsx"},
    Outfile:           "out.js",
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    Metafile:          true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{}))
}

该信息显示了哪些输入文件最终出现在每个输出文件中,以及它们最终占用了输出文件的百分比。如果您需要更多信息,可以启用“详细”模式。这目前显示了从入口点到每个输入文件的导入路径,它告诉您为什么将给定输入文件包含在捆绑包中

CLI JS Go
esbuild --bundle example.jsx --outfile=out.js --minify --analyze=verbose

  out.js ─────────────────────────────────────────────────────────────────── 27.6kb ─ 100.0%
   ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js ─ 19.2kb ── 69.8%
   │  └ node_modules/react-dom/server.browser.js
   │     └ example.jsx
   ├ node_modules/react/cjs/react.production.min.js ───────────────────────── 5.9kb ── 21.4%
   │  └ node_modules/react/index.js
   │     └ example.jsx
   ├ node_modules/object-assign/index.js ──────────────────────────────────── 962b ──── 3.4%
   │  └ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js
   │     └ node_modules/react-dom/server.browser.js
   │        └ example.jsx
   ├ example.jsx ──────────────────────────────────────────────────────────── 137b ──── 0.5%
   ├ node_modules/react-dom/server.browser.js ──────────────────────────────── 50b ──── 0.2%
   │  └ example.jsx
   └ node_modules/react/index.js ───────────────────────────────────────────── 50b ──── 0.2%
      └ example.jsx

...
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['example.jsx'],
  outfile: 'out.js',
  minify: true,
  metafile: true,
})

console.log(await esbuild.analyzeMetafile(result.metafile, {
  verbose: true,
}))
package main

import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"example.jsx"},
    Outfile:           "out.js",
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    Metafile:          true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{
    Verbose: true,
  }))
}

此分析只是对可以在 元文件 中找到的信息的可视化。如果此分析不完全适合您的需求,欢迎您使用元文件中的信息构建自己的可视化。

请注意,此格式化的分析摘要旨在供人类使用,而不是机器使用。具体格式可能会随着时间的推移而改变,这可能会破坏尝试解析它的任何工具。您不应该编写一个工具来解析这些数据。您应该使用 JSON 元数据文件 中的信息。此可视化中的所有内容都来自 JSON 元数据,因此您不会因为不解析 esbuild 的格式化分析摘要而丢失任何信息。

元文件

支持:构建

此选项告诉 esbuild 以 JSON 格式生成有关构建的一些元数据。以下示例将元数据放在名为 meta.json 的文件中

CLI JS Go
esbuild app.js --bundle --metafile=meta.json --outfile=out.js
import * as esbuild from 'esbuild'
import fs from 'node:fs'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  metafile: true,
  outfile: 'out.js',
})

fs.writeFileSync('meta.json', JSON.stringify(result.metafile))
package main

import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Metafile:    true,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  ioutil.WriteFile("meta.json", []byte(result.Metafile), 0644)
}

然后,其他工具可以分析这些数据。对于交互式可视化,您可以使用 esbuild 自己的 捆绑大小分析器。对于快速文本分析,您可以使用 esbuild 的内置 分析 功能。或者,您可以编写自己的分析,它使用这些信息。

元数据 JSON 格式如下所示(使用 TypeScript 接口描述)

interface Metafile {
  inputs: {
    [path: string]: {
      bytes: number
      imports: {
        path: string
        kind: string
        external?: boolean
        original?: string
        with?: Record<string, string>
      }[]
      format?: string
      with?: Record<string, string>
    }
  }
  outputs: {
    [path: string]: {
      bytes: number
      inputs: {
        [path: string]: {
          bytesInOutput: number
        }
      }
      imports: {
        path: string
        kind: string
        external?: boolean
      }[]
      exports: string[]
      entryPoint?: string
      cssBundle?: string
    }
  }
}

日志记录

颜色

受支持:构建转换

此选项启用或禁用 esbuild 写入终端 stderr 文件描述符的错误和警告消息中的颜色。默认情况下,如果 stderr 是 TTY 会话,则自动启用颜色,否则自动禁用。esbuild 中的彩色输出如下所示

[WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof]

    example.js:2:16:
      2 │ log(typeof x == "null")
        ╵                 ~~~~~~

  The expression "typeof x" actually evaluates to "object" in JavaScript, not "null". You need to
  use "x === null" to test for null.

[ERROR] Could not resolve "logger"

    example.js:1:16:
      1 │ import log from "logger"~~~~~~~~

  You can mark the path "logger" as external to exclude it from the bundle, which will remove this
  error and leave the unresolved path in the bundle.

通过将 color 设置为 true,可以强制启用彩色输出。如果您将 esbuild 的 stderr 输出管道到 TTY 本身,这将很有用

CLI JS Go
echo 'typeof x == "null"' | esbuild --color=true 2> stderr.txt
import * as esbuild from 'esbuild'

let js = 'typeof x == "null"'
await esbuild.transform(js, {
  color: true,
})
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "typeof x == 'null'"

  result := api.Transform(js, api.TransformOptions{
    Color: api.ColorAlways,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

彩色输出也可以设置为 false 以禁用颜色。

格式化消息

受支持:构建转换

此 API 调用可用于使用 esbuild 本身使用的相同格式将 build API 和 transform API 返回的日志错误和警告格式化为字符串。如果您想自定义 esbuild 的日志记录方式,例如在打印日志消息之前处理它们或将它们打印到控制台以外的位置,这将很有用。以下是一个示例

JS Go
import * as esbuild from 'esbuild'

let formatted = await esbuild.formatMessages([
  {
    text: 'This is an error',
    location: {
      file: 'app.js',
      line: 10,
      column: 4,
      length: 3,
      lineText: 'let foo = bar',
    },
  },
], {
  kind: 'error',
  color: false,
  terminalWidth: 100,
})

console.log(formatted.join('\n'))
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "strings"

func main() {
  formatted := api.FormatMessages([]api.Message{
    {
      Text: "This is an error",
      Location: &api.Location{
        File:     "app.js",
        Line:     10,
        Column:   4,
        Length:   3,
        LineText: "let foo = bar",
      },
    },
  }, api.FormatMessagesOptions{
    Kind:          api.ErrorMessage,
    Color:         false,
    TerminalWidth: 100,
  })

  fmt.Printf("%s", strings.Join(formatted, "\n"))
}

选项

以下选项可以提供以控制格式

JS Go
interface FormatMessagesOptions {
  kind: 'error' | 'warning';
  color?: boolean;
  terminalWidth?: number;
}
type FormatMessagesOptions struct {
  Kind          MessageKind
  Color         bool
  TerminalWidth int
}

日志级别

受支持:构建转换

可以更改日志级别以阻止 esbuild 将警告和/或错误消息打印到终端。六个日志级别是

可以像这样设置日志级别

CLI JS Go
echo 'typeof x == "null"' | esbuild --log-level=error
import * as esbuild from 'esbuild'

let js = 'typeof x == "null"'
await esbuild.transform(js, {
  logLevel: 'error',
})
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "typeof x == 'null'"

  result := api.Transform(js, api.TransformOptions{
    LogLevel: api.LogLevelError,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

日志限制

受支持:构建转换

默认情况下,esbuild 在报告了 10 条日志消息后停止报告日志消息。这避免了意外生成大量日志消息,这些消息很容易锁住速度较慢的终端模拟器,例如 Windows 命令提示符。它还避免了意外地将整个滚动缓冲区用于滚动缓冲区有限的终端模拟器。

日志限制可以更改为另一个值,也可以通过将其设置为零来完全禁用。这将显示所有日志消息。

CLI JS Go
esbuild app.js --log-limit=0
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  logLimit: 0,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    LogLimit:    0,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

日志覆盖

受支持:构建转换

此功能允许您更改单个日志消息类型的日志级别。您可以使用它来静默特定类型的警告,启用默认情况下未启用的其他警告,甚至将警告转换为错误。

例如,当针对较旧的浏览器时,esbuild 会自动将使用对这些浏览器来说过于新的功能的正则表达式字面量转换为 new RegExp() 调用,以允许生成的代码在不被浏览器视为语法错误的情况下运行。但是,如果您没有为 RegExp 添加 polyfill,这些调用仍然会在运行时抛出异常,因为该正则表达式语法仍然不受支持。如果您希望 esbuild 在您使用较新的不受支持的正则表达式语法时生成警告,您可以像这样操作。

CLI JS Go
esbuild app.js --log-override:unsupported-regexp=warning --target=chrome50
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  logOverride: {
    'unsupported-regexp': 'warning',
  },
  target: 'chrome50',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    LogOverride: map[string]api.LogLevel{
      "unsupported-regexp": api.LogLevelWarning,
    },
    Engines: []api.Engine{
      {Name: api.EngineChrome, Version: "50"},
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

每种消息类型的日志级别可以覆盖为 日志级别 设置支持的任何值。所有当前可用的消息类型列在下面(单击每个消息类型以查看示例日志消息)。

这些消息类型应该相当稳定,但将来可能会添加新的消息类型,偶尔也会删除旧的消息类型。如果删除了消息类型,对该消息类型的任何覆盖将被静默忽略。