Content Types

All of the built-in content types are listed below. Each content type has an associated "loader" which tells esbuild how to interpret the file contents. Some file extensions already have a loader configured for them by default, although the defaults can be overridden.

# JavaScript

Loader: js

This loader is enabled by default for .js, .cjs, and .mjs files. The .cjs extension is used by node for CommonJS modules and the .mjs extension is used by node for ECMAScript modules, although esbuild doesn't make a distinction between the two.

All modern JavaScript syntax is supported by esbuild. Newer syntax may not be supported by older browsers, however, so you may want to configure the target option to tell esbuild to convert newer syntax to older syntax as appropriate.

These syntax features are always transformed for older browsers:

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 () => {}
Spread properties es2018 let x = {...y}
Rest properties es2018 let {...x} = y
Optional catch binding es2019 try {} catch {}
Optional chaining es2020 a?.b
Nullish coalescing es2020 a ?? b
import.meta es2020 import.meta
Class instance fields esnext class { x }
Static class fields esnext class { static x }
Private instance methods esnext class { #x() {} }
Private instance fields esnext class { #x }
Private static methods esnext class { static #x() {} }
Private static fields esnext class { static #x }
Logical assignment operators esnext a ??= b

These syntax features are currently always passed through un-transformed:

Syntax transform Unsupported when --target is below Example
Asynchronous iteration es2018 for await (let x of y) {}
Async generators es2018 async function* foo() {}
BigInt es2020 123n
Hashbang grammar esnext #!/usr/bin/env node
Top-level await esnext await import(x)

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 not yet supported.

Some additional details:

# TypeScript

Loader: ts or tsx

This loader is enabled by default for .ts and .tsx files, which means esbuild has built-in support for parsing TypeScript syntax and discarding the type annotations. However, esbuild does not do any type checking so you will still need to run tsc -noEmit in parallel with esbuild to check types. This is not something esbuild does itself.

TypeScript type declarations like these are parsed and ignored (a non-exhaustive list):

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'

TypeScript-only syntax extensions are supported, and are always converted to JavaScript (a non-exhaustive list):

Syntax feature Example Notes
Namespaces namespace Foo {}
Enums enum Foo { A, B }
Const enums const enum Foo { A, B } Behaves the same as regular enums
Generic type parameters <T>(a: T): T => a Not available 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 {} The emitDecoratorMetadata flag is not supported

Some additional caveats:

# JSX

Loader: jsx or tsx

JSX is an XML-like syntax extension for JavaScript that was created for React. It's intended to be converted into normal JavaScript by your build tool. Each XML element becomes a normal JavaScript function call. For example, the following JSX code:

import Button from './button'
let button = <Button>Click me</Button>
render(button)

Will be converted to the following JavaScript code:

import Button from "./button";
let button = React.createElement(Button, null, "Click me");
render(button);

This loader is enabled by default for .jsx and .tsx files. Note that JSX syntax is not enabled in .js files by default. If you would like to enable that, you will need to configure it:

CLI JS Go
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

Using JSX syntax usually requires you to manually import the JSX library you are using. For example, if you are using React, by default you will need to import React into each JSX file like this:

import * as React from 'react'
render(<div/>)

This is because the JSX transform turns JSX syntax into a call to React.createElement but it does not itself import anything, so the React variable is not automatically present.

If you would like to avoid having to manually import your JSX library into each file, you can use esbuild's inject feature to automatically import it into every file. This feature is a general-purpose polyfill mechanism that replaces references to global variables with an export from the injected file. In this case we can use it to replace references to the React variable with exports from the react package.

First, create a file called react-shim.js that re-exports everything from the react package in an export called React:

// react-shim.js
export * as React from 'react'

Then use esbuild's inject feature to inject this into each file:

CLI JS Go
esbuild app.jsx --bundle --inject:./react-shim.js --outfile=out.js
require('esbuild').buildSync({
  entryPoints: ['app.jsx'],
  bundle: true,
  inject: ['./react-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{"app.jsx"},
    Bundle:      true,
    Inject:      []string{"./react-shim.js"},
    Outfile:     "out.js",
    Write:       true,
  })

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

Since the inject feature is a general-purpose code injection mechanism, it can be used with any JSX transform library, not just with React.

# Using JSX without React

If you're using JSX with a library other than React (such as Preact), you'll likely need to configure the JSX factory and JSX fragment settings since they default to React.createElement and React.Fragment respectively:

CLI JS Go
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)
  }
}

Alternatively, if you are using TypeScript, you can just configure JSX for TypeScript by adding this to your tsconfig.json file and esbuild should pick it up automatically without needing to be configured:

{
  "compilerOptions": {
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

You will also have to add import {h, Fragment} from 'preact' in files containing JSX syntax unless you use auto-importing as described above.

# JSON

Loader: json

This loader is enabled by default for .json files. It parses the JSON file into a JavaScript object at build time and exports the object as the default export. Using it looks something like this:

import object from './example.json'
console.log(object)

In addition to the default export, there are also named exports for each top-level property in the JSON object. Importing a named export directly means esbuild can automatically remove unused parts of the JSON file from the bundle, leaving only the named exports that you actually used. For example, this code will only include the version field when bundled:

import { version } from './package.json'
console.log(version)

# CSS

Loader: css

The CSS content type is new and is still a work in progress. There is also a known ordering issue with importing CSS files from JavaScript files. You can follow the tracking issue for updates about this feature.

This loader is enabled by default for .css files. It loads the file as CSS syntax. CSS is a first-class content type in esbuild, which means esbuild can bundle CSS files directly without needing to import your CSS from JavaScript code:

CLI JS Go
esbuild --bundle app.css --outfile=out.css
require('esbuild').buildSync({
  entryPoints: ['app.css'],
  bundle: true,
  outfile: 'out.css',
})
package main

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

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

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

You can @import other CSS files and reference image and font files with url() and esbuild will bundle everything together. Note that you will have to configure a loader for image and font files, since esbuild doesn't have any pre-configured. Usually this is either the data URL loader or the external file loader.

You can also import CSS from JavaScript. When you do this, esbuild will gather all CSS files referenced from a given entry point and bundle it into a sibling CSS output file next to the JavaScript output file for that JavaScript entry point. So if esbuild generates app.js it would also generate app.css containing all CSS files referenced by app.js. Here's an example of importing a CSS file from JavaScript:

import './button.css'

export let Button = ({ text }) =>
  <div className="button">{text}</div>

Note that esbuild doesn't yet support CSS modules, so the set of export names from a CSS file is currently always empty. Supporting a basic form of CSS modules is on the roadmap.

# Text

Loader: text

This loader is enabled by default for .txt files. It loads the file as a string at build time and exports the string as the default export. Using it looks something like this:

import string from './example.txt'
console.log(string)

# Binary

Loader: binary

This loader will load the file as a binary buffer at build time and embed it into the bundle using Base64 encoding. The original bytes of the file are decoded from Base64 at run time and exported as a Uint8Array using the default export. Using it looks like this:

import uint8array from './example.data'
console.log(uint8array)

If you need an ArrayBuffer instead, you can just access uint8array.buffer. Note that this loader is not enabled by default. You will need to configure it for the appropriate file extension like this:

CLI JS Go
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

Loader: base64

This loader will load the file as a binary buffer at build time and embed it into the bundle as a string using Base64 encoding. This string is exported using the default export. Using it looks like this:

import base64string from './example.data'
console.log(base64string)

Note that this loader is not enabled by default. You will need to configure it for the appropriate file extension like this:

CLI JS Go
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)
  }
}

If you intend to turn this into a Uint8Array or an ArrayBuffer, you should use the binary loader instead. It uses an optimized Base64-to-binary converter that is faster than the usual atob conversion process.

# Data URL

Loader: dataurl

This loader will load the file as a binary buffer at build time and embed it into the bundle as a Base64-encoded data URL. This string is exported using the default export. Using it looks like this:

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

The data URL includes a best guess at the MIME type based on the file extension and/or the file contents, and will look something like this:

data:image/png;base64,iVBORw0KGgo=

Note that this loader is not enabled by default. You will need to configure it for the appropriate file extension like this:

CLI JS Go
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

Loader: file

This loader will copy the file to the output directory and embed the file name into the bundle as a string. This string is exported using the default export. Using it looks like this:

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

Note that this loader is not enabled by default. You will need to configure it for the appropriate file extension like this:

CLI JS Go
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)
  }
}

By default the exported string is just the file name. If you would like to prepend a base path to the exported string, this can be done with the public path API option.