The Go API for esbuild is surprisingly robust, allowing you to bypass command-line flags entirely and build your JavaScript, CSS, and other assets directly from your Go applications.

Let’s see it in action. Imagine you have a simple index.js file:

import './style.css';

console.log('Hello from JavaScript!');

function greet(name) {
  return `Hello, ${name}!`;
}

document.getElementById('app').innerText = greet('World');

And a style.css file:

body {
  font-family: sans-serif;
  background-color: #f0f0f0;
}

#app {
  color: blue;
  font-size: 20px;
}

Here’s how you’d bundle them using esbuild’s Go API:

package main

import (
	"fmt"
	"os"

	"github.com/evanw/esbuild/pkg/api"
)

func main() {
	// Define the build options
	options := api.BuildOptions{
		EntryPoints: []string{"index.js"},
		Bundle:      true,
		Outfile:     "dist/bundle.js",
		Minify:      true,
		Sourcemap:   api.SourceMapLinked, // Generate linked sourcemaps
		Define: map[string]string{
			"process.env.NODE_ENV": `"production"`, // Example of defining a global
		},
		Loader: map[string]api.Loader{
			".css": api.LoaderCSS, // Tell esbuild how to handle CSS
		},
		LogLevel: api.LogLevelInfo,
	}

	// Perform the build
	result := api.Build(options)

	// Check for errors
	if len(result.Errors) > 0 {
		fmt.Fprintf(os.Stderr, "Build failed:\n")
		for _, err := range result.Errors {
			fmt.Fprintf(os.Stderr, "%s\n", err.Text)
		}
		os.Exit(1)
	}

	fmt.Println("Build successful! Output written to dist/bundle.js")
}

To run this, you’ll need to have Go installed and esbuild’s Go API imported. Save the Go code as main.go, create the index.js and style.css files in the same directory, and then run go run main.go. This will create a dist directory with bundle.js inside.

The mental model for esbuild’s Go API revolves around the api.BuildOptions struct. This struct is your central control panel. You specify your entry points (the starting files for your build), whether to bundle everything into a single output file, the output file path, and crucially, how to handle different file types through the Loader map. Minify and Sourcemap control the output’s size and debuggability, respectively. The Define map allows you to inject global constants into your code at build time, which is incredibly useful for environment-specific configurations like NODE_ENV.

esbuild’s speed comes from its implementation in Go and its efficient parsing and bundling algorithms. It doesn’t rely on a JavaScript runtime to perform the build itself, making it orders of magnitude faster than many JavaScript-based bundlers. This allows for incredibly fast development server rebuilds and CI/CD pipeline execution. The Loader map is key here; by default, esbuild knows how to handle .js, .jsx, .ts, and .tsx. For other asset types like .css, .svg, or .wasm, you explicitly tell esbuild which loader to use, enabling it to transform or embed them correctly into your final bundle.

One of the most powerful, yet often overlooked, aspects of the esbuild API is its ability to perform transformations without bundling. You can use api.Transform to process a single file or a string of code, applying minification, sourcemap generation, or even just transpilation without creating an entire dependency graph. This is perfect for scenarios where you need to process individual assets on the fly, like generating optimized versions of specific components or applying code transformations as part of a larger build pipeline that esbuild isn’t orchestrating end-to-end. You’d pass a api.TransformOptions struct, similar to api.BuildOptions but focused on a single input.

Understanding how to precisely control the Loader map and leveraging api.Transform for granular asset processing will unlock the full potential of esbuild for complex build systems.

Want structured learning?

Take the full Esbuild course →