What’s new in TypeScript 4.7
Tags: typescript, webdev
Published: 2022-06-14 15:00:44

**Written by [Ishan Manandhar](https://blog.logrocket.com/author/ishan-manandhar/)✏️**

There have been a lot of significant updates, introductions, and improvements included in TypeScript 4.7\. This update specifically revolves around new features for type inference, narrowing analysis, ES module integration, instantiation expressions, and more. 

In this article, we’ll take a look at each of the new changes and understand how we can use them today. Let’s go through each of them and understand what the new release implementation looks like.

*   [Setting up the environment](#setting-up-environment)
*   [Better control over module detection](#better-control-module-detection)
*   [Objects and methods have improved in function inference](#objects-methods-improved-function-inference)
*   [Specialized generic function with instantiation expressions](#specialized-generic-function-instantiation-expressions)
*   [More control flow analysis for calculated properties](#more-control-flow-analysis-calculated-properties)
*   [Object method snippet autocompletion inside IDE](#object-method-snippet-autocompletion-inside-IDE)
*   [`typeof` queries in `#private` fields are now allowed](#typeof-queries-private-fields-allowed)
*   [Optional variance annotations for type parameters](#optional-variance-annotations-type-parameters)
*   [Group-aware organize imports with auto sorting](#group-aware-organize-imports-auto-sorting)
*   [`Resolution` mode can be used on `import()` types](#resolution-mode-import-types)
*   [Customization of the resolution with `moduleSuffixes`](#customization-resolution-modulesuffixes)
*   [`Extends` constraints on `infer` type variables](#extends-constraints-infer-type-variables)
*   [ECMAScript module is supported in Node.js](#ecmascript-module-supported-node-js)
*   [Other breaking changes](#other-breaking-changes)

## Setting up the environment <a name="setting-up-environment">

To start with the implementation of features, we’ll need to tune in to our local dev environment and configure some files. 

First, run the command below to see a `package.json` file created for us in the root project directory:

```shell
mkdir ts4.7_walkthrough 
cd ts4.7_walkthrough 
npm init -y
```

We'll install the latest version of TypeScript with the <code>-D</code> flag, installing it as a dev dependency:

```shell
npm install typescript@rc -D
```

Next, we’ll run the `--init` command to initialize TypeScript:

```shell
npx tsc --init
```

This will create a `tsconfig.json` file for us. We will add an option here so we can specify the output folder of our files:

```typescript
{
 "compilerOptions": {
"outDir": "./output"
  "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,

 }
}
```

Until now, we have set the output directory to get the compiled JavaScript that’ll go into the folder. We’ll also update the `scripts` section of our `package.json` file to include the build and start `script`: 

```typescript
"scripts": {
 "build": "tsc",
 "start": "tsc -w"
},
```

With this in place, we can run our application with the command `npm start`, which listens to all changes to TypeScript files and compiles them down for us. 

Now, let's get our hands dirty exploring the newly added features and improvements.

## Better control over module detection <a name="better-control-module-detection">

Until this release, we needed to add `type=module` or `.mjs` extensions when writing our server-side code in Node. But, in plain JavaScript, modularized code runs slightly differently than traditional script code. Since they have different scoping rules, we need to decide how each file runs. 

In TypeScript, these files are treated as modules if any imports and exports are written in the file. With this latest release, we have an option called `moduleDetection` to give us more control. This option can take three values: `auto`, `legacy`, and `force`. Let's understand what each of them does. 

When using `auto` mode, TypeScript will check for import and export statements, as well as whether the current file is in JSX when running under `--jsx react-jsx`. It’ll also check if the `type` field in `package.json` is set to the module when running under `--module nodenext/--module node12`. 

`legacy` mode, on the other hand, only checks for import and export statements. 

Finally, `force` mode will force every file to be treated as a module.

## Objects and methods have improved in function inference <a name="objects-methods-improved-function-inference">

With the new version of TypeScript, we can perform more refined inferences from functions within objects, methods, and arrays. 

We tend to infer types from context-insensitive function arguments anywhere in the argument list, such as when we are contextually typing parameters of arrow functions, object literals, and function expressions in a generic function argument list, or from context-sensitive function arguments in preceding positions in the argument list.

```typescript
// Improved function inference in objects and methods
declare function myCharacter<T>(arg: {
 characterLetter: (a: string) => T,
 changeCharacter: (x: T) => void
}
): void;

// Works fine
myCharacter({
 characterLetter: () => "meow meow",
 changeCharacter: x => x.slice(0, 4),
});

// Works fine
myCharacter({
 characterLetter: (a: string) => a,
 changeCharacter: x => x.substring(0, 4),
});

// Was an error, but now it works.
myCharacter({
 characterLetter() { return "I love typescript" },
 changeCharacter: x => x.toUpperCase(),
});

// Now works with ^@4.7
myCharacter({
 characterLetter: a => a,
 changeCharacter: x => x.toUpperCase(),
});

// Now works with ^@4.7
myCharacter({
 characterLetter: function () { return "I love typescript"; },
 changeCharacter: x => x.toUpperCase(),
});
```

With these changes, we can now have the same left-to-right rules for information flow between context-sensitive, contextually-typed functions, regardless of whether the functions occur as discrete arguments or properties in the object or array literals.

## Specialized generic function with instantiation expressions <a name="specialized-generic-function-instantiation-expressions">

We can now specialize the generic function with instantiation expressions. To demonstrate this,, we’ll create a generic type interface called `makeJuice`. It will take in a generic to be passed into a general function:

```typescript
interface Fruits<T> {
  value: T;
}
function makeJuice<T>(value: T) {
  return { value };
}
```

There are often cases when we want to create a specialized function and wrap the function to make it more specialized. To achieve this, we can write:

```typescript
function orangeJuice(fruit: Orange) {
 return makeJuice(Orange);
}

// or can be written like

const appleJuice: (fruit: Fruits) => Fruits<Apple> = makeJuice;
```

This method definitely works, but creating a new function to wrap another function is a waste of time and, frankly, too much work. With the new release, we can simplify this by taking functions and constructors and feeding them type arguments directly:

```typescript
const appleJuice= makeJuice<Apple>;
const orangeJuice= makeJuice<Orange>;
```
We can also just receive a specific type and reject anything else:

```typescript
const makeAppleJuice = makeJuice<number>;

// TypeScript correctly rejects this.
makeAppleJuice('Apple');
```

This allows developers to specialize in the generic function and accept and reject good values.

## More control flow analysis for calculated properties <a name="more-control-flow-analysis-calculated-properties">

With the release of TypeScript 4.7, the TypeScript compiler can now parse the type of computed properties and reduce them correctly. You can see an example of this below:

```typescript
const key = Symbol();
const objectKey = Math.random() <= 0.2 ? 90 : "Lucky person!";

let obj = {
 [key]: objectKey,
};

if (typeof obj[key] === "string") {
 let str = obj[key].toUpperCase();
 console.log(`You really are ${str}`);
}
```

The [new version](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#groups-aware-organize-imports) knows that `obj[key]` is a string. TypeScript can correctly check that computed properties are initialized by the end of a constructor body.

## Object method snippet autocompletion inside IDE <a name="object-method-snippet-autocompletion-inside-IDE">

We can now receive snippet completions for object literal methods. TypeScript will provide us with a typical completion entry for the name of the method only, as well as an entry for separate completion for the full method definition.

![Object Method Snippet Autocompletion Inside IDE](https://blog.logrocket.com/wp-content/uploads/2022/06/object-method-snippet-autocompletion-inside-IDE.gif)

## `typeof` queries in `#private` fields are now allowed <a name="typeof-queries-private-fields-allowed">

With this update, we are now allowed to perform `typeof` queries on private fields. You can see an example of this below:

```typescript
class Programming {
 #str = "Typescript rocks!";

 get ourString(): typeof this.#str {
     return this.#str;
 }

 set ourString(value: typeof this.#str) {
     this.#str = value;
 }
}
```

## Optional variance annotations for type parameters <a name="optional-variance-annotations-type-parameters">

Further, we are now able to explicitly specify variance on type parameters:

```typescript
interface Programming {
 langList: string[];
}

interface typescriptLang extends Programming {
 tsLang: string;
}

type Getter<T> = (value: T) => T;

type Setter<T> = (value: T) => void;
```

Let’s assume we have two different `Getters` that are substitutable, entirely depending on generic `T`. In such a case, we can check that `Getter<TypeScript> → Getter<Programming>` is valid. In other words, we need to check if `TypeScript → Programming` is valid. 

Checking if `Setter<Typescript> → Setter<Programming>` is valid involves seeing whether `Typescript → Programming` is also valid. That flip in direction is similar to logic in math. Essentially, we are seeing whether −x < −y is the same y < x. 

When we need to flip directions like this to compare `T`, we say that `Setter` is contravariant on `T`. 

We can now explicitly state that `Getter` is covariant on `T` with the help of the `out` modifier:

```typescript
type Getter<out T> = () => T;
```

Similarly, if we want to make it explicit that the `Setter` is contravariant on `T`, we can give it a modifier:

```typescript
type Setter<in T> = (value: T) => void;
```

We use `out` and `in` modifiers here because a type parameter’s variance relies on whether it’s used in output or input.

## Group-aware organize imports with auto sorting <a name="group-aware-organize-imports-auto-sorting">

TypeScript now has an organized imports editor for both JavaScript and TypeScript, however, it may not meet our expectations. It’s actually better to natively sort our import statements. 

Let’s take an example in action:

```typescript
// local code
import * as cost from "./cost";
import * as expenses from "./expenses";
import * as budget from "./budget";

// built-ins
import * as fs from "fs";
import * as http from "http"
import * as path from "path";
import * as crypto from "crypto";
```

If we run organize imports on the following file, we’d see the following changes:

```typescript
// built-ins
import * as fs from "fs";
import * as http from "http"
import * as path from "path";
import * as crypto from "crypto";

//local code
import * as cost from "./cost";
import * as expenses from "./expenses";
import * as budget from "./budget";
```

The imports are sorted by their paths and our comments and newlines are preserved. This organizes imports in a group-aware manner.

## `Resolution` mode can be used on `import()` types <a name="resolution-mode-import-types">

TypeScript now allows `/// <reference types="…" />` directives and `import type` statements to specify a resolution strategy. This means we can resolve the imported Node ECMAScript resolution. However, it would be useful to reference the types of common JavaScript modules from an ECMAScript module or vice versa. 

In nightly versions of TypeScript, the `import type` can specify an import assertion to achieve similar results:

```typescript
// Resolve `pkg` as if we were importing with a `require()`
import type { TypeFromRequire } from "pkg" assert {
 "resolution-mode": "require"
};

// Resolve `pkg` as if we were importing with an `import`
import type { TypeFromImport } from "pkg" assert {
 "resolution-mode": "import"
};

export interface MergedType extends TypeFromRequire, TypeFromImport {}
```

These import assertions can also be used on `import()` types as well:

```typescript
export type TypeFromRequire =
   import("pkg", { assert: { "resolution-mode": "require" } }).TypeFromRequire;

export type TypeFromImport =
   import("pkg", { assert: { "resolution-mode": "import" } }).TypeFromImport;

export interface MergedType extends TypeFromRequire, TypeFromImport {}
```

Note that the `import type` and `import()` syntax only supports `resolution` mode in nightly builds of TypeScript. We would get errors such as:

```plaintext
**Resolution mode assertions are unstable. Use nightly TypeScript to silence this error. Try updating with 'npm install -D typescript@next'.
```

## Customization of the resolution with `moduleSuffixes` <a name="customization-resolution-modulesuffixes">

TypeScript now supports a `moduleSuffixes` option to customize how module specifiers are looked up. For example, if we are importing files like `import * as foo from "./foo"` and `moduleSuffixes` configurations, it looks something like this:

```typescript
{
 "compilerOptions": {
     "moduleSuffixes": [".ios", ".native", ""]
 }
}
```

With this configuration, we are forcing our application to look into the relative files in the path:

```typescript
./example.ios.ts
./example.native.ts
./example.ts
```

This feature will become really handy, especially in React Native projects where we add each targeted platform with different `moduleSuffixes` inside of `tsconfig.json`. Note that the empty string `""` in `moduleSuffixes` is necessary for TypeScript to also lookup `./example.ts`.

## `Extends` constraints on `infer` type variables <a name="extends-constraints-infer-type-variables">

Constraints on `infer` type variables allow developers to match and infer against the shape of types, as well as make decisions based upon them:

```typescript
type FirstStringCheck<T> =
   T extends [infer S, ...unknown[]]
       ? S extends string ? S : never
       : never;

// string
type A = FirstStringCheck<[string, number, number]>;

// "typescript"
type B = FirstStringCheck<["typescript", number, number]>;

// "typescript" | "rocks"
type C = FirstStringCheck<["typescript" | "rocks", boolean]>;

// never
type D = FirstStringCheck<[boolean, number, string]>;
```

`FirstStringCheck` matches against any tuple type with at least one element and grabs the first element’s type as `S`. It will then check if `S` is compatible with the string and return the type if it is. 

Previously, we needed to write the same logic of `FirstStringCheck` like this:

```typescript
type FirstStringCheck<T> =
   T extends [string, ...unknown[]]
       // Grab the first type out of `T`
       ? T[0]
       : never;
```

We are becoming more manual and less declarative in this case. Rather than just sticking with pattern matching on the `type` definition, we are providing a name to the first element and extracting the `[0]` element of `T`. 

This new version allows us to place a constraint on any inferred type, as seen in the example below:

```typescript
type FirstStringCheck<T> =
 T extends [infer S extends string, ...unknown[]]
 ? S
 : never;
```

When `S` matches, it will make sure that `S` is a type of string. If it’s not, it will take a false path of `never`.

## ECMAScript module is supported in Node.js <a name="ecmascript-module-supported-node-js">

The support for ECMAScript has been a tough task for Node.js since its ecosystem is built around CommonJS. Node.js extended its support for the ECMAScript module in their v12 update. 

[TypeScript v4.5 has rolled out this support for ESM in Node.js](https://blog.logrocket.com/typescript-4-7-ecmascript-module-support/) as a nightly feature to get feedback from the users. Now, it has introduced two new compiler options, `node12` and `nodenext`, to extend ECMAScript module support. With the arrival of these two features, it enables several other exciting features for us.

```typescript
{
 "compilerOptions": {
     "module": "nodenext",
 }
}
```

## Other breaking changes <a name="other-breaking-changes">

There are a few other exciting breaking changes with this update. Let’s discuss a few of them below!

### Stricter spread checks in JSX

While writing a `…spread` inside of JSX, we have a more strictly enforced rule inbuilt into the library. The values of `unknown` and `never` (as well as `null` and `undefined`) can no longer be spread into JSX elements. This makes the behavior more consistent with spreads in object literals.

```typescript
import React from "react";

interface Props {
   id?: string;
}

function Homepage(props: unknown) {
   return <div {...props} />;
}
```

With the code above, we will receive an error like this:

```plaintext
Spread types may only be created from object types.
```

### Stricter checks with template string expression

If we use a symbol value in JavaScript, both JavaScript and TypeScript will throw an error. However, TypeScript now checks if a generic value contained in a symbol is used in the template string.

```typescript
function keyPropertyLogger<S extends string | symbol>(key: S): S {
 // Now an error.
 console.log(`${key} is the key`);
 return key;
}

function get<T, K extends keyof T>(obj: T, key: K) {
 // Now an error.
 console.log(`This is a key ${key}`);
 return obj[key];
}
```

TypeScript will now throw us an error with the following issue:

```typescript
Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
```

### `readonly` tuples have a read-only length property

The `readonly` tuple will now treat the length property as read-only. This can be seen for tuples with optional trailing and rest element types.

```typescript
function strLength(tuple: readonly [string, string, string]) {
 // Throws an error now
 tuple.length = 7;
}
```

### `readFile` method is no longer optional on LanguageServiceHost

If we are creating a LanguageService instance, LanguageServiceHost will need to provide a `readFile` method. This was a needed change in order to support the new module detection compiler option.

## Conclusion

With a lot of effort and hard work from the team and contributors, we can now try the exciting new features and improvements with TypeScript 4.7. 

There are a lot of handy features we can use to scale and optimize our time and efficiency. [A comprehensive list of release topics can be found through the Microsoft blog](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-rc/), an excellent resource to dive into.

---

## Writing a lot of TypeScript? [Watch the recording](https://blog.logrocket.com/typescript-meetup-recap/?utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930#utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930) of our recent TypeScript meetup to learn about writing more readable code.

[![Write More Readable Code with TypeScript 4.4](https://blog.logrocket.com/wp-content/uploads/2021/07/typescript-4-4-more-readable-code.png)](https://blog.logrocket.com/typescript-meetup-recap/?utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930#utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930)

TypeScript brings type safety to JavaScript. There can be a tension between type safety and readable code. Watch the recording for a deep dive on some new features of TypeScript 4.4.