TypeScript 4.8 was announced on 25 August 2022. One of its breaking changes is written for JavaScript files: a name that is only a type, such as a @typedef, can no longer be imported or exported by name in a .js file, because the import would fail when the code runs.
The release also reports comparisons with an object or array literal, which can never be true, and improves how unknown and unconstrained generics narrow under strictNullChecks.
At a glance
| Release | TypeScript 4.8 |
|---|---|
| Released | 25 August 2022, the day of its announcement on the TypeScript blog |
| Source | TypeScript 4.8 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 4.7 |
| Next release | TypeScript 4.9 |
What changed for JavaScript
Types cannot be imported or exported by name in .js files
An ES module import of a name that has no value fails at run time, and before 4.8 TypeScript let a checked .js file write one: import { Product } from "./models.js", where Product is a @typedef. Under checkJs or // @ts-check that is now an error (TS18042), and so is an export { MyType } of a type-only name.
Two spellings work instead: an import type inside the braces, {import("./models.js").Product}, or, from TypeScript 5.5, the @import tag. To re-export a type, declare another @typedef for it; a @typedef is exported from its module already.
import { Product } from "./models.js";error TS18042: 'Product' is a type and cannot be imported in JavaScript files. Use 'import("./models.js").Product' in a JSDoc type annotation.
/** @param {Product} product */
export function priceOf(product) {
return product.price;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} /** @import { Product } from "./models.js" */
/** @param {import("./models.js").Product} product */
export function priceOf(product) {
return product.price;
}
/** @param {Product} product */
export function skuOf(product) {
return product.sku;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} Comparing with an object or array literal
JavaScript compares objects by reference, so tags === [] is false for every array, empty or not. TypeScript 4.8 reports the comparison (TS2839). Check tags.length === 0 instead.
/** @param {string[]} tags */
export function isUntaggedLoose(tags) {
return tags === [];error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value.
}
/** @param {string[]} tags */
export function isUntagged(tags) {
return tags.length === 0;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} unknown and unconstrained type parameters
Under strictNullChecks, unknown now behaves like {} | null | undefined: a truthiness check narrows it to {}. The other side of the same change is stricter: a @template type parameter with no constraint may be null or undefined, so passing it where {} or object is expected is an error (TS2345). Constrain it with @template {{}} T, or check for null first.
/** @param {{}} value */
function keysOf(value) {
return Object.keys(value);
}
/**
* @template T
* @param {T} input
*/
export function describeLoose(input) {
return keysOf(input);error TS2345: Argument of type 'T' is not assignable to parameter of type '{}'.
}
/**
* @template {{}} T
* @param {T} input
*/
export function describe(input) {
return keysOf(input);
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} Faster --watch, --build and --incremental
The release notes report 10 to 25 percent faster common operations in watch and build mode, and no-change rebuilds about 40 percent faster. File watching also survives a git checkout that replaces files, which used to leave stale errors on screen until tsc or the editor restarted.
What did not change
Importing values works as before, and a JSDoc import type (import("./x.js").Name) has worked since TypeScript 2.9. The new error only affects names that have no value.
Upgrade notes
Most new errors in checked JavaScript come from these three changes.
- Replace imports of
@typedefnames with import types (or@importonce you are on 5.5). - Constrain type parameters you pass to functions that take
{}orobject. Error.causeis now typedunknown, so read it like a caught error.
Gotchas
- A class is both a value and a type, so importing a class by name is still fine; the error is for names that exist only as types.
Sources
- TypeScript 4.8 release notes · TypeScript handbook
- Announcing TypeScript 4.8 · TypeScript blog
Last reviewed 20 October 2026.