You can build JavaScript projects with esbuild faster than you ever thought possible, but its speed isn’t just about a lightning-fast CLI. The real power unlock is its programmatic JavaScript API, which lets you embed esbuild’s build process directly into your own Node.js scripts and applications.

Let’s see esbuild in action, building a simple React app. We’ll start with a basic index.html and index.js file.

index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Esbuild React Example</title>
</head>
<body>
    <div id="root"></div>
    <script src="./dist/bundle.js"></script>
</body>
</html>

index.js:

import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
    return <h1>Hello from Esbuild and React!</h1>;
};

ReactDOM.render(<App />, document.getElementById('root'));

Now, we’ll write a Node.js script, build.js, to bundle this using esbuild’s API:

build.js:

const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['index.js'],
  bundle: true,
  outfile: 'dist/bundle.js',
  platform: 'browser',
  format: 'iife',
  sourcemap: true,
  define: {
    'process.env.NODE_ENV': JSON.stringify('production'),
  },
  loader: {
    '.js': 'jsx', // Tell esbuild to treat .js files as JSX
  },
}).then(() => console.log('Build successful!'))
  .catch(() => process.exit(1));

To run this, first install the necessary dependencies:

npm install esbuild react react-dom

Then execute the build script:

node build.js

After running, you’ll find a dist/bundle.js file. If you open index.html in your browser, you’ll see "Hello from Esbuild and React!".

The core of esbuild’s programmatic API is the esbuild.build() function, which accepts a configuration object. The entryPoints option is where you specify your application’s entry file(s). bundle: true tells esbuild to combine all your dependencies into a single output file. outfile dictates the destination path for the bundled code.

platform: 'browser' is crucial for front-end builds, ensuring esbuild generates code compatible with web browsers. format: 'iife' (Immediately Invoked Function Expression) is a common format for browser bundles, creating a self-executing function to avoid polluting the global scope. sourcemap: true generates a source map, invaluable for debugging your bundled code in the browser’s developer tools.

The define option allows you to substitute environment variables or other global constants at build time. Here, we’re setting process.env.NODE_ENV to 'production', a common practice for optimizing React builds. The loader option is powerful for handling files with non-JavaScript extensions. By setting '.js': 'jsx', we instruct esbuild to process our .js files as JSX, enabling React component syntax.

Beyond these basics, esbuild’s API offers fine-grained control. You can specify target to control the ECMAScript version your code compiles to (e.g., target: 'es2017'). The minify: true option enables aggressive code minification. For more complex scenarios, you can use plugins to hook into esbuild’s build process, transforming code or adding custom logic. You can also define multiple entry points, creating separate bundles for different parts of your application, like code splitting for different routes.

When you define platform: 'node', esbuild will generate CommonJS or ESM output suitable for Node.js environments, respecting format options like 'cjs' or 'esm'. This is how you’d bundle a server-side application or a CLI tool. The external option lets you specify modules that should not be bundled, assuming they’ll be available in the target environment (e.g., external: ['react'] for a Node.js project where react is installed via npm).

The esbuild.build() function returns a Promise that resolves with a BuildResult object. This object contains information about the build, such as errors and warnings. You can also use esbuild.watch() for a development build that recompiles on file changes, or esbuild.transform() to process a single file.

The API is designed for speed and efficiency, allowing you to integrate high-performance bundling into CI/CD pipelines, custom build tools, or even dynamic server-side rendering setups. The ability to programmatically control the build process, from defining entry points and loaders to managing environment variables and plugins, makes esbuild a versatile tool for any JavaScript project.

Many developers miss that esbuild.build is asynchronous and returns a Promise. This means you can’t just require the output of esbuild.build directly in the same Node.js script; you need to await its completion or use .then() before attempting to use the generated files, otherwise, you’ll be trying to access a file that doesn’t exist yet.

The next step is to explore how to integrate esbuild with other tools, such as using its watch mode for a seamless development experience.

Want structured learning?

Take the full Esbuild course →