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.
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.
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:
- a static HTML file from
src/html/index.html
toconstruct/index.html
- static photographs from
src/photographs/
toconstruct/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
toconstruct/css/predominant.css
- the entry JavaScript file
scr/js/predominant.js
toconstruct/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 ofesm
, however you possibly can optionally setiife
for older browsers orcommonjs
for Node.js.bundle
set totrue
inlines imported modules into the output file.goal
is the array of goal browsers outlined above.drop
is an array ofconsole
and/ordebugger
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 tolinked
(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 setinline
to incorporate the supply map contained in the bundled file,each
to create each, orexterior
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 exteriorphotographs
listing - the
h1
is nested insideheader
- 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.