Migrating a 10,000-line legacy JavaScript codebase to TypeScript
pgbovine.net
pgbovine.net
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.
[1] https://www.typescriptlang.org/docs/handbook/advanced-types....
" = 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.
const something = any;
You can ban the `any` type along with lots of other rules. You can also write your own custom rules, or implimnet rulesets by 3rd parties like Microsoft: https://github.com/Microsoft/tslint-microsoft-contrib and ESLint: https://github.com/buzinas/tslint-eslint-rules
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.
* 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.
[1] https://medium.com/@kevanahlquist/evolution-of-javascript-at...
The differences are in tooling.
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.
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.
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.
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 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.
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.
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".
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 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).
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.
A guide to the ISS personal hygiene facilities and operations.
I couldn't help myself...
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
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?)
Just take a look at the data division of COBOL. This has been very clear for a long time.
In a related note. Databases schemas usually outlast applications. I highly recommend taking a look at database refactoring.
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.
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).
Is perhaps just a marketing topic?
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.
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.
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.
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.
[0]: http://haxe.org/manual/target-javascript-expose.html [1]: http://haxe.org/manual/target-javascript-require.html [3]: https://github.com/explorigin/modular-js
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.
But that said, I am not really that much experienced with TypeScript.
"Haxe is similar to ECMAScript, although almost no ECMAScript code will run on Haxe without modifications."
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.
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.
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).
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.http://www.typescriptlang.org/play/#src=interface%20SomeInte...
if (typeof(x) === "string")
I opened an issue: https://github.com/Microsoft/TypeScript/issues/9391Thanks for the help!
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...
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.
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
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.