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
- Audit and pin your toolchain (TypeScript, bundlers, transpilers, @types).
- Update
tsconfig.jsonto explicit modern targets & libs. - Adjust browserslist/build targets and add runtime polyfills where needed.
- Fix new type errors introduced by strict mode incrementally.
- Add a CI type-check gate and run
tsc --noEmitearly 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 --noEmit2) 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: Record5) 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 outdatedand 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 --noEmitCommon 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