* [Architecture](#architecture) * [Design principles](#design-principles) * [Overview](#overview) * [Scan phase](#scan-phase) * [Compile phase](#compile-phase) * [Notes about parsing](#notes-about-parsing) * [Symbols and scopes](#symbols-and-scopes) * [Constant folding](#constant-folding) * [TypeScript parsing](#typescript-parsing) * [Notes about linking](#notes-about-linking) * [CommonJS linking](#commonjs-linking) * [ES6 linking](#es6-linking) * [Hybrid CommonJS and ES6 modules](#hybrid-commonjs-and-es6-modules) * [Scope hoisting](#scope-hoisting) * [Converting ES6 imports to CommonJS imports](#converting-es6-imports-to-commonjs-imports) * [The runtime library](#the-runtime-library) * [Tree shaking](#tree-shaking) * [Code splitting](#code-splitting) * [Notes about printing](#notes-about-printing) * [Symbol minification](#symbol-minification) # Architecture Documentation This document covers how esbuild's bundler works. It's intended to aid in understanding the code, in understanding what tricks esbuild uses to improve performance, and hopefully to enable people to modify the code. Note that there are some design decisions that have been made differently than other bundlers for performance reasons. These decisions may make the code harder to work with. Keep in mind that this project is an experiment in progress, and is not the result of a comprehensive survey of implementation techniques. The way things work now is not necessarily the best way of doing things. ### Design principles * **Maximize parallelism** Most of the time should be spent doing fully parallelizable work. This can be observed by taking a CPU trace using the `--trace=[file]` flag and viewing it using `go tool trace [file]`. * **Avoid doing unnecessary work** For example, many bundlers have intermediate stages where they write out JavaScript code and read it back in using another tool. This work is unnecessary because if the tools used the same data structures, no conversion would be needed. * **Transparently support both ES6 and CommonJS module syntax** The parser in esbuild processes a superset of both ES6 and CommonJS modules. It doesn't distinguish between ES6 modules and other modules so you can use both ES6 and CommonJS syntax in the same file if you'd like. * **Try to do as few full-AST passes as possible for better cache locality** Compilers usually have many more passes because separate passes makes code easier to understand and maintain. There are currently only three full-AST passes in esbuild because individual passes have been merged together as much as possible: 1. Lexing + parsing + scope setup + symbol declaration 2. Symbol binding + constant folding + syntax lowering + syntax mangling 3. Printing + source map generation * **Structure things to permit a "watch mode" where compilation can happen incrementally** Incremental builds mean only rebuilding changed files to the greatest extent possible. This means not re-running any of the full-AST passes on unchanged files. Data structures that live across builds must be immutable to allow sharing. Unfortunately the Go type system can't enforce this, so care must be taken to uphold this as the code evolves. ## Overview

| foo.js | bar.js | bundle.js |
|---|---|---|
| ```js exports.fn = () => 123 ``` | ```js const foo = require('./foo') console.log(foo.fn()) ``` | ```js let __commonJS = (callback, module) => () => { if (!module) { module = {exports: {}}; callback(module.exports, module); } return module.exports; }; // foo.js var require_foo = __commonJS((exports) => { exports.fn = () => 123; }); // bar.js const foo = require_foo(); console.log(foo.fn()); ``` |
| foo.js | bar.js | bundle.js |
|---|---|---|
| ```js export const fn = () => 123 ``` | ```js import {fn} from './foo' console.log(fn()) ``` | ```js // foo.js const fn = () => 123; // bar.js console.log(fn()); ``` |



| Chunk for index.js | Chunk for settings.js | Chunk for shared code |
|---|---|---|
| ```js import { api, session } from "./chunk.js"; // net.js function get(url) { return fetch(url).then((r) => r.text()); } // config.js function load() { return get(api + session); } // index.js let el = document.getElementById("el"); load().then((x) => el.textContent = x); ``` | ```js import { api, session } from "./chunk.js"; // net.js function put(url, body) { fetch(url, {method: "PUT", body}); } // config.js function save(value) { return put(api + session, value); } // settings.js let it = document.getElementById("it"); it.oninput = () => save(it.value); ``` | ```js // config.js let session = Math.random(); let api = "/api?session="; export { api, session }; ``` |
| entry1.js | entry2.js | data.js |
|---|---|---|
| ```js import {data} from './data' console.log(data) ``` | ```js import {setData} from './data' setData(123) ``` | ```js export let data export function setData(value) { data = value } ``` |
| Chunk for entry1.js | Chunk for entry2.js | Chunk for shared code |
|---|---|---|
| ```js import { data } from "./chunk.js"; // entry1.js console.log(data); ``` | ```js import { data } from "./chunk.js"; // data.js function setData(value) { data = value; } // entry2.js setData(123); ``` | ```js // data.js let data; export { data }; ``` |
| Chunk for entry1.js | Chunk for entry2.js | Chunk for shared code |
|---|---|---|
| ```js import { data } from "./chunk.js"; // entry1.js console.log(data); ``` | ```js import { setData } from "./chunk.js"; // entry2.js setData(123); ``` | ```js // data.js let data; function setData(value) { data = value; } export { data, setData }; ``` |
| Original code | Code with symbol minification |
|---|---|
| ```js function useReducer(reducer, initialState) { let [state, setState] = useState(initialState); function dispatch(action) { let nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; } ``` | ```js function useReducer(b, c) { let [a, d] = useState(c); function e(f) { let g = b(a, f); d(g); } return [a, e]; } ``` |