Tags: web-dev concept

Transpilation

Date: 2026-08-17


Converting source into equivalent code an older target can run. The compatibility target is the single biggest lever on output size that most teams never touch — and the default is usually far more conservative than their actual visitors need.


Transpilation converts source from one form to another at the same level of abstraction — modern JavaScript to older JavaScript, TypeScript to JavaScript, JSX to function calls.

What gets transformed

TYPE STRIPPING     TS → JS
                   → types erased, nothing
                     checked
                   ← fast, and NOT type
                     checking

SYNTAX DOWNLEVEL   ?? ?. async class fields
                   → equivalent older syntax

JSX                <div/> → jsx('div')

POLYFILLS          Array.at, Object.hasOwn
                   → runtime additions,
                     NOT syntax

Syntax and polyfills are different problems. Transpilation rewrites syntax; polyfills add missing runtime functions. A build can handle one and not the other. Both differ again from a shim, which wraps an API that already exists — Shims and Polyfills.

The tools

Written inType-checksSpeed
BabelJSNoSlowest, most configurable
SWCRustNoVery fast
esbuildGoNoVery fast
tscTSYesSlow

None of the fast ones type-check. They strip types and move on, which is why type checking has to run separately — Type Checking in CI.

vite build          # strips types, no checking
tsc --noEmit        # checks, emits nothing

Both are needed, and a build that only does the first will happily ship type errors.

The target is the lever

How far down you transpile determines how much code you ship.

target: es5
  → async/await becomes a state machine
  → classes become functions and
    prototypes
  → generators need a large runtime
  → SUBSTANTIALLY more output

target: es2020
  → most modern syntax passes through
    untouched
  → much smaller, and faster to parse

Every browser in meaningful use supports ES2020. Targeting ES5 in 2026 ships transformation for browsers nobody uses — and the cost is paid by every visitor on every page.

Check your actual analytics before choosing. The default in an inherited configuration is frequently a decision made years ago for a browser matrix that no longer applies.

Browserslist

The shared configuration most tools read:

# .browserslistrc
> 0.5%
last 2 versions
not dead

One declaration, honoured by the transpiler, the CSS pipeline and the polyfill injector. Worth setting deliberately rather than inheriting.

npx browserslist        # what does this resolve to?

Run that. The resolved list is frequently longer and older than anyone expects, and it silently sets the cost of every build.

Polyfills, and the differential option

// syntax — transpiled away
const x = a ?? b
 
// runtime API — needs a polyfill
[1,2,3].at(-1)
Object.hasOwn(obj, 'key')

core-js with useBuiltIns: 'usage' injects only the polyfills your code actually needs, which is far smaller than including the whole thing.

Differential serving — modern syntax to modern browsers, transpiled to old ones — was a popular pattern and has mostly stopped being worth the complexity, because the modern baseline is now so wide that the legacy bundle serves almost nobody.

CSS has the same problem

POSTCSS + AUTOPREFIXER   vendor prefixes
LIGHTNING CSS            downlevel nesting,
                         colour functions,
                         cascade layers

Driven by the same Browserslist config, which is why setting it correctly pays twice.

Where it goes wrong

  • Targeting far older browsers than your visitors use. The most common and most expensive mistake here
  • Transpiling node_modules. Slow, usually unnecessary, and occasionally breaks packages shipping deliberately-modern code
  • Assuming type stripping is type checking. It isn’t, and this is worth stating whenever a build “passes”
  • Polyfilling everything unconditionally rather than by usage
  • Two transpilers in one pipeline — a Babel config left behind after moving to SWC, doing the work twice