Sechno
Web Development

Migrating to TypeScript 6: Practical checklist, fixes, and build recipes

Concrete, practical guidance to migrate apps to TypeScript 6 — handle strict-by-default errors, drop ES5 assumptions, update build targets and polyfills, and keep CI green.

SSechno Team 5 min read 24 views
Migrating to TypeScript 6: Practical checklist, fixes, and build recipes

Why this matters

TypeScript 6 introduced two changes that impact many projects: strict mode enabled by default and removal of ES5 library/transform support. These make compiled output more modern and types safer, but they also cause new type errors and break builds that relied on ES5-era transforms or polyfills. This guide gives a focused, pragmatic migration path with actionable code snippets, CI checks, and tradeoffs.

Read the original announcement for details: TypeScript 6.0 release notes.

Quick migration checklist

  1. Audit and pin your toolchain (TypeScript, bundlers, transpilers, @types).
  2. Update tsconfig.json to explicit modern targets & libs.
  3. Adjust browserslist/build targets and add runtime polyfills where needed.
  4. Fix new type errors introduced by strict mode incrementally.
  5. Add a CI type-check gate and run tsc --noEmit early in pipelines.

1) Pin tool versions and run a dry type-check

Before changing code, make sure package.json pins TypeScript and your build tools so team members run the same compiler.

{
  "devDependencies": {
    "typescript": "^6.0.0",
    "esbuild": "^0.20.0",
    "webpack": "^6.0.0"
  }
}

Then run a baseline check:

npx tsc --noEmit

2) Use an explicit modern tsconfig

TypeScript 6 drops ES5-era runtime/library handling. Make your intent explicit so you control emitted features and available lib types.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "outDir": "dist",
    "noEmit": false
  },
  "include": ["src"]
}

Notes:

  • target/module/lib — pick the smallest modern baseline you support (ES2017/ES2018/ES2020) and ensure your bundler emits compatible code.
  • moduleResolution: bundler helps with modern packages that publish ESM packages.

3) Align browserslist/build targets and polyfills

Dropping ES5 support means older browsers (like IE11) won't be supported unless you explicitly downlevel and add polyfills. Update your package.json browserslist and choose a bundler output target accordingly.

{
  "browserslist": [
    ">0.5%",
    "last 2 versions",
    "not dead",
    "not IE 11"
  ]
}

If you must support older browsers, either:

  • Keep a separate legacy build that downlevels and injects necessary polyfills (cost: maintenance and bundle size).
  • Or drop legacy browser support and simplify the output pipeline (benefit: smaller/simpler bundles).

Example: add only the runtime polyfills you need in your entry point to minimize impact:

// src/polyfills.ts
// Import only required features to avoid a full polyfill bundle
import 'core-js/features/array/from';
import 'core-js/features/promise';

4) Fix common strict-mode type errors (practical examples)

Strict mode surfaces many safe-but-noisy cases: nullable values, implicit any, and uninitialized class properties. Fix incrementally — prefer precise annotations and small refactors instead of broad opt-outs.

Tip: Resist turning "strict" off globally. Use per-file or per-check fixes while you iterate.

Example — nullable parameter error:

// before (error in strict mode)
function getLength(s: string | null) {
  return s.length;
}
 
// after — explicit guard
function getLength(s: string | null): number {
  if (s == null) return 0;
  return s.length;
}

Example — missing property initializer:

// before
class Store {
  data: Record

5) Update your build pipeline: recipes

Pick a modern bundler path that respects ESM and modern targets. Here are two lightweight options.

esbuild (fast, modern-targets only)

{
  "scripts": {
    "build": "esbuild src/index.ts --bundle --target=es2020 --outfile=dist/bundle.js"
  }
}

tsc & Babel (if you need fine-grained transforms or a legacy build)

{
  "scripts": {
    "build:ts": "tsc -p tsconfig.build.json",
    "build:legacy": "babel dist --out-dir dist-legacy --presets=@babel/preset-env"
  }
}

Tradeoff: esbuild and swc are excellent when you target modern runtimes. If you support legacy browsers, add a separate legacy pipeline that runs Babel with a specific browserslist.

6) Third-party types and libraries

Strict mode will reveal faults in upstream types. Audit direct dependencies with runtime behaviour that relies on global polyfills (like URL, TextEncoder, or Intl). Steps:

  • Run npm outdated and upgrade packages that publish modern ESM builds.
  • Install missing type packages: npm i -D @types/somepkg.
  • For problematic packages, add small wrapper adapters to normalize types instead of broad type-ignore comments.

7) CI and developer ergonomics

Add a type-check job early in your CI pipeline so PRs fail fast on new errors:

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx tsc --noEmit

Common migration pitfalls and how to avoid them

  • Ignoring multiple small type errors — fix incrementally per package or module to keep reviews manageable.
  • Assuming runtime polyfills come from TypeScript — TS only types and transforms; add runtime shims explicitly.
  • Keeping divergent legacy vs modern builds forever — plan a sunset schedule if legacy support is only for a small user base.

Tradeoffs

  • Pros: safer code, smaller/simpler modern bundles, encourages dropping dead legacy cruft.
  • Cons: upfront migration effort, potential need for additional polyfills or a separate legacy build, and more strict code reviews.

Conclusion

TypeScript 6's stricter, modern-first defaults are an opportunity to improve runtime safety and simplify bundles — but they require deliberate migration. Make the change manageable by pinning tool versions, choosing an explicit modern target in tsconfig.json, aligning browser targets, adding focused polyfills, and running tsc --noEmit in CI. Tackle type errors incrementally and prefer narrowly-scoped fixes rather than global opt-outs to preserve the long-term benefits of stricter typing.

For the official release notes, see the announcement: TypeScript 6.0 launch.

Was this helpful?

Share this post

Comments (0)

Want to join the conversation?

Log in or sign up to leave a comment and share your thoughts.

Log in to Comment