内容类型
下面列出了所有内置的内容类型。每种内容类型都有一个关联的"加载器", 它告诉esbuild如何解释文件内容。某些文件扩展名默认已经配置了加载器, 尽管这些默认值可以被覆盖。
#JavaScript
加载器: js
这个加载器默认启用于.js
、.cjs
和.mjs
文件。 .cjs
扩展名被node用于CommonJS模块,而.mjs
扩展名被node用于ECMAScript模块。
请注意,默认情况下,esbuild的输出会利用所有现代JS特性。例如,当启用压缩时, a !==
将变成a ?? b
,这使用了来自ES2020 版本的JavaScript语法。如果不想要这种行为,你必须指定esbuild的 target设置,以说明你需要输出在哪些浏览器中正确工作。 然后esbuild将避免使用对这些浏览器来说过于现代的JavaScript特性。
esbuild支持所有现代JavaScript语法。然而,较新的语法可能不被旧浏览器支持, 因此你可能想要配置target选项,告诉esbuild适当地将较新的 语法转换为较旧的语法。
以下语法特性总是会为旧浏览器进行转换:
Syntax transform | Language version | Example |
---|---|---|
Trailing commas in function parameter lists and calls | es2017 |
foo(a, b, ) |
Numeric separators | esnext |
1_000_000 |
These syntax features are conditionally transformed for older browsers depending on the configured language target:
Syntax transform | Transformed when --target is below |
Example |
---|---|---|
Exponentiation operator | es2016 |
a ** b |
Async functions | es2017 |
async () => {} |
Asynchronous iteration | es2018 |
for await (let x of y) {} |
Async generators | es2018 |
async function* foo() {} |
Spread properties | es2018 |
let x = {...y} |
Rest properties | es2018 |
let {...x} = y |
Optional catch binding | es2019 |
try {} catch {} |
BigInt | es2020 |
123n |
Optional chaining | es2020 |
a?.b |
Nullish coalescing | es2020 |
a ?? b |
import.meta |
es2020 |
import.meta |
Logical assignment operators | es2021 |
a ??= b |
Class instance fields | es2022 |
class { x } |
Static class fields | es2022 |
class { static x } |
Private instance methods | es2022 |
class { #x() {} } |
Private instance fields | es2022 |
class { #x } |
Private static methods | es2022 |
class { static #x() {} } |
Private static fields | es2022 |
class { static #x } |
Ergonomic brand checks | es2022 |
#x in y |
Class static blocks | es2022 |
class { static {} } |
Import assertions | esnext |
import "x" assert {} 1 |
Import attributes | esnext |
import "x" with {} |
Auto-accessors | esnext |
class { accessor x } |
using declarations |
esnext |
using x = y |
Decorators | esnext |
@foo class Bar {} |
These syntax features are currently always passed through un-transformed:
Syntax transform | Unsupported when --target is below |
Example |
---|---|---|
RegExp dotAll flag |
es2018 |
/./s 1 |
RegExp lookbehind assertions | es2018 |
/(?<=x)y/ 1 |
RegExp named capture groups | es2018 |
/(?<foo>\d+)/ 1 |
RegExp unicode property escapes | es2018 |
/\p{ASCII}/u 1 |
Top-level await | es2022 |
await import(x) |
Arbitrary module namespace identifiers | es2022 |
export {foo as 'f o o'} |
RegExp match indices | es2022 |
/x(.+)y/d 1 |
RegExp set notation | es2024 |
/[\w--\d]/v 1 |
Hashbang grammar | esnext |
#!/usr/bin/env node |
See also the list of finished ECMAScript proposals and the list of active ECMAScript proposals. Note that while transforming code containing top-level await is supported, bundling code containing top-level await is only supported when the output format is set to esm
.
#JavaScript caveats
You should keep the following things in mind when using JavaScript with esbuild:
#ES5支持不完善
目前还不支持将ES6+语法转换为ES5。但是,如果你正在使用esbuild转换ES5代码, 你仍然应该将target设置为es5
。这可以防止esbuild在你的 ES5代码中引入ES6语法。例如,如果没有这个标志,对象字面量{x: x}
会变成 {x}
,字符串"a\nb"
在压缩时会变成多行模板字面量。这些替换都是因为 生成的代码更短,但如果target是es5
,就不会执行这些替换。
#私有成员性能
私有成员转换(用于#name
语法)使用WeakMap
和WeakSet
来保持此特性的私有性。 这与Babel和TypeScript编译器中的相应转换类似。大多数现代JavaScript引擎 (V8、JavaScriptCore和SpiderMonkey,但不包括ChakraCore)对于大型WeakMap
和WeakSet
对象可能没有良好的性能特征。
使用此语法转换创建许多带有私有字段或私有方法的类实例可能会给垃圾收集器 带来大量开销。这是因为现代引擎(除了ChakraCore)将弱值存储在实际的map对象中, 而不是作为键本身的隐藏属性,而大型map对象可能会导致垃圾收集的性能问题。 更多信息请参见此参考。
#导入遵循ECMAScript模块行为
你可能会尝试在导入需要全局状态的模块之前修改全局状态,并期望它能工作。 然而,JavaScript(因此也包括esbuild)实际上会将所有import
语句 "提升"到文件顶部,所以这样做是行不通的:
window.foo = {}
import './something-that-needs-foo'
有一些ECMAScript模块的实现(例如TypeScript编译器)在这方面没有遵循 JavaScript规范。用这些工具编译的代码可能会"工作",因为import
被替换为 内联的require()
调用,这忽略了提升要求。但是这样的代码在真正的 ECMAScript模块实现(如node、浏览器或esbuild)中将无法工作, 所以不建议编写这样的代码,因为它不具有可移植性。
正确的做法是将全局状态修改移到它自己的导入中。这样它_会_在其他导入之前运行:
import './assign-to-foo-on-window'
import './something-that-needs-foo'
#打包时避免直接使用eval
虽然表达式eval(x)
看起来像一个普通的函数调用,但它在JavaScript中实际上 具有特殊的行为。以这种方式使用eval
意味着存储在x
中的被评估代码可以通过 名称引用任何包含作用域中的任何变量。例如,代码 let y =
将返回123
。
这被称为"直接eval",在打包代码时会因为多种原因产生问题:
现代打包工具包含一个称为"作用域提升"的优化,它将所有打包的文件合并到一个 文件中,并重命名变量以避免名称冲突。然而,这意味着通过直接
eval
评估的代码 可以读取和写入包中任何文件中的变量!这是一个正确性问题,因为被评估的代码 可能试图访问一个全局变量,但可能意外地访问到另一个文件中具有相同名称的私有 变量。如果另一个文件中的私有变量包含敏感数据,这甚至可能成为一个安全问题。当引用使用
import
语句导入的变量时,被评估的代码可能无法正确工作。导入的变量是实时的 bindings to variables in another file. They are not copies of those variables. So when esbuild bundles your code, your imports are replaced with a direct reference to the variable in the imported file. But that variable may have a different name, in which case the code evaluated by directeval
will be unable to reference it by the expected name.Using direct
eval
forces esbuild to deoptimize all of the code in all of the scopes containing calls to directeval
. For correctness, it must assume that the evaluated code might need to access any of the other code in the file reachable from thateval
call. This means none of that code will be eliminated as dead code and none of that code will be minified.Because the code evaluated by the direct
eval
could need to reference any reachable variable by name, esbuild is prevented from renaming all of the variables reachable by the evaluated code. This means it can't rename variables to avoid name collisions with other variables in the bundle. So the directeval
causes esbuild to wrap the file in a CommonJS closure, which avoids name collisions by introducing a new scope instead. However, this makes the generated code bigger and slower because exported variables use run-time dynamic binding instead of compile-time static binding.
幸运的是,通常很容易避免直接使用eval
。有两种常用的替代方法可以避免上述 所有缺点:
(0, eval)('x')
这被称为"间接eval",因为
eval
不是直接被调用的,因此不会在JavaScript VM中 触发直接eval的语法特殊情况。你可以使用任何语法调用间接eval,除了完全形式为eval('x')
的表达式。例如,var eval2 =
、eval; eval2('x') [eval][0]('x')
和window.
都是 间接eval调用。当你使用间接eval时,代码是在全局作用域中而不是在调用者的 内联作用域中进行评估的。eval('x') new Function('x')
这在运行时构造一个新的函数对象。这就像你在全局作用域中写了
function()
,只不过{ x } x
可以是任意的代码字符串。 这种形式有时很方便,因为你可以向函数添加参数,并使用这些参数将变量 暴露给被评估的代码。例如,(new Function('env',
就像你写了'x'))( someEnv) (function(env)
。 当被评估的代码需要访问局部变量时,这通常是直接{ x })( someEnv) eval
的足够替代方案, 因为你可以将局部变量作为参数传入。
#函数(和类)上的toString()
值不会被保留
在JavaScript函数对象上调用toString()
然后将该字符串传递给某种形式的eval
以获取新的函数对象是比较常见的做法。这实际上将函数从包含的文件中"抽离"出来, 并破坏了与该文件中所有变量的链接。在esbuild中这样做不受支持,可能无法正常工作。 特别是,esbuild经常使用辅助方法来实现某些功能,并假设JavaScript作用域规则 没有被篡改。例如:
let pow = (a, b) => a ** b;
let pow2 = (0, eval)(pow.toString());
console.log(pow2(2, 3));
当这段代码被编译为ES6时,由于**
运算符在ES6中不可用,**
运算符会被替换为 对__pow
辅助函数的调用:
let __pow = Math.pow;
let pow = (a, b) => __pow(a, b);
let pow2 = (0, eval)(pow.toString());
console.log(pow2(2, 3));
如果你尝试运行这段代码,你会得到类似 ReferenceError:
的错误,因为函数(a, b)
依赖于局部作用域中的符号__pow
,而这个符号在全局作用域中不可用。 许多JavaScript语言特性都是这种情况,包括async
函数,以及一些esbuild特有的 功能,如keep names设置。
当人们使用.toString()
获取函数的源代码,然后尝试将其用作 Web Worker 的主体时,这个问题最常出现。如果你正在这样做并且想要使用esbuild, 你应该在单独的构建步骤中为Web Worker构建源代码,然后将Web Worker源代码 作为字符串插入到创建Web Worker的代码中。define功能是 在构建时插入字符串的一种方法。
#从模块命名空间对象调用的函数中的this
值不会被保留
在JavaScript中,函数中的this
值会根据函数的调用方式自动填充。例如,如果使用 obj.fn()
调用函数,那么在函数调用期间this
的值将是obj
。esbuild尊重这种行为, 但有一个例外:如果你从模块命名空间对象调用函数,this
的值可能不正确。 例如,考虑这段从模块命名空间对象ns
调用foo
的代码:
import * as ns from './foo.js'
ns.foo()
如果foo.js
尝试使用this
引用模块命名空间对象,那么在代码被esbuild打包后, 这可能不会正常工作:
// foo.js
export function foo() {
this.bar()
}
export function bar() {
console.log('bar')
}
原因是esbuild自动将大多数使用模块命名空间对象的代码重写为直接导入内容的代码。 这意味着上面的示例代码将被转换为以下内容,这会移除函数调用的this
上下文:
import { foo } from './foo.js'
foo()
这种转换极大地改善了树摇(tree shaking) (也称为死代码消除),因为它使esbuild能够理解哪些导出的符号未被使用。 它的缺点是这会改变使用this
访问模块导出的代码的行为,但这不是问题, 因为首先就不应该编写这种奇怪的代码。如果你需要从同一个文件中访问导出的函数, 只需直接调用它(即在上面的例子中使用bar()
而不是this.bar()
)。
#default
导出可能容易出错
ES模块格式(即ESM)有一个特殊的名为default
的导出,它有时的行为与所有其他 导出名称不同。当具有default
导出的ESM格式的代码被转换为CommonJS格式, 然后该CommonJS代码被导入到另一个ESM格式的模块中时,对于应该发生什么有两种 不同的解释,这两种都被广泛使用(Babel方式和 Node方式)。这非常不幸,因为它导致了无尽的兼容性问题, 尤其是因为JavaScript库通常以ESM编写但以CommonJS发布。
当esbuild打包执行这样操作的代码时,它必须决定使用哪种解释, 但没有完美的答案。esbuild使用的启发式方法与Webpack 使用的相同(详见下文)。由于Webpack是最广泛使用的打包工具,这意味着esbuild 在关于这个兼容性问题方面尽可能与现有生态系统兼容。因此,好消息是如果你可以让 有这个问题的代码在esbuild中工作,它也应该能在Webpack中工作。
这里有一个例子演示了这个问题:
// index.js
import foo from './somelib.js'
console.log(foo)
// somelib.js
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = 'foo';
以下是两种解释,两者都被广泛使用:
Babel的解释
如果使用Babel的解释,这段代码将打印
foo
。他们的理由是somelib.js
是从ESM 转换为CommonJS的(正如你可以通过__esModule
标记看出的那样),原始代码看起来 像这样:// somelib.js export default 'foo'
如果
somelib.js
没有从ESM转换为CommonJS,那么这段代码将打印foo
,所以无论 模块格式如何,它仍然应该打印foo
。这是通过检测CommonJS模块何时曾经是ES模块 (通过__esModule
标记,所有模块转换工具都设置它,包括Babel、TypeScript、 Webpack和esbuild)并在__esModule
标记存在时将默认导入设置为exports.
来实现的。这种行为很重要,因为它对于在 CommonJS环境中正确运行交叉编译的ESM是必要的,而且在Node最终添加原生ESM支持之前, 这是在Node中运行ESM代码的唯一方法。default Node的解释
如果使用Node的解释,这段代码将打印
{ default:
。 他们的理由是CommonJS代码使用动态导出,而ESM代码使用静态导出,因此将CommonJS 导入到ESM的完全通用方法是以某种方式暴露CommonJS的'foo' } exports
对象本身。对于 例如,CommonJS代码可以执行exports[
, 这在ESM语法中没有等效的表达方式。使用Math. random() ] = 'foo' default
导出是因为这实际上是设计ES模块 规范的人最初设计它的目的。这种解释对于普通的CommonJS模块来说是完全合理的。 它只会对曾经是ES模块的CommonJS模块(即当__esModule
存在时),在这种情况下, 行为与Babel的解释不同。
如果你是库作者: 在编写新代码时,你应该强烈考虑完全避免使用default
导出。 不幸的是,它已经被兼容性问题所污染,使用它可能会在某些时候给你的用户带来问题。
如果你是库用户: 默认情况下,esbuild将使用Babel的解释。如果你想让esbuild 使用Node的解释,你需要将代码放在以.mts
或.mjs
结尾的文件中,或者需要在 package.json
文件中添加"type":
。 理由是Node的原生ESM支持只能在文件扩展名为.mjs
或存在 "type":
时运行ESM代码,所以这样做是一个很好的 信号,表明代码打算在Node中运行,因此应该使用Node对default
导入的解释。 这与Webpack使用的启发式方法相同。
#TypeScript
加载器: ts
或 tsx
此加载器默认为.ts
、.tsx
、.mts
和.cts
文件启用,这意味着esbuild 内置了对解析TypeScript语法和丢弃类型注释的支持。然而,esbuild 不会 进行任何类型检查,所以你仍然需要与esbuild并行运行tsc -noEmit
来检查类型。 这不是esbuild自身所做的事情。
像这样的TypeScript类型声明会被解析并忽略(非详尽列表):
Syntax feature | Example |
---|---|
Interface declarations | interface Foo {} |
Type declarations | type Foo = number |
Function declarations | function foo(): void; |
Ambient declarations | declare module 'foo' {} |
Type-only imports | import type {Type} from 'foo' |
Type-only exports | export type {Type} from 'foo' |
Type-only import specifiers | import {type Type} from 'foo' |
Type-only export specifiers | export {type Type} from 'foo' |
支持TypeScript特有的语法扩展,并且始终转换为JavaScript(非详尽列表):
Syntax feature | Example | Notes |
---|---|---|
Namespaces | namespace Foo {} |
|
Enums | enum Foo { A, B } |
|
Const enums | const enum Foo { A, B } |
|
Generic type parameters | <T>(a: T): T => a |
Must write <T,>( ... with the tsx loader |
JSX with types | <Element<T>/> |
|
Type casts | a as B and <B>a |
|
Type imports | import {Type} from 'foo' |
Handled by removing all unused imports |
Type exports | export {Type} from 'foo' |
Handled by ignoring missing exports in TypeScript files |
Experimental decorators | @sealed class Foo {} |
Requires experimentalDecorators , does not support emitDecoratorMetadata |
Instantiation expressions | Array<number> |
TypeScript 4.7+ |
extends on infer |
infer A extends B |
TypeScript 4.7+ |
Variance annotations | type A<out B> = () => B |
TypeScript 4.7+ |
The satisfies operator |
a satisfies T |
TypeScript 4.9+ |
const type parameters |
class Foo<const T> {} |
TypeScript 5.0+ |
#TypeScript caveats - TypeScript使用注意事项
在使用esbuild处理TypeScript时,除了JavaScript注意事项外, 你还需要注意以下几点:
#Files are compiled independently - 文件独立编译
即使在转译单个模块时,TypeScript编译器实际上仍会解析导入的文件,以便判断导入的名称是类型还是值。 然而,像esbuild和Babel这样的工具(以及TypeScript编译器的transpileModule
API)会独立编译每个文件, 因此无法判断导入的名称是类型还是值。
因此,如果你在esbuild中使用TypeScript,应该启用isolatedModules
TypeScript配置选项。此选项可以防止你使用那些在像esbuild这样独立编译每个文件(不跨文件追踪类型引用)的环境中 可能导致错误编译的功能。例如,它会阻止你使用export
从另一个模块重新导出类型(你需要使用export
代替)。
#Imports follow ECMAScript module behavior - 导入遵循ECMAScript模块行为
由于历史原因,TypeScript编译器默认将ESM(ECMAScript模块)语法编译为CommonJS语法。例如, import *
会被编译为 const foo =
。这可能是因为当TypeScript采用这种语法时, ECMAScript模块还只是一个提案。然而,这是一个遗留行为,与这些语法在node等实际平台上的行为不符。 例如,require
函数可以返回任何JavaScript值(包括字符串),但import * as
语法总是会得到一个对象, 不可能是字符串。
为了避免这个遗留特性带来的问题,如果你在esbuild中使用TypeScript,应该启用 esModuleInterop
TypeScript配置选项。启用它会禁用这个遗留行为,并使TypeScript的类型系统与ESM兼容。 这个选项默认不启用是因为它会对现有的TypeScript项目造成破坏性更改,但Microsoft 强烈建议在新项目和现有项目中都应用它 (然后更新你的代码),以便更好地与生态系统的其他部分兼容。
具体来说,这意味着如果要使用ESM导入语法从CommonJS模块导入非对象值,必须使用默认导入而不是使用import * as
。 所以如果一个CommonJS模块通过module.
导出一个函数, 你需要使用import
而不是 import *
。
#Features that need a type system are not supported - 不支持需要类型系统的功能
esbuild将TypeScript类型视为注释并忽略它们,因此TypeScript被视为"经过类型检查的JavaScript"。 类型注解的解释由TypeScript类型检查器负责,如果你使用TypeScript,你应该在esbuild之外运行类型检查器。 这与Babel的TypeScript实现使用的是相同的编译策略。然而,这意味着一些需要类型解释才能工作的TypeScript 编译功能在esbuild中不起作用。
具体来说:
不支持
emitDecoratorMetadata
TypeScript配置选项。此功能会将TypeScript类型对应的JavaScript表示传递给附加的装饰器函数。 由于esbuild不复制TypeScript的类型系统,它没有足够的信息来实现这个功能。不支持
declaration
TypeScript配置选项(即生成.d.ts
文件)。如果你用TypeScript编写库,并想将编译后的JavaScript代码 作为包发布供他人使用,你可能还需要发布类型声明。由于esbuild不保留任何类型信息,它无法为你完成这项工作。 你可能需要使用TypeScript编译器生成类型声明,或者手动编写它们。
#Only certain tsconfig.json
fields are respected - 仅支持特定的tsconfig.json
字段
在打包过程中,esbuild的路径解析算法会考虑最近的父目录中的tsconfig.json
文件内容,并相应地修改其行为。 也可以使用esbuild的tsconfig
设置在构建API中显式设置tsconfig.json
路径, 或使用esbuild的tsconfigRaw
设置在转换API中显式传入tsconfig.json
文件的内容。 但是,esbuild目前只检查tsconfig.json
文件中的以下字段:
experimentalDecorators
此选项启用TypeScript文件中装饰器语法的转换。当启用
experimentalDecorators
时, 转换遵循TypeScript自身遵循的过时的装饰器设计。请注意,JavaScript正在添加一个更新的装饰器设计,当
experimentalDecorators
禁用时, TypeScript也会使用这个新设计。esbuild目前还没有实现这一点,因此当experimentalDecorators
禁用时,esbuild当前不会转换装饰器。target
useDefineForClassFields
这些选项控制TypeScript文件中的类字段是使用"define"语义还是"assign"语义编译:
Define语义(esbuild的默认行为):TypeScript类字段的行为与普通JavaScript类字段相同。 字段初始化器不会触发基类上的setter。你应该在今后编写所有新代码时使用这种方式。
Assign语义(需要显式启用):esbuild模拟TypeScript的遗留类字段行为。 字段初始化器会触发基类的setter。这可能需要用来运行遗留代码。
在esbuild中禁用define语义(因此启用assign语义)的方式与在TypeScript中相同: 在
tsconfig.json
文件中将useDefineForClassFields
设置为false
。为了与TypeScript兼容,esbuild也复制了TypeScript的行为:当未指定
useDefineForClassFields
时, 如果tsconfig.json
中的target
早于ES2022
,它默认为false
。但我建议如果你需要它, 就显式设置useDefineForClassFields
,而不是依赖于来自target
设置的这个默认值。 请注意,tsconfig.json
中的target
设置仅用于确定useDefineForClassFields
的默认值。 它不会影响esbuild自己的target
设置,尽管它们同名。baseUrl
paths
这些选项影响esbuild将
import
/require
路径解析到文件系统中的文件的方式。 你可以使用它来定义包别名和以其他方式重写导入路径。请注意,使用esbuild进行导入路径转换 需要启用bundling
,因为esbuild的路径解析只在打包过程中发生。 另外请注意,esbuild还有一个原生的alias
功能,你可能想用它来代替。jsx
jsxFactory
jsxFragmentFactory
jsxImportSource
These options affect esbuild's transformation of JSX syntax into JavaScript. They are equivalent to esbuild's native options for these settings:
jsx
,jsxFactory
,jsxFragment
, andjsxImportSource
.alwaysStrict
strict
If either of these options are enabled, esbuild will consider all code in all TypeScript files to be in strict mode and will prefix generated code with
"use strict"
unless the outputformat
is set toesm
(since all ESM files are automatically in strict mode).verbatimModuleSyntax
importsNotUsedAsValues
preserveValueImports
By default, the TypeScript compiler will delete unused imports when converting TypeScript to JavaScript. That way imports which turn out to be type-only imports accidentally don't cause an error at run-time. This behavior is also implemented by esbuild.
These options allow you to disable this behavior and preserve unused imports, which can be useful if for example the imported file has useful side-effects. You should use
verbatimModuleSyntax
for this, as that replaces the olderimportsNotUsedAsValues
andpreserveValueImports
settings (which TypeScript has now deprecated).extends
此选项允许你将
tsconfig.json
文件拆分为多个文件。这个值可以是一个字符串(用于单继承) 或一个数组(用于多继承,TypeScript 5.0+新增)。
所有其他tsconfig.json
字段(即不在上述列表中的字段)都将被忽略。
#You cannot use the tsx
loader for *.ts
files - 不能对*.ts
文件使用tsx
加载器
tsx
加载器不是ts
加载器的超集。它们是两种不同的、部分不兼容的语法。例如,字符序列 <a>1</a>/g
在ts
加载器中解析为<a>(1 < (/a>/g))
,而在tsx
加载器中解析为 (<a>1</a>) / g
。
这导致的最常见问题是在使用tsx
加载器时无法在箭头函数表达式上使用泛型类型参数, 比如<T>() => {}
。这是有意为之的,与官方TypeScript编译器的行为一致。在tsx
语法中, 这个语法空间是为JSX元素保留的。
#CSS
加载器: css
这个加载器默认启用于.css
文件。当CSS文件被导入到JavaScript中时, CSS代码会被打包,并在运行时注入到文档的<head>
中。
import './styles.css'
#JSX
加载器:jsx
或tsx
JSX是为React创建的 JavaScript的XML类语法扩展。它旨在被你的构建工具转换为普通的JavaScript。每个XML元素都会变成 一个普通的JavaScript函数调用。例如,以下JSX代码:
import Button from './button'
let button = <Button>Click me</Button>
render(button)
将被转换为以下JavaScript代码:
import Button from "./button";
let button = React.createElement(Button, null, "Click me");
render(button);
这个加载器默认对.jsx
和.tsx
文件启用。注意,JSX语法在.js
文件中默认是不启用的。 如果你想启用它,你需要进行配置:
esbuild app.js --bundle --loader:.js=jsx
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.js': 'jsx' },
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{
".js": api.LoaderJSX,
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#Auto-import for JSX - JSX的自动导入
使用JSX语法通常需要你手动导入你正在使用的JSX库。例如,如果你使用React,默认情况下 你需要在每个JSX文件中像这样导入React:
import * as React from 'react'
render(<div/>)
这是因为JSX转换会将JSX语法转换为对React.
的调用, 但它本身不会导入任何内容,所以React
变量不会自动存在。
如果你想避免在每个文件中手动import
你的JSX库,你可以通过将esbuild的JSX 转换设置为automatic
来实现,它会为你生成导入语句。请记住,这也会完全改变JSX转换的 工作方式,所以如果你使用的不是React的JSX库,它可能会破坏你的代码。配置方式如下:
esbuild app.jsx --jsx=automatic
require('esbuild').buildSync({
entryPoints: ['app.jsx'],
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"},
JSX: api.JSXAutomatic,
Outfile: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#Using JSX without React - 不使用React的JSX
如果你使用React以外的库(如Preact)的JSX,你可能需要配置 JSX factory和JSX fragment设置, 因为它们分别默认为React
和React
:
esbuild app.jsx --jsx-factory=h --jsx-fragment=Fragment
require('esbuild').buildSync({
entryPoints: ['app.jsx'],
jsxFactory: 'h',
jsxFragment: 'Fragment',
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"},
JSXFactory: "h",
JSXFragment: "Fragment",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
另外,如果你使用TypeScript,你可以通过在tsconfig.json
文件中添加以下内容来配置JSX, esbuild应该会自动识别这些配置,而不需要额外设置:
{
"compilerOptions": {
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
#Text - 文本
加载器:text
此加载器默认对.txt
文件启用。它在构建时将文件作为字符串加载,并将该字符串作为默认导出。 使用它看起来像这样:
import string from './example.txt'
console.log(string)
注意,如果文件中存在UTF-8 BOM, 此加载器会自动将其删除。BOM是一个特殊的字节序列,某些程序(如Windows上的记事本)在 保存文件时有时会插入。
#Binary - 二进制
加载器:binary
此加载器会在构建时将文件作为二进制缓冲区加载,并使用Base64编码将其嵌入到包中。 文件的原始字节在运行时从Base64解码,并使用默认导出作为Uint8Array
导出。 使用它看起来像这样:
import uint8array from './example.data'
console.log(uint8array)
如果你需要ArrayBuffer
,你可以直接访问uint8array
。 注意,此加载器默认不启用。你需要为相应的文件扩展名配置它,像这样:
esbuild app.js --bundle --loader:.data=binary
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.data': 'binary' },
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{
".data": api.LoaderBinary,
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#Base64
加载器:base64
此加载器会在构建时将文件作为二进制缓冲区加载,并使用Base64编码将其作为字符串嵌入到包中。 这个字符串使用默认导出导出。使用它看起来像这样:
import base64string from './example.data'
console.log(base64string)
注意,此加载器默认不启用。你需要为相应的文件扩展名配置它,像这样:
esbuild app.js --bundle --loader:.data=base64
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.data': 'base64' },
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{
".data": api.LoaderBase64,
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
如果你打算将其转换为Uint8Array
或ArrayBuffer
,你应该使用binary
加载器。 它使用优化的Base64到二进制转换器,比通常的atob
转换过程更快。
#Data URL - 数据URL
加载器:dataurl
此加载器会在构建时将文件作为二进制缓冲区加载,并将其作为Base64编码的数据URL嵌入到包中。 这个字符串使用默认导出导出。使用它看起来像这样:
import url from './example.png'
let image = new Image
image.src = url
document.body.appendChild(image)
数据URL包含基于文件扩展名和/或文件内容的最佳MIME类型猜测,对于二进制数据看起来像这样:
data:image/png;base64,iVBORw0KGgo=
...或者对于文本数据看起来像这样:
data:image/svg+xml,<svg></svg>%0A
注意,此加载器默认不启用。你需要为相应的文件扩展名配置它,像这样:
esbuild app.js --bundle --loader:.png=dataurl
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.png': 'dataurl' },
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,
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#External file - 外部文件
根据你需要的行为,有两种不同的加载器可以用于外部文件。两种加载器都在下面描述:
#The file
loader - file
加载器
加载器:file
此加载器会将文件复制到输出目录,并将文件名作为字符串嵌入到包中。这个字符串使用默认导出导出。 使用它看起来像这样:
import url from './example.png'
let image = new Image
image.src = url
document.body.appendChild(image)
这种行为有意类似于Webpack的file-loader
包。注意,此加载器默认不启用。你需要为相应的文件扩展名配置它,像这样:
esbuild app.js --bundle --loader:.png=file --outdir=out
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.png': 'file' },
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",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
默认情况下,导出的字符串只是文件名。如果你想在导出的字符串前添加基本路径,可以使用 public path API选项来实现。
#The copy
loader - copy
加载器
加载器:copy
此加载器会将文件复制到输出目录,并重写导入路径以指向复制的文件。这意味着导入语句仍会 存在于最终的包中,并且最终的包仍会引用该文件而不是将文件包含在包内。如果你在esbuild的 输出上运行额外的打包工具,或者你想从包中省略一个很少使用的数据文件以提高启动性能, 或者你想依赖运行时的特定行为(由导入触发),这可能会很有用。例如:
import json from './example.json' assert { type: 'json' }
console.log(json)
如果你使用以下命令打包上述代码:
esbuild app.js --bundle --loader:.json=copy --outdir=out --format=esm
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.json': 'copy' },
outdir: 'out',
format: 'esm',
})
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{
".json": api.LoaderCopy,
},
Outdir: "out",
Write: true,
Format: api.FormatESModule,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
生成的out/app.js
文件可能看起来像这样:
// app.js
import json from "./example-PVCBWCM4.json" assert { type: "json" };
console.log(json);
注意导入路径已被重写以指向复制的文件out/example-PVCBWCM4.json
(由于asset names 设置的默认值,添加了内容哈希),并且保留了JSON的import assertion, 这样运行时就能加载JSON文件。
#Empty file - 空文件
加载器:empty
此加载器告诉esbuild假装一个文件是空的。在某些情况下,这可以帮助从你的包中移除内容。 例如,你可以将.css
文件配置为使用empty
加载器,以防止esbuild打包导入到JavaScript 文件中的CSS文件:
esbuild app.js --bundle --loader:.css=empty
require('esbuild').buildSync({
entryPoints: ['app.js'],
bundle: true,
loader: { '.css': 'empty' },
})
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{
".css": api.LoaderEmpty,
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
此加载器还允许你从CSS文件中移除导入的资源。例如,你可以将.png
文件配置为使用empty
加载器,这样CSS代码中对.png
文件的引用(如url(image.png)
)会被替换为url()
。