Sometimes when you join a NodeJS project and work with legacy code you may face problems with old versions of the packages and some incompatibilities between them. In many cases, they are on particular versions due to specific reasons like breaking changes which don’t allow to easily bump versions, in other cases it is because developers sadly just don’t care about being up to date. One package which definitely should be always up to date is Typescript. This package is used only for development purposes but helps to create maintainable, efficient and safe code. In my case I had a situation where the backend was a big monolith project with a very old version of Typescript and it had to be upgraded to the newest one. Main reason was to improve project security and introduce helpful features of newer versions. I faced many refactor problems where code was not compatible with the updated package. I've decided to bump them one by one to the newer minor version. So from 4.3.5 I had eight steps to reach 5.3.3. I will try to introduce problems and solutions to adapt code to be compatible with the target version. I will also shortly describe changes in every new minor version that I've updated and possible adaptations in the TS configuration file. I will focus only on problematic cases which can force you to do code adaptations, show possible solutions and list only the useful new features in any version bump due to the full changelog being available on Typescript homepage.
4.3.5 -> 4.4.4
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-4-4
Description
First change of typescript library was a little problematic due to a new helpful feature which can greatly increase the project security. Let's talk about that.
Unknown types of errors
First step in the process of updating TS to 4.4.4 was enabling the useUnknownInCatchVariables
flag in tsconfig file. This flag was the main change in that bump and we decided to enable that. In short it forces caught errors in try-catch statements as unknown
and not as any
by default in the project.
What does that change mean to developers? In short there is more complexity in handling errors in the catch section. If you simply log error and eventually push/throw it forward there are no adaptations needed. But if there is some logic or handling properties inside that block you need to ensure that you know directly what type of error it is.
Look at the examples:
Before setting the flag, our error is of type any
After setting the flag its type is unknown
And in first case if we want to access message property of the error we can do it without receiving compilation errors since the type is any
In the second case - after setting the flag, we will receive a compilation error. This is due to the fact that the type unknown
doesn't allow us to access a property which we don’t know about.
What can we do in that situation? We need to ensure that we directly know what we can access in the error object. For example checking that our caught error is an instance of a generic Error object.
But for sure we won’t be able to handle all possible situations. Everything depends on our try statement. It can be: an API call, an object validation, a file access etc. So we need to consider all the cases. Below you see possible scenario when we expect generic Error, AxiosError, ZodError or unknown error.
If we are not sure yet what error type to expect but want to make code our buildable and allow to fix it in steps in some urgent situations we can set error type as any
directly so the code will behave as if the flag wasn't enabled for this part of the code. This is not recommended.
Other new features in version 4.4 worth mentioning
static
blocks in classes This allows you to write a sequence of statements with their own scope that can access private fields within the containing class. That means that we can write initialization code with all the capabilities of writing statements, no leakage of variables, and full access to our class’s internals.
4.5.5
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-4-5
Description
Version 4.5 doesn't have a very long list of breaking changes and new features but has very nice adaptations which can allow you for example to decrease the size of the compiled project.
New features in version 4.5 worth to mention
type
Modifiers on Import Names Theimport type
statement is used to import only the type information from a module. It indicates that you are interested in using the types of exported values but not the actual runtime values. It will give us less bundle size and can cause less load times and overall performance growth because it explicitly signals to the bundler that we don't need imported values. Code is also more readable because we strictly show that we are interested in importing types and they are in one scope not mixed with other modules.
If we want the compiler to warn us about that and force importing types directly with that statement set preserveValueImports
flag in your tsconfig.json
file.
-
support for module
es2022
-
template String Types as Discriminants In your code you can check values that have template string types, and also recognize template string types as discriminants. Example shows us that we can indicate the type of variable and then access its properties.
4.6.4 & 4.7.4 & 4.8.4
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-4-6 https://devblogs.microsoft.com/typescript/announcing-typescript-4-7 https://devblogs.microsoft.com/typescript/announcing-typescript-4-8
Description
Those versions are bundled together because they did not provide us much problems with upgrading but provide some helpful changes for type inferring, type narrowing and type comparison. Read about details in changelog links.
4.9.5
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-4-9
Description
Last ver.4 before the jump to ver.5. provided us updates connected to for ex. editor helpers like:
- "Remove Unused Imports" and "Sort Imports" Commands
- Go-to-Definition on return Keywords
New types comparison possibility:
- Check for equality with
NaN
New operators:
- The
satisfies
operator - lets us validate that the type of an expression matches some type, without changing the resulting type of that expression - Unlisted property narrowing with
in
operator - give us a possibility to check in object if properties exist, for example when we’re getting a response from a server or reading a configuration file
5.0.4
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-5-0
Description
New major version gave us in the first line Speed, Memory, and Package Size Optimizations and provided a lot of new features but also strong changes. In many cases some of you will face problems when bumping to higher versions that do not directly relate for your project but to the packages that you are using inside it which may be incompatible with newer versions. But let's leave those specific cases and focus on new features and general possible problems.
New features
- Decorators - give us a possibility to decorate functions inside classes out of the box
const
type parameters - will allow us to determine readonly inference of the variable in function body- Enums now are unions - by default all enum members became separated types
- Support for
export type *
- allow us to use exported variables only as types in imported files - Case-Insensitive Import Sorting in Editors
Possible problems inside codebase
Some places inside code people used to set type object
or initialization of the variable as and then setting its properties. In TS 5 there were some changes in handling that situation that were not directly mentioned in the changelog. Type object
is the type of all non-primitive values - and with this type, we can’t access any properties of a value (read or write will give us an error). In previous versions even when we use in
operator on that type property it stays as an object and we cannot access properties. In many cases people used casting unknown typed variables as objects. In TS 5 if we use in
operator on object
type for example to check for example
property we extends its type to intersection object & Record<’example’, unknown>
so I guess it’s better to use Records instead of object type in the code base when we define something universal on which we need to do some business logic. In such cases we won't get an error when trying to access property from an object that is not directly defined.
Another case would be when we want to access a property whose key is inside a variable. Such cases may cause security/detect-object-injection
warning or even an error (depending on your linter configuration). In many cases developers say that this error is a false-positive. But is it really? When you define that variable by yourself - true. But when you get that value from outside (for example user input, headers etc.) an attacker could then provide something unexpected, like the name of a method on the prototype chain. In such cases we need to check that the property name defined in the variable exists in our object. If you want to keep your code clean and safe, a good idea will be creating a function to safely retrieve values from objects.
Another helpful mechanism to handle types in such cases is the use of keyof and typeof operators separately or together. For example we want to delete properties from an object base on list of properties in second object to return only those props which are present in both objects. Using keyof on type we can produce a string or numeric literal union of its keys. Using typeof we can determine the type of an object. So using keyof and typeof we can produce a list of keys of object to use for getting data from an object property defined in a variable.
5.1.6
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-5-1
Description
No big changes worth mentioning.
5.2.2
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-5-2
Description
- Couple of nice features but not many big changes that would require adaptations in the code.
- Copying Array Methods - TypeScript 5.2 includes definitions for the methods added to ECMAScript in the "Change Array by Copy" proposal.
- Easier Method Usage for Unions of Arrays -
string[] | number[]
is transformed into(string | number)[]
(orArray<string | number>
) - Comma Completions for Object Members
- Inline Variable Refactoring
5.3.3
Changelog
https://devblogs.microsoft.com/typescript/announcing-typescript-5-3
Description
- Couple of nice features but not many changes that would require adaptations in the code.
- Import Attributes - import attributes is used to provide information about the expected format of a module to the runtime.
- Narrowing On Comparisons to Booleans
- Interactive Inlay Hints for Types