Saturday, June 15, 2024

An Introduction to the esbuild Bundler — SitePoint

Must read


esbuild is a quick bundler that may optimize JavaScript, TypeScript, JSX, and CSS code. This text will make it easier to stand up to hurry with esbuild and present you the right way 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 Keep away from esbuild?
  5. Tremendous-quick Begin
  6. Instance Mission
  7. Mission Overview
  8. Configuring esbuild
  9. JavaScript Bundling
  10. CSS Bundling
  11. Watching, Rebuilding, and Serving
  12. Abstract

How Does esbuild Work?

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

  • esbuild bundles JavaScript code right into a single file in the same approach to bundlers comparable to Rollup. That is esbuild’s major operate, and it resolves modules, experiences syntax points, “tree-shakes” to take away unused features, 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 growth server with computerized bundling and hot-reloading, so there’s no must refresh. It doesn’t have all of the options provided by Browsersync, but it surely’s ok for many circumstances.

The code under will make it easier to perceive esbuild ideas so you may examine additional configuration alternatives on your initiatives.

Why Bundle?

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

  • you may develop smaller, self-contained supply recordsdata that are simpler to take care of
  • you may lint, prettify, and syntax-check code throughout the bundling course of
  • the bundler can take away unused features — generally known as tree-shaking
  • you may 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 help
  • 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 occasions quicker than Rollup, Parcel, or Webpack. It might save weeks of growth time over the lifetime of a mission.

As well as, esbuild additionally gives:

  • built-in bundling and compilation for JavaScript, TypeScript, JSX, and CSS
  • command-line, JavaScript, and Go configuration APIs
  • help for ES modules and CommonJS
  • an area growth server with watch mode and stay reloading
  • plugins so as to add additional performance
  • complete documentation and a web-based experimentation device

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 continuously up to date and choices might change between variations. The documentation recommends you stick to a selected model. You’ll be able to replace it, however you might must migrate your configuration recordsdata and delve into new documentation to find breaking modifications.

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

Tremendous-quick Begin

If essential, create a brand new Node.js mission with npm init, then set up esbuild domestically as a growth 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’ll be able to set up esbuild in different methods when you’re not utilizing Node.js.

Instance Mission

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

npm set up

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

npm begin

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

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 bundle.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 pictures from src/pictures/ to construct/pictures/
"init": "npm run clear && mkdir ./construct && cp ./src/html/* ./construct/ && cp -r ./src/pictures ./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 may develop 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 growth parameter handed to ./esbuild.config.js to manage the construct:

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

When ./esbuild.config.js runs, it determines whether or not it ought to create minified manufacturing recordsdata (the default) or growth recordsdata with computerized 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

bundle.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 growth:

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

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

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

Bundle goal

Observe that the goal 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 help particular platforms. For instance, esbuild can:

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

In addition to browsers, it’s also possible to goal node and es variations comparable to es2020 and esnext (the newest 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 mission 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 gives dozens of configuration choices. Right here’s a rundown of those used right here:

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

  • format units the output format. The instance makes use of esm, however you may 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 growth builds retain them.

  • logLevel defines the logging verbosity. The instance above exhibits errors throughout manufacturing builds and extra verbose data messages throughout growth builds.

  • minify reduces the code dimension by eradicating feedback and whitespace and renaming variables and features the place doable. The instance mission minifies throughout manufacturing builds however prettifies code throughout growth builds.

  • sourcemap set to linked (in growth mode solely) generates a linked supply map in a .map file so the unique supply file and line is out there in browser developer instruments. You can even set inline to incorporate the supply map contained in the bundled file, each to create each, or exterior to generate a .map file with no 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 — sometimes 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 features. 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 features. predominant.js imports formatHMS(), however that makes use of the opposite features 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 growth bundle removes (tree shakes) get() from dom.js however consists of all of the time.js features. 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);
}

(Observe 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 mission 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 possibility as an array of recordsdata and paths to exclude from the construct. Within the instance mission, recordsdata within the src/pictures/ listing are copied to the construct listing so the HTML, CSS, or JavaScript can reference them instantly. 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 possibility modifications how esbuild handles an imported file that’s not referenced as an exterior asset. On this instance:

  • SVG pictures develop into inlined as information URIs
  • PNG and JPG pictures 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. Observe:

  • the physique has a background picture loaded from the exterior pictures 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);
  colour: var(--color-fore);
  background: var(--color-back) url(/pictures/internet.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 growth 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);
  colour: var(--color-fore);
  background: var(--color-back) url(/pictures/internet.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><fashion>*{fill:none;stroke:%23fff;stroke-width:1.5;stroke-miterlimit:10}</fashion></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);colour:var(--color-fore);background:var(--color-back) url(/pictures/internet.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 growth builds, the script retains operating, watches for file modifications, and robotically bundles once more. The buildCSS context launches a growth internet 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 growth pages to stay reload. When modifications happen, esbuild sends details about the replace by way of a server-sent occasion. The only possibility is to totally reload the web page when any change happens:

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

The instance mission makes use of the CSS context object to create the server. That’s as a result of I want 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 consists of the next script to interchange up to date CSS recordsdata with no full web page refresh (a hot-reload):

<script sort="module">
// esbuild server-sent occasion - stay 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;
  }

  // substitute 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.consists of(path) && url.host === location.host) {

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

    }

  })

});

Observe that esbuild doesn’t at the moment help JavaScript hot-reloading — not that I’d belief it anyway!

Abstract

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

There’s a complete set of plugins do you have to require extra superior performance. Remember these typically embrace Sass, PostCSS, or related construct instruments, in order that they successfully use esbuild as a process runner. You’ll be able to at all times create your individual plugins when you 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 continuously. 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.





Supply hyperlink

More articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest article