13 ms·
Tips for Performant TypeScript
- joubert 6y agoWorth emphasizing the first sentence: faster compilations and editing experiences i.e. not runtime perf.
- tantalor 6y agoWell, of course not. You can't "run" TypeScript.
- karmakaze 6y agoNo--but certain constructs could translate to slower than expected execution speed.
- cogman10 6y agoThat's something that needs proof from a profiler. Intuition on what's "slower" is really awful, particularly for JITed languages. I don't think you could give general advice on what is slower as that's a constantly moving target.
- not_kurt_godel 6y agoIf TS could compile to asm.js you certainly could. (The fact that it can't is a big reason why I believe it's a fundamentally bad solution to writing JS scaleably.)
- tantalor 6y agoI think you mean Wasm; asm.js is dead. Looks like assemblyscript compiles "a strict variant of TypeScript" directly to Wasm: https://github.com/AssemblyScript/assemblyscript https://github.com/AssemblyScript/assemblyscript This is pretty close to "running" TypeScript.
- not_kurt_godel 6y agoI tend to refer to asm.js and wasm interchangeably, but yes, wasm would be the technically preferable term. Never heard of AssemblyScript, but yeah, it or a statically typed language like it is where frontend development needs to go. In concordance with my point about TS being fundamentally unsuitable, the AssemblyScript docs (https://www.assemblyscript.org/basics.html#strictness https://www.assemblyscript.org/basics.html#strictness) say: > WebAssembly is fundamentally different from JavaScript, ultimately enabling entirely new use cases not only on the web. Consequently, AssemblyScript is much more similar to a static compiler than it is to a JavaScript VM. One can think of it as if TypeScript and C had a somewhat special child. > Unlike TypeScript, which targets a JavaScript environment with all of its dynamic features, AssemblyScript targets WebAssembly with all of its static guarantees, hence intentionally avoids the dynamicness of JavaScript where it cannot be compiled ahead of time efficiently.
- arc0 6y agoDeno[0] supports running TypeScript without needing to compile it to JS. [0] -- https://deno.land/ https://deno.land/
- nicoburns 6y agoDeno still compiles it to JS. It just does it for you.
- capableweb 6y agoWhich at one point gets compiled to machine code. At one point, you have to stop and say, "Yeah, this does effectively runs TypeScript", otherwise as I said, you'll end up saying nothing gets run but machine code. While that's correct, not very useful.
- nicoburns 6y agoI think the key point in this case is that the performance characteristics of Deno don't significantly differ from TypeScript independently transpiled to JavaScript and run in Node because the compilation pipeline is more or less the same (Deno is using tsc and v8 internally)
- ulucs 6y agoI think the distinction will hold when you can access type information at runtime
- ragnese 6y agoWhy? Haskell doesn't even have runtime type information. So what would you call the stuff that runs after I compile my Haskell code? Not-Haskell?
- staticassertion 6y agoYou can't "run" C, or Go, or Javascript, etc.
- the_af 6y agoI think the parent comment meant that typescript is a static checker, which means it's not exercised at run time but in a prior step. Therefore "performant typescript" means to shorten the time it takes to perform these static checks, not the time it takes to run your code. In contrast, when people talk about optimizing C code they most often (but not always, of course) mean to write code that runs fast.
- ragnese 6y agoI find the mentality around TypeScript to be bizarre. "It's just a type checker for JavaScript" - not really. It's a language that transpiles to JavaScript and it happens to be a superset of JavaScript (namespaces and enums, anyone?). But saying that it's just a layer on top of JavaScript sounds like saying C++ is just a layer on top of C.
- the_af 6y ago> C++ is just a layer on top of C C++ used to be just a layer on top of C, but not anymore. If it still was, it wouldn't be a bad thing to say. Type checks that run during compilation/transpilation/typechecking do not "run" as the code is executed. This is what happens with Typescript, and that's what the original commenter was saying.
- choward 6y agoI'm not sure I understand your point. Typescript is a language with a type checker and compiler. It's the same thing with C. Typescript has to be compiled to run. C has to be compiled to run. When I think of language performance I always default to thinking about runtime performance.
- the_af 6y ago
- asutekku 6y agoThat’s a topic not related to typescript, it’s javascript after all.
- nicoburns 6y agoIn practice I don't think there are, but it's not inconceivable that there could be performance considerations specific to how TypeScript generates JavaScript.
- corytheboyd 6y agoI assumed it was going to be about compilation as the subject is TypeScript, which feels like a safe assumption. I suppose there could actually be runtime consequences of TypeScript too, as you’re still writing runtime code, just through a game of telephone with TypeScript :p
- ragnese 6y ago> I suppose there could actually be runtime consequences of TypeScript too, as you’re still writing runtime code, just through a game of telephone with TypeScript :p Right. One of the first things you learn while doing TypeScript is that interfaces don't exist after compile time. So in general, it's better to write interfaces and use plain-old-objects than to use `class`, which generates real JavaScript code. Within reason, of course. Classes are still useful. But it's not necessarily the first thing to reach for.
- renke1 6y agoThat's rather sad, union types are really what I like most about TypeScript. This might explain why VS Code feels so slow sometimes when type checking, because I have a few types that rely heavily on unions.
- ojosilva 6y ago> However, if your union has more than a dozen elements, it can cause real problems in compilation speed. For instance, to eliminate redundant members from a union, the elements have to be compared pairwise, which is quadratic. This sort of check might occur when intersecting large unions, where intersecting over each union member can result in enormous types that then need to be reduced. This statement makes me think... how come the TS compiler is not using something like a hash/map (object) of union members to basically ignore redundancy? Or any other strategy really. The union of unique values in 2 or more arrays is a classic CS problem for which there are many, many performant solutions. Anyone familiar with the TS internals? Maybe I'm not seeing the forest.
- jitl 6y agoWhat you’re not seeing is that TS is a structurally typed language, not a nominally typed language. Two type signatures defined in different places with a different name may be assignable to each other - so typescript usually has to deep-compare, recursively, the two types until a conflicting field is found.
- Etheryte 6y agoNote how some of these are directly at odds with writing easily maintainable code. For example, using type annotations for return types [0]: - import { otherFunc } from "other"; + import { otherFunc, otherType } from "other"; - export function func() { + export function func(): otherType { return otherFunc(); } Not manually annotating the return type here reduces both the work you need to do when refactoring and the visual overload when working with the code. In my opinion, both of those are far more important than small changes in compile time. [0] https://github.com/microsoft/TypeScript/wiki/Performance#using-type-annotations https://github.com/microsoft/TypeScript/wiki/Performance#usi...
- staticassertion 6y agoI honestly find it so annoying when return types aren't annotated. It's considerably less overhead for me when I can look at the signature and see the return type, even if a few characters are added.
- jasonhansel 6y agoAgreed. I think Rust had the right approach here: require type annotations for function parameters & return values, but perform type inference within functions. That makes it obvious when a function's public interface (so to speak) has changed.
- aszen 6y agoNot really, rust is unusable for interactive programming because of this reason. I understand that's not rust target domain but still there's downsides to be had with their approach.
- ImprobableTruth 6y ago>rust is unusable for interactive programming because of this reason I'm not sure I understand, could you elaborate more?
- jamamp 6y agoOf the three code-related sections, I think only Using Type Annotations makes sense. While the compiler _can_ infer the return type and the user can mouse-over the function to see what the language server has determined the type to be, I feel that explicitly noting what the return type is preferred. Yes, the compiler can act more quickly, but also it makes it more clear quickly to others working on the same project what the function does. Even in languages like Swift which are happy to use type inference, you still must annotate your functions. The other two code-related sections seem odd, to write code that improves compile-time performance. It would be beneficial to see compile duration differences between projects that heavily use union types and projects that don't. Otherwise, changing your coding style and not using explicit features of a language that are hard to find in other languages seems counter-productive. That said, the actual compiler configuration changes that follow seem very useful, from someone who doesn't write much TS.
- corytheboyd 6y agoMy only issue with this is that it introduces the possibility for human error. It’s rare, but if the returned object fits more than one type (say, a superclass vs concrete class instance) the incorrect one could be selected and the code still compile. Is this even a valid concern? The only time I see manual type annotation cause problems consistently is with React.FC<Props>: (props: T). People don’t always remember to provide their props interface as the generic, and instead directly annotate the props argument of the function. This is a subtle issue that breaks the “magic” props added by React (like children), leading to people adding their own children definitions to their props interfaces d’oh
- codeflo 6y agoI personally find that manual return type annotations actually prevent some errors. A common case: I forget the return statement in one branch, and TypeScript is happy to infer something like number|undefined as the return type.
- Klathmon 6y ago>leading to people adding their own children definitions to their props interfaces IMO this is a feature not a bug. Type definitions aren't just for the compiler, they're also for the developer. Being able to see at a glance which components expect children and which don't is really valuable. Not to mention that there are situations where I want to restrict what kinds of children can be passed in (think render-props, or named slot projection patterns). In other words, just because React supports an implicit definition of what a "child" can be doesn't mean that my specific component supports all of those same possibilities.
- Wintamute 6y agoIt feels like some of these suggestions reduce the readability and maintainability of the TypeScript, as well as rail roading you into patterns that perhaps don't suit your architecture. Manually annotating return types of functions rather than letting them be inferred, and preferring interfaces to intersections for example. I think I'll largely continue to write my TypeScript in the way that seems best to me using the syntax provided to me, and let MS hopefully optimise these issues under the hood over time.
- stupidcar 6y agoWe've encountered huge performance problems using TypeScript with the styled-components library. Something about how that library is typed results in multi-second delays before the VSCode intellisense updates on any file in our project. It's absolutely agonising.
- renke1 6y agoYeah, I had the same problem, kind of made me switch to Tailwind, actually. The places where I used dynamic rules based on props were replaced with dynamic classes.
- brendanmc6 6y agoYup! Among other type definition problems. The types are very poorly maintained. I’m planning to give Emotion a try as it has the same ‘styled’ api.
- danlugo92 6y agoThis should be abstracted away, optimizing for compiler performance feels straight out of the 80s.
- smt88 6y ago> optimizing for compiler performance feels straight out of the 80s I think you're misunderstanding this situation based on the suggestive word, "compiler". TypeScript's type system is Turing-complete. That means you can theoretically write programs with the type system that cause the compiler to never complete. Instead of thinking about this as optimizing a compiler, think of it as optimizing runtime performance of a program by better understanding the language that the program is written in.
- smt88 6y agoFor people who have not used TypeScript: I've never had issues with the TypeScript compiler being slow, and I write my types with as much complexity as the underlying data/situation demands. I have several projects that consume dozens of REST APIs, and I have types for all of their requests and responses, and my compilation time is still fine.
- simlevesque 6y agoOnly problem I ever had is some weird deep nested graphql autogenerated types. Otherwise it just works.
- DanRosenwasser 6y agoIn chatting with users, I've heard the generated types vary in quality by a lot depending on the tool you pick. Supposedly this one is pretty good: https://graphql-code-generator.com/docs/plugins/typescript https://graphql-code-generator.com/docs/plugins/typescript
- momokoko 6y agoI’ll match your anecdote with my own. I’ve worked with several large codebases in TypeScript and slow compilation is a common problem I’ve dealt with. To the point where some people I’ve worked with specifically chose to work on desktop PCs for the extra CPU. With that said, compilation performance is something most compiled languages deal with so I wouldn’t treat it as some kind of flaw specific only to TypeScript
- erikw 6y agoInteresting- they mention an example of adding a return type if you've "identified a slow part of your code". How would you identify that the complier is struggling in a particular area?
- DanRosenwasser 6y agoGlad you asked! We're trying to find good ways to help people investigate this. One of my colleagues wrote up on his work on TypeScript 4.1's `--generateTrace` flag: https://github.com/microsoft/TypeScript/wiki/Performance-Tracing https://github.com/microsoft/TypeScript/wiki/Performance-Tra... That page can give some guidance on diagnosing what the compiler's time is going into. Most users won't need to do this, but there are plenty of bigger teams/organizations that are really invested into TS and do want to keep their build times lean.
- dsagal 6y agoI had much hope for project references with --incremental and --watch mode to be quicker than using external tools, but they still seem to recompile a lot (and so take a long time). Or maybe just because I am some tsc versions behind?
- jitl 6y agoI had the same experience on Typescript 3.4.5. I hear that project references with incremental/watch are more performant on newer versions. (Notion is stuck on 3.4 because of this issue: https://github.com/microsoft/TypeScript/issues/31445 https://github.com/microsoft/TypeScript/issues/31445)
- ht85 6y agoWould you care to share a reproduction - or the actual thing - of the problem this behavior creates in your codebase? I'm sure you've already spent time on this, but it piques my interest that there isn't a good solution to the issue.
- DanRosenwasser 6y agoHi all, original author of the wiki page here. Please take the advice with a grain of salt. Specifically - union types aren't always bad, DON'T avoid them - DON'T feel the need to annotate every single thing Please apply a critical lens as you read through this page. The document was meant for users who've hit rough perf issues, and we tried pretty hard to explain the nuances of each piece of advice.
- presentation 6y agoDo you have any tips on diagnosing what a problem might be? I don’t know how to interpret the diagnostics flag output to actionable changes to my company’s code, and while I can blindly do what the wiki article suggests (found it a while back when trying to figure out what to do) I would much prefer if I weren’t just trying time consuming changes to our large codebase randomly... been stuck with slow compile performance with typescript for almost a year now and I can’t tell what I’m supposed to do, or if the TypeScript compiler is just too slow.
- LinguaBrowse 6y agoThanks for these tips; really interesting. Getting a clearer understanding of the differences between types and interfaces, and got some confirmation of the merits of writing explicit return types. I was wondering whether any more could be done to improve editing performance when large .d.ts files are included. This is a problem in particular for NativeScript, which has a vast set of large types files to include to express the entirety of the iOS and Android SDKs, e.g.: https://github.com/NativeScript/NativeScript/tree/master/packages/types-ios/src/lib/ios/objc-x86_64 https://github.com/NativeScript/NativeScript/tree/master/pac... In fact, the skipLibCheck flag was originally developed to improve NativeScript compile time: https://github.com/microsoft/TypeScript/issues/8521 https://github.com/microsoft/TypeScript/issues/8521 Unfortunately, editing still feels slow to me when including NativeScript’s iOS/Android types (have to wait 1-2 seconds after any keystroke for any IntelliSense to appear); beyond including fewer of the types files, could editing performance be improved somehow? Thanks!
- musicale 6y agoIn spite of the HN headline, I'm glad the wiki page doesn't use the word "performant." However, I was surprised that "performance" refers to compilation/editing speed rather than execution speed.
- brundolf 6y agoIf performance is enough of a problem to warrant a post like this, I'm a little surprised that tsc is itself still written in TypeScript. I get the benefits of that, but it doesn't seem super uncommon that the project gets pushed to its limits these days.
- jariel 6y agoPlease use the right design idiom for the project unless it's literally make or break on performance, which should be very rare. Some of these things can be used, yes, those that don't change the approach to the project absolutely, but otherwise ... stick with 'the right pattern' for the architecture.
- grammarnazzzi 6y agoFYI: Nowhere in the linked page is the word "Performant" used by Microsoft. Definition: Performant adjective. COMPUTING. Word used by idiots desperately trying to appear smarter than they actually are. "Me write performant software! ME SMART!"
- reificator 6y agoThere are however, 20 matches for `Performance` including the header and not including the URL. Is your objection that the word performant does not match your arbitrary criteria for a `real word`? I don't want to put words in your mouth but I can't figure out any other meaning behind your comment. History tends to look unfavorably on the stance of _____ is not in the dictionary so it's not a real word. While it can initially be a popular argument along the lines of "They're outsiders because they use a new word and therefore we should bond over hating them" it tends to very quickly start looking like "Old man shakes fist at cloud". Time is a performant system for yeeting this kind of elitism.
- triceratops 6y agoTo be fair, check out GP's username.
- kostarelo 6y ago> Type inference is very convenient, so there's no need to do this universally - however, it can be a useful thing to try if you've identified a slow section of your code. Can we actually identify which piece takes longer to compile?
- nandi95 6y agoI think you can in 4.1 add --generateTrace and the output path. The result you can inspect in the browser. Also there's -- extendedDiagnistics if I remember correctly that tells you which part of the compilation process takes what time
- danvk 6y agoI've run into many more performance issues with editor latency (i.e. tsserver) than with compiling (tsc). Unfortunately, these tend to be harder to diagnose. The worst turned out to be a crashing bug in tsserver that manifested as autocomplete and error checking feeling sluggish, I think because the language service kept restarting. It made me feel grumpy about TS for a month or two before I finally looked at the server logs (using the instructions on this page). That quickly led me to this bug: https://github.com/microsoft/TypeScript/issues/35036 https://github.com/microsoft/TypeScript/issues/35036. Even though it took a while to get fixed, I felt immensely better that the problem was real, acknowledged, etc. Nowadays my biggest performance issue is with the Material-UI typings.