Javascript is required
PitfallsPublished 2026-07-28Updated 2026-08-086 min read

Modern JavaScript Minification: Syntax Compatibility

Modern JavaScript features syntax like optional chaining (?.), nullish coalescing (??), and top-level await. In build pipelines, if a minifier's parsing target falls behind the source syntax version, the minifier throws Parse Error: Unexpected token. This article breaks down boundaries across all four toolchain layers.

By: Tiny
JavaScriptES6+Optional ChainingParse ErrorTerser

1. Problem: version misalignments across the four toolchain layers

Frontend compilation involves four distinct layers: (1) Parser, (2) Transpiler (Babel/SWC), (3) Minifier (Terser/esbuild), and (4) Runtime (Browser/Node.js). When source code uses ES2020 ?. or ES2022 top-level await, but the minifier defaults to an es5 target, it fails to tokenize modern syntax, throwing an Unexpected token error.

2. Minimal reproduction: modern syntax failing under outdated minifier targets

The JavaScript snippet below includes optional chaining and top-level await. Passing it directly to a minifier targeted at ES5 triggers a parse-stage crash.

/* Source code with ES2020 optional chaining and ES2022 top-level await */
const config = await fetchConfig();
const dbPort = config?.database?.port ?? 5432;

/* When Terser is configured for { ecma: 5 }: Throws Parse Error: Unexpected token '.' */

3. Root cause: hard boundaries between minifier tokenizers and ECMAScript specs

A minifier's first step is tokenizing source text into an AST. If its tokenizer state machine lacks ES2020/ES2022 support, it splits ?. into ? and .. Syntax transpilation and code minification represent separate compilation stages.

4. Recommendation: pipeline phase isolation and target configuration matching

1. Transpile-first: lower modern ES6+ syntax to the target ECMAScript version (e.g., ES2015) via Babel/SWC before minification. 2. Native modern minification: use modern minifiers (esbuild, SWC, or Terser) with ecma: 2020 or target: 'es2020' aligned with your runtime. 3. Ensure top-level await uses ES Module bundles (format: 'esm').

5. Complete code: toolchain ECMAScript version compatibility validator

This TypeScript snippet demonstrates how to validate whether a target ECMAScript version supports optional chaining (?.) and top-level await syntax.

type EcmaVersion = "es5" | "es2015" | "es2020" | "es2022";

interface CompilerConfig {
  targetEcma: EcmaVersion;
}

function validateSyntaxSupport(syntax: "optional-chaining" | "top-level-await", config: CompilerConfig): boolean {
  const ecmaOrder: Record<EcmaVersion, number> = {
    es5: 5,
    es2015: 6,
    es2020: 11,
    es2022: 13,
  };

  const targetLevel = ecmaOrder[config.targetEcma];

  if (syntax === "optional-chaining") {
    return targetLevel >= 11;
  }
  if (syntax === "top-level-await") {
    return targetLevel >= 13;
  }
  return false;
}

console.log(validateSyntaxSupport("optional-chaining", { targetEcma: "es5" })); // false (Parse Error under lower spec)
console.log(validateSyntaxSupport("optional-chaining", { targetEcma: "es2020" })); // true
console.log(validateSyntaxSupport("top-level-await", { targetEcma: "es2022" })); // true

6. Incorrect approaches

Using legacy UglifyJS (ES5-only) directly on modern ES6+ codebases. Confusing transpilation with minification and relying solely on a minifier for syntax down-leveling.

7. Boundaries: IIFE wrappers and top-level await module limits

Top-level await can only exist in ES Modules (ESM). Forcing minifiers to output an IIFE wrapper over top-level await causes invalid syntax.

8. Verification

Assert target/ecma minifier options, scan final bundles for unsupported syntax, and run startup, routing, and critical-interaction smoke tests in the oldest supported browser environment.

9. FAQ

Why does Terser throw an Unexpected token ?. error? Terser's ecma option is set to 5 or an older version that cannot parse ES2020 token streams.

What is the recommended order for Transpilers and Minifiers? Transpile modern syntax down to the target spec first, then pass the code to a minifier configured with matching targets.

Can top-level await be fully transpiled to ES5 by Babel? It requires module loader or async wrapper runtime support; text replacement alone cannot remove ESM async loading semantics.

10. Summary

Align configurations across Parser, Transpiler, Minifier, and Runtime layers. Configure minifier ecma/target parameters or transpile before minifying to build modern ES6+ code smoothly.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool