- Alpha
- On npm: @ts-r.dev/build
ts-r-build: bundler and minifier
An esbuild-compatible bundler and minifier. Same flags as esbuild 0.28.2, ES2022 and later targets, no plugins or CSS bundling yet.
ts-r-build bundles and minifies TypeScript and JavaScript for production. Its command line follows esbuild 0.28.2, so most esbuild commands work after swapping the binary name. Like esbuild, it strips types without checking them. Run ts-r-check or tsc for that.
What works today
- Bundling ES modules and CommonJS, with tree shaking that honours
sideEffectsand/*#__PURE__*/annotations. - Output formats
esm,cjsandiife, and code splitting with--format=esm --splitting. - Platforms
browser,nodeandneutral. - tsconfig
pathsandbaseUrl, from the nearesttsconfig.jsonor from--tsconfig. - Minification of whitespace, identifiers and syntax, separately or all at once with
--minify. - Source maps: linked, external, inline or both.
- JSX with the classic or the automatic runtime.
--watch, which rebuilds when an input changes.- A persistent build cache in
~/.cache/ts-r.
Not yet
- No plugins and no JavaScript config file. Everything is set on the command line.
- No CSS bundling. CSS imports are concatenated into a sibling
.cssfile and@importis not followed. - No serve mode with
--watch, and no legal-comment extraction. - No
--mangle-props,--drop,--pureor--keep-names. - No syntax lowering: targets below ES2022 are rejected.
- IIFE output isn't re-indented, and runtime helpers are repeated per chunk.
- Not implemented:
import deferandimport source, globrequireandimport()of template strings, imports ofdata:JavaScript,with { type }loader overrides, Yarn PnP. - Minified output is about 3% larger than esbuild's.
The full list is in the design record, 0008 bundler.
Command line
Usage: ts-r-build [options] [entry points]
Simple options:
--bundle Bundle all dependencies into the output files
--define:K=V Substitute K with V while parsing
--external:M Exclude module M from the bundle (can use * wildcards)
--format=... Output format (iife | cjs | esm)
--loader:X=L Use loader L for .X files (js, jsx, ts, tsx, json, text,
file, css, empty, base64, dataurl, binary)
--minify Minify the output (sets all --minify-* flags)
--outdir=... The output directory (for multiple entry points)
--outfile=... The output file (for one entry point)
--packages=external Exclude every package import from the bundle
--platform=... Platform target (browser | node | neutral)
--sourcemap Emit a source map
--splitting Enable code splitting (requires --format=esm)
--target=... Environment target (es2022, esnext, node18, chrome110, ...)
--watch Rebuild when a source file changes
Advanced options:
--banner:js=... Text at the start of every JS output
--chunk-names=... Path template for shared chunks (default "[name]-[hash]")
--conditions=... Custom package.json exports conditions
--entry-names=... Path template for entry points (default "[dir]/[name]")
--footer:js=... Text at the end of every JS output
--global-name=... Name of the global for --format=iife exports
--ignore-annotations Ignore /*#__PURE__*/ and sideEffects
--jsx=... transform | automatic | preserve
--jsx-dev Use React's automatic runtime in development mode
--jsx-factory=... What to use for JSX instead of React.createElement
--jsx-fragment=... What to use for JSX instead of React.Fragment
--jsx-import-source=... Override the package name for the automatic runtime
--log-level=... verbose | debug | info | warning | error | silent
--main-fields=... Override the main file order in package.json
--metafile=... Write metadata about the build to a JSON file
--minify-whitespace Remove whitespace
--minify-identifiers Shorten identifiers
--minify-syntax Use equivalent but shorter syntax
--no-cache Do not use the persistent build cache
--out-extension:.js=.mjs Use a custom output extension
--public-path=... Prefix for file-loader URLs
--resolve-extensions=... Comma-separated list of implicit extensions
--sourcemap=... linked | external | inline | both
--sources-content=false Omit sourcesContent in source maps
--tree-shaking=... Force tree shaking on or off (true | false)
--tsconfig=... Use this tsconfig.json instead of the nearest one
--threads=N Worker threads (default: TS_R_THREADS or all cores, max 8)
--version Print the version
The flags mean what they mean in esbuild. Where ts-r differs:
- Unknown flags are errors. A few esbuild flags that only change cosmetics, such as
--charset, are accepted and ignored. - Without
--outfileor--outdir, the bundle goes to stdout. --targetmust allow at least ES2022. Older targets are an error, not a down-level.--threadsdefaults to theTS_R_THREADSenvironment variable, else the number of cores, capped at 8.--no-cacheskips the persistent cache (thebuildnamespace in~/.cache/ts-r).
Exit codes: 1 on any error, 0 otherwise. You can also run it through the launcher as ts-r build.
Next to esbuild and Vite
ts-r-build works on its own. Use it where you call the esbuild command line today, or to build a library while the app keeps Vite. It has no JavaScript API, so scripts that call esbuild.build({...}) from Node need to switch to the command line first. It doesn't read vite.config.
ts-r-dev uses the same bundler to pre-bundle an app's dependencies.
Measured
Apple M-series Mac (a shared laptop), 4 threads, against esbuild 0.28.2, on public projects pinned in bench/repos.toml (cargo xtask bench --repos excalidraw,three-x10).
| Input | ts-r-build | esbuild |
|---|---|---|
| excalidraw app, minified ESM, no cache (median of 10) | 255 ms | 201 ms |
| Same, with the persistent cache | 132 ms | 201 ms |
| excalidraw: first build plus 20 edit-and-rebuild cycles, one process | 1,358 ms | 3,457 ms |
| three.js x10, minified IIFE with source maps, no cache (median of 10) | 378 ms | 225 ms |
| Same, with the persistent cache | 241 ms | 225 ms |
Without its cache, ts-r-build is slower than esbuild on both. It also doesn't bundle template-literal import()s yet, so on excalidraw it leaves out the 61 locale chunks esbuild emits (7.2 MB of output against 9.3 MB), which flatters its times. The three.js x10 bundles agree: both load in Node with the same exports; ts-r's is 2.2% larger.
Correctness:
- esbuild's end-to-end bundler tests, run against ts-r-build: 651 of 746 compared cases pass (87.3%).
- Ecosystem bundle stage: 844 packages pass and 71 fail (92.2%). Minified output is 3.1% larger than esbuild's in total, with a median per-package ratio of 1.048.
Method and commits: build, ecosystem and correctness.