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.
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:
- a static HTML file from
src/html/index.html
toconstruct/index.html
- static pictures from
src/pictures/
toconstruct/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
toconstruct/css/predominant.css
- the entry JavaScript file
scr/js/predominant.js
toconstruct/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 ofesm
, however you may 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 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 tolinked
(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 setinline
to incorporate the supply map contained in the bundled file,each
to create each, orexterior
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 exteriorpictures
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);
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.