JavaScript with TypeScript's type system? Understand everything about the new proposal!

technology10 min

byLucas Santos

This page was machine translated. Read original / Suggest a fix

Recently a news story caused a lot of buzz in the JavaScript development community, with special focus on TypeScript.

The big news was the presentation of a proposal crafted by Gil Tayar about how it would be possible to include data types equal to TypeScript’s natively in JavaScript, meaning essentially removing the TypeScript compilation step.

This left many people very stressed saying that JavaScript should always be a dynamic language without native types, however, a large portion of the community was also in favor of this proposal! Let’s understand how it all works!

Context#

To understand why this proposal is so important, first we need to understand the context on which it is based and why it was created.

How it all started#

Over the last decade, several people and companies have tried to add static typing to JavaScript in an attempt to make development of more complex systems able to remain stable and scalable over time.

Initially, JSDoc did a great job explaining what was happening in the code, especially regarding the parameters coming in and going out of a function. Much of JSDoc’s popularity comes from the fact that you don’t need to do anything extra in your pipeline or in your runtime to include types, they were read as comments:

/**
* Function to add two numbers
*
* @param {number} n1 First value to be added
* @param {number} n2 Second value to be added
* @returns {number} The sum of the two values
*/
function add (n1, n2) {
return n1+n2
}

The idea was very good, but JSDoc suffered from two main problems:

  • Support for JSDoc in text editors natively was scarce and not many took into account the rules of these comments to actually type the code. It worked as a cool way to automatically generate documentation, but the types weren’t enforced, so runtime errors were very common
  • The documentation generated by JSDoc wasn’t part of the code, so you didn’t need to actually update a JSDoc comment if you changed the function, meaning the types ended up becoming outdated very quickly and often, like documentation, ended up being useless after a long time without updates

There is a third problem. JSDoc doesn’t allow all the convergences of types and features that TypeScript would support, moreover, it’s extremely verbose to write complex types using JSDoc.

If you want to know more, this question answers several doubts about JSDoc.

The idea of JSDoc was that you could just comment for documentation purposes, but the proposal was never to enforce data types on any code.

Type systems#

As time went on, other companies like Google, Facebook, and Microsoft began developing their own systems. And from there came, respectively, Closure Compiler, Flow, and TypeScript. The latter being the one that had the most traction and success.

These type systems, especially TypeScript, solved the two main problems that JSDoc had, because now types became part of the code, meaning you couldn’t update the types without updating the code and vice versa. In the same way that they were now enforced on the code you were writing, so there was no way for you to “get around it” and return a different type than defined.

While Closure Compiler and Flow had a less invasive proposal in the language, TypeScript left behind all the old customs and actually replaced JavaScript with its own syntax, becoming a superset of the original language, meaning all JavaScript code is valid TypeScript code, but the opposite isn’t always true.

function add (n1, n2) {
return n1+n2
} // Works in TypeScript
function add (n1: number, n2: number): number {
return n1+n2
} // Doesn't work in JavaScript

This alone caused a lot of mess in the community, because now people would need to use TypeScript’s compiler (the tsc) to compile the code before running it, meaning adding another step to an already complex JavaScript code pipeline, but okay!

This wasn’t a big problem in 2012 because many browsers didn’t have constant updates, other browsers implemented their own versions of the JS compiler, and we still had the Internet Explorer problem, which I won’t go into detail about. So compilation steps were normal, you had to compile your code to support the N oldest versions of the current browser and also the latest version of Internet Explorer at some point.

So adding another component to that compilation wasn’t a big problem, in fact, the step of creating a code bundle in a single super optimized file was quite common, it wasn’t any problem to complete the pipeline with another step that, for the most part, would just remove the types from your code to make it valid JavaScript code again.

But as time went on, browsers became even more stable and started having native support for modules so the bundling step became more of an optional optimization step than a necessary compatibility step, so TypeScript ended up becoming the thorn in the side because now it added a step that maybe didn’t need to exist.

This was mitigated with TypeScript’s already present feature of being able to check types in JavaScript files as well, without needing a file with a different extension, becoming more of a linter than a true type checker. So you could write code like this:

/**
* Function to add two numbers
*
* @param {number} n1 First value to be added
* @param {number} n2 Second value to be added
* @returns {number} The sum of the two values
*/
function add (n1, n2) {
return n1+n2
}

And add a small //@ts-check comment at the top of the file to make TypeScript check your code for type inconsistencies. But if you only wanted to add type support to the IDE, it was totally possible using VSCode.

Where we are today#

As we can see from the proposal itself, in 2020 and 2021 the State of JS satisfaction survey, the most important and comprehensive survey from the community about the language, showed that the most requested feature in the language was static types.

Furthermore, as we can also see from the proposal itself that TypeScript is among the top 10 “languages” most used in the world in recent consecutive years.

So why not have the best of both worlds? Both the static typing of TypeScript as part of the code (with the syntax and everything) and not having to add something to a new pipeline? What if JavaScript was able to, by itself, ignore the types so you could run your code directly? That in a completely optional way, of course.

What’s the idea#

One of the biggest problems, which also contributed to the delay of this proposal, is that when developers had to answer the question: “How should types be in JavaScript?”, some people simply said they should be completely ignorable, comments, others said they should have some kind of meaning so the compiler could know how to better optimize the system.

There were more radical ideas saying that the type system should alter the program’s semantics and dictate what could or couldn’t be done, making JS a strongly typed language.

As time has gone on, the community has increasingly converged on the idea of type checking at compilation time, but ignoring types at runtime, meaning types would be like comments in the code, giving an idea to whoever was developing, but wouldn’t be enforced in the browser, where it would run as if it were normal JavaScript code. The following image explains this idea very well.

And then, for those who wanted to have their types checked, they could use tools like TypeScript does today.

But then you might be asking yourself: And what’s the advantage of all this? If types are going to be optional, why not just leave everything as it is today?

The answer is that having types in JavaScript natively can reduce even further the barrier that developers have in entering the language without needing to understand what TypeScript is first, meaning it would be natural for anyone developing JS that there are optional types. This was done before, with PHP in its version 7. Just by not requiring someone to know something completely new, we dramatically reduce the barrier to entry and use of the language.

How it’s all going to work#

Since we’re simply adding a way to turn type notation into something native to the language, but in the form of comments, we’re essentially talking about the possibility of adding new tokenization and interpretation rules to JavaScript. So this proposal essentially boils down to including JavaScript’s ability to understand and ignore types in the code.

For this, some fundamental parts of TypeScript need to be added to the JavaScript engine:

  • Support for syntaxes like type declarations with : in variables, arguments, and functions
  • The optionality modifier ?, making an argument optional, for example (arg?: number)
  • External type declarations with interface and type, as well as type extensions like Pick, Omit (not confirmed)
  • Support for generics export type T<G>
  • Assertiveness modifiers like ! in const a = foo!, and as as in const b = foo as string

And then we fall into features that are a bit more complicated to separate from the code, because they contain a deeper and broader meaning, for example, visibility modifiers like private, public and protected and even abstract classes and methods with abstract.

These are open for debate to be included in the scope of the language but, at least in my view, I don’t see a good enough way to turn these structures into comments, since they’re essentially including more semantics in the code just by being there.

Unsupported types#

However, some TypeScript types won’t be able to be supported because they essentially contain code behavior, such as the case of enum, which essentially creates a new code block at the end of compilation.

Another unsupported type would be namespaces, which are creating another scope outside the current scope of the function or even of the type.

And the third unsupported type would be what are called parameter properties, which is the act of declaring properties that are initialized together with the class directly in the constructor, for example, in JavaScript, this:

class foo {
#privado
publico
constructor (privado = 0, publico = 1) {
this.#privado = privado
this.publico = publico
}
}

Would be equivalent to this in TypeScript:

class foo {
constructor (
private privado: number = 0,
public publico: number = 1
) { }
}

However, field declarations inside classes using type annotations are supported.

Types open for debate#

Some types are supported by the idea of “types as comments”, but would greatly increase the scope of the original proposal, so they are open for debate on the official repository.

  • Ambient declarations with declare serve to inform type checkers like TS that some types exist in scope, or even some module, even when that type or module doesn’t have types declared. They’re the famous .d.ts files.
  • Function overloads **is something that can be implemented in the type system through the use of redeclaring the function signature but omitting its body.

Conclusion#

While it’s unlikely that JavaScript will accept runtime type checking, this proposal still creates some hope that we could, in the future, see some kind of optional native runtime checking option.

However, even if there are studies showing that type checking in JavaScript adds negligible computation time, still, it’s not in the nature of the language and, historically speaking, I don’t believe that this kind of functionality will ever be available.

To finish, I want to remind you that this is a stage 0 proposal, meaning it’s just a draft that can change at any time.

If you don’t know how JavaScript evolves and want to understand a bit more about the proposal system, take a look at my video on this subject:

Play

As long as it doesn’t reach at least stage 3, we can’t say whether it will or won’t be in the future, and this answer can take years. As it was, for example, the case of the temporal proposal which has been open for at least 4 years.

So we can only wait and, of course, comment and help in the discussion of the proposal there on Github!

Footnotes#

  1. For a list of all supported types, take a look at the official proposal. ↩