⚡ TECH BLOG
Home
Blog
Tags
About
⚡

Powered by Next.js 15 & Modern Web Tech ⚡

Back to Home

TypeScript 4.7: New Features and Improvements

August 15, 2022
typescriptjavascriptprogrammingweb-development
TypeScript 4.7: New Features and Improvements

TypeScript 4.7: New Features and Improvements

TypeScript 4.7 brings significant improvements to module resolution, type inference, and developer experience. Let's explore the key features that make your TypeScript code safer and more expressive.

Module Detection Control

TypeScript 4.7 introduces the moduleDetection setting to control how TypeScript determines whether a file is a module.

The Problem

Before 4.7, TypeScript would treat any file with import or export as a module. Files without these were treated as scripts, which could cause issues.

The Solution

// tsconfig.json
{
  "compilerOptions": {
    // "force" - All files are treated as modules
    // "legacy" - Old behavior (imports/exports determine module status)
    // "auto" - Default, smart detection based on module setting
    "moduleDetection": "force"
  }
}

When to Use "force"

// script.ts - Without moduleDetection: "force", this causes errors
const greeting = "Hello"
export { greeting } // Now works in every file

// Useful for projects where every file should be a module
// Prevents global scope pollution

Control Flow Analysis for Computed Properties

TypeScript 4.7 improves narrowing for computed property access:

function getPropertyValue(obj: Record<string, unknown>, key: string) {
  if (key in obj) {
    // TypeScript now knows obj[key] is defined
    return obj[key] // Type: unknown (correctly narrowed)
  }
  return undefined
}

// Example with object types
interface Config {
  debug?: boolean
  production?: boolean
}

function setConfig(config: Config, key: 'debug' | 'production') {
  if (key in config && config[key] !== undefined) {
    // Properly narrowed
    console.log(`Setting ${key} to ${config[key]}`)
  }
}

Improved Function Inference in Objects

TypeScript 4.7 improves inference for functions in object literals:

type Functions = {
  func?: (value: string) => string
}

declare function register(obj: Functions): void

// Before 4.7: 'value' would be 'any'
register({
  func(value) {
    // Now correctly inferred as string
    return value.toUpperCase()
  }
})

// Works with methods too
type Methods = {
  method?(value: number): boolean
}

register({
  method(value) {
    // Correctly inferred as number
    return value > 0
  }
})

Instantiation Expressions

A new syntax for specifying generic type arguments without calling the function:

class Box<T> {
  value: T
  
  constructor(value: T) {
    this.value = value
  }
}

// Create a type without calling the constructor
const NumberBox = Box<number>
type NumberBox = InstanceType<typeof NumberBox>

function makeBox<T>(value: T) {
  return new Box(value)
}

// Specify generic without calling
const makeStringBox = makeBox<string>
type StringBox = ReturnType<typeof makeStringBox> // Box<string>

// Practical example with error handling
type ErrorHandler<E> = (error: E) => void

function createHandler<E>(handler: ErrorHandler<E>) {
  return handler
}

const networkErrorHandler = createHandler<NetworkError>

extends Constraint on infer

TypeScript 4.7 allows adding constraints to infer type variables:

// Before: No way to constrain the inferred type
type FirstString<T> = T extends [infer S, ...unknown[]]
  ? S
  : never

// Would accept any first element
type A = FirstString<[number, string]> // number (not desired)

// After 4.7: Can constrain the inferred type
type FirstStringConstrained<T> = T extends [infer S extends string, ...unknown[]]
  ? S
  : never

type B = FirstStringConstrained<[number, string]> // never
type C = FirstStringConstrained<["hello", number]> // "hello"

// Practical use case
type ExtractStringProperty<T> = T extends { name: infer N extends string }
  ? N
  : never

type Result = ExtractStringProperty<{ name: "Alice"; age: 30 }> // "Alice"
type Failed = ExtractStringProperty<{ name: 123; age: 30 }> // never

Optional Variance Annotations

TypeScript 4.7 adds explicit variance annotations for type parameters:

Understanding Variance

// Covariant: Used in output positions
type Getter<T> = () => T

// Contravariant: Used in input positions
type Setter<T> = (value: T) => void

// Invariant: Used in both
type Box<T> = {
  get(): T
  set(value: T): void
}

Explicit Annotations

// Covariant parameter
interface Getter<out T> {
  get(): T
}

// Contravariant parameter
interface Setter<in T> {
  set(value: T): void
}

// Invariant (both directions)
interface Box<in out T> {
  get(): T
  set(value: T): void
}

// Practical example with generic collections
interface Producer<out T> {
  produce(): T
}

interface Consumer<in T> {
  consume(item: T): void
}

// TypeScript can now verify variance at definition time
interface Producer<out T> {
  produce(): T
  bad(item: T): void // Error! Contravariant use of out param
}

Resolution Customization with moduleSuffixes

For projects with platform-specific modules:

// tsconfig.json
{
  "compilerOptions": {
    "moduleSuffixes": [".browser", ".native", ""]
  }
}

Usage

src/
├── utils.browser.ts
├── utils.native.ts
└── utils.ts
// In code
import { helper } from './utils'
// TypeScript will look for:
// 1. ./utils.browser.ts
// 2. ./utils.native.ts
// 3. ./utils.ts

// On web: resolves to utils.browser.ts
// On native: resolves to utils.native.ts
// Default: resolves to utils.ts

Resolution Customization for package.json Exports

TypeScript 4.7 supports package.json exports field for better module resolution:

// package.json
{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./utils": {
      "import": "./dist/esm/utils.js",
      "require": "./dist/cjs/utils.js"
    }
  }
}

Consuming Package

// TypeScript resolves based on module setting
import { something } from 'my-package'
import { utility } from 'my-package/utils'

node16 and nodenext Module Resolution

New module resolution modes for modern Node.js:

// tsconfig.json
{
  "compilerOptions": {
    "module": "node16",
    "moduleResolution": "node16"
  }
}

Benefits

  • Supports ESM and CJS interop
  • Respects package.json exports
  • Proper .mjs and .cjs handling
  • File extension required for ESM imports
// With node16 resolution
import { something } from './module.js' // Extension required for ESM
import { other } from './commonjs.cjs' // CJS module

Object Method Syntax Improvements

interface Obj {
  property: string
  method(): void
  asyncMethod?(): Promise<void>
}

const obj: Obj = {
  property: "value",
  method() {
    // Correct 'this' typing
    console.log(this.property)
  },
  async asyncMethod() {
    // Async method syntax
  }
}

Improved Type Inference for Array Methods

// Better inference for array methods
const items = [
  { type: 'a', value: 1 },
  { type: 'b', value: 'two' }
] as const

// TypeScript 4.7 correctly infers
type ItemType = typeof items[number]
// { readonly type: "a"; readonly value: 1 } | { readonly type: "b"; readonly value: "two" }

// Filter with type narrowing
const aItems = items.filter(item => item.type === 'a')
// Type is correctly narrowed to a-type items

Stricter Spread Checks

interface Song {
  name: string
  artist: string
}

interface Album {
  name: string
  artist: string
  tracks: Song[]
}

const song: Song = { name: "Song", artist: "Artist" }

// Before 4.7: This would work (incorrectly)
const album: Album = { 
  ...song, 
  tracks: [] 
}

// 4.7: Better checking - catches potential issues
interface Strict {
  required: string
  optional?: number
}

// Spreading from wider to narrower
declare let source: { required: string }
let target: Strict = { ...source } // Error: optional missing

Performance Improvements

TypeScript 4.7 includes significant performance improvements:

  • Faster type checking - Up to 20% faster in some projects
  • Reduced memory usage - Better for large codebases
  • Faster incremental builds - Improved caching

New --assumeChangesOnlyAffectDirectDependencies Flag

// tsconfig.json
{
  "compilerOptions": {
    "assumeChangesOnlyAffectDirectDependencies": true
  }
}

Speeds up incremental builds by assuming file changes only affect direct dependencies. Use with caution in complex projects.

Editor Improvements

Organize Imports Improvements

// Before organizing
import { useState, useEffect, useCallback, useMemo } from 'react'
import { Something } from 'unused-module'

// After organizing (removes unused, sorts)
import { useCallback, useEffect, useMemo, useState } from 'react'

Go-to-Definition Improvements

  • Better navigation in composite projects
  • Improved handling of re-exports

Breaking Changes

Stricter Checks for Spread Operations

// This pattern now may produce errors
interface Base {
  foo: string
}

interface Extended extends Base {
  bar: number
}

const base: Base = { foo: 'hello' }
const extended: Extended = { ...base, bar: 1 }
// Warning: Spread may lose optional properties

in Operator Stricter

// Before 4.7
"toString" in {} // true (runtime)
// TypeScript allowed this

// 4.7 is stricter
const obj = {} as Record<string, unknown>
if ("toString" in obj) {
  // Still works for Record types
}

Migration Guide

  1. Update TypeScript:
npm install typescript@4.7
  1. Update tsconfig.json:
{
  "compilerOptions": {
    "target": "es2020",
    "module": "node16",
    "moduleResolution": "node16",
    "moduleDetection": "force"
  }
}
  1. Fix any new errors:
// Add file extensions for ESM imports
import { something } from './module.js'

// Use explicit variance annotations if needed
interface Producer<out T> {
  produce(): T
}

Conclusion

TypeScript 4.7 brings meaningful improvements to the language, especially for module resolution and type system expressiveness. The new variance annotations and infer constraints enable more precise type definitions.

Key Takeaways

  • Use moduleDetection: "force" for safer module handling
  • Leverage extends constraint on infer for better type narrowing
  • Consider explicit variance annotations for complex generic types
  • Adopt node16 module resolution for modern Node.js projects

Upgrade to TypeScript 4.7 to take advantage of these improvements!

Share:

💬 Comments