Ten Years of TypeScript
devblogs.microsoft.com
devblogs.microsoft.com
Aside from the known direct benefits of safety and self-documentation, I've found over time that having a pleasant, smooth coding experience and producing elegant code required me to think differently. I work on a project with very complicated and overloaded business logic, but nowadays my code looks very... "algebraic"? state machines within state machines, exhaustive switches everywhere...
Maintaining and modifying such code has been a joy compared to the old ways. Most of the work is just adding a new member to some union or an attribute to a type, following the red trail fixing errors and voilà.
It seems like you might be highlighting the structural typing aspects of TypeScript’s type system versus nominal or concrete types in many others, but that’s been clear for most TS usage for since well before `satisfies` so I’m not sure if my interpretation is right.
But you can check contracts at compile time too. It's quite the same thing as static typing with something like refinement types. That's because, while with contracts we can add preconditions like "the size of this array passed as parameter must be a prime number", with refinement types we can define the type of arrays whose size is a prime number, and then have this type as the function argument. (likewise, postconditions can be modeled by the return type of the function)
See for example this Rust library: https://docs.rs/contracts/latest/contracts/
It will by default check the contracts at runtime, but has an option to check them at compile time with https://github.com/facebookexperimental/MIRAI
Now, this Rust library isn't generally understood as creating another type system on top of Rust, but we could do the legwork to develop a type theory that models how it works, and show the equivalence.
Or, another example, Liquid Haskell: https://ucsd-progsys.github.io/liquidhaskell/ it implements a variant of refinement types called liquid types, which is essentially design by contract checked at compile type. In this case, the type theory is already developed. I expect Liquid Haskell to be roughly comparable to Rust's contracts checked by MIRAI.
Now, what we could perhaps say is that refinement types are so powerful that they don't feel like regular types! And, while that's true, there are type systems even more powerful: dependent types used in languages like Coq, Lean and F* to prove mathematical theorems (your type is a theorem, and your code, if it typechecks, is a proof of that theorem).
Dependent types were leveraged to create a verified TLS implementation that mathematically proves the absence of large class of bugs, miTLS https://www.mitls.org/ (they discovered a number of vulnerabilities in TLS implementations and proved that their implementation isn't vulnerable), and HACL* https://github.com/hacl-star/hacl-star a verified crypto implementation used by Firefox and Wireguard. They are part of Project Everest https://project-everest.github.io/ which aims to develop provably secure communications software.
Good call on Sour Grapes though. I had thought you meant that one. I also reallllly enjoyed it!
"a refinement type is a type endowed with a predicate which is assumed to hold for any element of the refined type. Refinement types can express preconditions when used as function arguments or postconditions when used as return types"
- https://en.wikipedia.org/wiki/Refinement_type
There's even some literature linking contracts and types directly:
"Traditional static type systems are effective for verifying basic interface specifications. Dynamically-checked contracts support more precise specifications, but these are not checked until run time, resulting in incomplete detection of defects. ... This paper explores the key ideas and implications of hybrid type checking, in the context of the λ-calculus extended with contract types, i.e., with dependent function types and with arbitrary refinements of base types."
I absolutely loathe working with TypeScript. The community is the most fragmented I've ever experienced, the silly amount of package managers, builders... TypeScript just doesn't fit with me.
Please correctly blame the ecosystem that you dislike, instead of thoughtlessly using a language as a label for an ecosystem.
I think you are assuming I am not being respectful. However I believe that a blunt reply is definitely showing respect to them in this situation. I do need to be careful not to make comments that are personal attacks (or that could be mistaken for), which I certainly was not trying to do.
(To quote you: "you’re making the claim, you defend it. Otherwise the assumption is just, like, your opinion man." ;)
Hopefully repox can reply, although they don't often comment, so I would guess they are unlikely to check replies. Anyways, definitely off topic!
But I’ll also be blunt when I notice a critique is probably misplaced.
To explain: I quickly scanned some of their recent comments to see if my assumptions could be wrong. When I noticed they were Danish, I then wondered if you were American, so I used hn.algolia.com to scan your comments for “American” as a keyword. You said you were, but at that point I luckily noticed the comment of yours that I quoted, which I just couldn’t resist cheekily passing back to you, because it fitted the discussion on comment quality at a meta level</smirk>.
I often write personal notes like this after the topic has dropped from the front page. If there are a lot of people still reading the comment threads, I try harder to not be a distraction to others and I try to keep on topic.
I really do appreciate your effort to keep my comment quality high - if we all do that for many comments (especially through voting) then the whole community benefits. I don’t realise when I get tone wrong. In this case I have been very slightly downvoted on both comments which is not usual for me - so your comment was an extra help.
While I understand your point, I can't help but feel like the two are connected.
I _do_ have some beefs with the language itself, though. The export syntax concept is awful, the overly flexible type system to which I still haven't seen the point with and of course the whole thing needs to be transpiled to actually run.
Minor annoyances, sure. Easily to blame my abilities and capabilities as the reason for not understanding and enjoying TypeScript. But the ecosystem really hits the nail and by working with TypeScript on a daily basis, I'm forced to interact with it.
The developer community is so fragmented to anything else I’ve experienced. Granted, I don’t know every language and their respective communities, but the whole NodeJS/TypeScript community seems to have no common direction whether it comes to dependency managers, coding styles, transpilers, bundlers or much about any other “best practice” paradigms. And this also something that is pestering packages and the maintainers. I don’t experience this vast amount of fragmentation in PHP, Python, Kotlin or Dart. Whenever you reach out to the community, you’re rarely met with more opinions than you have fingers and toes; sure - not all agree on everything, but there’s more often than not, a sense of consensus on what a common approach to any given problem could be. While I of course could just have been very unlucky for the past two years, the community is the main (but not the only) reason I will not work with anything related to TypeScript or NodeJS ever again.
They are connected, but almost exclusively in the JS -> TS direction: typescript imports are javascript imports, the type system is messy because javascript is messy (whether complete compatibility with javascript was a good goal is another question...), etc.
> But the whole NodeJS/TypeScript community seems to have no common direction whether it comes to dependency managers, coding styles, transpilers, bundlers or much about any other “best practice” paradigms.
Yep.
The Kotlin situation isn't comparable because there is an established community that uses Kotlin in the backend, without using Android.
But perhaps you could compare with Swift: while Swift may technically run on non-Apple systems, that's not the experience of most developers. So it's totally valid to avoid Swift because you don't want to deal with Apple and Xcode.
I think typescript is great but the heavy, heavy dependency on starter kits that come with a dozen build dependencies configured to within an inch of their life is a testimony to how much of TypeScript is voodoo.
Every dependency you add to a project requires new incantations and prayers and probably some sort of sacrifice.
I tried to port a vanilla js browser game to typescript and I found it introduced a lot of complexity to the project. It sucked me into the npm ecosystem and forced me to rewrite every file to use js modules import/export syntax and a bundler like webpack to resolve all the modules business for browsers when none of those were needed before.
Of course I could set TS modules to none but then I lose access to typing of any third party libraries I'm using like PixiJS.
And my CI pipelines are like 10x slower now because I need npm to install and compile and bundle stuff which is really slow compared to my previous CI pipeline which simply concatenated the js files together with `cat`.
Does this sound right or an I missing something? What I want:
To be able to just type annotate my existing JS without needing modules, but also have TS be able to pull in type information of 3rd party libraries by pointing it to the appropriate .d.ts file. I suppose that's having your cake and eating it too in this case.
I write a fair amount of Typescript, but it's frustrating to see it used so ubiquitously, when in a lot of cases it's just not necessary.
A strongly typed language definitely has a place in web UI development though. But my hope is to see it replaced by a WASM based language, or better still, a choice of WASM based languages.
Granted, a checkout, or a clinical case management form (for example) will definitely benefit from the kind of precision a strong type system will encourage.
Beyond that, we should think more about the how and why, because type wrangling can slow down delivery, experimentation, and cannot guarantee the prevention of bugs.
Gmail and Google Maps for example, at least when those projects started out, had no TypeScript in their UI code.
Google Maps, even in it's earliest iteration, far outstrips the complexity of most TypeScript applications in the wild today. And also has a greater requirement for precision than many of the applications that we can call to mind, outside of Finance, Transport, Construction, or Medicine.
People seem to talk as though certain applications were impossible to build without without TypeScript, but that is simply not true.
So TLDR, answering the root of your question, TS is not at all necessary. But it can be helpful. Yet we're in a position now where it's almost intractable, and I don't think that's an ideal situation.
If that's the case, you could add the libraries .d.ts to your project and augment the global.Window interface with types pulled from those definitions. You would then be able to call `window.something` and have the correct type and should work after cat'ing everything together.
Which bundler are you using? If you don't need any of the advanced webpack / rollup stuff, have you tried a fast one like esbuild?
> If that's the case, you could add the libraries .d.ts to your project and augment the global.Window interface
Do you know where I can find an example of this? I've been able to do "npm install pixi.js", which gives me access to the .d.ts file but I'm not sure how to then map those types to the global PIXI object exposed by pixi.min.js without turning everything into a module and doing an "import PIXI" from node_modules of some flavor or another.
> Which bundler are you using? If you don't need any of the advanced webpack / rollup stuff, have you tried a fast one like esbuild?
My bundler is literally "cat *.js > bundle.js" which works well. I have not tried esbuild, though I've read good things about it. I'm sort of gunshy about learning new js tooling like this though ever since I learned grunt and then gulp once upon a time....
tsconfig.json
{
"include": ["index.d.ts"],
"compilerOptions": {
"target": "es2015",
"lib": ["es2015", "dom"],
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
}
}
index.d.ts import * as PIXI from 'pixi.js'
declare global {
interface Window {
PIXI: typeof PIXI
}
}
index.ts console.log(window.PIXI)
There is still an import but because it is in the .d.ts file it won't be included in the runtime code.import type X from "package"
So assuming you are pulling in PixiJS with a <script> tag you could npm install the PixiJS package just for the types. It could look like this:
import type * as IPixi from "pixi.js"
declare const PIXI: IPixi
Then when you compile all the "import type" statements will be removed.
Anyway, ES modules have good browser support these days:
let Typescript have it’s win here… I want to hate it because I loath Microsoft so deeply, but it really has changed everything in the web world, I wouldn’t hire a front-end dev who couldn’t work in it now.
But I agree that it's a shame that the JS ecosystem is such a mess. Granted, dealing with it is essentially Typescript's purpose, but I would love for it to be a full fledged language on its own, divorced from JS. How cool would it be if you could write normal backend apps in it and compile them to native code?
I use Deno a bit and pretend, but it's not quite the same.
TypeScript is too C#-ish for my taste.
Well, still better than nothing, I guess.
Very much not like C#. Frameworks like Angular use it very much like c# though.
Often bette than pure JavaScript, which is also an okay-ish language.
So, I think, overall we're better off
It's mashing things better by making lib authors like about their API.
jQuery typing showed what a shitty api it actually was. You had no idea what you'd get back f on a call, one element, an array, what type of element.
It made it impossible to build reliable software and why everyone hates jQuery now.
So many early projects had "magic" APIs to save you a few characters or lines of code, only to become a maintenance nightmares because without the docs (or TRYING to analyze source) you had no idea what it did or how.
Anders Hejlsberg (Microsoft) and Lars Bak (Google): TypeScript, JavaScript, and Dart
* "Impose no runtime overhead on emitted programs." * "Align with current and future ECMAScript proposals." * "Preserve runtime behavior of all JavaScript code." * "Avoid adding expression-level syntax." * "Use a consistent, fully erasable, structural type system."
I also really liked the callout of their approach to "innovating in the type system around conventions and patterns found 'in the wild' of the JavaScript ecosystem."
This "you can still write the JS-style APIs you want, just safely" is a stark contrast to the Dart/ActionScript/others options mentioned in the HN thread, which said "you have to give up the JS-style APIs you have, and instead write 'basically Java'".
It's also amazing how TS 1.x itself was "basically Java" (in terms of a type system, albeit except being structural), but so many of the TS 2.x type systems innovations (mapped types, conditional types, etc) look as if they were designed in the language from day 1.
One other point, the post calls out they didn't add extra syntax to JS, only types; in this regard, I think TypeScript frankly got lucky by how much JavaScript itself has evolved in the last 10 years. Like if JS had been going through a "10 years of sterility" period like Java did from ~2005-2015, then TS itself would have been greatly hindered and probably would have had to jump to syntax-level changes, like the Scalas and Koltins and other Java.nexts had to do.
So, kudos to JS itself for also evolving extremely well from its ~2010 everything-is-a-callback early days, and letting TS stand on its shoulders.
I think TS team is doing really great job. I would not call it standing on the shoulders of predecessor, more like trying to build something stable on the swamp. JS definitely does not have a positive influence on TS.
TS is not a great programming language, but it is really great accomplishment considering that it supports and extends JS in a such good way.
There are a few things I miss, and a few things that Flow did do better; TS is obviously not perfect. But it's a real joy to use.
As for people writing JS in 2022 without any kind of static typing: I fear for you.
I'll get back to you when I fix the other 97 but until then there's no accounting for taste.
Love that there's an online playground, LSP, and VSCode extension along with it.
A couple not so greats:
Typescript has added a lot fo confusion & chaos to the ESM transition. A lot of typescript code is in ESM style, byt if you pull the package, it outputs cjs or what not. TypeScript 4.8- very recent- is the first to actually have a semi-viable Node.js + ESM story going; being a respectably modern package hasnt even really been possible with typescript until just recently. Not fully typescript's issue, but writing a package.json that fully helps consumers is quite difficult, and there's a lot more to grol.woth typescript in the mix. There's such a long long tail of typescript packages that are going to be the long long hold up for getting to ESM cleanly. It's not that hard to change, but awareness is just low, and friction is high while we are still so stuck-in-the-middle. Typescript makes writing code easy, but outputting a good respectable usable library has been impossible & is still not easy. Woe.
I really hope EcmaScript does get type annotations, which could eliminate so much of the need to compile & let typed code just run untyped, elide so many of these difficult transpilation challenges.
Another major issue for typescript is that it is stil so compile-time focused, that type metadata isnt kept intact. It's wild to have such a vast typing system, but to just throw it all out at compile time. To be fair, object metadata in js has been tied to annotations, which has been long long delayed, a huge struggle for the language, so there's not clear targets for how to output type information, but there have been some goes, some works to add runtime type information to typescript & there's so little follow up, so little engagement. All TypeScript feels like such a sharply more limited less useful less ambitious project than what it should be doing, than what a real language+runtime would be. It feels like typescript lives in the shadow of a much more clear & visible greatness.
Im not sure what Im experiencing in jest. It's definitely not the same level of typechecking. But there's definitely something there & it's definitely much faster start time. Thanks for writing; Im curious too.
[1]https://github.com/microsoft/TypeScript-wiki/blob/main/Perfo...
But they admitted namespaces, enums, and interfaces into the language (the latter becoming more and more confusing as type aliases got more expressive),
> "Avoid adding expression-level syntax."
Is "as", "is", or "satisfies" expression-level?
> "Use a consistent, fully erasable, structural type system."
But the enums!
And decorators. But this was very early on and they won’t ever do it again unless there’s a drastic change on principle and probably a reorg of global proportion. They categorically reject anything with runtime implications now, and to the point of decorators are actively working to align them with the standard as it’s approaching stability.
> and interfaces into the language (the latter becoming more and more confusing as type aliases got more expressive) […] Is "as", "is", or "satisfies" expression-level?
No. All of this is completely separate from the runtime and on a standards course to be treated effectively as comments.
> But the enums!
I’m one of the minority who actually likes TS enums, but I strongly suspect they’ll be deprecated, alongside namespaces, as soon as there’s general consensus around types as comments. The TypeScript team considers these mistakes and would very much like to be able to drop them. I’d welcome that too even though I quite like enums.
The fact is TS has considerable backwards compatibility expectations, and aligning their mistakes with their goals is great on principle but something which would require thousands upon thousands of hours of labor for people to accommodate.
You can snipe all you want, but if you think it’s that easy to resolve maybe I can direct you to https://github.com/microsoft/TypeScript/pulls
I’m not affiliated with the team in any way but I’m almost totally certain they’d welcome a contribution that gets them closer to their stated principles where historical designs are entrenched, without breaking workflows for thousands of people and interrupting releases for millions.
- exact object type
- match expression
flow is still better at:
- OO - nominal typing for classes, conforming to liskov substitution principles
- first class opaque types
- first class exact object types
- better flow based inference
- comment types - no extra dsl, full access to the language, so simple, so powerfull for the times you don't want transpilation phase
- [edit] spread types map to spread in runtime
This is easily solved by a tagged type in TS [1], though a bit of syntactic sugar over it would be nice.
Tagging is good for making sum types out of union types, but that's not enough for class inheritance.
Flow does it better by having first class opaque type aliases btw (and having nominal types with oo inheritance support on classes of course).
Though I do miss an easy way to create something like a type Email of string, as it conforms to a set of rules. But that’s a small price to pay.
For making Email type distinct from string, you want opaque type (first class in flow) which you can emulate as tagged type in ts.
I know of TS’ opaque types, but it always feels dirty. So here I am, relying on named params instead.
The specifics of the proposal will likely shift a bit since it's still stage 1 (2?).
Flow's team have made it clear [0] that they don't care about usage outside internal Facebook projects, so since Facebook (from what I hear) uses few external JS dependencies, support for library types is terrible - there's no practical way for library developers to ship Flow types with their library. The third-party flow-typed repository helps somewhat, but relatively few libraries are covered there, so in practice you'll need to write your own typedefs for most libraries you want covered.
When I worked in a Flow codebase, I enjoyed writing Flow types for things, but it was only ever fun in a sudoku-puzzle-solving, Zachtronics-game kind of way. The tools provided were technically enough to express whatever you wanted, but it always took some lateral thinking. TS certainly allows for the same kind of type astronautery, but as a non-library app developer you never have to resort to that; you can always fall back to something slightly less strict that covers you from most real-world problems without the all-or-nothing strictness Flow mandates. In the end I was the last holdout on the team advocating for not migrating to TS. The migration went well and the team was more productive for it.
It's fun to think about an alternate universe where Facebook gave Flow the investment and community support it deserved, and today it's part of a beautiful React/Flux/Jest/Flow/Reason ecosystem (eventually leading to a complete takeover of the browser frontend by Ocaml). For real work, though, Typescript gets the job done.
[0] https://medium.com/flow-type/clarity-on-flows-direction-and-...
What would that achieve that intersection types don't already?
With that said, TS is definitely a blessing; I recently had the privilege of migrating to it after having written a hobby project in plain JS, and the difference in usability between the two is night and day. But I can't help but feel that I've seen this all before years ago in AS3.
I think MS, who had a browser monopoly at the time, was never going to agree to something that made Adobe Flash more important. It seems crazy now, but at the time it seemed like a real possibility that browsers could end up mere shells for the real internet runtime, Flash Player.
Microsoft and Yahoo voted against ES4 adoption at the ECMA committee, cause Microsoft had, what was it called again? Silverlight, Their flash alternative to promote so they weren't interested in improving Javascript in anyway. Due to Microsoft stupidity, carelessness for standards, the web lost a decade of improvements.
Adding insult to injury in that absurd era, Microsoft had its own AS3 implementation called JScript.net before C# became more popular.
I'm surprised nobody ever wrote a book about this saga, it's completely absurd and petty but there are plenty of stories to tell.
I remember writing a long-running digital display app and the memory would balloon to the point where we would use a product (mdx/mdm?) to restart the flash player periodically.
The main misfeature is their dogmatic refusal to rewrite import paths (citing the “Preserve runtime behavior of all JavaScript code” principle mentioned in this article). Here’s a good summary of the problems this causes: https://github.com/microsoft/TypeScript/issues/42151
I’m curious, how many people are using TSC only for type-checking, and a different system (eg esbuild or ts-node) to actually compile/bundle/execute their code?
I think TypeScript would be even stronger if they focused fully on type-checking, and relaxed some of those dogmatic restrictions (and the many, many confusing config options) imposed by the JS code generator.
Look at this thread. So many comments about how great the language is, but with the caveat about how horrible the ecosystem is. Think about that for a second and maybe you'll get it.
What is it about strongly typed languages that attracts developers enamored with their own cleverness? I'd rather work with Java. And I loathe Java.
To me it seems more logical to use something like a double-colon or some other syntactic sugar to imply `satisfies` when you declare a variable... and possibly a <Satisfies Classname> or just <Classname*> prepend for casting.
Looking forward to more great ideas in the future of ways to 'fix' Javascript.
I'd never heard of this, so here are a couple things I think are true after poking around a bit:
• TypeScript is a strict superset of JavaScript that compiles to JavaScript, while ReScript is unique language that compiles to JavaScript.
• ReScript is apparently a re-brand of "Reason(ML)", which is apparently derived from OCaml.
type Foo = { prop1?: number; prop2: string; prop3?: boolean };
I'm sure there is some value to having this than not having it at all, but I find it hilarious the amount of times I'm writing TypeScript that I can't even trust the types given to me. Of course this isn't a fault of TS itself, but I think what TS has inadvertently done is give programmer yet another outlet to express their bad habits.
It’s not that I hate it, it’s just that TS/JS does not give you anything in terms of built-ins. This testing suite is so fragmented, it’s this ugly thing that I dread working with.
Anyone have advice for repos I can use that have rich testing frameworks? Or books? I’d love to learn to love TS, but it’s not there yet.
Some of the definitions can be very complex, a dev needs time to decrypt what other dev wrote and what I can finally use as a parameter in a function. Sometimes even if you put the correct parameters, it still doesn't work, and you need to fiddle around with webpack or other config files or spend hours on google to find a possible solution for a "simple" thing.
I just want to create code and solution... I used to work on a project with typescript only, and it was a huge pita, especially because the ts was very complex. Never again typescript projects for me.
I don't thing your problem is Typescript.
To me, that's saying something.
I’m curious about this, and runs counter to my understanding of typescript. Do you have any sources on this?
I’m googling a bit but not really finding anything.
Wrong. TS doesn’t bundle files. TS doesn’t produce code unless you target an earlier ES version than what you write, in which case there’s no way around it: native for-of loops and await/async will always be faster regardless of what you use to transpile it.
I think you’re confusing the tool with something else.
Non-const enums and namespaces are probably the only aspects of typescript that actually have any significance at runtime, but they get compiled into simple objects. The compiler output is very close to what you'd write by hand. Take a look at this compiler output here [1].
Unless you're constantly recreating enums and namespaces inside of a loop (which you'd never do in real life code), I can't imagine there'd be any performance penalty.
[1] https://www.typescriptlang.org/play?target=99#code/HYQwtgpgz...
It would be nice if not only type system but all ts would be erasable, ie. so you can write ts to js transpiler by replacing ts code by spaces and the remaining part would be valid, runnable js.
With enums, modules/namespaces it's violated. And if it's violated already then they should just go ahead and support things like match expressions and move statements to expressions coffeescript style. Because you have insight into types, performance would not suffer - you'd pay overhead of expression-instead-of-statement if you'd actually mean to use it, which is great tradeoff.
I think in general the js/ts community is starting to realize that if we're going to have all of these build tools, we might as well use them to do more than just format, transpile, and bundle. Svelte has sort of pushed this idea with its abuse of the js label feature (it can detect when a variable is reassigned if you prefix it with the dollar sign label, which is technically not possible with js, even with proxies). There's also React Forget, which is promising to completely rewrite components at compile-time to avoid all of the gotchas that hooks have (and improve performance) [1].
In terms of making all of ts erasable, you can technically just write all your type definitions using js-doc comments, in which case the compiler can just be run with the --noEmit flag, and then the code is fully valid js without any modifications.
Turning `export function a(){}` into `exports.a = function a(){}` hardly qualifies as generated code, much less "slower than hand-written"