esbuild is a quick bundler that may optimize JavaScript, TypeScript, JSX, and CSS code. This text will provide help to rise up to hurry with esbuild and present you methods to create your individual construct system with out different dependencies.

Desk of Contents
  1. How Does esbuild Work?
  2. Why Bundle?
  3. Why Use esbuild?
  4. Why Avoid esbuild?
  5. Super-quick Start
  6. Example Project
  7. Project Overview
  8. Configuring esbuild
  9. JavaScript Bundling
  10. CSS Bundling
  11. Watching, Rebuilding, and Serving
  12. Summary

How Does esbuild Work?

Frameworks comparable to Vite have adopted esbuild, however you need to use esbuild as a standalone device in your individual initiatives.

  • esbuild bundles JavaScript code right into a single file in the same option to bundlers comparable to Rollup. That is esbuild’s main operate, and it resolves modules, stories syntax points, β€œtree-shakes” to take away unused capabilities, erases logging and debugger statements, minifies code, and supplies supply maps.

  • esbuild bundles CSS code right into a single file. It’s not a full substitute for pre-processors comparable to Sass or PostCSS, however esbuild can deal with partials, syntax points, nesting, inline asset encoding, supply maps, auto-prefixing, and minification. That could be all you want.

  • esbuild additionally supplies an area improvement server with automated bundling and hot-reloading, so there’s no must refresh. It doesn’t have all of the options supplied by Browsersync, but it surely’s adequate for many circumstances.

The code beneath will provide help to perceive esbuild ideas so you possibly can examine additional configuration alternatives in your initiatives.

Why Bundle?

Bundling code right into a single file provides numerous advantages. Listed below are a few of them:

  • you possibly can develop smaller, self-contained supply recordsdata that are simpler to keep up
  • you possibly can lint, prettify, and syntax-check code in the course of the bundling course of
  • the bundler can take away unused capabilities β€” generally known as tree-shaking
  • you possibly can bundle different variations of the identical code, and create targets for older browsers, Node.js, Deno, and so forth
  • single recordsdata load quicker than a number of recordsdata and the browser doesn’t require ES module assist
  • production-level bundling can enhance efficiency by minifying code and eradicating logging and debugging statements

Why Use esbuild?

In contrast to JavaScript bundlers, esbuild is a compiled Go executable which implements heavy parallel processing. It’s fast and as much as 100 instances quicker than Rollup, Parcel, or Webpack. It might save weeks of improvement time over the lifetime of a undertaking.

As well as, esbuild additionally provides:

  • built-in bundling and compilation for JavaScript, TypeScript, JSX, and CSS
  • command-line, JavaScript, and Go configuration APIs
  • assist for ES modules and CommonJS
  • an area improvement server with watch mode and dwell reloading
  • plugins so as to add additional performance
  • comprehensive documentation and an online experimentation tool

Why Keep away from esbuild?

On the time of writing, esbuild has reached model 0.18. It’s dependable however nonetheless a beta product.

esbuild is incessantly up to date and choices might change between variations. The documentation recommends you follow a selected model. You possibly can replace it, however you could must migrate your configuration recordsdata and delve into new documentation to find breaking modifications.

Word additionally that esbuild doesn’t carry out TypeScript sort checking, so that you’ll nonetheless must run tsc -noEmit.

Tremendous-quick Begin

If vital, create a brand new Node.js undertaking with npm init, then set up esbuild domestically as a improvement dependency:

npm set up esbuild --save-dev --save-exact

The set up requires round 9MB. Verify it really works by operating this command to see the put in model:

./node_modules/.bin/esbuild --version

Or run this command to view CLI assist:

./node_modules/.bin/esbuild --help

Use the CLI API to bundle an entry script (myapp.js) and all its imported modules right into a single file named bundle.js. esbuild will output a file utilizing the default, browser-targeted, immediately-invoked operate expression (IIFE) format:

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

You possibly can install esbuild in other ways for those who’re not utilizing Node.js.

Instance Mission

Obtain the instance recordsdata and an esbuild configuration from Github. It’s a Node.js undertaking, so set up the one esbuild dependency with:

npm set up

Construct the supply recordsdata in src to a construct listing and begin a improvement server with:

npm begin

Now navigate to localhost:8000 in your browser to view an internet web page displaying a real-time clock. While you replace any CSS file in src/css/ or src/css/partials, esbuild will re-bundle the code and dwell reload the kinds.

esbuild example clock project

Press Ctrl|Cmd + Ctrl|Cmd to cease the server.

Create a manufacturing construct for deployment utilizing:

npm run construct

Study the CSS and JavaScript recordsdata within the construct listing to see the minified variations with out supply maps.

Mission Overview

The true-time clock web page is constructed in a construct listing utilizing supply recordsdata from src.

The package deal.json file defines 5 npm scripts. The primary deletes the construct listing:

"clear": "rm -rf ./construct",

Earlier than any bundling happens, an init script runs clear, creates a brand new construct listing and copies:

  1. a static HTML file from src/html/index.html to construct/index.html
  2. static photographs from src/photographs/ to construct/photographs/
"init": "npm run clear && mkdir ./construct && cp ./src/html/* ./construct/ && cp -r ./src/photographs ./construct",

An esbuild.config.js file controls the esbuild bundling course of utilizing the JavaScript API. That is simpler to handle than passing choices to the CLI API, which might turn into unwieldy. An npm bundle script runs init adopted by node ./esbuild.config.js:

"bundle": "npm run init && node ./esbuild.config.js",

The final two npm scripts run bundle with both a manufacturing or improvement parameter handed to ./esbuild.config.js to regulate the construct:

"construct": "npm run bundle -- manufacturing",
"begin": "npm run bundle -- improvement"

When ./esbuild.config.js runs, it determines whether or not it ought to create minified manufacturing recordsdata (the default) or improvement recordsdata with automated updates, supply maps, and a live-reloading server. In each circumstances, esbuild bundles:

  • the entry CSS file src/css/predominant.css to construct/css/predominant.css
  • the entry JavaScript file scr/js/predominant.js to construct/js/predominant.js

Configuring esbuild

package deal.json has a "sort" of "module" so all .js recordsdata can use ES Modules. The esbuild.config.js script imports esbuild and units productionMode to true when bundling for manufacturing or false when bundling for improvement:

import { argv } from 'node:course of';
import * as esbuild from 'esbuild';

const
  productionMode = ('improvement' !== (argv[2] || course of.env.NODE_ENV)),
  goal = 'chrome100,firefox100,safari15'.cut up(',');

console.log(`${ productionMode ? 'manufacturing' : 'improvement' } construct`);

Bundle goal

Word that the target variable defines an array of browsers and model numbers to make use of within the configuration. This impacts the bundled output and modifications the syntax to assist particular platforms. For instance, esbuild can:

  • increase native CSS nesting into full selectors (nesting would stay if "Chrome115" was the one goal)
  • add CSS vendor-prefixed properties the place vital
  • polyfill the ?? nullish coalescing operator
  • take away # from non-public class fields

In addition to browsers, you may as well goal node and es variations comparable to es2020 and esnext (the most recent JS and CSS options).

JavaScript Bundling

The only API to create a bundle:

await esbuild.construct({
  entryPoints: ['myapp.js'],
  bundle: true
  outfile: 'bundle.js'
});

This replicates the CLI command used above:

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

The instance undertaking makes use of extra superior choices comparable to file watching. This requires a long-running construct context which units the configuration:


const buildJS = await esbuild.context({

  entryPoints: [ './src/js/main.js' ],
  format: 'esm',
  bundle: true,
  goal,
  drop: productionMode ? ['debugger', 'console'] : [],
  logLevel: productionMode ? 'error' : 'information',
  minify: productionMode,
  sourcemap: !productionMode && 'linked',
  outdir: './construct/js'

});

esbuild provides dozens of configuration options. Right here’s a rundown of those used right here:

  • entryPoints defines an array of file entry factors for bundling. The instance undertaking has one script at ./src/js/predominant.js.

  • format units the output format. The instance makes use of esm, however you possibly can optionally set iife for older browsers or commonjs for Node.js.

  • bundle set to true inlines imported modules into the output file.

  • goal is the array of goal browsers outlined above.

  • drop is an array of console and/or debugger statements to take away. On this case, manufacturing builds take away each and improvement builds retain them.

  • logLevel defines the logging verbosity. The instance above reveals errors throughout manufacturing builds and extra verbose info messages throughout improvement builds.

  • minify reduces the code measurement by eradicating feedback and whitespace and renaming variables and capabilities the place attainable. The instance undertaking minifies throughout manufacturing builds however prettifies code throughout improvement builds.

  • sourcemap set to linked (in improvement mode solely) generates a linked supply map in a .map file so the unique supply file and line is offered in browser developer instruments. It’s also possible to set inline to incorporate the supply map contained in the bundled file, each to create each, or exterior to generate a .map file with out a hyperlink from the bundled JavaScript.

  • outdir defines the bundled file output listing.

Name the context object’s rebuild() technique to run the construct as soon as β€” usually for a manufacturing construct:

await buildJS.rebuild();
buildJS.dispose(); 

Name the context object’s watch() technique to maintain operating and robotically re-build when watched recordsdata change:

await buildJS.watch();

The context object ensures subsequent builds are processed incrementally and that they reuse work from earlier builds to enhance efficiency.

JavaScript enter and output recordsdata

The entry src/js/predominant.js file imports dom.js and time.js modules from the lib sub-folder. It finds all parts with a category of clock and units their textual content content material to the present time each second:

import * as dom from './lib/dom.js';
import { formatHMS } from './lib/time.js';


const clock = dom.getAll('.clock');

if (clock.size) {

  console.log('initializing clock');

  setInterval(() => {

    clock.forEach(c => c.textContent = formatHMS());

  }, 1000);

}

dom.js exports two capabilities. predominant.js imports each however solely makes use of getAll():




export operate get(selector, doc = doc) {
  return doc.querySelector(selector);
}


export operate getAll(selector, doc = doc) {
  return Array.from(doc.querySelectorAll(selector));
}

time.js exports two capabilities. predominant.js imports formatHMS(), however that makes use of the opposite capabilities within the module:




operate timePad(n) {
  return String(n).padStart(2, '0');
}


export operate formatHM(d = new Date()) {
  return timePad(d.getHours()) + ':' + timePad(d.getMinutes());
}


export operate formatHMS(d = new Date()) {
  return formatHM(d) + ':' + timePad(d.getSeconds());
}

The ensuing improvement bundle removes (tree shakes) get() from dom.js however contains all of the time.js capabilities. A supply map can also be generated:


operate getAll(selector, doc = doc) {
  return Array.from(doc.querySelectorAll(selector));
}


operate timePad(n) {
  return String(n).padStart(2, "0");
}

operate formatHM(d = new Date()) {
  return timePad(d.getHours()) + ":" + timePad(d.getMinutes());
}

operate formatHMS(d = new Date()) {
  return formatHM(d) + ":" + timePad(d.getSeconds());
}


var clock = getAll(".clock");
if (clock.size) {
  console.log("initializing clock");
  setInterval(() => {
    clock.forEach((c) => c.textContent = formatHMS());
  }, 1e3);
}

(Word that esbuild can rewrite let and const to var for correctness and pace.)

The ensuing manufacturing bundle minifies the code to 322 characters:

operate o(t,c=doc){return Array.from(c.querySelectorAll(t))}operate e(t){return String(t).padStart(2,"0")}operate l(t=new Date){return e(t.getHours())+":"+e(t.getMinutes())}operate r(t=new Date){return l(t)+":"+e(t.getSeconds())}var n=o(".clock");n.size&&setInterval(()=>{n.forEach(t=>t.textContent=r())},1e3);

CSS Bundling

CSS bundling within the instance undertaking makes use of the same context object to JavaScript above:


const buildCSS = await esbuild.context({

  entryPoints: [ './src/css/main.css' ],
  bundle: true,
  goal,
  exterior: ['/images/*'],
  loader: {
    '.png': 'file',
    '.jpg': 'file',
    '.svg': 'dataurl'
  },
  logLevel: productionMode ? 'error' : 'information',
  minify: productionMode,
  sourcemap: !productionMode && 'linked',
  outdir: './construct/css'

});

It defines an exterior choice as an array of recordsdata and paths to exclude from the construct. Within the instance undertaking, recordsdata within the src/photographs/ listing are copied to the construct listing so the HTML, CSS, or JavaScript can reference them straight. If this was not set, esbuild would copy recordsdata to the output construct/css/ listing when utilizing them in background-image or related properties.

The loader choice modifications how esbuild handles an imported file that’s not referenced as an exterior asset. On this instance:

  • SVG photographs turn into inlined as information URIs
  • PNG and JPG photographs are copied to the construct/css/ listing and referenced as recordsdata

CSS enter and output recordsdata

The entry src/css/predominant.css file imports variables.css and parts.css from the partials sub-folder:


@import './partials/variables.css';
@import './partials/parts.css';

variables.css defines default customized properties:


:root {
  --font-body: sans-serif;
  --color-fore: #fff;
  --color-back: #112;
}

parts.css defines all kinds. Word:

  • the physique has a background picture loaded from the exterior photographs listing
  • the h1 is nested inside header
  • the h1 has a background SVG which can be inlined
  • the goal browsers require no vendor prefixes

*, *::earlier than, ::after {
  box-sizing: border-box;
  font-weight: regular;
  padding: 0;
  margin: 0;
}

physique {
  font-family: var(--font-body);
  coloration: var(--color-fore);
  background: var(--color-back) url(/photographs/net.png) repeat;
  margin: 1em;
}


header {

  & h1 {
    font-size: 2em;
    padding-left: 1.5em;
    margin: 0.5em 0;
    background: url(../../icons/clock.svg) no-repeat;
  }

}

.clock {
  show: block;
  font-size: 5em;
  text-align: middle;
  font-variant-numeric: tabular-nums;
}

The ensuing improvement bundle expands the nested syntax, inlines the SVG, and generates a supply map:


:root {
  --font-body: sans-serif;
  --color-fore: #fff;
  --color-back: #112;
}


*,
*::earlier than,
::after {
  box-sizing: border-box;
  font-weight: regular;
  padding: 0;
  margin: 0;
}
physique {
  font-family: var(--font-body);
  coloration: var(--color-fore);
  background: var(--color-back) url(/photographs/net.png) repeat;
  margin: 1em;
}
header h1 {
  font-size: 2em;
  padding-left: 1.5em;
  margin: 0.5em 0;
  background: url('information:picture/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><type>*{fill:none;stroke:%23fff;stroke-width:1.5;stroke-miterlimit:10}</type></defs><circle cx="12" cy="12" r="10.5"></circle><circle cx="12" cy="12" r="0.95"></circle><polyline factors="12 4.36 12 12 16.77 16.77"></polyline></svg>') no-repeat;
}
.clock {
  show: block;
  font-size: 5em;
  text-align: middle;
  font-variant-numeric: tabular-nums;
}



The ensuing manufacturing bundle minifies the code to 764 characters (the SVG is omitted right here):

:root{--font-body: sans-serif;--color-fore: #fff;--color-back: #112}*,*:earlier than,:after{box-sizing:border-box;font-weight:400;padding:0;margin:0}physique{font-family:var(--font-body);coloration:var(--color-fore);background:var(--color-back) url(/photographs/net.png) repeat;margin:1em}header h1{font-size:2em;padding-left:1.5em;margin:.5em 0;background:url('information:picture/svg+xml,<svg...></svg>') no-repeat}.clock{show:block;font-size:5em;text-align:middle;font-variant-numeric:tabular-nums}

Watching, Rebuilding, and Serving

The rest of the esbuild.config.js script bundles as soon as for manufacturing builds earlier than terminating:

if (productionMode) {

  
  await buildCSS.rebuild();
  buildCSS.dispose();

  await buildJS.rebuild();
  buildJS.dispose();

}

Throughout improvement builds, the script retains operating, watches for file modifications, and robotically bundles once more. The buildCSS context launches a improvement net server with construct/ as the foundation listing:

else {

  
  await buildCSS.watch();
  await buildJS.watch();

  
  await buildCSS.serve({
    servedir: './construct'
  });

}

Begin the event construct with:

npm begin

Then navigate to localhost:8000 to view the web page.

In contrast to Browsersync, you’ll want so as to add your individual code to improvement pages to dwell reload. When modifications happen, esbuild sends details about the replace through a server-sent event. The only choice is to totally reload the web page when any change happens:

new EventSource('/esbuild').addEventListener('change', () => location.reload());

The instance undertaking makes use of the CSS context object to create the server. That’s as a result of I favor to manually refresh JavaScript modifications β€” and since I couldn’t discover a manner for esbuild to ship an occasion for each CSS and JS updates! The HTML web page contains the next script to switch up to date CSS recordsdata with out a full web page refresh (a hot-reload):

<script sort="module">
// esbuild server-sent occasion - dwell reload CSS
new EventSource('/esbuild').addEventListener('change', e => {

  const { added, eliminated, up to date } = JSON.parse(e.information);

  // reload when CSS recordsdata are added or eliminated
  if (added.size || eliminated.size) {
    location.reload();
    return;
  }

  // change up to date CSS recordsdata
  Array.from(doc.getElementsByTagName('hyperlink')).forEach(hyperlink => {

    const url = new URL(hyperlink.href), path = url.pathname;

    if (up to date.contains(path) && url.host === location.host) {

      const css = hyperlink.cloneNode();
      css.onload = () => hyperlink.take away();
      css.href = `${ path }?${ +new Date() }`;
      hyperlink.after(css);

    }

  })

});

Word that esbuild doesn’t at the moment assist JavaScript hot-reloading β€” not that I’d belief it anyway!

Abstract

With a bit of configuration, esbuild may very well be sufficient to deal with all of your undertaking’s improvement and manufacturing construct necessities.

There’s a complete set of plugins must you require extra superior performance. Bear in mind these typically embrace Sass, PostCSS, or related construct instruments, in order that they successfully use esbuild as a process runner. You possibly can all the time create your own plugins for those who want extra light-weight, customized choices.

I’ve been utilizing esbuild for a yr. The pace is astounding in comparison with related bundlers, and new options seem incessantly. The one minor draw back is breaking modifications that incur upkeep.

esbuild doesn’t declare to be a unified, all-in-one construct device, but it surely’s in all probability nearer to that objective than Rome.