esbuild is so fast it can often bundle your entire project faster than you can type npm install.
Let’s get esbuild working with a simple "Hello, World!" project.
First, create a new directory for your project and cd into it.
mkdir my-esbuild-app
cd my-esbuild-app
Now, create two files: index.js and message.js.
In index.js, we’ll have our main application logic:
import { getMessage } from './message.js';
console.log(getMessage());
And in message.js, we’ll define the message:
export function getMessage() {
return "Hello from esbuild!";
}
Next, we need to install esbuild. You can install it globally or as a dev dependency. For this example, let’s install it locally:
npm init -y
npm install --save-dev esbuild
Now, we can use esbuild to bundle our project. Open your package.json file and add a script for building:
{
"name": "my-esbuild-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "esbuild index.js --bundle --outfile=bundle.js --platform=node"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"esbuild": "^0.19.5"
}
}
The build script does the following:
esbuild index.js: This tellsesbuildto start processing fromindex.js.--bundle: This flag tellsesbuildto combine all imported modules into a single output file.--outfile=bundle.js: This specifies the name of the output file.--platform=node: This tellsesbuildto target a Node.js environment, meaning it will use Node.js-compatible APIs and not browser-specific ones.
Run the build script:
npm run build
After running this, you’ll find a new file named bundle.js in your project directory. This file contains your entire application, including the code from message.js, all bundled together.
You can now run this bundled file using Node.js:
node bundle.js
You should see the output: Hello from esbuild!
The magic of esbuild lies in its speed, achieved through a Go backend and a highly optimized JavaScript parser. Unlike many bundlers that rely heavily on Node.js for their core logic, esbuild delegates the heavy lifting to a compiled binary. This means it can parse, transform, and generate code at speeds that are orders of magnitude faster. It accomplishes this by performing a single pass over your code where possible, minimizing I/O operations and leveraging concurrent processing extensively.
When esbuild processes your import statements, it doesn’t just follow them linearly. It constructs an abstract syntax tree (AST) for each file and then analyzes the dependency graph. During the bundling phase, it effectively flattens this graph, resolving imports and exports across files. For index.js importing getMessage from ./message.js, esbuild reads message.js, finds the export function getMessage, and then inlines that function definition into bundle.js at the point where index.js expects it, ensuring that getMessage is available in the scope of the bundled code.
A key aspect that often trips people up when moving from development to production with esbuild is understanding how it handles code splitting and tree shaking. By default, with the --bundle flag alone, esbuild aims to produce a single output file. However, for more complex applications or when targeting web browsers, you’ll often want to split your code into smaller chunks that can be loaded on demand. This is where options like --splitting come into play. Without --splitting, esbuild will perform aggressive tree shaking on the entire bundled output to remove unused code, but it won’t create separate output files for different parts of your application.
The next step in optimizing your esbuild workflow is to explore code splitting and advanced transformations for browser targets.