This is kind of a random Typescript tip, but when migrating regular Javascript, there is an alternative to adding the type 'any' to every object to 'silence' the compiler.
That is to introduce a preliminary type definition. Instead of:
const oldVar: any = { field: 1, ... }
function foo(bar: any) { ... }
You can write:
declare type OldVarType = any
const oldVar: OldVarType = { field: 1, ... }
function foo(bar: OldVarType) { ... }
This way, you can signal that it's not just any kind of any, but a particular kind of any, which is now trackable in your codebase.
When you're ready, you can gradually update the OldVarType declaration and solve the compiler type check warnings from there. Union types [1] can be quite useful then too.
Ah, that's a neat idea. I wonder if there's any easy way to then scan your code for type aliases that are `any` to make sure you've caught all of those.
Yes! We have several 100% TypeScript applications and we've found TSLint to be invaluable for maintaining code quality - http://palantir.github.io/tslint/
I think that would be more trouble than it's worth. Since "any" is a keyword, you wouldn't even need regex to search for it. Something like this would work:
" = any"
This would only show types that are aliases of the "any" type. As a side note, the following would create a compiler error ("cannot find name 'any'") because types are metadata and can't be used as program data.
Both those versions should work the same from the compiler's point of view. But from a programmer's point of view, specifically someone migrating a JS system to TS, it's useful to read "oldVarType" instead of "any", because it tells you that it was just a quick way to migrate rather than necessarily designed to be "any".
Yes, and it would be nice if the compiler could produce such an annotated version automatically, instead of the programmer having to use the highly error-prone technique of typing it all out manually.
For a single function it's not a bug deal, but if you are migrating 10k lines, I'd view it as a near necessity.
So you want the compiler to automatically generate alias types for every un-typed variable and parameter? I assume you would also want the compiler to (somehow?) infer types that should be the same type and merge them, since otherwise the first step is kind of silly. I'd argue that that's a pretty difficult task, tantamount to inferring programmer intent.
Yes, I want merging, but I imagine there could also be an indication that the merge was automatic and in an IDE setting, I'd expect to be able to easily split out the types, if I want.
I'm about halfway through migrating a ~30kloc ES5 codebase to ES6 with a transpiler (Babel), linter (ESLint), and fast test suite (Mocha) [1]. Once all files are going through the transpiler I'll probably add TypeScript or Flow. We already had RequireJS in place so the conversion from AMD to ES6 module format has been pretty straightforward, but I'm continually amazed with every file I convert I usually find a couple real bugs with the linter. The most common have been:
* Undefined variables referenced in a seldom-traveled conditional branch.
* Leaked globals due to missing `var` keyword.
Every bug I find is immediate feedback that this is a worthwhile use of our time.
I can't recommend flow highly enough for gradually typing existing code (as it requires far fewer code changes than TypeScript). Not sure what your timelines are like, but it takes no time at all to set flow up, and you might find it makes the task of migrating a little more varied.
The main blocker and main reason why I don't recommend flow when entering a new team, is that they don't provide windows binaries: https://github.com/facebook/flow/issues/6
The fact that I can add TypeScript to any project's workflow by simply `npm install --save-dev typescript typings` is a massive advantage. The tooling around typescript is much better as well (typings + definitely typed for example). With TypeScript 2.0, I'm not sure I'll have any reason to use Flow anymore...
I think the main differences are in the type engines. From what I have seen, Flow can infer much more informations about a program, even on unannotated JavaScript. As a result, with the same amount of annotations, it seems to me that you can find more bugs using Flow than using TypeScript.
TypeScript recently added an allowJS mode which makes it much easier to convert JS to TS a file at a time and Typescript will infer what it can from the unannotated JS files. From what I hear it's not quite as much as Flow picks up, but it is getting better, especially because it's also doing double duty as the new JS IntelliSense language service (codename "Salsa") for VS Code and intended to eventually replace Visual Studio's IntelliSense language service for JS as well.
I find typescript pays off with the excellent intellisense and refactoring in editor support. They did a great job of having the language service and compiler use same core code base.
I'm working on a babel plugin [1] that should make the migration even more gradual. At the end of the process you get both static and runtime type safety.
I have tried to use it ~6 months ago and the tooling felt kind-of wonky and I didn't feel like maintaining the interface files if so much of it seemed to be in flux.
Based on a quick play recently, I'd say still slightly wonky. Nuclide has improved a lot but autocomplete etc. is noticeably sluggish compared to the atom-typescript plugin. On my travel 12" Macbook, Atom with Nuclide it's too slow to comfortably use and the Sublime plugin for Flow seems to be quite buggy, whereas the Sublime TS plugin is pretty solid.
In terms of type definitions available for third party libraries, Typescript is way ahead. I also think the documentation and community around Typescript is stronger, and will only get more so with the move to use TS as the default in Angular 2. Also the stability is great and the roadmap looks promising.
Nothing against Flow, I think it is great and definitely has some advantages (and anything which adds types to JS is a winner in my book!), but it feels slightly more like an internal Facebook tool that happens to be released to the public versus Typescript, which is more of a release quality product.
But yeah, either is great improvement over plain JS and I'd definitely consider Flow for a personal project, but in a bigger company/team environment I think the stability, tooling and community of Typescript would make me push for that.
Works for me! Some of the non-affiliated tooling (e.g. the Sublime Linter plugin) can be a little difficult to use, but the command-line tool is pretty clear.
Refining the .flowconfig so it worked universally also took a bit of time, but that's more due to our very large codebase than flow.
I'm working on a real time (as you type) JS static analyzer witch will hopefully detect these errors. So I'm interested in what sort of bugs is common.
I believe that types don't belong in a high level programming language like JS, where the JS engine decides the best type to use and optimize things like string concatenation. A good static analyzer and tests should cover most of the advantages of a strictly typed compiled language.
Don't confuse types with the implementation. Regardless of how the string concatenation works, you'll still need to make sure that you get something that sort of looks and behaves like a string. That's where the static typing comes in.
It's in an early stage and doesn't do much ATM that can't be accomplished with something like "Tern". If you drop your e-mail in your profile, I'll send you a link when I got something to show.
We're using a variant of Airbnb's config, it disallows `var` in favor of `let` or `const`. We're a Java shop and people get tripped up by hoisting issues pretty often.
I curious how using Typescript makes the project more "structured" compared to using plain JS.
Typescript just gives you static types and classes. I can understand "classes" make the code more modular and thereby more structured, but can't you already do that using ES5 "class"? Types help in catching errors early and can help in documenting the interface of the methods etc.
Also, if the other problem was isolating the scope of modules and dependency injection, it could be easily done using Requirejs and all the modules could be concatenated during build time. Webpack is a cool tool, but given the nature of its documentation, the maintenance would be really costly in the long run. Requirejs is just more established and satisfies all the author's requirements.
It would've been really helpful if the author had included additional reasoning behind the choices made.
The basic idea here is that complex data structures are usually easy to understand, while complex code isn't. This is one of the few fundamental insights about programming that withstood the test of time.
Note that while this all is not necessarily about types, this is about documenting the data structures - and good type systems enable you to do so in an unambiguous and even machine-checkable way.
1975: There's a famous quote in "The Mythical Man-Month" by Frederick Brooks:
Show me your flowcharts and conceal your tables,
and I shall continue to be mystified.
Show me your tables,
and I won’t usually need your flowcharts;
they’ll be obvious.
Note that this is old language from the 1970s, where "tables" roughly means "types definitions" (or comments describing the structure of input and output values, think of database table definitions) and flowcharts roughly means "function definitions" (think of hand-written flowcharts written next to low-level code to make all its GOTOs understandable).
1989: You can find a similar statement in "Notes on Programming in C" by Rob Pike:
Data dominates.
If you've chosen the right data structures and organized things well,
the algorithms will almost always be selfevident.
Data structures, not algorithms, are central to programming.
Note that Rob Pike refers directly to Frederick Brooks.
1995: Frederick Brooks reiterates this statement in the second edition of "The Mythical Man-Month" (20 years later).
2003: The same idea is also stated as one of the basic design rules in "The Art of Unix Programming" by Eric Raymond:
Rule of Representation:
Fold knowledge into data, so program logic can be stupid and robust.
Note that Eric Raymond refers directly to Rob Pike.
2006: Another quite famous quote from the Git mailing list by Linus Torvalds:
Bad programmers worry about the code.
Good programmers worry about data structures and their relationships.
And so on.
(BTW, does anyone know about a similar quote before 1975? Or a popular reformulation after 2006?)
I have found a lot of bang for the buck in software development comes from "the schema". I can use them to automatically enforce the runtime contracts between my code and my consumers'. As a communication mechanism between myself and other developers, the well-annotated schema builds an unambiguous picture for everyone of the entities, relationships w/ restrictions, and cardinalities in our systems.
At the boundaries of our microservices, we JSON Schema validate messages before they are sent on the service and upon receipt by the client (at least in the non-production environments.) We use the schema to guarantee the services and clients are always "speaking the same language" even when teams are releasing versions of their code at different times.
Structure means walls. There are tons of crazy things you can do 'outside' the walls with javascript. There are no checks if anything you reference exists or that your assignments are compatible. The browser's compiler will figure it all out and/or blow up at run-time.
Type and reference checking creates the walls your code must stay inside of otherwise there are compile errors. The compiler enforces a certain structure on your code that you must adhere to in order for the compilation to succeed.
Think of it like a coloring book where TypeScript keeps you inside the lines. Ever see a three year old's coloring book? :p
As much as I love programming with no self imposed 'walls', I fear maintaining anything larger than 10,000 lines created that way. When you step into an existing enormous code base with no testing implemented, static typing and the associated tooling are fantastic.
The types are what give it structure. Types are the structure. When you create a type, you are saying in concrete terms "this is exactly how things happen and nothing else".
This is very useful information for a large project. Think of it like a map. You don't need one for your house, but you need one to walk cross country. Or like NASA procedural manuals for something normal people don't need a guide for...like an astronaut taking a shower or something.
> Types are the structure. When you create a type, you are saying in concrete terms "this is exactly how things happen and nothing else".
Types make the interface concrete - which is better for compiler/interpreter (catch bugs) and better for developers (easy to understand instead of guesswork)
But when I think about "structure", It is usually about how the codebase is organized. How different modules interact with each other and is the flow of the code easily understandable. This could be easily achieved without Types.
Maybe it's just my interpretation of the term "structure".
It is pretty much impossible to have any sort of inferred structure from plain javascript. The reason you can use super powerful tools to refactor, rename, and find all references across files in typescript is because the IDE can build a 100% accurate data model of your code base when it's written in typescript.
To do things like that in a dynamic language, you need to either infer types somehow, or rely on metadata like JSDoc, beyond the language itself, and that sucks – if another developer works on the project with a dumber IDE, their code will probably lack such metadata or it will not be entirely correct.
I have been using IntelliJ daily for the last five years for PHP, JS and TS development.
For PHP, I write typehints and PHPDoc wherever possible to help the IDE make sense of things. For JS, I tried doing the same with jsdoc but it didn't work out, and I have decided to avoid any automated refactoring or even autocomplete, it's far too fragile. For TS, using the types in my code makes the IDE smart and reliable, although IntelliJ's support for type inference in TS is still far behind VSCode.
Special mention here goes to Magento developers, whose PHPDoc, 98% of the time, is either lacking or outright incorrect (referencing classes that don't exist – incredibly frustrating).
Yeah, but at the end of the day it's a guess. Nothing more. The IDE cannot prove that it's change all instances of the "thing" being passed into a function, for it's just using names and heuristics to correlate them and derive meaning, not an actual mapping.
Jetbrains' editors for e.g. Python help me significantly less than their editors for e.g. java.
Not that it's a bad thing, but basically Pycharm is an editor with some nice-to-have Python env integration, linter, etc. IntelliJ has significantly better refactoring tools.
I use IntelliJ when writing Ruby, and the navigation and refactoring tools are much less reliable than those for Java. Finding symbol references can return a lot of false positives since it cannot use the type of the receiver, and a lot of the refactoring operations are not available.
I agree with everyone else, I don't find Webstorm's js refactoring to be anywhere near Jetbrain's static offerings. And the js auto-completion is about the same as Atom's autocomplete-plus applied to generic text (which is really great, but nothing like static type tooling).
When you use TypeScript, you don't have to organize your program any differently than you would in JavaScript.
If "structure" means how the directories and files are organized, then TypeScript makes no difference. I personally use the outDir setting in my tsconfig.json file to separate my JS from my TS, but not everyone does that.
If "structure" means "paradigm" (such as OOP), TypeScript might make a difference. You might find, for example, that functional programming makes more sense than object orientation to you once you have types. It really depends on you. Again, there's no real reason you'd change your preferred paradigm.
If "structure" means the types themselves (the structure of your data), then TypeScript isn't changing anything. It's just documenting your intentions in a way that other people and the compiler can understand.
Maybe initially, but that `any` type really defeats the purpose of having types in the first place. Where I work we have several 100% TS apps and we've banned the `any` type except for very rare exceptions. It's always better to define a type so that it can be easily used elsewhere, and it also helps prevent typos.
By using haxe he could have started now transpiling some of those 10k lines also to the python backend just for free plus all the additional prepocessor and type checks that are common for typescript and haxe.
I don't really understand why people jumps to typescript and not haxe, which in my opinion has more strategic advantages and warranties (with very few exceptions).
I've used Haxe in several production projects, and if I'm starting a new code-base from scratch I would pick it again. (I prefer type system, the cross-compilation options, and the meta programming)
But if you're migrating an existing code base, TypeScript has a more obvious path forward. Because TypeScript is a superset of JS, it means that all of the JS files in your project will be compiled by TypeScript as is. And you can add the extra typing information as you go, it doesn't have to be an "all or nothing" transition.
Compare to Haxe, where you have to create a separate *.hx file, with a slightly different syntax. You can either translate all of your code (a massive undertaking), or type external files as an `extern Class`, or just use `Dynamic` (equivalent of TypeScript's "any"). But all of these involve a little bit of work for every single part of your code base, so it's going to add up.
From what you say most of those problems are just syntactical, isn't it? Somehow I started to believe that it is a matter of visibility and self selling skills in the haxe community.
The problems you mention seems to be possible to solve with software (AST generation and transformation) however it still has to be done. Let's hope we will see soon a project like that in haxe-land.
You are right that the difference is syntax, but the thing is, JS syntax will always be different from Haxe syntax. You'll never be able to use "jQuery.js" as valid Haxe code, but it is valid TypeScript code.
There might be some interesting possibilities. For example back2dos has an example of Haxe seamlessly integrating with external ".as" files on the Flash target. This could be conceivable for other strict languages like Java or C# too. You might even be able to seamlessly integrate with ".ts" files, because they have some typing information. (That would be a fun project!)
But JS dynamic typing can be pretty crazy, and I'm not sure how feasible it is to be able to infer type information from JS source code without special type hints. It would end up just calling everything "Dynamic", and then you're getting no benefit.
Well, for one, Typescript is a superset of Javascript, meaning that all valid Javascript syntax will compile properly when passed through the Typescript compiler. This removes the need to rewrite 10k lines of code in another language, even if syntactically similar. Avoiding that overhead is a huge time saver - Javascript and Typescript are simply much closer than Javascript and Haxe, making the time savings a strong incentive.
You're talking about two different things. Haxe is a different language, while TS is a superset of JS (all JS is valid TS).
JS -> Haxe requires rewriting 10k lines before the program will run.
JS -> TS requires changing the file names, doing some imports, and adding "any" in a few places. The program will then run, and type information can be added gradually.
I do not see that much of differences between Haxe and JS, mostly because Haxe is a superset of EcmaScript. However the process is not that automatic because TypeScript is more permissive I would say (though there is already a convertor from typescript to haxe: ts2hx).
But that said, I am not really that much experienced with TypeScript.
haxe have some incompatibility with syntax js. like don't support classic for and in if statement null must be explicited (you must do if (x==null)). but thanks to macros the porting of js generic more simple.
Is it easy to partially move things over to haxe? As in just translate one js file to haxe and have it properly populate the global namespace so that other js files can still use it?
It is easy enough to expose your classes and types from Haxe into the global namespace [0].
And it's easy enough to require() external JS code and use it seamlessly in your Haxe code.
What's not easy yet is using Haxe to generate small modules, so some modules in your codebase are written in Haxe, and some in JS (or Typescript or anything else). By default Haxe will output one big js file per compilation. The compiler is very configurable, so in theory you could output ES6 modules instead, but I haven't seen it done yet. (Though, there is a generator for outputting your code as AMD modules instead of one-big-JS-file [3], so it's definitely possible). But it's not easy yet, and that's why I think that for now migrating an existing JS codebase to Haxe would be harder than migrating it to TypeScript.
Yes, you can expose Haxe classes so you can use them as any other API from JS. There is also the option to declare externs for existing JS and the require function.
It's a catch 22: relatively few people use haxe (that's purely guesswork from me) and it attracts less people because of that. I see marketing as one way to break that loop but there are other ways like offering something very useful and unique or being far ahead of the curve on something very important (like performance).
I've always wondered if you could create a runtime inferrer of types for these sorts of projects. As in, attach something that observes function calls and sees over the course of some amount of time what types are passed into them, and have them make a best guess of what arguments are what types. While of course it couldn't be perfect, it could save a lot of grunt work.
I made a quick version of this idea in Python 3.4, where it was much easier since I could modify the AST on import, but I never quite got it where I wanted it and sort of lost passion.
I've wondered about this, in the context of generating skeleton Typescript definition files automatically by analysing the source of the library. It appears that Tern (http://ternjs.net/) is the most advanced project for extracting this kind of information from Javascript source files, and can be used as an editor plugin for some degree of intelligent autocompletion. You could also potentially use Esprima (http://esprima.org/doc/) to analyse the AST.
The old Visual Studio Javascript autocomplete engine apparently worked in a similar way to what you suggest, running the code in a sandbox and then analysing the types, but they've now moved to a static engine (which I think is also used in VS Code?) - some information at https://blogs.msdn.microsoft.com/visualstudio/2016/04/08/pre...
You could probably do 80% of the inference statically as well. For instance, given something like this:
function incrX(obj) {
obj.x += 1
}
We can infer incrX expects an object with property x, which is probably an integer. And therefore, a call like `incrX({x: "hello"})` is probably incorrect. In fact, I think this is how type inference in languages like OCaml works.
That said, I do wish statically-typed languages generally had runtime type checking built in. For instance, I can tell Typescript what type of response we're expecting to get from an API call, but that doesn't mean the compiled Typescript will warn me if the API call ends up with a different type.
> We can infer incrX expects an object with property x, which is probably an integer. And therefore, a call like `incrX({x: "hello"})` is probably incorrect. In fact, I think this is how type inference in languages like OCaml works.
Except OCaml's typing is much stricter than JS's, in JS `obj.x += 1` is perfectly valid if `x` is a string. In fact due to all the runtime type conversion protocols you can have pretty much anything as `x` and have `obj.x += 1` succeed.
So that requires a strict break and separation with JS semantics and treating JS (or whatever) as an implementation detail and "assembly", which is the opposite of Typescript's purpose (it's more of an Elm or Purescript or ocaml_in_js thing)
> That said, I do wish statically-typed languages generally had runtime type checking built in.
Statically typed languages with lots of holes (at the type-system level) like Java or C# do have runtime checks. Those with less holes like OCaml or Haskell don't as the only way to get the "wrong" types at runtime is to have wilfully undermined the type system in which case the developer is on the hook for making sure they do that correctly.
> For instance, I can tell Typescript what type of response we're expecting to get from an API call, but that doesn't mean the compiled Typescript will warn me if the API call ends up with a different type.
That would require TypeScript having its own runtime rather than compiling to regular Javascript, or it would require that TypeScript inject a fuckton of type checks which would make the output orders of magnitude slower (both from the overhead of javascript-level type-checking and from the increase in code size and decrease in JIT optimisation opportunities).
To expand, with regard to (GHC) Haskell: you can perform run-time type checks with the Typeable typeclass, and you can forgo compile-time checks in favor of runtime checks with Data.Dynamic. You can also replace compile-time detected type errors with "if this is executed, explode", with the -fdefer-type-errors flag. And of course, in any typed language you can apply sufficiently general types that you need to perform manual checks for safety.
Typescript couldn't actually do this for interfaces. Typescript interfaces don't exist post-compilation, and the only way to check for them would be to ensure that every expected property exists on the incoming object.
This has a very annoying side effect of never being able to exclude interfaces from a union type:
function doStuff(thing : string | SomeInterface) : void {
if (typeof(thing) === "string") {
// thing is still typed as string | SomeInterface,
// because there's no guarantee that the
// string object doesn't implement the interface.
// If SomeInterface was a class instead of an interface,
// then thing would only have the string type here.
}
}
Also, this is ignoring the fact that you would also need to check the parameter types on an incoming function, which AFAIK isn't possible at all.
I work with TypeScript fairly closely and was surprised at your example, because I was sure that this already works. It turns out the type narrowing is actually special-cased on only some particular AST. You can see it working here, where "charAt" would not be allowed if it was still |SomeInterface:
I was wanting the same kind of thing! We have an old Node (lol, funny to write that) code base of about 60 services that fetch data from 3rd party APIs. They're all very dis-organized and written in different styles, so a tool like that would be really useful. I've been experimenting with using Facebook's Flow on this, but it doesn't do the magic that you describe (which I'd like)
The good thing about TS is that you can add types gradually. I actually think there's a lot of value in adding the typing manually because A) you're guaranteed to find bugs, and B) you have to revisit your assumptions with the advantage of hindsight.
With Flow you can also add types gradually. Actually, one of the reasons why Facebook created Flow is that they thought that TypeScript was not "gradual enough". A good presentation on how Flow differs from other type systems: https://www.youtube.com/watch?v=VEaDsKyDxkY
I also wonder why no one experimented on exporting the types from jitters and making preliminary type annotations from that information. Sounds very basic when I think about it but I have no idea how jitters work so maybe it's just nonsense.
Nice article, and I am going to give webpack a try. I took an eDX class in Typescript a year ago and really like the language but decided modern JavaScript was getting "good enough." I regret that decision sometimes because Tyoescript really is better IMHO.
Typescript is ridiculously easy to learn. The only hard part is getting your typings/build/environment set up, the actual semantics of the language are a breeze.
There are some subtle nuances to structural typing and interface definitions if you want to write really slick code, but for the most part you can get by without them.
Lots of intelligent replies here (like much of HN); having a Java & C/C++ background, I picked up Javascript some yrs ago for web work.
Now, I don't know how/if to move to Typescript or ES6? And any resources/books/vids you all highly recommend? (I built some corp stuff in Angular 1 but guess I'll have to move to TS for Ang 2 or learn React?)
Many commenters in this thread seem experienced in TS so thanks in advance for sharing any advice
> Since globals defined in different JavaScript files share a common namespace, I often found it convenient to reference globals...I knew all along that these were bad habits
This line resonated with me. In my first CS course (Intro to programming), I learned about why using globals can be problematic.
A good reminder that engineering is all about trade-offs. Sacrifice future maintainability for present day productivity.
I feel that academic code is generally more prone to bad-smell pressure than industry code, since people generally produce for papers and deadlines and "just get it working for now." It's good to see that it's possible to produce some semblance of order in the constant grind.
That is to introduce a preliminary type definition. Instead of:
You can write: This way, you can signal that it's not just any kind of any, but a particular kind of any, which is now trackable in your codebase.When you're ready, you can gradually update the OldVarType declaration and solve the compiler type check warnings from there. Union types [1] can be quite useful then too.
[1] https://www.typescriptlang.org/docs/handbook/advanced-types....