esbuild’s primary goal is speed, and it achieves this by being incredibly opinionated and minimalist. It doesn’t have built-in features for injecting arbitrary code like banners or footers directly into its output files. You’re expected to handle that after esbuild has done its job.

Here’s how you can add that code, and why it works the way it does.

Let’s say you have a simple JavaScript file, src/index.js:

console.log('Hello from my app!');

And you want to build it with esbuild, then prepend a banner and append a footer.

First, the esbuild command. We’ll bundle src/index.js into dist/bundle.js:

esbuild src/index.js --bundle --outfile=dist/bundle.js --platform=node --format=cjs

This will produce dist/bundle.js containing just:

// src/index.js
console.log('Hello from my app!');

Now, for the banner and footer. The simplest way is to use standard shell commands.

For the banner, we’ll use echo and cat:

echo "// My awesome banner" | cat - dist/bundle.js > dist/bundle.tmp && mv dist/bundle.tmp dist/bundle.js

Let’s break this down:

  • echo "// My awesome banner": This prints your banner string to standard output.
  • cat - dist/bundle.js: cat concatenates files. The - tells cat to read from standard input (which is our echoed banner). So, it prints the banner first, then the contents of dist/bundle.js.
  • > dist/bundle.tmp: This redirects the combined output into a temporary file. We do this because you can’t reliably modify a file in place like this with cat directly.
  • && mv dist/bundle.tmp dist/bundle.js: If the cat command was successful (indicated by &&), we rename the temporary file, overwriting the original dist/bundle.js with the banner prepended.

For the footer, it’s very similar:

echo "// My footer code" >> dist/bundle.js

The >> operator appends to the file.

After running these two commands, dist/bundle.js will look like this:

// My awesome banner
// src/index.js
console.log('Hello from my app!');
// My footer code

This approach is robust because it leverages tools designed for text manipulation. esbuild produces a clean, functional JavaScript file, and then standard Unix utilities layer on the additional code. There are no complex configurations or plugins needed for this basic use case.

You can also automate this within a build script. If you’re using npm scripts, you could chain these commands:

{
  "scripts": {
    "build": "esbuild src/index.js --bundle --outfile=dist/bundle.js --platform=node --format=cjs && echo '// My awesome banner' | cat - dist/bundle.js > dist/bundle.tmp && mv dist/bundle.tmp dist/bundle.js && echo '// My footer code' >> dist/bundle.js"
  }
}

Running npm run build would perform all steps.

A more sophisticated approach for managing banners and footers, especially if they are dynamic or come from separate files, is to use a dedicated tool or a more advanced build system. For instance, you could write a small Node.js script that reads the esbuild output, prepends/appends content, and writes the final file.

Consider this Node.js script (postbuild.js):

const fs = require('fs');
const path = require('path');

const banner = '// My dynamic banner\nconsole.log("Timestamp:", new Date().toISOString());';
const footer = '\nconsole.log("Build complete!");';
const outputPath = path.join(__dirname, 'dist', 'bundle.js');

// 1. Build with esbuild (assuming this is run before this script)
//    esbuild src/index.js --bundle --outfile=dist/bundle.js --platform=node --format=cjs

// 2. Read the bundled file
let bundledCode = fs.readFileSync(outputPath, 'utf8');

// 3. Prepend the banner
bundledCode = `${banner}\n${bundledCode}`;

// 4. Append the footer
bundledCode = `${bundledCode}${footer}`;

// 5. Write the final file
fs.writeFileSync(outputPath, bundledCode, 'utf8');

console.log(`Banner and footer injected into ${outputPath}`);

You would then modify your npm script:

{
  "scripts": {
    "build": "esbuild src/index.js --bundle --outfile=dist/bundle.js --platform=node --format=cjs && node postbuild.js"
  }
}

This Node.js script offers more flexibility. You can read banner/footer content from other files, generate dynamic content (like timestamps or version numbers), and handle more complex logic than simple shell commands. The core principle remains: esbuild does its job, and then another process modifies its output.

The reason esbuild doesn’t have a built-in --banner or --footer flag like some other bundlers is tied to its design philosophy. It aims to be a fast, single-purpose tool. Adding features like arbitrary code injection, especially with templating or dynamic content, would increase its complexity and potentially slow it down. By leaving this as an external step, esbuild maintains its core performance and simplicity.

The most common pitfall is forgetting the newline characters (\n) when concatenating. If you don’t explicitly add newlines between your banner/footer and the bundled code, they might end up on the same line, leading to syntax errors. For example, // My banner// src/index.js is invalid. You need // My banner\n// src/index.js. The shell commands and the Node.js script shown above correctly handle these newlines.

The next thing you’ll likely want to figure out is how to handle source maps when you’re injecting code like this, as the source map might not accurately reflect the final file structure.

Want structured learning?

Take the full Esbuild course →