API
API可以通过三种语言之一访问:命令行、JavaScript和Go。这些语言的概念和参数在很大程度上是相同的, 因此它们将一起而不是为每种语言分别编写文档。您可以使用右上角的CLI
、JS
和Go
选项卡 在这些代码示例之间切换。每种语言的一些具体内容:
CLI: 如果您使用命令行API,您可能会发现了解标志有三种形式:
--foo
、--foo=bar
或--foo:bar
会有所帮助。形式--foo
用于启用布尔标志(例如--minify
), 形式--foo=bar
用于只指定一次且具有单个值的标志(例如--platform=
), 形式--foo:bar
用于可以多次重新指定且具有多个值的标志(例如--external:
)。还要记住,使用CLI(一般而言,不仅仅是esbuild)意味着您当前的shell会在您运行的命令看到它们之前解释命令的参数。 例如,即使
echo
命令只是写出它读取的内容,echo "foo"
也可能打印foo
而不是"foo"
, 而echo *.json
也可能打印package.json
而不是*.json
(具体行为取决于您使用的shell)。 如果您想避免shell特定行为可能导致的问题,那么您应该使用esbuild的JavaScript或Go API,而不是esbuild的CLI。JavaScript: 如果您使用JavaScript,请务必查看JS特定详细信息和浏览器 部分。您还可能会发现esbuild的TypeScript类型定义 对于esbuild有用作参考。
Go: 如果您使用Go,您可能会发现esbuild的自动生成的Go文档对您有用作参考。 有两个公共Go包的文档:
pkg/api
和pkg/cli
。
#概述
esbuild的两个最常用的API是build和transform。 每个都在高层次上进行了描述,后面是每个单独的API选项的文档。
#构建
这是esbuild的主要接口。您通常会传递一个或多个入口点文件进行处理, 以及各种选项,然后esbuild会将结果写回文件系统。以下是一个简单的示例,启用了捆绑和输出目录:
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:
- 监视模式告诉esbuild监视文件系统,当您编辑并保存可能使构建失效的文件时, 自动为您重新构建。以下是一个示例:
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{})
- 服务模式启动一个本地开发服务器,提供最新构建的结果。 传入的请求会自动启动新的构建,这样当您在浏览器中重新加载页面时, 您的Web应用程序始终是最新的。以下是一个示例:
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{})
- 重建模式让您手动调用构建。这在将esbuild与其他工具集成时非常有用 (例如,使用自定义文件监视器或开发服务器,而不是esbuild的内置工具)。以下是一个示例:
# 命令行没有"重建"的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都接受以下选项:
#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:
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:
#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:
- You can use plugins with the asynchronous API
- The current thread is not blocked so you can perform other work in the meantime
- You can run many simultaneous esbuild API calls concurrently which are then spread across all available CPUs for maximum performance
Cons:
- Using promises can result in messier code, especially in CommonJS where top-level await is not available
- Doesn't work in situations that must be synchronous such as within
require
.extensions
#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:
- Avoiding promises can result in cleaner code, especially when top-level await is not available
- Works in situations that must be synchronous such as within
require
.extensions
Cons:
- You can't use plugins with the synchronous API since plugins are asynchronous
- It blocks the current thread so you can't perform other work in the meantime
- Using the synchronous API prevents esbuild from parallelizing esbuild API calls
#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:
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:
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:
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.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/' +
will matchx + '.json' x
with anything in any subdirectory while'./data-' +
will only matchx + '.json' 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:
# 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:
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:
These events only trigger when esbuild's output changes. They do not trigger when files unrelated to the build being watched are changed. If your HTML file references other files that esbuild doesn't know about and those files are changed, you can either manually reload the page or you can implement your own live reloading infrastructure instead of using esbuild's built-in behavior.
The
EventSource
API is supposed to automatically reconnect for you. However, there's a bug in Firefox that breaks this if the server is ever temporarily unreachable. Workarounds are to use any other browser, to manually reload the page if this happens, or to write more complicated code that manually closes and re-creates theEventSource
object if there is a connection error.Browser vendors have decided to not implement HTTP/2 without TLS. This means that when using the
http://
protocol, each/esbuild
event source will take up one of your precious 6 simultaneous per-domain HTTP/1.1 connections. So if you open more than six HTTP tabs that use this live-reloading technique, you will be unable to use live reloading in some of those tabs (and other things will likely also break). The workaround is to enable thehttps://
protocol.
#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
:
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 bundling is enabled the default output format is set to
iife
, which wraps the generated JavaScript code in an immediately-invoked function expression to prevent variables from leaking into the global scope.If a package specifies a map for the
browser
field in itspackage.json
file, esbuild will use that map to replace specific files or modules with their browser-friendly versions. For example, a package might contain a substitution ofpath
withpath-browserify
.The main fields setting is set to
browser,
but with some additional special behavior: if a package providesmodule, main module
andmain
entry points but not abrowser
entry point thenmain
is used instead ofmodule
if that package is ever imported usingrequire()
. This behavior improves compatibility with CommonJS modules that export a function by assigning it tomodule.exports
. If you want to disable this additional special behavior, you can explicitly set the main fields setting tobrowser,
.module, main The conditions setting automatically includes the
browser
condition. This changes how theexports
field inpackage.json
files is interpreted to prefer browser-specific code.If no custom conditions are configured, the Webpack-specific
module
condition is also included. Themodule
condition is used by package authors to provide a tree-shakable ESM alternative to a CommonJS file without creating a dual package hazard. You can prevent themodule
condition from being included by explicitly configuring some custom conditions (even an empty list).When using the build API, all
process.
expressions are automatically defined toenv. NODE_ENV "production"
if all minification options are enabled and"development"
otherwise. This only happens ifprocess
,process.env
, andprocess.env.NODE_ENV
are not already defined. This substitution is necessary to avoid React-based code crashing instantly (sinceprocess
is a node API, not a web API).The character sequence
</script>
will be escaped in JavaScript code and the character sequence</style>
will be escaped in CSS code. This is done in case you inline esbuild's output directly into an HTML file. This can be disabled with esbuild's supported feature by settinginline-script
(for JavaScript) and/orinline-style
(for CSS) tofalse
.
When the platform is set to node
:
When bundling is enabled the default output format is set to
cjs
, which stands for CommonJS (the module format used by node). ES6-style exports usingexport
statements will be converted into getters on the CommonJSexports
object.All built-in node modules such as
fs
are automatically marked as external so they don't cause errors when the bundler tries to bundle them.The main fields setting is set to
main,
. This means tree shaking will likely not happen for packages that provide bothmodule module
andmain
since tree shaking works with ECMAScript modules but not with CommonJS modules.Unfortunately some packages incorrectly treat
module
as meaning "browser code" instead of "ECMAScript module code" so this default behavior is required for compatibility. You can manually configure the main fields setting tomodule,
if you want to enable tree shaking and know it is safe to do so.main The conditions setting automatically includes the
node
condition. This changes how theexports
field inpackage.json
files is interpreted to prefer node-specific code.If no custom conditions are configured, the Webpack-specific
module
condition is also included. Themodule
condition is used by package authors to provide a tree-shakable ESM alternative to a CommonJS file without creating a dual package hazard. You can prevent themodule
condition from being included by explicitly configuring some custom conditions (even an empty list).When the format is set to
cjs
but the entry point is ESM, esbuild will add special annotations for any named exports to enable importing those named exports using ESM syntax from the resulting CommonJS file. Node's documentation has more information about node's detection of CommonJS named exports.The
binary
loader will make use of node's built-inBuffer.from
API to decode the base64 data embedded in the bundle into aUint8Array
. This is faster than what esbuild can do otherwise since it's implemented by node in native code.
When the platform is set to neutral
:
When bundling is enabled the default output format is set to
esm
, which uses theexport
syntax introduced with ECMAScript 2015 (i.e. ES6). You can change the output format if this default is not appropriate.The main fields setting is empty by default. If you want to use npm-style packages, you will likely have to configure this to be something else such as
main
for the standard main field used by node.The conditions setting does not automatically include any platform-specific values.
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:
Files are stored in memory and are not re-read from the file system if the file metadata hasn't changed since the last build. This optimization only applies to file system paths. It does not apply to virtual modules created by plugins.
Parsed ASTs are stored in memory and re-parsing the AST is avoided if the file contents haven't changed since the last build. This optimization applies to virtual modules created by plugins in addition to file system modules, as long as the virtual module path remains the same.
Here's how to do a rebuild:
# 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
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/
:
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:
# 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
}
host
By default, esbuild makes the web server available on all IPv4 network interfaces. This corresponds to a host address of
0.0.0.0
. If you would like to configure a different host (for example, to only serve on the127.0.0.1
loopback interface without exposing anything to the network), you can specify the host using this argument.If you need to use IPv6 instead of IPv4, you just need to specify an IPv6 host address. The equivalent to the
127.0.0.1
loopback interface in IPv6 is::1
and the equivalent to the0.0.0.0
universal interface in IPv6 is::
.port
The HTTP port can optionally be configured here. If omitted, it will default to an open port with a preference for ports in the range 8000 to 8009.
Note that the Go API differs from the CLI and JS API regarding port 0. Unix reserves the port 0 to mean "pick a random ephemeral port" but esbuild reserves the port 0 for the default behavior described above, as 0 is the default value of an integer field in Go. Instead esbuild uses a sentinel value of -1 to pick a random ephemeral port in Go. The CLI and JS APIs don't have this problem and allow you to specify a port of 0 to pick a random ephemeral port just like other standard Unix APIs.
servedir
This is a directory of extra content for esbuild's HTTP server to serve instead of a 404 when incoming requests don't match any of the generated output file paths. This lets you use esbuild as a general-purpose local web server.
For example, you might want to create an
index.html
file and then setservedir
to"."
to serve the current directory (which includes theindex.html
file). If you don't setservedir
then esbuild will only serve the build results, but not any other files.keyfile
andcertfile
If you pass a private key and certificate to esbuild using
keyfile
andcertfile
, then esbuild's web server will use thehttps://
protocol instead of thehttp://
protocol. See enabling HTTPS for more information.fallback
This is a HTML file for esbuild's HTTP server to serve instead of a 404 when incoming requests don't match any of the generated output file paths. You can use this for a custom "not found" page. You can also use this as the entry point of a single-page application that mutates the current URL and therefore needs to be served from many different URLs simultaneously.
cors
This is for Cross-Origin Request Sharing. See that link for more information.
onRequest
This is called once for each incoming request with some information about the request. This callback is used by the CLI to print out a log message for each request. The time field is the time to generate the data for the request, but it does not include the time to stream the request to the client.
Note that this is called after the request has completed. It's not possible to use this callback to modify the request in any way. If you want to do this, you should put a proxy in front of esbuild instead.
#Return values
# 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
}
hosts
This is an array of hosts that ended up being used by the web server. If the host is an unspecified address (which it is by default), then the array includes the loopback interface as well as any other network interfaces that are currently active, such as the interface for your WiFi network. For example, the unspecified IPv4 address
0.0.0.0
might cause the array to contain both127.0.0.1
and192.168.0.1
, and the unspecified IPv6 address::
might cause the array to contain both::1
andfe80::b0ba:cafe
. The actual hosts returned for an unspecified host depends on your current network configuration.port
This is the port that ended up being used by the web server. You'll want to use this if you don't specify a port since esbuild will end up picking an arbitrary open port, and you need to know which port it picked to be able to connect to it.
#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:
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
Pass
your.key
andyour.cert
to esbuild using thekeyfile
andcertfile
serve arguments.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
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:
- Injecting your own 404 page (the example above)
- Customizing the mapping of routes to files on the file system
- Redirecting some routes to an API server instead of to esbuild
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:
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
:
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:
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:
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:
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:
# 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=
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:
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:
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:
*
This wildcard matches any number of characters (including none) except that it does not match a slash character (i.e. a
/
), which means it does not cause esbuild to traverse into subdirectories. For example,*.js
will matchfoo.js
but notbar/foo.js
./**/
This wildcard matches zero or more path segments, which means it can be used to tell esbuild to match against a whole directory tree. For example,
./**/*.js
will match./foo.js
and./bar/foo.js
and./a/b/c/foo.js
.
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:
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:
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:
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:
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
#Banner
Use this to insert an arbitrary string at the beginning of generated JavaScript and CSS files. This is commonly used to insert comments:
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
to your HTML or serve it with the correct Content-
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:
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:
This does not yet escape non-ASCII characters embedded in regular expressions. This is because esbuild does not currently parse the contents of regular expressions at all. The flag was added despite this limitation because it's still useful for code that doesn't contain cases like this.
This flag does not apply to comments. I believe preserving non-ASCII data in comments should be fine because even if the encoding is wrong, the run time environment should completely ignore the contents of all comments. For example, the V8 blog post mentions an optimization that avoids decoding comment contents completely. And all comments other than license-related comments are stripped out by esbuild anyway.
This option simultaneously applies to all output file types (JavaScript, CSS, and JSON). So if you configure your web server to send the correct
Content-
header and want to use the UTF-8 charset, make sure your web server is configured to treat bothType .js
and.css
files as UTF-8.
#Footer
Use this to insert an arbitrary string at the end of generated JavaScript and CSS files. This is commonly used to insert comments:
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:
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:
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:
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:
- In the browser, you can load a module using
<script
. Do not forgetsrc=" file.js" type=" module"> </script> type="
as this will break your code in subtle and confusing ways (omittingmodule" type="
means that all top-level variables will end up in the global scope, which will then collide with top-level variables that have the same name in other JavaScript files).module"
- In node, you can load a module using
node
. Note that node requires thefile.mjs .mjs
extension unless you have configured"type":
in your"module" package.json
file. You can use the out extension setting in esbuild to customize the output extension for the files esbuild generates. You can read more about using ECMAScript modules in node here.
#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:
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:
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();
})();
#Legal comments
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:
none
Do not preserve any legal comments.inline
Preserve all legal comments.eof
Move all legal comments to the end of the file.linked
Move all legal comments to a.LEGAL.txt
file and link to them with a comment.external
Move all legal comments to a.LEGAL.txt
file but to not link to them.
The default behavior is eof
when bundling is enabled and inline
otherwise. Setting the legal comment mode looks like this:
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个字符后不久将其换行:
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 (代码分割)
这启用了"代码分割",它有两个目的:
多个入口点之间共享的代码会被分离到一个单独的共享文件中,两个入口点都会导入该文件。 这样,如果用户先浏览一个页面,然后浏览另一个页面,如果共享部分已经被下载并缓存在浏览器中, 他们就不必从头开始下载第二个页面的所有JavaScript。
通过异步
import()
表达式引用的代码将被分离到一个单独的文件中,并且只在该表达式被评估时加载。 这允许您通过只下载启动时需要的代码来改善应用程序的初始下载时间,然后在稍后需要时懒加载额外的代码。如果没有启用代码分割,
import()
表达式将变成Promise
。 这仍然保留了表达式的异步语义,但这意味着导入的代码包含在同一个包中,而不是被分离到一个单独的文件中。.resolve() .then(() => require())
当您启用代码分割时,您必须同时使用outdir设置配置输出目录:
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 (允许覆盖)
启用此设置允许输出文件覆盖输入文件。默认情况下不启用此功能,因为这样做意味着覆盖您的源代码, 如果您的代码未提交,这可能导致数据丢失。但支持此功能可以通过避免使用临时目录使某些工作流程更简单。 因此,当您想要故意覆盖源代码时,可以启用此功能:
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/
会将所有资源放入输出目录中名为assets
的子目录中,并在文件名中包含资源的内容哈希。这样做看起来像这样:
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)
}
}
资源路径模板中可以使用四个占位符:
[dir]
This is the relative path from the directory containing the asset file to the outbase directory. Its purpose is to help asset output paths look more aesthetically pleasing by mirroring the input directory structure inside of the output directory.
[name]
This is the original file name of the asset without the extension. For example, if the asset was originally named
image.png
then[name]
will be substituted withimage
in the template. It is not necessary to use this placeholder; it only exists to provide human-friendly asset names to make debugging easier.[hash]
This is the content hash of the asset, which is useful to avoid name collisions. For example, your code may import
components/
andbutton/ icon.png components/
in which case you'll need the hash to distinguish between the two assets that are both namedselect/ icon.png icon
.[ext]
This is the file extension of the asset (i.e. everything after the end of the last
.
character). It can be used to put different types of assets into different directories. For example,--asset-names=
might write out an asset namedassets/ [ext]/ [name]-[hash] image.png
asassets/
.png/ image-CQFGD2NG.png
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/
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:
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:
[name]
This will currently always be the text
chunk
, although this placeholder may take on additional values in future releases.[hash]
This is the content hash of the chunk. Including this is necessary to distinguish different chunks from each other in the case where multiple chunks of shared code are generated.
[ext]
This is the file extension of the chunk (i.e. everything after the end of the last
.
character). It can be used to put different types of chunks into different directories. For example,--chunk-names=
might write out a chunk aschunks/ [ext]/ [name]-[hash] chunks/
.css/ chunk-DEFJT7KY.css
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]/
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:
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:
[dir]
This is the relative path from the directory containing the input entry point file to the outbase directory. Its purpose is to help you avoid collisions between identically-named entry points in different subdirectories.
For example, if there are two entry points
src/
andpages/ home/ index.ts src/
, the outbase directory ispages/ about/ index.ts src
, and the entry names template is[dir]/[name]
, the output directory will containpages/
andhome/ index.js pages/
. If the entry names template had been justabout/ index.js [name]
instead, bundling would have failed because there would have been two output files with the same output pathindex.js
inside the output directory.[name]
This is the original file name of the entry point without the extension. For example, if the input entry point file is named
app.js
then[name]
will be substituted withapp
in the template.[hash]
This is the content hash of the output file, which can be used to take optimal advantage of browser caching. Adding
[hash]
to your entry point names means esbuild will calculate a hash that relates to all content in the corresponding output file (and any output file it imports if code splitting is active). The hash is designed to change if and only if any of the input files relevant to that output file are changed.After that, you can have your web server tell browsers that to cache these files forever (in practice you can say they expire a very long time from now such as in a year). You can then use the information in the metafile to determine which output file path corresponds to which input entry point so you know what path to include in your
<script>
tag.[ext]
This is the file extension that the entry point file will be written out to (i.e. the out extension setting, not the original file extension). It can be used to put different types of entry points into different directories. For example,
--entry-names=
might write the output file forentries/ [ext]/ [name] app.ts
toentries/
.js/ app.js
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:
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/
and src/
and the outbase directory is src
, the output directory will contain pages/
and pages/
. Here's how to use it:
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/
in the example above, which means by default the output directory will contain home/
and about/
instead.
#Outdir
This option sets the output directory for the build operation. For example, this command will generate a directory called out
:
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/
and src/
, the output directory will contain home/
and about/
. 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:
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:
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:
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
:
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
:
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:
default
This condition is always active. It is intended to come last and lets you provide a fallback for when no other condition applies. This condition is also active when you run your code natively in node.
import
This condition is only active when the import path is from an ESM
import
statement orimport()
expression. It can be used to provide ESM-specific code. This condition is also active when you run your code natively in node (but only in an ESM context).require
This condition is only active when the import path is from a CommonJS
require()
call. It can be used to provide CommonJS-specific code. This condition is also active when you run your code natively in node (but only in a CommonJS context).browser
This condition is only active when esbuild's platform setting is set to
browser
. It can be used to provide browser-specific code. This condition is not active when you run your code natively in node.node
This condition is only active when esbuild's platform setting 设置为
node
时。它可以用来提供特定于node的代码。当您在node中原生运行代码时, 此条件也是激活的。
当platform设置为browser
或node
且未配置自定义条件时, 以下条件也会自动包含在内。如果配置了任何自定义条件(即使是空列表), 则此条件将不再自动包含:
module
此条件可用于告诉esbuild为给定的导入路径选择ESM变体,以在打包时提供更好的树摇动效果。 当您在node中原生运行代码时,此条件不会激活。它特定于打包工具,源自Webpack。
请注意,当您使用require
和import
条件时,您的包可能在打包中多次出现! 这是一个微妙的问题,除了导致最终打包体积膨胀外,还可能因为代码状态的重复副本而导致错误。 这通常被称为双包危害。
避免双包危害的一种方法(对打包工具和在node中原生运行都有效)是将所有代码放在 require
条件下作为CommonJS,而让import
条件只是一个轻量级的ESM包装器, 它调用对您的包的require
并使用ESM语法重新导出包。然而,这种方法不能提供良好的树摇动效果, 因为esbuild不对CommonJS模块进行树摇动。
避免双包危害的另一种方法是使用打包工具特定的module
条件,指导打包工具始终加载 您包的ESM版本,同时让node始终回退到您包的CommonJS版本。import
和module
都旨在与ESM一起使用,但与import
不同,即使导入路径是使用require
调用加载的, module
条件始终处于活动状态。这对打包工具有效,因为打包工具支持使用require
加载ESM,但这不适用于node,因为node故意不实现使用require
加载ESM。
#External (外部依赖)
您可以将文件或包标记为外部依赖,从而将其排除在构建之外。导入将被保留 (对于iife
和cjs
格式使用require
,对于esm
格式使用import
), 并将在运行时而不是构建时进行评估。
这有几种用途。首先,它可以用来从您的包中删除您知道永远不会执行的代码路径的不必要代码。 例如,一个包可能包含只在node中运行的代码,但您只会在浏览器中使用该包。 它还可以用于在运行时从无法打包的包中导入代码。例如,fsevents
包包含一个原生扩展, esbuild不支持这种扩展。将某物标记为外部依赖看起来像这样:
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/
开头的路径:
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)
}
}
外部路径在路径解析前后都会应用,这使您可以同时匹配源代码中的导入路径和绝对文件系统路径。 如果外部路径在任一情况下匹配,则该路径被视为外部依赖。具体行为如下:
在路径解析开始前,导入路径会与所有外部路径进行比对。此外,如果外部路径看起来像包路径 (即不以
/
或./
或../
开头),则会检查导入路径是否将该包路径作为路径前缀。这意味着
--external:
隐含地也意味着@foo/ bar --external:
,它匹配导入路径@foo/ bar/* @foo/
。因此,它也将bar/ baz @foo/bar
包内的所有路径标记为外部依赖。在路径解析结束后,解析后的绝对路径会与所有不像包路径的外部路径(即以
/
或./
或../
开头的路径) 进行比对。但在比对之前,外部路径会与当前工作目录连接,然后进行规范化,变成绝对路径 (即使它包含*
通配符)。这意味着您可以使用
--external:
将目录./dir/* dir
中的所有内容标记为外部依赖。 注意,开头的./
很重要。使用--external:
将被视为包路径, 并且在路径解析结束后不会进行检查。dir/*
#Main fields (主要字段)
当您在node中导入一个包时,该包的package.json
文件中的main
字段决定导入哪个文件 (同时还有许多其他规则)。 包括esbuild在内的主要JavaScript打包工具允许您在解析包时指定额外的package.json
字段。 至少有三个这样的字段常被使用:
main
这是标准字段,用于所有旨在与node一起使用的包。
main
名称被硬编码到node的模块解析逻辑本身中。因为它旨在与node一起使用,所以可以合理地期望 此字段中的文件路径是CommonJS风格的模块。module
这个字段来自一个提案, 该提案描述了如何将ECMAScript模块集成到node中。因此,可以合理地期望此字段中的文件路径 是ECMAScript风格的模块。这个提案没有被node采纳(node使用
"type":
), 但它被主要打包工具采纳,因为ECMAScript风格的模块可以实现更好的树摇动, 或者说死代码移除。"module" 对于包作者:一些包错误地将
module
字段用于浏览器特定代码,将node特定代码留给main
字段。 这可能是因为node忽略了module
字段,而人们通常只将打包工具用于浏览器特定代码。 然而,打包node特定代码也很有价值(例如,它减少了下载和启动时间),而将浏览器特定代码 放在module
中的包会阻止打包工具有效地进行树摇动。如果您试图在包中发布浏览器特定代码, 请使用browser
字段。browser
这个字段来自一个提案, 该提案允许打包工具用浏览器友好版本替换特定于node的文件或模块。它让您指定一个替代的 浏览器特定入口点。请注意,一个包可以同时使用
browser
和module
字段(见下面的注释)。
默认的主要字段取决于当前的平台设置。这些默认值应该与 existing package ecosystem. But you can customize them like this if you want to:
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节点构建。
请注意,使用main
、module
和browser
是实现这一目的的旧方法。还有一种更新的方法, 您可能更喜欢使用:package.json
中的exports
字段。 它提供了一组不同的权衡。例如,它让您对包中所有子路径的导入有更精确的控制 (而main
字段只能控制入口点),但根据您的配置方式,它可能导致您的包被多次导入。
#Node paths (Node路径)
Node的模块解析算法支持一个名为NODE_PATH
的环境变量,它包含一个全局目录列表,用于解析导入路径。除了所有父目录中的node_modules
目录外, 还会搜索这些路径来查找包。您可以通过CLI的环境变量和JS与Go API的数组将此目录列表传递给esbuild:
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特定功能(如__dirname
、 import.meta.url
、fs.readFileSync
和*.node
原生二进制模块)。 有两个可能的值:
bundle
这是默认值。它意味着允许打包包导入。请注意,此值并不意味着所有包都将被打包, 只是允许它们被打包。您仍然可以使用external从打包中排除单个包。
external
这意味着所有包导入都被视为打包的外部依赖,不会被打包。_注意,当您的打包运行时, 您的依赖项仍然必须存在于文件系统上。_它与手动将每个依赖项传递给external 具有相同的效果,但更简洁。如果您想自定义哪些依赖项是外部的,哪些不是, 那么您应该将此设置为
bundle
,然后对单个依赖项使用external。此设置将原始源代码中所有"看起来像"包导入的导入路径视为包导入。具体来说, 不以路径段
/
或.
或..
开头的导入路径被认为是包导入。 这条规则只有两个例外:子路径导入 (以#
字符开头)和通过tsconfig.json
中的paths
和/或baseUrl
进行的TypeScript路径重映射(这些首先被应用)。
使用它看起来像这样:
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)
}
}
请注意,此设置仅在启用打包时才有效。还要注意,将导入路径标记为外部 发生在导入路径被任何配置的别名重写之后,因此在使用此设置时, 别名功能仍然有效。
#Preserve symlinks (保留符号链接)
此设置反映了node中的--preserve-symlinks
设置。如果您使用该设置(或Webpack中类似的resolve.symlinks
设置),您可能也需要在esbuild中启用此设置。它可以这样启用:
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.js
、./file.json
和./file.node
。包括esbuild在内的现代打包工具 也将这个概念扩展到其他文件类型。esbuild中隐式文件扩展名的完整顺序可以使用解析扩展名设置进行 自定义,默认为.tsx,
:
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的工作目录:
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语法。以下是可用选项:
transform
这告诉esbuild使用通用转换将JSX转换为JS,这种转换在许多使用JSX语法的库之间共享。 每个JSX元素都会被转换为对JSX工厂函数的调用, 该函数的第一个参数是元素的组件(或者对于片段使用JSX片段)。 第二个参数是props数组(如果没有props则为
null
)。任何存在的子元素将成为 第二个参数之后的额外参数。如果您想在每个文件的基础上配置此设置,您可以使用
// @jsxRuntime
注释。 这是来自Babel的JSX插件的约定, esbuild遵循这一约定。classic preserve
这会在输出中保留JSX语法,而不是将其转换为函数调用。JSX元素被视为一等语法, 仍然受到其他设置的影响,如压缩和属性名混淆。
请注意,这意味着输出文件不再是有效的JavaScript代码。 当您想在打包后由另一个工具转换esbuild输出文件中的JSX语法时,可以使用此功能。
automatic
这种转换是在React 17+中引入的, 非常特定于React。它自动从JSX导入源生成
import
语句, 并引入了许多关于如何处理语法的特殊情况。这里详细内容太复杂无法描述。 有关更多信息,请阅读React关于其新JSX转换的文档。 如果您想启用此转换的开发模式版本,您需要额外启用JSX开发设置。如果您想在每个文件的基础上配置此设置,您可以使用
// @jsxRuntime
注释。 这是来自Babel的JSX插件的约定, esbuild遵循这一约定。automatic
以下是将JSX转换设置为preserve
的示例:
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
以外的其他值,则此设置不起作用。 以下是启用此设置的示例:
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使用):
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
注释。 请注意,当JSX转换设置为automatic
时,此设置不适用。
#JSX fragment (JSX片段)
这设置了为每个JSX片段调用的函数。通常,像这样的JSX片段表达式:
<>Stuff</>
会被编译成使用React.Fragment
组件,如下所示:
React.createElement(React.Fragment, null, "Stuff");
您可以通过更改JSX片段来使用除React.Fragment
之外的组件。例如, 使用组件Fragment
(被其他库如Preact使用):
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
注释。 请注意,当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开发模式关闭时使用jsx
和jsxs
导入, 当JSX开发模式打开时使用jsxDEV
导入。这些的含义在 React关于其新JSX转换的文档中有描述。 无论JSX开发模式如何,当元素有一个属性展开后跟一个key
属性时,都会使用createElement
导入,如下所示:
return <div {...props} key={key} />
以下是将JSX导入源设置为preact
的示例:
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
注释。如果JSX转换尚未通过 其他方式设置,或者如果您也希望在每个文件的基础上设置它,您可能还需要添加 // @jsxRuntime
注释。
#JSX side effects (JSX副作用)
默认情况下,esbuild假设JSX表达式没有副作用,这意味着它们带有/* @__PURE__ */
注释, 并且在未使用时会在打包期间被移除。这遵循了JSX用于虚拟DOM的常见用法,适用于绝大多数JSX库。 然而,有些人编写的JSX库没有这种特性(特别是JSX表达式可能有任意副作用,在未使用时不能被移除)。 如果您使用这样的库,您可以使用此设置告诉esbuild JSX表达式有副作用:
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
设置时, 这会为您配置,您通常应该使用该设置而不是此设置。如果除此设置外还指定了目标, 则此设置将覆盖目标所指定的内容。
以下是您可能希望使用此设置而不是或除了设置目标的一些例子:
JavaScript运行时通常对较新的语法功能进行快速实现,这比等效的旧JavaScript慢, 通过告诉esbuild假装不支持这种语法功能,您可以获得加速。例如,V8 有一个关于对象展开的长期性能bug, 可以通过手动复制属性而不是使用对象展开语法来避免。
There are many other JavaScript implementations in addition to the ones that esbuild's
target
setting recognizes, and they may not support certain features. If you are targeting such an implementation, you can use this setting to configure esbuild with a custom syntax feature compatibility set without needing to change esbuild itself. For example, TypeScript's JavaScript parser may not support arbitrary module namespace identifier names so you may want to turn those off when targeting TypeScript's JavaScript parser.You may be processing esbuild's output with another tool, and you may want esbuild to transform certain features and the other tool to transform certain other features. For example, if you are using esbuild to transform files individually to ES5 but you are then feeding the output into Webpack for bundling, you may want to preserve
import()
expressions even though they are a syntax error in ES5.
If you want esbuild to consider a certain syntax feature to be unsupported, you can specify that like this:
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:
arbitrary-module-namespace-names
(任意模块命名空间名称)array-spread
(数组展开)arrow
(箭头函数)async-await
(异步/等待)async-generator
(异步生成器)bigint
(大整数)class
(类)class-field
(类字段)class-private-accessor
(类私有访问器)class-private-brand-check
(类私有品牌检查)class-private-field
(类私有字段)class-private-method
(类私有方法)class-private-static-accessor
(类私有静态访问器)class-private-static-field
(类私有静态字段)class-private-static-method
(类私有静态方法)class-static-blocks
(类静态块)class-static-field
(类静态字段)const-and-let
(const和let)decorators
(装饰器)default-argument
(默认参数)destructuring
(解构)dynamic-import
(动态导入)exponent-operator
(指数运算符)export-star-as
(export * as)for-await
(for await循环)for-of
(for-of循环)function-name-configurable
(可配置的函数名)function-or-class-property-access
(函数或类属性访问)generator
(生成器)hashbang
(哈希注释)import-assertions
(导入断言)import-attributes
(导入属性)import-meta
(import.meta)inline-script
(内联脚本)logical-assignment
(逻辑赋值)nested-rest-binding
(嵌套剩余绑定)new-target
(new.target)node-colon-prefix-import
(node:前缀导入)node-colon-prefix-require
(node:前缀require)nullish-coalescing
(空值合并)object-accessors
(对象访问器)object-extensions
(对象扩展)object-rest-spread
(对象剩余/展开)optional-catch-binding
(可选catch绑定)optional-chain
(可选链)regexp-dot-all-flag
(正则表达式点全标志)regexp-lookbehind-assertions
(正则表达式后行断言)regexp-match-indices
(正则表达式匹配索引)regexp-named-capture-groups
(正则表达式命名捕获组)regexp-set-notation
(正则表达式集合表示法)regexp-sticky-and-unicode-flags
(正则表达式粘性和Unicode标志)regexp-unicode-property-escapes
(正则表达式Unicode属性转义)rest-argument
(剩余参数)template-literal
(模板字面量)top-level-await
(顶级await)typeof-exotic-object-is-object
(typeof特殊对象是对象)unicode-escapes
(Unicode转义)using
(using声明)
CSS:
color-functions
(颜色函数)gradient-double-position
(渐变双位置)gradient-interpolation
(渐变插值)gradient-midpoints
(渐变中点)hwb
(HWB颜色)hex-rgba
(十六进制RGBA)inline-style
(内联样式)inset-property
(inset属性)is-pseudo-class
(is伪类)modern-rgb-hsl
(现代RGB/HSL)nesting
(嵌套)rebecca-purple
(丽贝卡紫色)
#Target (目标环境)
这设置生成的JavaScript和/或CSS代码的目标环境。它告诉esbuild将对这些环境来说太新的 JavaScript语法转换为在这些环境中可以工作的旧JavaScript语法。例如,??
运算符是在 Chrome 80中引入的,因此当目标为Chrome 79或更早版本时,esbuild会将其转换为等效 (但更冗长)的条件表达式。
请注意,这仅关注语法功能,而不是API。它不会自动添加这些环境不使用的新API的 polyfills。 您将不得不为需要的API显式导入polyfills(例如,通过导入 core-js
)。自动polyfill注入 超出了esbuild的范围。
每个目标环境都是环境名称后跟版本号。目前支持以下环境名称:
chrome
deno
edge
firefox
hermes
ie
ios
node
opera
rhino
safari
此外,您还可以指定JavaScript语言版本,如es2020
。默认目标是esnext
,这意味着 默认情况下,esbuild将假设支持所有最新的JavaScript和CSS功能。以下是配置多个目标环境的示例。 您不需要指定所有这些;您可以只指定您的项目关心的目标环境子集。如果您愿意, 您也可以更精确地指定版本号(例如,使用node12.19.0
而不仅仅是node12
):
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 (定义)
此功能提供了一种用常量表达式替换全局标识符的方法。它可以在不更改代码本身的情况下 改变构建之间某些代码的行为:
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
条目都映射到包含代码的字符串。省略引号意味着替换值是一个标识符。 这在下面的示例中有所演示:
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在构建前编辑您的源代码以删除某些结构。目前有两种可以删除的内容:
debugger
传递此标志会导致所有
debugger
语句 从输出中删除。这类似于流行的UglifyJS和 Terser JavaScript压缩工具中可用的drop_debugger: true
标志。JavaScript的
debugger
语句使活动调试器将该语句视为自动配置的断点。包含此语句的代码在 调试器打开时会自动暂停。如果没有打开调试器,该语句不执行任何操作。从代码中删除这些语句 只是防止调试器在代码运行时自动停止。您可以这样删除
debugger
语句:
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)
}
}
console
传递此标志会导致所有
console
API调用 从输出中删除。这类似于流行的UglifyJS和 Terser JavaScript压缩工具中可用的drop_console: true
标志。使用此标志可能会在您的代码中引入错误!此标志会删除整个调用表达式,包括所有调用参数。 这是有意为之的,因为如果这些调用参数计算成本很高,删除调用参数的计算对于提高生产环境中的性能很有用。 然而,如果这些参数中的任何一个有重要的副作用,使用此标志将改变您代码的行为。 使用此标志时要非常小心。如果您想删除console API调用而不删除有副作用的参数(这样您就不会引入错误),您应该将相关的 API调用标记为纯函数。例如,您可以使用
--pure:
将console.log console.log
标记为纯函数。这将导致在启用压缩时安全地删除这些API调用。您可以这样删除
console
API调用:
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):
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支持两种形式的副作用注解:
函数调用前的内联
/* @__PURE__ */
注释告诉esbuild,如果结果值未被使用,则可以移除该函数调用。 有关更多信息,请参阅pure API选项。package.json
中的sideEffects
字段可用于告诉esbuild,如果来自该文件的所有导入最终未被使用, 则可以移除包中的哪些文件。这是来自Webpack的约定,许多发布到npm的库在其包定义中已经有了这个字段。 您可以在Webpack的文档中了解有关此字段的更多信息。
这些注解可能会有问题,因为编译器完全依赖开发者的准确性,而开发者有时会发布带有 不正确注解的包。对于开发者来说,sideEffects
字段特别容易出错,因为默认情况下, 如果没有使用导入,它会导致包中的所有文件被视为死代码。如果您添加一个包含副作用的 新文件但忘记更新该字段,当人们尝试打包它时,您的包可能会出现问题。
这就是为什么esbuild包含了一种忽略副作用注解的方法。 只有当您遇到因为必要的代码意外地从包中移除而导致包出现问题时, 才应该启用此功能:
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
的所有引用替换为 来自该文件的导入:
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
. 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":
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:
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({
混淆为print({
:
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({
will be mangled into print({
while print({
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:
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__
:
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)
}
}
#持久化重命名决策
属性混淆功能的高级用法涉及将原始名称到混淆名称的映射存储在持久缓存中。 启用后,所有混淆的属性重命名会在初始构建期间记录在缓存中。后续构建会重用 缓存中存储的重命名,并为任何新添加的属性添加额外的重命名。这带来了几个后果:
您可以通过在传递给esbuild之前编辑缓存来自定义混淆属性的重命名方式。
缓存作为所有被混淆属性的列表。您可以轻松扫描它以查看是否有任何意外的属性重命名。
您可以通过将重命名值设置为
false
而不是字符串来禁用单个属性的混淆。 这类似于reserve props设置,但是基于每个属性。您可以确保构建之间的重命名一致性(例如,主线程文件和Web Worker,或库和插件)。 没有此功能,每个构建都会进行独立的重命名操作,混淆的属性名称可能不一致。
例如,考虑以下输入文件:
console.log({
someProp_: 1,
customRenaming_: 2,
disabledRenaming_: 3
});
如果我们想要将customRenaming_
重命名为cR_
,而不想重命名disabledRenaming_
, 我们可以将以下混淆缓存JSON传递给esbuild:
{
"customRenaming_": "cR_",
"disabledRenaming_": false
}
混淆缓存JSON可以像这样传递给esbuild:
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中启用压缩如下所示:
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 局部变量名称更短。通常您希望执行所有这些操作,但如有必要,这些选项也可以单独启用:
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:
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作为压缩器时,请记住以下几点:
当启用压缩时,您可能还应该设置target选项。默认情况下,esbuild利用现代 JavaScript特性使您的代码更小。例如,
a ===
可以被压缩为undefined || a === null ? 1 : a a ?? 1
。如果您不希望esbuild在压缩时利用现代 JavaScript特性,您应该使用较旧的语言目标,如--target=es6
。在JavaScript模板字面量中,字符转义序列
\n
将被替换为换行符。如果target 支持模板字面量,并且这样做会导致更小的输出,字符串字面量也会被转换为模板字面量。 **这不是一个bug。**压缩意味着您要求更小的输出,而转义序列\n
占用两个字节, 而换行符仅占用一个字节。您可以在关于此主题的FAQ条目 中了解更多信息。默认情况下,esbuild不会压缩顶层声明的名称。这是因为esbuild不知道您将如何处理输出。 您可能会将压缩代码注入到其他代码的中间,在这种情况下,压缩顶层声明名称将是不安全的。 设置输出格式(或启用打包,如果您尚未设置格式,它将为您选择 输出格式)告诉esbuild输出将在其自己的作用域内运行,这意味着压缩顶层声明名称是安全的。
压缩对于100%的JavaScript代码都不是安全的。这对esbuild和其他流行的JavaScript压缩器 (如terser)都是如此。特别是,esbuild并非 设计用于保留对函数调用
.toString()
的值。这是因为如果所有函数内的所有代码都必须 逐字保留,压缩几乎不会有任何作用,实际上毫无用处。然而,这意味着依赖于.toString()
返回值的JavaScript代码在压缩时可能会出现问题。例如,AngularJS 框架中的一些模式在代码压缩时会出现问题,因为AngularJS使用.toString()
读取函数的 参数名称。一种解决方法是使用显式注解。默认情况下,esbuild不会保留函数和类对象上
.name
的值。这是因为大多数代码 不依赖于这个属性,而使用更短的名称是一个重要的大小优化。然而,有些代码确实 依赖于.name
属性用于注册和绑定目的。如果您需要依赖这个,您应该启用 keep names选项。The minifier assumes that built-in JavaScript features behave the way they are expected to behave. These assumptions help esbuild generate more compact code. If you want a JavaScript minifier that doesn't make any assumptions about the behavior of built-in JavaScript features, then esbuild may not be the right JavaScript minifier for you. Here are some examples of these kinds of assumptions (note that this is not an exhaustive list):
It's expected that
Array.prototype.join
behaves as specified. This means it's safe for esbuild's minifier to transformx = [
into1, 2, 3] + '' x="1,2,3";
.Accessing the
log
property on theconsole
global is expected to not have any side effects. This means it's safe for esbuild's minifier to transformvar a, b =
intoa ? console. log( x) : console. log( y); var a,b=
(i.e. esbuild is assuming evaluatingconsole. log( a?x:y); console.log
can't change the value ofa
).
Use of certain JavaScript features can disable many of esbuild's optimizations including minification. Specifically, using direct
eval
and/or thewith
statement prevent esbuild from renaming identifiers to smaller names since these features cause identifier binding to happen at run time instead of compile time. This is almost always unintentional, and only happens because people are unaware of what directeval
is and why it's bad.If you are thinking about writing some code like this:
// Direct eval (will disable minification for the whole file) let result = eval(something)
You should probably write your code like this instead so your code can be minified:
// Indirect eval (has no effect on the surrounding code) let result = (0, eval)(something)
There is more information about the consequences of direct
eval
and the available alternatives here.The minification algorithm in esbuild does not yet do advanced code optimizations. In particular, the following code optimizations are possible for JavaScript code but are not done by esbuild (not an exhaustive list):
- Dead-code elimination within function bodies
- Function inlining
- Cross-statement constant propagation
- Object shape modeling
- Allocation sinking
- Method devirtualization
- Symbolic execution
- JSX expression hoisting
- TypeScript enum detection and inlining
If your code makes use of patterns that require some of these forms of code optimization to be compact, or if you are searching for the optimal JavaScript minification algorithm for your use case, you should consider using other tools. Some examples of tools that implement some of these advanced code optimizations include Terser and Google Closure Compiler.
#Pure (纯函数标记)
各种JavaScript工具使用一种约定,在new或调用表达式前使用包含/* @__PURE__ */
或/* #__PURE__ */
的特殊注释,表示如果结果值未被使用,则可以移除该表达式。 它看起来像这样:
let button = /* @__PURE__ */ React.createElement(Button, null);
打包工具(如esbuild)在树摇动(即死代码移除)过程中使用此信息,在打包工具 由于JavaScript代码的动态特性而无法自行证明移除是安全的情况下,执行跨模块边界 的未使用导入的细粒度移除。
请注意,虽然注释说"pure"(纯),但令人困惑的是它_不_表示被调用的函数是纯函数。 例如,它不表示可以缓存对该函数的重复调用。这个名称本质上只是"如果未使用则可以 移除"的抽象简写。
一些表达式(如JSX和某些内置全局对象)在esbuild中会自动被注解为/* @__PURE__ */
。 您还可以配置其他全局对象被标记为/* @__PURE__ */
。例如,您可以将全局 document.
函数标记为纯函数,这样当打包被压缩时, 只要结果未被使用,它就会自动从您的打包中移除。
值得一提的是,注解的效果仅扩展到调用本身,而不是参数。即使启用了压缩, 具有副作用的参数仍然会被保留:
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
打包此文件,未使用的函数将自动被丢弃,留下以下输出:
// 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
打包此文件,未使用的函数和未使用的导入仍将自动被丢弃,留下以下输出:
// lib.js
function one() {
console.log("one");
}
// input.js
one();
这样,esbuild只会打包您实际使用的包的部分,这有时可以节省大量空间。请注意, esbuild的树摇动实现依赖于使用ECMAScript模块的import
和export
语句。 它不适用于CommonJS模块。npm上的许多包同时包含这两种格式,esbuild默认会尝试 选择适用于树摇动的格式。您可以根据包的情况,使用main fields 和/或conditions选项自定义esbuild选择的格式。
默认情况下,树摇动仅在启用bundling或输出format设置为iife
时 才启用,否则树摇动将被禁用。您可以通过将其设置为true
来强制启用树摇动:
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
来强制禁用树摇动:
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" + cd
和foo.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生态系统中的许多其他工具中通用,包括流行的 UglifyJS和Terser JavaScript压缩器(这些工具被其他主要工具使用,包括Webpack 和Parcel)。
请注意,这些注解会导致esbuild假设被注解的代码是无副作用的。如果注解是错误的, 而代码实际上确实有重要的副作用,这些注解可能会导致代码出现问题。如果您正在 打包带有错误编写的注解的第三方代码,您可能需要启用忽略注解 以确保打包的代码是正确的。
#Source maps (源码映射)
#Source root (源码根目录)
此功能仅在启用源码映射时相关。它允许您设置源码映射中sourceRoot
字段的值,该字段指定源码映射中所有其他路径的相对路径。如果此字段不存在,源码 映射中的所有路径都被解释为相对于包含源码映射的目录。
您可以像这样配置sourceRoot
:
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>
。可以像这样配置:
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
文件。
源码映射生成有四种不同的模式:
-
linked
(链接)这种模式意味着源码映射生成到一个单独的
.js.map
输出文件中,与.js
输出文件 并排,并且.js
输出文件包含一个特殊的//# sourceMappingURL=
注释,指向.js.map
输出文件。这样,当您打开调试器时,浏览器就知道在哪里找到给定文件的 源码映射。像这样使用linked
源码映射模式:
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)
}
}
-
external
(外部)这种模式意味着源码映射生成到一个单独的
.js.map
输出文件中,与.js
输出文件 并排,但与linked
模式不同,.js
输出文件不包含//# sourceMappingURL=
注释。 像这样使用external
源码映射模式:
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)
}
}
-
inline
(内联)这种模式意味着源码映射作为base64负载添加到
.js
输出文件的末尾,放在//# sourceMappingURL=
注释中。不会生成额外的.js.map
输出文件。请记住, 源码映射通常非常大,因为它们包含所有原始源代码,所以您通常不希望发布包含inline
源码映射的代码。要从源码映射中移除源代码(只保留文件名和行/列映射), 请使用sources content选项。像这样使用inline
源码映射模式:
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)
}
}
-
both
(两者)这种模式是
inline
和external
的组合。源码映射内联添加到.js
输出文件的末尾, 并且相同源码映射的另一个副本被写入到一个单独的.js.map
输出文件中,与.js
输出文件并排。像这样使用both
源码映射模式:
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.
仍将显示包含 编译代码的未映射堆栈跟踪。以下是如何在浏览器的开发者工具中启用此设置:
- Chrome: ⚙ → Enable JavaScript source maps
- Safari: ⚙ → Sources → Enable source maps
- Firefox: ··· → Enable Source Maps
在node中,从版本v12.12.0开始原生支持源码映射。 此功能默认禁用,但可以通过标志启用。与浏览器不同,node中的实际堆栈跟踪也会被修改, 因此在您的代码中检查error.
将给出包含原始源代码的映射堆栈跟踪。 以下是如何在node中启用此设置(--enable-
标志必须 放在脚本文件名之前):
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
字段以使源码映射更小:
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 --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"(详细)模式。这会显示从入口点到每个 输入文件的导入路径,告诉您为什么特定的输入文件被包含在包中:
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
的文件中:
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,这很有用:
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的日志记录方式,例如在打印之前处理日志消息或将它们打印到控制台以外的 地方,这很有用。以下是一个示例:
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 (选项)
可以提供以下选项来控制格式化:
interface FormatMessagesOptions {
kind: 'error' | 'warning';
color?: boolean;
terminalWidth?: number;
}
type FormatMessagesOptions struct {
Kind MessageKind
Color bool
TerminalWidth int
}
kind
控制这些日志消息是作为错误还是警告打印。
color
如果为
true
,则包含Unix风格的终端转义码以实现彩色输出。terminalWidth
提供一个正值,以便包装长行,使它们不会超过提供的列宽。提供
0
以禁用自动换行。
#Log level (日志级别)
可以更改日志级别以防止esbuild将警告和/或错误消息打印到终端。六个日志级别是:
silent
不显示任何日志输出。这是使用JS transform API时的默认日志级别。error
只显示错误。warning
只显示警告和错误。这是使用JS build API时的默认日志级别。info
显示警告、错误和输出文件摘要。这是使用CLI时的默认日志级别。debug
记录info
中的所有内容和一些可能帮助您调试损坏包的其他消息。 此日志级别有性能影响,并且一些消息可能是误报,因此默认不显示此信息。verbose
这会生成大量日志消息,是为了调试文件系统驱动程序的问题而添加的。 不适合一般使用。
日志级别可以像这样设置:
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命令提示符。它还避免了 意外地用完带有有限滚动缓冲区的终端模拟器的整个滚动缓冲区。
日志限制可以更改为另一个值,也可以通过将其设置为零来完全禁用。这将显示所有日志消息:
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
添加polyfill, 这些调用仍然会在运行时抛出异常,因为该正则表达式语法仍然不受支持。如果您 希望esbuild在您使用较新的不支持的正则表达式语法时生成警告,您可以这样做:
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)
}
}
每种消息类型的日志级别都可以被覆盖为日志级别设置支持的任何值。 所有当前可用的消息类型列在下面(点击每一个以查看示例日志消息):
-
JS (JavaScript):
assert-to-with
▲ [WARNING] The "assert" keyword is not supported in the configured target environment [assert-to-with] example.js:1:31: 1 │ import data from "./data.json" assert { type: "json" } │ ~~~~~~ ╵ with Did you mean to use "with" instead of "assert"?
assert-type-json
▲ [WARNING] Non-default import "value" is undefined with a JSON import assertion [assert-type-json] example.js:1:78: 1 │ import * as data from "./data.json" assert { type: "json" }; console.log(data.value) ╵ ~~~~~ The JSON import assertion is here: example.js:1:45: 1 │ import * as data from "./data.json" assert { type: "json" }; console.log(data.value) ╵ ~~~~~~~~~~~~ You can either keep the import assertion and only use the "default" import, or you can remove the import assertion and use the "value" import.
assign-to-constant
▲ [WARNING] This assignment will throw because "foo" is a constant [assign-to-constant] example.js:1:15: 1 │ const foo = 1; foo = 2 ╵ ~~~ The symbol "foo" was declared a constant here: example.js:1:6: 1 │ const foo = 1; foo = 2 ╵ ~~~
assign-to-define
▲ [WARNING] Suspicious assignment to defined constant "DEFINE" [assign-to-define] example.js:1:0: 1 │ DEFINE = false ╵ ~~~~~~ The expression "DEFINE" has been configured to be replaced with a constant using the "define" feature. If this expression is supposed to be a compile-time constant, then it doesn't make sense to assign to it here. Or if this expression is supposed to change at run-time, this "define" substitution should be removed.
assign-to-import
▲ [WARNING] This assignment will throw because "foo" is an import [assign-to-import] example.js:1:23: 1 │ import foo from "foo"; foo = null ╵ ~~~ Imports are immutable in JavaScript. To modify the value of this import, you must export a setter function in the imported file (e.g. "setFoo") and then import and call that function here instead.
call-import-namespace
▲ [WARNING] Calling "foo" will crash at run-time because it's an import namespace object, not a function [call-import-namespace] example.js:1:28: 1 │ import * as foo from "foo"; foo() ╵ ~~~ Consider changing "foo" to a default import instead: example.js:1:7: 1 │ import * as foo from "foo"; foo() │ ~~~~~~~~ ╵ foo
class-name-will-throw
▲ [WARNING] Accessing class "Foo" before initialization will throw [class-name-will-throw] example.js:1:40: 1 │ class Foo { static key = "foo"; static [Foo.key] = 123 } ╵ ~~~
commonjs-variable-in-esm
▲ [WARNING] The CommonJS "exports" variable is treated as a global variable in an ECMAScript module and may not work as expected [commonjs-variable-in-esm] example.js:1:0: 1 │ exports.foo = 1; export let bar = 2 ╵ ~~~~~~~ This file is considered to be an ECMAScript module because of the "export" keyword here: example.js:1:17: 1 │ exports.foo = 1; export let bar = 2 ╵ ~~~~~~
delete-super-property
▲ [WARNING] Attempting to delete a property of "super" will throw a ReferenceError [delete-super-property] example.js:1:42: 1 │ class Foo extends Object { foo() { delete super.foo } } ╵ ~~~~~
direct-eval
▲ [WARNING] Using direct eval with a bundler is not recommended and may cause problems [direct-eval] example.js:1:22: 1 │ let apparentlyUnused; eval("actuallyUse(apparentlyUnused)") ╵ ~~~~ You can read more about direct eval and bundling here: https://esbuild.github.io/link/direct-eval
duplicate-case
▲ [WARNING] This case clause will never be evaluated because it duplicates an earlier case clause [duplicate-case] example.js:1:33: 1 │ switch (foo) { case 1: return 1; case 1: return 2 } ╵ ~~~~ The earlier case clause is here: example.js:1:15: 1 │ switch (foo) { case 1: return 1; case 1: return 2 } ╵ ~~~~
duplicate-class-member
▲ [WARNING] Duplicate member "x" in class body [duplicate-class-member] example.js:1:19: 1 │ class Foo { x = 1; x = 2 } ╵ ^ The original member "x" is here: example.js:1:12: 1 │ class Foo { x = 1; x = 2 } ╵ ^
duplicate-object-key
▲ [WARNING] Duplicate key "bar" in object literal [duplicate-object-key] example.js:1:16: 1 │ foo = { bar: 1, bar: 2 } ╵ ~~~ The original key "bar" is here: example.js:1:8: 1 │ foo = { bar: 1, bar: 2 } ╵ ~~~
empty-import-meta
▲ [WARNING] "import.meta" is not available in the configured target environment ("chrome50") and will be empty [empty-import-meta] example.js:1:6: 1 │ foo = import.meta ╵ ~~~~~~~~~~~
equals-nan
▲ [WARNING] Comparison with NaN using the "!==" operator here is always true [equals-nan] example.js:1:24: 1 │ foo = foo.filter(x => x !== NaN) ╵ ~~~ Floating-point equality is defined such that NaN is never equal to anything, so "x === NaN" always returns false. You need to use "Number.isNaN(x)" instead to test for NaN.
equals-negative-zero
▲ [WARNING] Comparison with -0 using the "!==" operator will also match 0 [equals-negative-zero] example.js:1:28: 1 │ foo = foo.filter(x => x !== -0) ╵ ~~ Floating-point equality is defined such that 0 and -0 are equal, so "x === -0" returns true for both 0 and -0. You need to use "Object.is(x, -0)" instead to test for -0.
equals-new-object
▲ [WARNING] Comparison using the "!==" operator here is always true [equals-new-object] example.js:1:24: 1 │ foo = foo.filter(x => x !== []) ╵ ~~~ Equality with a new object is always false in JavaScript because the equality operator tests object identity. You need to write code to compare the contents of the object instead. For example, use "Array.isArray(x) && x.length === 0" instead of "x === []" to test for an empty array.
html-comment-in-js
▲ [WARNING] Treating "<!--" as the start of a legacy HTML single-line comment [html-comment-in-js] example.js:1:0: 1 │ <!-- comment --> ╵ ~~~~
impossible-typeof
▲ [WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof] example.js:1:32: 1 │ foo = foo.map(x => 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.
indirect-require
▲ [WARNING] Indirect calls to "require" will not be bundled [indirect-require] example.js:1:8: 1 │ let r = require, fs = r("fs") ╵ ~~~~~~~
private-name-will-throw
▲ [WARNING] Writing to getter-only property "#foo" will throw [private-name-will-throw] example.js:1:39: 1 │ class Foo { get #foo() {} bar() { this.#foo++ } } ╵ ~~~~
semicolon-after-return
▲ [WARNING] The following expression is not returned because of an automatically-inserted semicolon [semicolon-after-return] example.js:1:6: 1 │ return ╵ ^
suspicious-boolean-not
▲ [WARNING] Suspicious use of the "!" operator inside the "in" operator [suspicious-boolean-not] example.js:1:4: 1 │ if (!foo in bar) { │ ~~~~ ╵ (!foo) The code "!x in y" is parsed as "(!x) in y". You need to insert parentheses to get "!(x in y)" instead.
suspicious-define
▲ [WARNING] "process.env.NODE_ENV" is defined as an identifier instead of a string (surround "production" with quotes to get a string) [suspicious-define] <js>:1:34: 1 │ define: { 'process.env.NODE_ENV': 'production' } │ ~~~~~~~~~~~~ ╵ '"production"'
suspicious-logical-operator
▲ [WARNING] The "&&" operator here will always return the left operand [suspicious-logical-operator] example.js:1:25: 1 │ const isInRange = x => 0 && x <= 1 ╵ ~~ The "=>" symbol creates an arrow function expression in JavaScript. Did you mean to use the greater-than-or-equal-to operator ">=" here instead? example.js:1:20: 1 │ const isInRange = x => 0 && x <= 1 │ ~~ ╵ >=
suspicious-nullish-coalescing
▲ [WARNING] The "??" operator here will always return the left operand [suspicious-nullish-coalescing] example.js:1:26: 1 │ return name === user.name ?? "" ╵ ~~ The left operand of the "??" operator here will never be null or undefined, so it will always be returned. This usually indicates a bug in your code: example.js:1:7: 1 │ return name === user.name ?? "" ╵ ~~~~~~~~~~~~~~~~~~
this-is-undefined-in-esm
▲ [WARNING] Top-level "this" will be replaced with undefined since this file is an ECMAScript module [this-is-undefined-in-esm] example.js:1:0: 1 │ this.foo = 1; export let bar = 2 │ ~~~~ ╵ undefined This file is considered to be an ECMAScript module because of the "export" keyword here: example.js:1:14: 1 │ this.foo = 1; export let bar = 2 ╵ ~~~~~~
unsupported-dynamic-import
▲ [WARNING] This "import" expression will not be bundled because the argument is not a string literal [unsupported-dynamic-import] example.js:1:0: 1 │ import(foo) ╵ ~~~~~~
unsupported-jsx-comment
▲ [WARNING] Invalid JSX factory: 123 [unsupported-jsx-comment] example.jsx:1:8: 1 │ // @jsx 123 ╵ ~~~
unsupported-regexp
▲ [WARNING] The regular expression flag "d" is not available in the configured target environment ("chrome50") [unsupported-regexp] example.js:1:3: 1 │ /./d ╵ ^ This regular expression literal has been converted to a "new RegExp()" constructor to avoid generating code with a syntax error. However, you will need to include a polyfill for "RegExp" for your code to have the correct behavior at run-time.
unsupported-require-call
▲ [WARNING] This call to "require" will not be bundled because the argument is not a string literal [unsupported-require-call] example.js:1:0: 1 │ require(foo) ╵ ~~~~~~~
-
CSS (层叠样式表):
css-syntax-error
▲ [WARNING] Expected identifier but found "]" [css-syntax-error] example.css:1:4: 1 │ div[] { ╵ ^
invalid-@charset
▲ [WARNING] "@charset" must be the first rule in the file [invalid-@charset] example.css:1:19: 1 │ div { color: red } @charset "UTF-8"; ╵ ~~~~~~~~ This rule cannot come before a "@charset" rule example.css:1:0: 1 │ div { color: red } @charset "UTF-8"; ╵ ^
invalid-@import
▲ [WARNING] All "@import" rules must come first [invalid-@import] example.css:1:19: 1 │ div { color: red } @import "foo.css"; ╵ ~~~~~~~ This rule cannot come before an "@import" rule example.css:1:0: 1 │ div { color: red } @import "foo.css"; ╵ ^
invalid-@layer
▲ [WARNING] "initial" cannot be used as a layer name [invalid-@layer] example.css:1:7: 1 │ @layer initial { ╵ ~~~~~~~
invalid-calc
▲ [WARNING] "-" can only be used as an infix operator, not a prefix operator [invalid-calc] example.css:1:20: 1 │ div { z-index: calc(-(1+2)); } ╵ ^ ▲ [WARNING] The "+" operator only works if there is whitespace on both sides [invalid-calc] example.css:1:23: 1 │ div { z-index: calc(-(1+2)); } ╵ ^
js-comment-in-css
▲ [WARNING] Comments in CSS use "/* ... */" instead of "//" [js-comment-in-css] example.css:1:0: 1 │ // comment ╵ ~~
undefined-composes-from
▲ [WARNING] The value of "zoom" in the "foo" class is undefined [undefined-composes-from] example.module.css:1:1: 1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; } ╵ ~~~ The first definition of "zoom" is here: lib.module.css:1:7: 1 │ .bar { zoom: 2 } ╵ ~~~~ The second definition of "zoom" is here: example.module.css:1:44: 1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; } ╵ ~~~~ The specification of "composes" does not define an order when class declarations from separate files are composed together. The value of the "zoom" property for "foo" may change unpredictably as the code is edited. Make sure that all definitions of "zoom" for "foo" are in a single file.
unsupported-@charset
▲ [WARNING] "UTF-8" will be used instead of unsupported charset "ASCII" [unsupported-@charset] example.css:1:9: 1 │ @charset "ASCII"; ╵ ~~~~~~~
unsupported-@namespace
▲ [WARNING] "@namespace" rules are not supported [unsupported-@namespace] example.css:1:0: 1 │ @namespace "ns"; ╵ ~~~~~~~~~~
unsupported-css-property
▲ [WARNING] "widht" is not a known CSS property [unsupported-css-property] example.css:1:6: 1 │ div { widht: 1px } │ ~~~~~ ╵ width Did you mean "width" instead?
unsupported-css-nesting
▲ [WARNING] Transforming this CSS nesting syntax is not supported in the configured target environment ("chrome50") [unsupported-css-nesting] example.css:2:5: 2 │ .foo & { ╵ ^ The nesting transform for this case must generate an ":is(...)" but the configured target environment does not support the ":is" pseudo-class.
-
Bundler (打包器):
ambiguous-reexport
▲ [WARNING] Re-export of "foo" in "example.js" is ambiguous and has been removed [ambiguous-reexport] One definition of "foo" comes from "a.js" here: a.js:1:11: 1 │ export let foo = 1 ╵ ~~~ Another definition of "foo" comes from "b.js" here: b.js:1:11: 1 │ export let foo = 2 ╵ ~~~
different-path-case
▲ [WARNING] Use "foo.js" instead of "Foo.js" to avoid issues with case-sensitive file systems [different-path-case] example.js:2:7: 2 │ import "./Foo.js" ╵ ~~~~~~~~~~
empty-glob
▲ [WARNING] The glob pattern import("./icon-*.json") did not match any files [empty-glob] example.js:2:16: 2 │ return import("./icon-" + name + ".json") ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~
ignored-bare-import
▲ [WARNING] Ignoring this import because "node_modules/foo/index.js" was marked as having no side effects [ignored-bare-import] example.js:1:7: 1 │ import "foo" ╵ ~~~~~ "sideEffects" is false in the enclosing "package.json" file: node_modules/foo/package.json:2:2: 2 │ "sideEffects": false ╵ ~~~~~~~~~~~~~
ignored-dynamic-import
▲ [WARNING] Importing "foo" was allowed even though it could not be resolved because dynamic import failures appear to be handled here: [ignored-dynamic-import] example.js:1:7: 1 │ import("foo").catch(e => { ╵ ~~~~~ The handler for dynamic import failures is here: example.js:1:14: 1 │ import("foo").catch(e => { ╵ ~~~~~
import-is-undefined
▲ [WARNING] Import "foo" will always be undefined because the file "foo.js" has no exports [import-is-undefined] example.js:1:9: 1 │ import { foo } from "./foo" ╵ ~~~
require-resolve-not-external
▲ [WARNING] "foo" should be marked as external for use with "require.resolve" [require-resolve-not-external] example.js:1:26: 1 │ let foo = require.resolve("foo") ╵ ~~~~~
-
Source maps (源码映射):
invalid-source-mappings
▲ [WARNING] Bad "mappings" data in source map at character 3: Invalid original column value: -2 [invalid-source-mappings] example.js.map:2:18: 2 │ "mappings": "aAAFA,UAAU;;" ╵ ^ The source map "example.js.map" was referenced by the file "example.js" here: example.js:1:21: 1 │ //# sourceMappingURL=example.js.map ╵ ~~~~~~~~~~~~~~
missing-source-map
▲ [WARNING] Cannot read file ".": Incorrect function. [missing-source-map] example.js:1:21: 1 │ //# sourceMappingURL=. ╵ ^
unsupported-source-map-comment
▲ [WARNING] Unsupported source map comment: could not decode percent-escaped data: invalid URL escape "%\"" [unsupported-source-map-comment] example.js:1:21: 1 │ //# sourceMappingURL=data:application/json,"%" ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~
-
Resolver (解析器):
package.json
▲ [WARNING] "esm" is not a valid value for the "type" field [package.json] package.json:1:10: 1 │ { "type": "esm" } ╵ ~~~~~ The "type" field must be set to either "commonjs" or "module".
tsconfig.json
▲ [WARNING] Unrecognized target environment "ES4" [tsconfig.json] tsconfig.json:1:33: 1 │ { "compilerOptions": { "target": "ES4" } } ╵ ~~~~~
这些消息类型应该相当稳定,但将来可能会添加新的类型,有时也可能会删除旧的类型。 如果删除了某个消息类型,针对该消息类型的任何覆盖都将被静默忽略。