API

API可以通过三种语言之一访问:命令行、JavaScript和Go。这些语言的概念和参数在很大程度上是相同的, 因此它们将一起而不是为每种语言分别编写文档。您可以使用右上角的CLIJSGo选项卡 在这些代码示例之间切换。每种语言的一些具体内容:

概述

esbuild的两个最常用的API是buildtransform。 每个都在高层次上进行了描述,后面是每个单独的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)
  }
}

build 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
# 命令行没有"重建"的API
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都接受以下选项:

Path resolution:
Transformation (转换):
Optimization (优化):

Transform

This is a limited special-case of build that transforms a string of code representing an in-memory file in an isolated environment that's completely disconnected from any other files. Common uses include minifying code and transforming TypeScript into JavaScript. Here's an example:

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)
  }
}

Taking a string instead of a file as input is more ergonomic for certain use cases. File system isolation has certain advantages (e.g. works in the browser, not affected by nearby package.json files) and certain disadvantages (e.g. can't be used with bundling or plugins). If your use case doesn't fit the transform API then you should use the more general build API instead.

The transform API takes the following options:

General options:
Input:
Output contents:
Transformation (转换):
Optimization (优化):

JS-specific details

The JS API for esbuild comes in both asynchronous and synchronous flavors. The asynchronous API is recommended because it works in all environments and it's faster and more powerful. The synchronous API only works in node and can only do certain things, but it's sometimes necessary in certain node-specific situations. In detail:

Async API

Asynchronous API calls return their results using a promise. Note that you'll likely have to use the .mjs file extension in node due to the use of the import and top-level await keywords:

import * as esbuild from 'esbuild'

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

Pros:

Cons:

Sync API

Synchronous API calls return their results inline:

let esbuild = require('esbuild')

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

Pros:

Cons:

In the browser

The esbuild API can also run in the browser using WebAssembly in a Web Worker. To take advantage of this you will need to install the esbuild-wasm package instead of the esbuild package:

npm install esbuild-wasm

The API for the browser is similar to the API for node except that you need to call initialize() first, and you need to pass the URL of the WebAssembly binary. The synchronous versions of the API are also not available. Assuming you are using a bundler, that would look something like this:

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)

If you're already running this code from a worker and don't want initialize to create another worker, you can pass worker: false to it. Then it will create a WebAssembly module in the same thread as the thread that calls initialize.

You can also use esbuild's API as a script tag in a HTML file without needing to use a bundler by loading the lib/browser.min.js file with a <script> tag. In this case the API creates a global called esbuild that holds the API object:

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

If you want to use this API with ECMAScript modules, you should import the esm/browser.min.js file instead:

<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>

General options

Bundle

To bundle a file means to inline any imported dependencies into the file itself. This process is recursive so dependencies of dependencies (and so on) will also be inlined. By default esbuild will not bundle the input files. Bundling must be explicitly enabled like this:

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)
  }
}

Refer to the getting started guide for an example of bundling with real-world code.

Note that bundling is different than file concatenation. Passing esbuild multiple input files with bundling enabled will create multiple separate bundles instead of joining the input files together. To join a set of files together with esbuild, import them all into a single entry point file and bundle just that one file with esbuild.

Non-analyzable imports

Import paths are currently only bundled if they are a string literal or a glob pattern. Other forms of import paths are not bundled, and are instead preserved verbatim in the generated output. This is because bundling is a compile-time operation and esbuild doesn't support all forms of run-time path resolution. Here are some examples:

// 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);

The way to work around non-analyzable imports is to mark the package containing this problematic code as external so that it's not included in the bundle. You will then need to ensure that a copy of the external package is available to your bundled code at run-time.

Some bundlers such as Webpack try to support all forms of run-time path resolution by including all potentially-reachable files in the bundle and then emulating a file system at run-time. However, run-time file system emulation is out of scope and will not be implemented in esbuild. If you really need to bundle code that does this, you will likely need to use another bundler instead of esbuild.

Glob-style imports

Import paths that are evaluated at run-time can now be bundled in certain limited situations. The import path expression must be a form of string concatenation and must start with either ./ or ../. Each non-string expression in the string concatenation chain becomes a wildcard in a glob pattern. Some examples:

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

When you do this, esbuild will search the file system for all files that match the pattern and include all of them in the bundle along with a map that maps the matching import path to the bundled module. The import expression will be replaced with a lookup into that map. An error will be thrown at run-time if the import path is not present in the map. The generated code will look something like this (unimportant parts were omitted for brevity):

// 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`);

This feature works with require(...) and import(...) because these can all accept run-time expressions. It does not work with import and export statements because these cannot accept run-time expressions. If you want to prevent esbuild from trying to bundle these imports, you should move the string concatenation expression outside of the require(...) or import(...). For example:

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

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

Note that using this feature means esbuild will potentially do a lot of file system I/O to find all possible files that might match the pattern. This is by design, and is not a bug. If this is a concern, there are two ways to reduce the amount of file system I/O that esbuild does:

  1. The simplest approach is to put all files that you want to import for a given run-time import expression in a subdirectory and then include the subdirectory in the pattern. This limits esbuild to searching inside that subdirectory since esbuild doesn't consider .. path elements during pattern-matching.

  2. Another approach is to prevent esbuild from searching into any subdirectory at all. The pattern matching algorithm that esbuild uses only allows a wildcard to match something containing a / path separator if that wildcard has a / before it in the pattern. So for example './data/' + x + '.json' will match x with anything in any subdirectory while './data-' + x + '.json' will only match x with anything in the top-level directory (but not in any subdirectory).

Cancel

If you are using rebuild to manually invoke incremental builds, you may want to use this cancel API to end the current build early so that you can start a new one. You can do that like this:

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)
    }()
  }
}

Make sure to wait until the cancel operation is done before starting a new build (i.e. await the returned promise when using JavaScript), otherwise the next rebuild will give you the just-canceled build that still hasn't ended yet. Note that plugin on-end callbacks will still be run regardless of whether or not the build was canceled.

Live reload

Live reload is an approach to development where you have your browser open and visible at the same time as your code editor. When you edit and save your source code, the browser automatically reloads and the reloaded version of the app contains your changes. This means you can iterate faster because you don't have to manually switch to your browser, reload, and then switch back to your code editor after every change. It's very helpful when changing CSS, for example.

There is no esbuild API for live reloading directly. Instead, you can construct live reloading by combining watch mode (to automatically start a build when you edit and save a file) and serve mode (to serve the latest build, but block until it's done) plus a small bit of client-side JavaScript code that you add to your app only during development.

The first step is to enable watch and serve together:

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)
  }
}

The second step is to add some code to your JavaScript that subscribes to the /esbuild server-sent event source. When you get the change event, you can reload the page to get the latest version of the app. You can do this in a single line of code:

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

That's it! If you load your app in the browser, the page should now automatically reload when you edit and save a file (assuming there are no build errors).

This should only be included during development, and should not be included in production. One way to remove this code in production is to guard it with an if statement such as if (!window.IS_PRODUCTION) and then use define to set window.IS_PRODUCTION to true in production.

Live reload caveats

Implementing live reloading like this has a few known caveats:

Hot-reloading for CSS

The change event also contains additional information to enable more advanced use cases. It currently contains the added, removed, and updated arrays with the paths of the files that have changed since the previous build, which can be described by the following TypeScript interface:

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

The code sample below enables "hot reloading" for CSS, which is when the CSS is automatically updated in place without reloading the page. If an event arrives that isn't CSS-related, then the whole page will be reloaded as a fallback:

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()
})

Hot-reloading for JavaScript

Hot-reloading for JavaScript is not currently implemented by esbuild. It's possible to transparently implement hot-reloading for CSS because CSS is stateless, but JavaScript is stateful so you cannot transparently implement hot-reloading for JavaScript like you can for CSS.

Some other development servers implement hot-reloading for JavaScript anyway, but it requires additional APIs, sometimes requires framework-specific hacks, and sometimes introduces transient state-related bugs during an editing session. Doing this is outside of esbuild's scope. You are welcome to use other tools instead of esbuild if hot-reloading for JavaScript is one of your requirements.

However, with esbuild's live-reloading you can persist your app's current JavaScript state in sessionStorage to more easily restore your app's JavaScript state after a page reload. If your app loads quickly (which it already should for your users' sake), live-reloading with JavaScript can be almost as fast as hot-reloading with JavaScript would be.

Platform

By default, esbuild's bundler is configured to generate code intended for the browser. If your bundled code is intended to run in node instead, you should set the platform to 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)
  }
}

When the platform is set to browser (the default value):

When the platform is set to node:

When the platform is set to neutral:

See also bundling for the browser and bundling for node.

Rebuild

You may want to use this API if your use case involves calling esbuild's build API repeatedly with the same options. For example, this is useful if you are implementing your own file watcher service. Rebuilding is more efficient than building again because some of the data from the previous build is cached and can be reused if the original files haven't changed since the previous build. There are currently two forms of caching used by the rebuild API:

Here's how to do a rebuild:

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()
}

Serve

If you want your app to automatically reload as you edit, you should read about live reloading. It combines serve mode with watch mode to listen for changes to the file system.

Serve mode starts a web server that serves your code to your browser on your device. Here's an example that bundles src/app.ts into www/js/app.js and then also serves the www directory over http://localhost:8000/:

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{})
}

If you create the file www/index.html with the following contents, the code contained in src/app.ts will load when you navigate to http://localhost:8000/:

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

One benefit of using esbuild's built-in web server instead of another web server is that whenever you reload, the files that esbuild serves are always up to date. That's not necessarily the case with other development setups. One common setup is to run a local file watcher that rebuilds output files whenever their input files change, and then separately to run a local file server to serve those output files. But that means reloading after an edit may reload the old output files if the rebuild hasn't finished yet. With esbuild's web server, each incoming request starts a rebuild if one is not already in progress, and then waits for the current rebuild to complete before serving the file. This means esbuild never serves stale build results.

Note that this web server is intended to only be used in development. Do not use this in production.

Arguments

The arguments to the serve API are as follows:

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
  cors?: CORSOptions
  onRequest?: (args: ServeOnRequestArgs) => void
}

interface CORSOptions {
  origin?: string | string[]
}

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
  CORS      CORSOptions
  OnRequest func(ServeOnRequestArgs)
}

type CORSOptions struct {
  Origin []string
}

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

Return values

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

 > Local:   http://127.0.0.1:8000/
 > Network: http://192.168.0.1:8000/
interface ServeResult {
  hosts: string[]
  port: number
}
type ServeResult struct {
  Hosts []string
  Port  uint16
}

Enabling HTTPS

By default, esbuild's web server uses the http:// protocol. However, certain modern web features are unavailable to HTTP websites. If you want to use these features, then you'll need to tell esbuild to use the https:// protocol instead.

To enable HTTPS with esbuild:

  1. Generate a self-signed certificate. There are many ways to do this. Here's one way, assuming you have the openssl command installed:

     openssl req -x509 -newkey rsa:4096 -keyout your.key -out your.cert -days 9999 -nodes -subj /CN=127.0.0.1 
  2. Pass your.key and your.cert to esbuild using the keyfile and certfile serve arguments.

  3. Click past the scary warning in your browser when you load your page (self-signed certificates aren't secure, but that doesn't matter since we're just doing local development).

If you have more complex needs than this, you can still put a proxy in front of esbuild and use that for HTTPS instead. Note that if you see the message Client sent an HTTP request to an HTTPS server when you load your page, then you are using the incorrect protocol. Replace http:// with https:// in your browser's URL bar.

Keep in mind that esbuild's HTTPS support has nothing to do with security. The only reason to enable HTTPS in esbuild is because browsers have made it impossible to do local development with certain modern web features without jumping through these extra hoops. Please do not use esbuild's development server for anything that needs to be secure. It's only intended for local development and no considerations have been made for production environments whatsoever.

Customizing server behavior

It's not possible to hook into esbuild's local server to customize the behavior of the server itself. Instead, behavior should be customized by putting a proxy in front of esbuild.

Here's a simple example of a proxy server to get you started, using node's built-in http module. It adds a custom 404 page instead of esbuild's default 404 page:

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 { hosts, port } = await ctx.serve({ servedir: '.' })

// Then start a proxy server on port 3000
http.createServer((req, res) => {
  const options = {
    hostname: hosts[0],
    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)

This code starts esbuild's server on random local port and then starts a proxy server on port 3000. During development you would load http://localhost:3000 in your browser, which talks to the proxy. This example demonstrates modifying a response after esbuild has handled the request, but you can also modify or replace the request before esbuild has handled it.

You can do many things with a proxy like this including:

You can also use a real proxy such as nginx if you have more advanced needs.

Cross-Origin Resource Sharing

By default, esbuild's development server does not allow access from arbitrary origins. This ensures that visiting a malicious website in your browser doesn't allow that website to access esbuild's development server, which could divulge confidential information (source code, API keys, directory listing, etc.).

However, you may want to grant access to esbuild's development server for certain origins that you control. This can be done with Cross-Origin Resource Sharing (a.k.a. CORS). Specifically, passing your origin(s) to esbuild will cause esbuild to set the Access-Control-Allow-Origin response header when the request has a matching Origin header.

Here is a simple example that allows any page on https://example.com to access esbuild's development server:

CLI JS Go
esbuild --servedir=. --cors-origin=https://example.com
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({})

await ctx.serve({
  servedir: '.',
  cors: {
    origin: 'https://example.com',
  },
})
package main

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

func main() {
  ctx, err := api.Context(api.BuildOptions{})
  if err != nil {
    os.Exit(1)
  }

  result, err2 := ctx.Serve(api.ServeOptions{
    Servedir: ".",
    CORS: api.CORSOptions{
      Origin: []string{"https://example.com"},
    },
  })
  if err2 != nil {
    os.Exit(1)
  }
}

You can also provide an array of multiple origins to allow, and you can match many origins with a single pattern by using a * wildcard character. The following example matches both https://example.com and all subdomains that match https://*.example.com:

CLI JS Go
esbuild --servedir=. "--cors-origin=https://example.com,https://*.example.com"
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({})

await ctx.serve({
  servedir: '.',
  cors: {
    origin: [
      'https://example.com',
      'https://*.example.com',
    ],
  },
})
package main

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

func main() {
  ctx, err := api.Context(api.BuildOptions{})
  if err != nil {
    os.Exit(1)
  }

  result, err2 := ctx.Serve(api.ServeOptions{
    Servedir: ".",
    CORS: api.CORSOptions{
      Origin: []string{
        "https://example.com",
        "https://*.example.com",
      },
    },
  })
  if err2 != nil {
    os.Exit(1)
  }
}

Note that this feature currently only works for simple requests, which are requests that don't send a preflight OPTIONS request, as esbuild's development server doesn't currently support OPTIONS requests.

Tsconfig

Normally the build API automatically discovers tsconfig.json files and reads their contents during a build. However, you can also configure a custom tsconfig.json file to use instead. This can be useful if you need to do multiple builds of the same code with different settings:

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

This option can be used to pass your tsconfig.json file to the transform API, which doesn't access the file system. It can also be used to pass the contents of your tsconfig.json file to the build API inline without writing it to a file. Using it looks like this:

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)
  }
}

Watch

Enabling watch mode tells esbuild to listen for changes on the file system and to automatically rebuild whenever a file changes that could invalidate the build. Using it looks like this:

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{})
}

If you want to stop watch mode at some point in the future, you can call dispose on the context object to terminate the file watcher:

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")
}

Watch mode in esbuild is implemented using polling instead of OS-specific file system APIs for portability. The polling system is designed to use relatively little CPU vs. a more traditional polling system that scans the whole directory tree at once. The file system is still scanned regularly but each scan only checks a random subset of your files, which means a change to a file will be picked up soon after the change is made but not necessarily instantly.

With the current heuristics, large projects should be completely scanned around every 2 seconds so in the worst case it could take up to 2 seconds for a change to be noticed. However, after a change has been noticed the change's path goes on a short list of recently changed paths which are checked on every scan, so further changes to recently changed files should be noticed almost instantly.

Note that it is still possible to implement watch mode yourself using esbuild's rebuild API and a file watcher library of your choice if you don't want to use a polling-based approach.

If you are using the CLI, keep in mind that watch mode will be terminated when esbuild's stdin is closed. This prevents esbuild from accidentally outliving the parent process and unexpectedly continuing to consume resources on the system. If you have a use case that requires esbuild to continue to watch forever even when the parent process has finished, you may use --watch=forever instead of --watch.

Input

Entry points

This is an array of files that each serve as an input to the bundling algorithm. They are called "entry points" because each one is meant to be the initial script that is evaluated which then loads all other aspects of the code that it represents. Instead of loading many libraries in your page with <script> tags, you would instead use import statements to import them into your entry point (or into another file that is then imported into your entry point).

Simple apps only need one entry point but additional entry points can be useful if there are multiple logically-independent groups of code such as a main thread and a worker thread, or an app with separate relatively unrelated areas such as a landing page, an editor page, and a settings page. Separate entry points helps introduce separation of concerns and helps reduce the amount of unnecessary code that the browser needs to download. If applicable, enabling code splitting can further reduce download sizes when browsing to a second page whose entry point shares some already-downloaded code with a first page that has already been visited.

The simple way to specify entry points is to just pass an array of file paths:

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)
  }
}

This will generate two output files, out/home.js and out/settings.js corresponding to the two entry points home.ts and settings.ts.

For further control over how the paths of the output files are derived from the corresponding input entry points, you should look into these options:

In addition, you can also specify a fully custom output path for each individual entry point using an alternative entry point syntax:

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)
  }
}

This will generate two output files, out/out1.js and out/out2.js corresponding to the two entry points home.ts and settings.ts.

Glob-style entry points

If an entry point contains the * character, then it's considered to be a glob pattern. This means esbuild will use that entry point as a pattern to search for files on the file system and will then replace that entry point with any matching files that were found. So for example, an entry point of *.js will cause esbuild to consider all files in the current directory that end in .js to be entry points.

The glob matcher that esbuild implements is intentionally simple, and does not support more advanced features found in certain other glob libraries. Only two kinds of wildcards are supported:

If you are using esbuild via the CLI, keep in mind that if you do not quote arguments that contain shell metacharacters before you pass them to esbuild, your shell will likely expand them before esbuild sees them. So if you run esbuild "*.js" (with quotes) then esbuild will see an entry point of *.js and the glob-style entry point rules described above will apply. But if you run esbuild *.js (without quotes) then esbuild will see whatever your current shell decided to expand *.js into (which may include seeing nothing at all if your shell expanded it into nothing). Using esbuild's built-in glob pattern support can be a convenient way to ensure cross-platform consistency by avoiding shell-specific behavior, but it requires you to quote your arguments correctly so that your shell doesn't interpret them.

Loader

This option changes how a given input file is interpreted. For example, the js loader interprets the file as JavaScript and the css loader interprets the file as CSS. See the content types page for a complete list of all built-in loaders.

Configuring a loader for a given file type lets you load that file type with an import statement or a require call. For example, configuring the .png file extension to use the data URL loader means importing a .png file gives you a data URL containing the contents of that image:

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)

The above code can be bundled using the build API call like this:

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)
  }
}

This option is specified differently if you are using the build API with input from stdin, since stdin does not have a file extension. Configuring a loader for stdin with the build API looks like this:

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)
  }
}

The transform API call just takes a single loader since it doesn't involve interacting with the file system, and therefore doesn't deal with file extensions. Configuring a loader (in this case the ts loader) for the transform API looks like this:

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)
  }
}

Stdin

Normally the build API call takes one or more file names as input. However, this option can be used to run a build without a module existing on the file system at all. It's called "stdin" because it corresponds to piping a file to stdin on the command line.

In addition to specifying the contents of the stdin file, you can optionally also specify the resolve directory (used to determine where relative imports are located), the sourcefile (the file name to use in error messages and source maps), and the loader (which determines how the file contents are interpreted). The CLI doesn't have a way to specify the resolve directory. Instead, it's automatically set to the current working directory.

Here's how to use this feature:

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)
  }
}

Output contents

Use this to insert an arbitrary string at the beginning of generated JavaScript and CSS files. This is commonly used to insert comments:

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)
  }
}

This is similar to footer which inserts at the end instead of the beginning.

Note that if you are inserting non-comment code into a CSS file, be aware that CSS ignores all @import rules that come after a non-@import rule (other than a @charset rule), so using a banner to inject CSS rules may accidentally disable imports of external stylesheets.

Charset

By default esbuild's output is ASCII-only. Any non-ASCII characters are escaped using backslash escape sequences. One reason is because non-ASCII characters are misinterpreted by the browser by default, which causes confusion. You have to explicitly add <meta charset="utf-8"> to your HTML or serve it with the correct Content-Type header for the browser to not mangle your code. Another reason is that non-ASCII characters can significantly slow down the browser's parser. However, using escape sequences makes the generated output slightly bigger, and also makes it harder to read.

If you would like for esbuild to print the original characters without using escape sequences and you have ensured that the browser will interpret your code as UTF-8, you can disable character escaping by setting the charset:

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)
  }
}

Some caveats:

Use this to insert an arbitrary string at the end of generated JavaScript and CSS files. This is commonly used to insert comments:

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)
  }
}

This is similar to banner which inserts at the beginning instead of the end.

Format

This sets the output format for the generated JavaScript files. There are currently three possible values that can be configured: iife, cjs, and esm. When no output format is specified, esbuild picks an output format for you if bundling is enabled (as described below), or doesn't do any format conversion if bundling is disabled.

IIFE

The iife format stands for "immediately-invoked function expression" and is intended to be run in the browser. Wrapping your code in a function expression ensures that any variables in your code don't accidentally conflict with variables in the global scope. If your entry point has exports that you want to expose as a global in the browser, you can configure that global's name using the global name setting. The iife format will automatically be enabled when no output format is specified, bundling is enabled, and platform is set to browser (which it is by default). Specifying the iife format looks like this:

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

The cjs format stands for "CommonJS" and is intended to be run in node. It assumes the environment contains exports, require, and module. Entry points with exports in ECMAScript module syntax will be converted to a module with a getter on exports for each export name. The cjs format will automatically be enabled when no output format is specified, bundling is enabled, and platform is set to node. Specifying the cjs format looks like this:

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

The esm format stands for "ECMAScript module". It assumes the environment supports import and export syntax. Entry points with exports in CommonJS module syntax will be converted to a single default export of the value of module.exports. The esm format will automatically be enabled when no output format is specified, bundling is enabled, and platform is set to neutral. Specifying the esm format looks like this:

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)
  }
}

The esm format can be used either in the browser or in node, but you have to explicitly load it as a module. This happens automatically if you import it from another module. Otherwise:

Global name

This option only matters when the format setting is iife (which stands for immediately-invoked function expression). It sets the name of the global variable which is used to store the exports from the entry point:

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)
  }
}

Specifying the global name with the iife format will generate code that looks something like this:

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

The global name can also be a compound property expression, in which case esbuild will generate a global variable with that property. Existing global variables that conflict will not be overwritten. This can be used to implement "namespacing" where multiple independent scripts add their exports onto the same global object. For example:

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)
  }
}

The compound global name used above generates code that looks like this:

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

A "legal comment" is considered to be any statement-level comment in JS or rule-level comment in CSS that contains @license or @preserve or that starts with //! or /*!. These comments are preserved in output files by default since that follows the intent of the original authors of the code. However, this behavior can be configured by using one of the following options:

The default behavior is eof when bundling is enabled and inline otherwise. Setting the legal comment mode looks like this:

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来说"规则级别"意味着 注释必须出现在允许多个语句或规则的上下文中,如顶级作用域或语句或规则块中。 因此,表达式内部或声明级别的注释不被视为合法注释。

Line limit (行限制)

此设置是防止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压缩后也是如此)。

Splitting (代码分割)

代码分割仍在进行中。目前它只适用于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)
  }
}

Output location (输出位置)

Allow overwrite (允许覆盖)

启用此设置允许输出文件覆盖输入文件。默认情况下不启用此功能,因为这样做意味着覆盖您的源代码, 如果您的代码未提交,这可能导致数据丢失。但支持此功能可以通过避免使用临时目录使某些工作流程更简单。 因此,当您想要故意覆盖源代码时,可以启用此功能:

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)
  }
}

Asset names (资源名称)

此选项控制当loader设置为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)
  }
}

资源路径模板中可以使用四个占位符:

Asset path templates do not need to include a file extension. The original file extension of the asset will be automatically added to the end of the output path after template substitution.

This option is similar to the chunk names and entry names options.

Chunk names

This option controls the file names of the chunks of shared code that are automatically generated when code splitting is enabled. It configures the output paths using a template with placeholders that will be substituted with values specific to the chunk when the output path is generated. For example, specifying a chunk name template of chunks/[name]-[hash] puts all generated chunks into a subdirectory called chunks inside of the output directory and includes the content hash of the chunk in the file name. Doing that looks like this:

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)
  }
}

There are three placeholders that can be used in chunk path templates:

Chunk path templates do not need to include a file extension. The configured out extension for the appropriate content type will be automatically added to the end of the output path after template substitution.

Note that this option only controls the names for automatically-generated chunks of shared code. It does not control the names for output files related to entry points. The names of these are currently determined from the path of the original entry point file relative to the outbase directory, and this behavior cannot be changed. An additional API option will be added in the future to let you change the file names of entry point output files.

This option is similar to the asset names and entry names options.

Entry names

This option controls the file names of the output files corresponding to each input entry point file. It configures the output paths using a template with placeholders that will be substituted with values specific to the file when the output path is generated. For example, specifying an entry name template of [dir]/[name]-[hash] includes a hash of the output file in the file name and puts the files into the output directory, potentially under a subdirectory (see the details about [dir] below). Doing that looks like this:

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)
  }
}

There are four placeholders that can be used in entry path templates:

Entry path templates do not need to include a file extension. The appropriate out extension based on the file type will be automatically added to the end of the output path after template substitution.

This option is similar to the asset names and chunk names options.

Out extension

This option lets you customize the file extension of the files that esbuild generates to something other than .js or .css. In particular, the .mjs and .cjs file extensions have special meaning in node (they indicate a file in ESM and CommonJS format, respectively). This option is useful if you are using esbuild to generate multiple files and you have to use the outdir option instead of the outfile option. You can use it like this:

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

If your build contains multiple entry points in separate directories, the directory structure will be replicated into the output directory relative to the outbase directory. For example, if there are two entry points src/pages/home/index.ts and src/pages/about/index.ts and the outbase directory is src, the output directory will contain pages/home/index.js and pages/about/index.js. Here's how to use it:

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)
  }
}

If the outbase directory isn't specified, it defaults to the lowest common ancestor directory among all input entry point paths. This is src/pages in the example above, which means by default the output directory will contain home/index.js and about/index.js instead.

Outdir

This option sets the output directory for the build operation. For example, this command will generate a directory called 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",
    Write:       true,
  })

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

The output directory will be generated if it does not already exist, but it will not be cleared if it already contains some files. Any generated files will silently overwrite existing files with the same name. You should clear the output directory yourself before running esbuild if you want the output directory to only contain files from the current run of esbuild.

If your build contains multiple entry points in separate directories, the directory structure will be replicated into the output directory starting from the lowest common ancestor directory among all input entry point paths. For example, if there are two entry points src/home/index.ts and src/about/index.ts, the output directory will contain home/index.js and about/index.js. If you want to customize this behavior, you should change the outbase directory.

Outfile

This option sets the output file name for the build operation. This is only applicable if there is a single entry point. If there are multiple entry points, you must use the outdir option instead to specify an output directory. Using outfile looks like this:

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,
    Outfile:     "out.js",
    Write:       true,
  })

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

Public path

This is useful in combination with the external file loader. By default that loader exports the name of the imported file as a string using the default export. The public path option lets you prepend a base path to the exported string of each file loaded by this loader:

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)
  }
}

Write

The build API call can either write to the file system directly or return the files that would have been written as in-memory buffers. By default the CLI and JavaScript APIs write to the file system and the Go API doesn't. To use the in-memory buffers:

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)
  }
}

The hash property is a hash of the contents field and has been provided for convenience. The hash algorithm (currently XXH64) is implementation-dependent and may be changed at any time in between esbuild versions.

Path resolution

Alias

This feature lets you substitute one package for another when bundling. The example below substitutes the package oldpkg with the package newpkg:

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)
  }
}

These new substitutions happen first before all of esbuild's other path resolution logic. One use case for this feature is replacing a node-only package with a browser-friendly package in third-party code that you don't control.

Note that when an import path is substituted using an alias, the resulting import path is resolved in the working directory instead of in the directory containing the source file with the import path. If needed, the working directory that esbuild uses can be set with the working directory feature.

Conditions

This feature controls how the exports field in package.json is interpreted. Custom conditions can be added using the conditions setting. You can specify as many of these as you want and the meaning of these is entirely up to package authors. Node has currently only endorsed the development and production custom conditions for recommended use. Here is an example of adding the custom conditions custom1 and custom2:

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)
  }
}

How conditions work

Conditions allow you to redirect the same import path to different file locations in different situations. The redirect map containing the conditions and paths is stored in the exports field in the package's package.json file. For example, this would remap require('pkg/foo') to pkg/required.cjs and import 'pkg/foo' to pkg/imported.mjs using the import and require conditions:

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

Conditions are checked in the order that they appear within the JSON file. So the example above behaves sort of like this:

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

By default there are five conditions with special behavior that are built in to esbuild, and cannot be disabled:

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

请注意,当您使用requireimport条件时,您的包可能在打包中多次出现! 这是一个微妙的问题,除了导致最终打包体积膨胀外,还可能因为代码状态的重复副本而导致错误。 这通常被称为双包危害

避免双包危害的一种方法(对打包工具和在node中原生运行都有效)是将所有代码放在 require条件下作为CommonJS,而让import条件只是一个轻量级的ESM包装器, 它调用对您的包的require并使用ESM语法重新导出包。然而,这种方法不能提供良好的树摇动效果, 因为esbuild不对CommonJS模块进行树摇动。

避免双包危害的另一种方法是使用打包工具特定的module条件,指导打包工具始终加载 您包的ESM版本,同时让node始终回退到您包的CommonJS版本。importmodule 都旨在与ESM一起使用,但与import不同,即使导入路径是使用require调用加载的, module条件始终处于活动状态。这对打包工具有效,因为打包工具支持使用require 加载ESM,但这不适用于node,因为node故意不实现使用require加载ESM。

External (外部依赖)

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

这有几种用途。首先,它可以用来从您的包中删除您知道永远不会执行的代码路径的不必要代码。 例如,一个包可能包含只在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)
  }
}

外部路径在路径解析前后都会应用,这使您可以同时匹配源代码中的导入路径和绝对文件系统路径。 如果外部路径在任一情况下匹配,则该路径被视为外部依赖。具体行为如下:

Main fields (主要字段)

当您在node中导入一个包时,该包的package.json文件中的main字段决定导入哪个文件 (同时还有许多其他规则)。 包括esbuild在内的主要JavaScript打包工具允许您在解析包时指定额外的package.json字段。 至少有三个这样的字段常被使用:

默认的主要字段取决于当前的平台设置。这些默认值应该与 existing package ecosystem. But you can customize them like this if you want to:

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)
  }
}

For package authors

如果您想创作一个同时使用browser字段和module字段的包,那么您可能需要填写 CommonJS-vs-ESM和浏览器-vs-node兼容性矩阵中的**所有四个条目**。为此,您需要使用 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。关于使用哪种模块格式的决定 与是否使用特定于浏览器或特定于node的变体的决定是独立的。如果您省略了这四个条目中的一个, 那么您就有可能选择错误的变体。例如,如果您省略了CommonJS浏览器构建的条目, 那么可能会选择CommonJS节点构建。

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

Node paths (Node路径)

Node的模块解析算法支持一个名为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本身使用的格式相同。

Packages (包)

使用此设置来控制是否从打包中排除所有包的依赖项。这在 为node打包时非常有用, 因为许多npm包使用esbuild在打包时不支持的node特定功能(如__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)
  }
}

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

此设置反映了node中的--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通过原始文件路径(即不跟随符号链接的路径)而不是 真实文件路径(即跟随符号链接后的路径)来确定文件标识。这对于某些目录结构可能有益。 请记住,这意味着如果有多个符号链接指向同一个文件,那么该文件可能会被赋予多个标识, 这可能导致它在生成的输出文件中多次出现。

注意:"symlink"表示符号链接, 指的是文件系统的一个功能,其中一个路径可以重定向到另一个路径。

Resolve extensions (解析扩展名)

node使用的解析算法 支持隐式文件扩展名。您可以使用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扩展名。Node的解析算法 不将这些作为隐式文件扩展名处理,因此esbuild也不这样做。如果您想导入带有这些扩展名的文件, 您应该在导入路径中明确添加扩展名,或者更改此设置以包含您希望隐式处理的其他扩展名。

Working directory (工作目录)

这个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能够找到清单文件。

Transformation (转换)

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开发模式)

如果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 factory (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 fragment (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 import source (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开发模式关闭时使用jsxjsxs导入, 当JSX开发模式打开时使用jsxDEV导入。这些的含义在 React关于其新JSX转换的文档中有描述。 无论JSX开发模式如何,当元素有一个属性展开后跟一个key属性时,都会使用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注释。如果JSX转换尚未通过 其他方式设置,或者如果您也希望在每个文件的基础上设置它,您可能还需要添加 // @jsxRuntime automatic注释。

JSX side effects (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)
  }
}

Supported (支持的功能)

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

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

If you want esbuild to consider a certain syntax feature to be unsupported, you can specify that like this:

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:

Target (目标环境)

这设置生成的JavaScript和/或CSS代码的目标环境。它告诉esbuild将对这些环境来说太新的 JavaScript语法转换为在这些环境中可以工作的旧JavaScript语法。例如,??运算符是在 Chrome 80中引入的,因此当目标为Chrome 79或更早版本时,esbuild会将其转换为等效 (但更冗长)的条件表达式。

请注意,这仅关注语法功能,而不是API。它不会自动添加这些环境不使用的新API的 polyfills。 您将不得不为需要的API显式导入polyfills(例如,通过导入 core-js)。自动polyfill注入 超出了esbuild的范围。

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

此外,您还可以指定JavaScript语言版本,如es2020。默认目标是esnext,这意味着 默认情况下,esbuild将假设支持所有最新的JavaScript和CSS功能。以下是配置多个目标环境的示例。 您不需要指定所有这些;您可以只指定您的项目关心的目标环境子集。如果您愿意, 您也可以更精确地指定版本号(例如,使用node12.19.0而不仅仅是node12):

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加载器了解哪些语法功能是在哪些语言版本中 引入的详细信息。请记住,虽然像es2020这样的JavaScript语言版本是按年份标识的, 但那是规范获得批准的年份。这与所有主要浏览器实现该规范的年份无关, 实现通常早于或晚于该年份。

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

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

Optimization (优化)

Define (定义)

此功能提供了一种用常量表达式替换全局标识符的方法。它可以在不更改代码本身的情况下 改变构建之间某些代码的行为:

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功能来实现。

Drop (删除)

这告诉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)
  }
}

Drop labels (删除标签)

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

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

If you use this option to drop all labels named DEV, then esbuild will give you this:

function example() {
  return normalCodePath();
}

You can configure this feature like this (which will drop both the DEV and TEST labels):

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将其替换为某些内容。 使用标签方法的一些缺点是,当标签被删除时,条件性移除代码会变得略微难以理解, 并且它不适用于嵌套表达式中的代码。对于给定项目使用哪种方法取决于个人偏好。

Ignore annotations (忽略注解)

由于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字段。 然而,它仍然会对未使用的导入进行自动树摇动,因为这不依赖于 开发者的注解。理想情况下,此标志只是一个临时解决方案。您应该向包的维护者报告 这些问题以便修复,因为它们表明包存在问题,并且可能会绊倒其他人。

Inject (注入)

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

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

这旨在替换Node.js的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)。

当JSX转换未设置为automatic时,您可以使用esbuild的注入功能自动导入JSX表达式的 工厂函数片段。这里有一个可以注入的示例文件来实现这一点:

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

此代码以React库为例,但您也可以使用这种方法与任何其他JSX库一起使用,只需适当更改。

不带导入的文件注入

You can also use this feature with files that have no exports. In that case the injected file just comes first before the rest of the output as if every input file contained import "./file.js". Because of the way ECMAScript modules work, this injection is still "hygienic" in that symbols with the same name in different files are renamed so they don't collide with each other.

Conditionally injecting a file

If you want to conditionally import a file only if the export is actually used, you should mark the injected file as not having side effects by putting it in a package and adding "sideEffects": false in that package's package.json file. This setting is a convention from Webpack that esbuild respects for any imported file, not just files used with inject.

Keep names

In JavaScript the name property on functions and classes defaults to a nearby identifier in the source code. These syntax forms all set the name property of the function to "fn":

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

However, minification renames symbols to reduce code size and bundling sometimes need to rename symbols to avoid collisions. That changes value of the name property for many of these cases. This is usually fine because the name property is normally only used for debugging. However, some frameworks rely on the name property for registration and binding purposes. If this is the case, you can enable this option to preserve the original name values even in minified code:

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)
  }
}

Note that this feature is unavailable if the target has been set to an old environment that doesn't allow esbuild to mutate the name property on functions and classes. This is the case for environments that don't support ES6.

Mangle props (混淆属性)

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

此设置允许您向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__ 这样的名称,您可以考虑使用更复杂的正则表达式,如[^_]_$(即必须以非下划线后跟下划线结尾)。

This is a separate setting instead of being part of the minify setting because it's an unsafe transformation that does not work on arbitrary JavaScript code. It only works if the provided regular expression matches all of the properties that you want mangled and does not match any of the properties that you don't want mangled. It also only works if you do not under any circumstances reference a mangled property indirectly. For example, it means you can't use obj[prop] to reference a property where prop is a string containing the property name. Specifically the following syntax constructs are the only ones eligible for property mangling:

Syntax Example
Dot property accesses x.foo_
Dot optional chains x?.foo_
Object properties x = { foo_: y }
Object methods x = { foo_() {} }
Class fields class x { foo_ = y }
Class methods class x { foo_() {} }
Object destructuring bindings let { foo_: x } = y
Object destructuring assignments ({ foo_: x } = y)
JSX element member expression <X.foo_></X.foo_>
JSX attribute names <X foo_={y} />
TypeScript namespace exports namespace x { export let foo_ = y }
TypeScript parameter properties class x { constructor(public foo_) {} }

When using this feature, keep in mind that property names are only consistently mangled within a single esbuild API call but not across esbuild API calls. Each esbuild API call does an independent property mangling operation so output files generated by two different API calls may mangle the same property to two different names, which could cause the resulting code to behave incorrectly.

Quoted properties

By default, esbuild doesn't modify the contents of string literals. This means you can avoid property mangling for an individual property by quoting it as a string. However, you must consistently use quotes or no quotes for a given property everywhere for this to work. For example, print({ foo_: 0 }.foo_) will be mangled into print({ a: 0 }.a) while print({ 'foo_': 0 }['foo_']) will not be mangled.

If you would like for esbuild to also mangle the contents of string literals, you can explicitly enable that behavior like this:

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)
  }
}

Enabling this makes the following syntax constructs also eligible for property mangling:

Syntax Example
Quoted property accesses x['foo_']
Quoted optional chains x?.['foo_']
Quoted object properties x = { 'foo_': y }
Quoted object methods x = { 'foo_'() {} }
Quoted class fields class x { 'foo_' = y }
Quoted class methods class x { 'foo_'() {} }
Quoted object destructuring bindings let { 'foo_': x } = y
Quoted object destructuring assignments ({ 'foo_': x } = y)
String literals to the left of in 'foo_' in x

Mangling other strings

Mangling quoted properties still only mangles strings in property name position. Sometimes you may also need to mangle property names in strings at arbitrary other locations in your code. To do that, you can prefix the string with a /* @__KEY__ */ comment to tell esbuild that the contents of a string should be treated as a property name that can be mangled. For example:

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

This will cause the contents of the string 'foo_' to be mangled as a property name (assuming property mangling is enabled and foo_ is eligible for renaming). The /* @__KEY__ */ comment is a convention from Terser, a popular JavaScript minifier with a similar property mangling feature.

Preventing renaming

If you would like to exclude certain properties from mangling, you can reserve them with an additional setting. For example, this uses the regular expression ^__.*__$ to reserve all properties that start and end with two underscores, such as __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"
}

Minify (压缩)

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

在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)
  }
}

This option does three separate things in combination: it removes whitespace, it rewrites your syntax to be more compact, and it renames 局部变量名称更短。通常您希望执行所有这些操作,但如有必要,这些选项也可以单独启用:

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作为压缩器时,请记住以下几点:

Pure (纯函数标记)

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

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

打包工具(如esbuild)在树摇动(即死代码移除)过程中使用此信息,在打包工具 由于JavaScript代码的动态特性而无法自行证明移除是安全的情况下,执行跨模块边界 的未使用导入的细粒度移除。

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

一些表达式(如JSX和某些内置全局对象)在esbuild中会自动被注解为/* @__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的, 不适用于其他调用表达式。

Tree shaking (树摇动)

树摇动是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默认会尝试 选择适用于树摇动的格式。您可以根据包的情况,使用main fields 和/或conditions选项自定义esbuild选择的格式。

默认情况下,树摇动仅在启用bundling或输出format设置为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__ */,只能放在new或调用表达式之前。您可以 注解一个立即调用的函数表达式,并在函数体内放置任意副作用:

// 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假设被注解的代码是无副作用的。如果注解是错误的, 而代码实际上确实有重要的副作用,这些注解可能会导致代码出现问题。如果您正在 打包带有错误编写的注解的第三方代码,您可能需要启用忽略注解 以确保打包的代码是正确的。

Source maps (源码映射)

Source root (源码根目录)

此功能仅在启用源码映射时相关。它允许您设置源码映射中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)
  }
}

Sourcefile (源文件)

当使用没有文件名的输入时,此选项设置文件名。这在使用transform API和使用带有 stdin的build API时会发生。配置的文件名会反映在错误消息和源码映射中。如果 未配置,文件名默认为<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)
  }
}

Sourcemap (源码映射)

源码映射可以使调试代码更容易。它们编码了从生成的输出文件中的行/列偏移量转换回 相应原始输入文件中的行/列偏移量所需的信息。如果您的生成代码与原始代码有足够大的 差异(例如,您的原始代码是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(内联)

    这种模式意味着源码映射作为base64负载添加到.js输出文件的末尾,放在 //# sourceMappingURL=注释中。不会生成额外的.js.map输出文件。请记住, 源码映射通常非常大,因为它们包含所有原始源代码,所以您通常不希望发布包含 inline源码映射的代码。要从源码映射中移除源代码(只保留文件名和行/列映射), 请使用sources content选项。像这样使用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. both(两者)

    这种模式是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

Sources content (源代码内容)

源码映射使用源码映射格式的版本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)
  }
}

Build metadata (构建元数据)

Analyze (分析)

如果您正在寻找交互式可视化,请尝试esbuild的包大小分析器。 您可以上传您的esbuild metafile来查看包大小的细分。

使用分析功能生成一个易于阅读的关于包内容的报告:

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{}))
}

此信息显示哪些输入文件最终出现在每个输出文件中,以及它们占输出文件的百分比。 如果您想要额外的信息,可以启用"verbose"(详细)模式。这会显示从入口点到每个 输入文件的导入路径,告诉您为什么特定的输入文件被包含在包中:

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,
  }))
}

这个分析只是对metafile中可以找到的信息的可视化。如果这种分析 不完全符合您的需求,您可以使用metafile中的信息构建自己的可视化。

请注意,这种格式化的分析摘要是为人类而非机器设计的。具体格式可能会随时间变化, 这可能会破坏任何尝试解析它的工具。您不应该编写工具来解析这些数据。您应该使用 JSON元数据文件中的信息。这个可视化中的所有内容都是从JSON元数据 派生的,因此不解析esbuild的格式化分析摘要不会丢失任何信息。

Metafile (元数据文件)

此选项告诉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内置的 analyze功能。或者您可以编写自己的分析来使用这些信息。

元数据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
    }
  }
}

Logging (日志记录)

Color (颜色)

此选项启用或禁用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.

X [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)
  }
}

Colored output can also be set to false to disable colors.

Format messages (格式化消息)

此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"))
}

Options (选项)

可以提供以下选项来控制格式化:

JS Go
interface FormatMessagesOptions {
  kind: 'error' | 'warning';
  color?: boolean;
  terminalWidth?: number;
}
type FormatMessagesOptions struct {
  Kind          MessageKind
  Color         bool
  TerminalWidth int
}

Log level (日志级别)

可以更改日志级别以防止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)
  }
}

Log limit (日志限制)

默认情况下,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)
  }
}

Log override (日志覆盖)

此功能允许您更改单个类型的日志消息的日志级别。您可以使用它来消除特定类型的警告, 启用默认情况下未启用的其他警告,甚至将警告转换为错误。

例如,当针对较旧的浏览器时,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)
  }
}

每种消息类型的日志级别都可以被覆盖为日志级别设置支持的任何值。 所有当前可用的消息类型列在下面(点击每一个以查看示例日志消息):

这些消息类型应该相当稳定,但将来可能会添加新的类型,有时也可能会删除旧的类型。 如果删除了某个消息类型,针对该消息类型的任何覆盖都将被静默忽略。