Bundling Is One Step of the Build
esbuild is an open-source bundler for JavaScript and TypeScript, created by Evan Wallace and written in Go. A bundler takes the files and npm packages your code imports and combines them into a few output files that a browser or a server can load. esbuild bundles ES modules, CommonJS modules and CSS, turns TypeScript and JSX into plain JavaScript, removes unused code, minifies, and writes source maps. It runs from the command line, from JavaScript or from Go, and its build API adds a watch mode, a local development server and a plugin system. esbuild is free under the MIT license, and its current release is version 0.28.
Elements does not ship a standalone bundler, and it is not a single-purpose build tool. Bundling, for Elements, is one job inside an integrated app environment that people and their agents build web apps in. At its center is a project server that runs while you work: it holds the build graph and build state in memory, so on every save the build state is updated almost instantly, and it builds in milliseconds and answers agents and humans in microseconds. Installing packages, type checking server code, browser code and templates, compiling code for the server and the browser, running tests and applying migrations are all steps of that one build. Next to the build sit the parts a bundler never touches: a default app framework, Postgres, and a command that deploys over SSH.
esbuild is a widely used bundler, and it set out to do one job: turn source files into output files. Its FAQ says esbuild should not "become an all-in-one solution for all frontend needs," and it keeps type checking ("just run tsc separately") and hot module reloading out of its core. Elements set out to be the environment a web app is built, checked and deployed in, and the job a bundler does is one step of that.
A project built around esbuild still needs a package manager, a type checker running beside it, a test runner, a server framework, a database, migrations and a deploy pipeline, each chosen and run on its own. Elements ships all of them as one coherent system, with installing, type checking, testing and migrating as steps of one build, so you and your agent learn about a type error, a failing test or a broken migration within moments of saving, from one command.
At a Glance
| Elements | esbuild | |
|---|---|---|
| What it is | An integrated app environment: a project server, a default app framework, a database and a deploy command | A bundler and minifier |
| What it builds | The app: server code, browser code, templates, styles, migrations and tests | JavaScript, TypeScript, JSX and CSS files into bundles |
| Type checking | A step of the build, for server code, browser code and templates | Not performed; the docs say to run tsc in parallel |
| While you work | A project server that holds the build state and answers in microseconds | A watch mode or a local server that bundles again when files change |
| Hot reload | The running server and the browser, patched in place | Not implemented for JavaScript; the page reloads |
| Production output | One file per module, content-hashed and cached long term | Bundled files; code splitting is marked a work in progress |
| Package install | A step of the build | npm or another package manager |
| Tests | Run by the build, in Postgres transactions that roll back | Not part of esbuild |
| Server framework | Built in: routes, @rpc server functions, sessions |
Not part of esbuild |
| Database and migrations | Postgres, bundled, with migrations applied by the build | Not part of esbuild |
| Deploy | elements deploy to any Ubuntu server over SSH; each deploy runs the tests and applies migrations |
Not part of esbuild |
| One answer for agents | elements build -json: type errors, test failures and migration errors together |
esbuild's own errors, from its own run |
A Bundler Is One Step. Elements Is the Loop.
In a typical JavaScript project, the bundler is one tool among several. esbuild reads your entry files, follows their imports, and writes output files. Its watch mode "tells esbuild to watch the file system and automatically rebuild for you whenever you edit and save a file," and its serve mode serves the latest build to the browser. In esbuild, that covers the bundle, and the rest of the loop runs beside it:
terminalesbuildesbuild app.ts --bundle --outdir=dist --watch # the bundle tsc --noEmit --watch # type checking, per esbuild's docs # plus a package manager, a test runner, the app server, the database and a migration tool
In Elements, one command starts the project, and one more reads its build state:
terminalElementselements start # builds and runs the app elements build -json # the current build state, for you or your agent
In Elements, a build means everything it takes to get an app ready to deliver: installing packages, compiling, migrating, testing and releasing. The project server does it as you save files, and you do not run each step yourself. Every source in the project has a version derived from its content and everything it depends on, so a change redoes the work that depends on it and nothing else.
Because esbuild does one job well, it often runs inside other tools. The Angular CLI's default build system for new apps uses esbuild, and Vite used it for dependency pre-bundling and TypeScript transforms until Vite 8 moved that work to Rolldown in March 2026. A bundler is a component. Elements is the environment the components live in.
Every Build Error Comes From One Build
TypeScript is two jobs: transpiling, which strips the types so the code can run, and type checking, which finds the mistakes. esbuild does the first. Its docs are direct about the second: "esbuild does not do any type checking so you will still need to run tsc -noEmit in parallel with esbuild to check types."
Elements does both jobs in one place, with the TypeScript checker compiled into its binary. It runs in the same process as the build, against the same source graph in memory, and a save rechecks the files that depend on what changed. Both targets are checked: server code against Node's types, and browser code against the DOM.
Templates go through the same checker. The expressions in an Elements HTML template are checked against the template's typed inputs, with the same error messages as a .ts file:
app/shared/templates/comment.ehtmlElements<Comment (comment: Comment)> <p>{comment.txet}</p> <!-- error: Property 'txet' does not exist on type 'Comment' --> </Comment>
The checker also knows which side of the wire each file runs on. If code that ends up in a browser bundle calls sql() or session.login(), the checker flags that line, which keeps queries and secrets out of the browser bundle. A bundler makes no such check. A type error is a build error, and a build with a type error does not release.
The Running App Is Patched in Place
When you save, you want to see the change without losing what is on screen. esbuild can reload the page. Its docs say "hot-reloading for JavaScript is not currently implemented by esbuild". The server side of your app is outside esbuild altogether: something else has to restart it.
Elements hot reloads both sides. Your app runs as one long-lived process that is patched in place when you save server code, and never restarted. The browser is patched the same way, at the smallest level the change requires. Test workers hot reload too, so the tests affected by a change run against it within moments, each inside a Postgres transaction that rolls back when it ends (tests).
Output Built for the Browser Cache
When you ship a change, returning visitors download the files it touched. A bundler combines modules into bundles, so a change to one module gives its bundle new contents, and the browser fetches that bundle again. esbuild can split a bundle into shared chunks, and its docs say "code splitting is still a work in progress" that works with ES module output.
Elements emits each browser module as its own file with a content hash in its name, linked by a small loader in each page, and tells the browser to cache those URLs long term. A change replaces the files of the modules it touched, and returning visitors and the CDN keep everything else. Unused exports are removed file by file. Static assets such as images and fonts get hashed URLs the same way.
Every import is resolved at build time and rewritten to the exact file it resolved to, so the file your app loads at runtime is the file the build checked. A module that cannot be found fails the build, not the running app.
One Answer for You and Your Agent
esbuild reports on its own run: syntax errors and unresolved imports in the files it bundled. Type errors come from tsc, test failures from the test runner, and migration errors from the migration tool, each in its own terminal and its own format.
Elements gathers those four sources into one: elements build -json returns type and compile errors from code and templates, migration errors and test results as a single JSON document (build). The answer reflects what is on disk: an agent that saves a file and asks in the same command gets the result of that save, not the state before it. Your terminal, your editor and every agent working in parallel talk to the same project server and see the same build state.
The Framework, the Database and the Deploy Ship Too
A bundler hands you output files. A web app also needs a server, a database, migrations, sign-in and a way to go live. Elements ships them in the same system:
- A default app framework,
@elements/app, with routes, typed server functions the browser calls directly (@rpc), sessions, database rows that stay in sync in every open browser (LiveTable), background jobs and email. Pages are Elements HTML, markup with reactive expressions in braces, rather than JSX. Elements is not React compatible; its own template language is what lets one checker type check a template and compile it for the server and the browser. - Postgres, bundled and set up for you, queried with the
sql()function, with migrations applied as part of the build. - Deploys over SSH to any Ubuntu server.
elements deployprovisions the machine, brings up Postgres and a load balancer with TLS, runs the tests on every machine, and applies pending migrations once, on the first machine. An error in any step stops the deploy, and the previous release keeps serving. There is nodistfolder to upload: your local project server syncs the changed files to its counterpart on the machine, which takes a few seconds the first time and often under a second after. Postgres on the app server fits a prototype or one machine, andDB_HOSTpoints the app at a hosted Postgres such as Amazon RDS once you want several servers or managed backups and scaling (setup).
npm packages install as is, into a real node_modules tree, as a step of the same build (packages).
Questions
What is the difference between esbuild and Elements?
esbuild is an open-source bundler: it turns JavaScript, TypeScript, JSX and CSS files into bundles a browser or server can load. Elements is an integrated app environment. Its project server installs packages, type checks, compiles code for the server and the browser, runs tests and applies migrations as one build, and the same system ships a default app framework, a bundled Postgres database and a deploy command.
Does esbuild type check TypeScript?
No. esbuild's docs say it does not do any type checking and that you still need to run tsc -noEmit in parallel to check types. In Elements, type checking is a step of the build, for server code, browser code and templates.
Is Elements a bundler?
Elements does not ship a standalone bundler. Compiling code for the server and the browser is one step of an Elements build, alongside installing packages, type checking, running tests and applying migrations, all run by the project server as you save.
Do I need esbuild in an Elements project?
No. The Elements build compiles TypeScript, templates and CSS for the server and the browser, and emits content-hashed files the browser caches long term. There is no bundler to add or configure.
Does esbuild support hot module reloading?
Not for JavaScript. esbuild's docs say hot reloading for JavaScript is not implemented, and its serve mode reloads the page instead. Elements patches the running server process and the browser in place when you save, without a restart.
Where can I deploy an Elements app?
On a server you rent from any provider, running Ubuntu and reachable by SSH. elements deploy provisions the server, runs Postgres and a load balancer with TLS on it, and runs your tests before the new release goes live. Later deploys often finish in under a second.