Bundling and esbuild¶
This document explains how wilco bundles TypeScript components into JavaScript that can run in the browser. It covers esbuild configuration, the external dependency model, source map handling, and the pre-compilation pipeline.
How bundling works¶
When a component is requested, wilco’s Python bundler runs esbuild to transform the TypeScript source into a single JavaScript file:
graph LR
A["index.tsx"] --> B[esbuild]
C["Button.tsx"] --> B
D["styles.ts"] --> B
B --> E["ESM bundle"]
E --> F["Source map rewrite"]
F --> G["SHA-256 hash"]
G --> H["BundleResult<br/>(code + hash)"]
style B fill:#fef3c7,stroke:#d97706
style H fill:#d1fae5,stroke:#059669
esbuild configuration¶
The bundler invokes esbuild with these flags:
Flag |
Purpose |
|---|---|
|
Inline all local imports (files within the component directory) |
|
Output ES modules (transformed to runtime registry by loader.js) |
|
Browser compatibility target |
|
React 17+ JSX transform (no manual |
|
Don’t bundle React (provided by loader.js at runtime) |
|
Don’t bundle ReactDOM |
|
Don’t bundle JSX runtime |
|
Don’t bundle wilco’s React hooks |
|
Don’t bundle CSS-in-JS library |
|
Include source maps in the bundle (development) |
|
Embed original source in source maps |
For production builds (wilco build), additional flags:
--minify— reduce bundle size (can be disabled with--no-minify)--sourcemapis off by default (enable with--sourcemapflag)
External dependencies¶
External dependencies are not bundled with the component. Instead, they’re
provided at runtime via the module registry (window.__MODULES__):
graph TD
subgraph "Bundled by esbuild"
A[index.tsx]
B[Button.tsx]
C[styles.ts]
end
subgraph "Provided at runtime"
D[react]
E[react-dom]
F["@wilcojs/react"]
G[goober]
end
A --> B
A --> C
A -.->|"import"| D
A -.->|"import"| F
style D fill:#e0e7ff,stroke:#4f46e5
style E fill:#e0e7ff,stroke:#4f46e5
style F fill:#e0e7ff,stroke:#4f46e5
style G fill:#e0e7ff,stroke:#4f46e5
Why externals?
Single React instance — all components share one React, avoiding hooks and context issues that arise from multiple React copies
Smaller bundles — React alone is ~130KB minified. Without externals, every component would include its own copy
Consistent versions — the loader controls which React version all components use
Limitation: components can only import modules registered in
window.__MODULES__. Arbitrary npm packages cannot be used unless they are
local files bundled with the component.
To use a third-party library in a component, install it locally and import it from a relative path (esbuild will bundle it):
// This works: local file, bundled by esbuild
import { formatDate } from "./utils";
// This works: registered external
import { useState } from "react";
// This does NOT work: not in module registry
import dayjs from "dayjs";
esbuild resolution order¶
The bundler searches for esbuild in this order:
Frontend node_modules —
src/wilcojs/react/node_modules/.bin/esbuild(development, when the monorepo is available)Global PATH —
esbuildon the system PATHCommon npm paths —
/opt/homebrew/bin/esbuild,/usr/local/bin/esbuild, etc.npx fallback —
npx --yes esbuild(downloads esbuild on demand)
If none are found, a BundlerNotFoundError is raised with installation
instructions.
Source map handling¶
Source maps enable debugging original TypeScript in browser DevTools.
Rewriting source URLs¶
esbuild generates source maps with filesystem paths as sources. The bundler rewrites these to a custom URL scheme:
Before: "../components/store/product/Button.tsx"
After: "component://store:product/Button.tsx"
This provides:
Clean source names in DevTools (no filesystem paths leaked)
Component identification in stack traces
Consistent naming regardless of the server’s file layout
The transformation:
Extract the inline source map (base64-encoded JSON after
//# sourceMappingURL=)Decode and parse the JSON
Rewrite each
sourcesentry using the component nameRe-encode and replace the original source map comment
A //# sourceURL comment is also added for DevTools identification:
//# sourceURL=components://bundles/store:product.js
Content hashing¶
After bundling and source map rewriting, the bundler computes a SHA-256 hash of the final JavaScript code (first 12 hex characters). This hash is used for:
Cache busting — appended as a query parameter in API mode
Immutable filenames — part of the filename in static mode (e.g.,
counter.a1b2c3d4e5f6.js)
Pre-compilation pipeline¶
The wilco build command pre-compiles all components for production:
graph TD
A[ComponentRegistry] -->|"discover all"| B[Component list]
B --> C{For each component}
C --> D[bundle_component]
D --> E["sanitize name<br/>(: → --)"]
E --> F["Write bundles/{name}.{hash}.js"]
C --> G[manifest.json]
style A fill:#e0e7ff,stroke:#4f46e5
style G fill:#d1fae5,stroke:#059669
Filename sanitization¶
Component names can contain colons (e.g., store:product), which are not
valid in filenames on all platforms. The build process replaces colons with
double dashes:
store:product → store--product.a1b2c3d4.js
ui.button → ui.button.e5f6g7h8.js
Manifest format¶
The manifest.json file maps component names to their output files:
{
"store:product": {
"file": "bundles/store--product.a1b2c3d4.js",
"hash": "a1b2c3d4"
},
"counter": {
"file": "bundles/counter.e5f6g7h8.js",
"hash": "e5f6g7h8"
}
}
The file path is relative to the manifest’s directory. The hash is the
same SHA-256 prefix used for cache busting.
Build output structure¶
dist/wilco/
├── manifest.json
└── bundles/
├── counter.a1b2c3d4.js
├── store--product.e5f6g7h8.js
└── ui.button.i9j0k1l2.js
Django integration¶
For Django, the WilcoBundleFinder integrates with collectstatic:
graph LR
A["wilco_build"] -->|"generates"| B["dist/wilco/"]
B -->|"WilcoBundleFinder"| C["collectstatic"]
C -->|"copies to"| D["STATIC_ROOT/wilco/"]
D -->|"served by"| E["WhiteNoise / nginx"]
style A fill:#e0e7ff,stroke:#4f46e5
style E fill:#d1fae5,stroke:#059669
See also¶
System Architecture — high-level system overview
Request Lifecycle — how bundles are loaded and transformed at runtime
CLI Reference —
wilco buildcommand referenceComponent Specification — component structure and naming