I moved jobs recently. Previously I had been using web technologies to create an application visualization development environment, a bit of a mouthful. The application was a form of website builders which is the simplest description I’ve been able to come up with. Using web technologies meant JavaScript and the place where I was working felt it might be a bit of a risky technology to use. The reasons from where firstly, JavaScript being an interpreted language meant that it was an inherently unsafe, in the sense that you’d have to wait till runtime for things to blow up, language. The other primary driver (look at me going all business-speak) was the very large gap between JavaScript and Java (the main development language in the rest of the company) meaning you might not be as easily able to use programmers from other teams on this new one. Plus tooling, development environments, documentation…etc.
A decision was made, and I ended up with two years+ worth of TypeScript experience.
TLDR: It took me a long time to warm up to it, but TypeScript turned out to be a great tool, jump to the conclusion to find out more.
What is TypeScript?
TypeScript lets you write JavaScript the way you really want to. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open Source.
TypeScript give you Ecma 6 features today. It’s a transpiler that takes Ecma 6 syntax and various additional TypeScript features and turns them into valid Ecma 5 or Ecma 3 code. The good people over at Microsoft have been working very hard on achieving this and have done a fantastic job so far. On top of that you get something that is not present in Ecma 6, type safety. Type safety is of course a very contentious issue. I’d be surprised if the average programmer hasn’t been witness to, or been part of, the great debate of static versus dynamic typing. I never really had strong feelings either way, I just had a general unease with the clunkiness of Java’s type system versus the elegance and succinctness of languages such as Python and Ruby. To be honest I think most of the debate around static versus dynamic is probably Java’s fault, a view I’ve seen kicking for a while.
The genius of TypeScript is the fact that the type system is optional. You can happily use TypeScript for its other features, such as; its class syntax, the module system and other types of inferred type checking without ever breaking out explicit: “number”, “boolean”, “string”, “Object”, “Event”, “any”…etc
The Good Parts
Some of the following “good parts” I must admit are a bit tenuous. There are plenty of features in TypeScript that you could of gotten from the likes of CoffeeScript (initially at least, TypeScript’s feature list keep growing though). For instance if all you wanted was JavaScript with a slightly nicer syntax and something to handle chunky code around closures, you would be better off with CoffeeScript.
That Sweet Sugary Syntax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
I’m not going to go into a full feature list. But as you can see the above style of syntax is the kind of thing you’d expect from an OOP language. One of the main developers of the language was Anders Hejlsberg, who is the chief designer of the C# development language and .NET framework. So it coming from an excellent pedagogy.
As you can see from the code snippet, there is a class syntax which is cleaner and more recognizably traditional OOP development languages like C# and Java. Under the hood the code that is generated is essentially the usual prototypical inheritance system you’d expect.
Naturally for an OOP language it has interfaces, public/private methods and class attributes.
Finally, for me one of the great features of TypeScript is its syntax, for the most part, does what you’d expect. You don’t need to worry about the context of “this” so much. Using the “fat arrow” Ecma 6 syntax (which original came from CoffeeScript) you can get around a lot of messy closure issues.
Reduced testing
1 2 3 4 5 6 |
|
Imagine a world where you didn’t need to check every parameter into a method for “nil” state? Great isn’t it? Well with a complication step you express your desire for a method to never be called without some parameter and then the compile makes it so.
Now that isn’t the whole story, but it pretty much captures it. TypeScript (and generally anything that isn’t plain old JavaScript) gives you extra tools of expression and puts rules in place to make sure your intentions are respected. Surely that can’t be so bad?
DefinitelyTyped
Having an outline of an API of libraries was a godsend. No longer did you need to know a library inside out before being able to use it effectively. In fact I would say that would be one of JavaScripts major issue, there is only so much context and code you can hold in your mind at one time (that is the case for me anyway) and the more things you have to juggle the slower you will go.
Now that isn’t the whole story, but let me save that for the Bad Parts below.
Readability
As I mentioned above, TypeScript gives you a few more syntax tools to allow you to express your intentions. As such you have a better idea about the intent of a method beyond just the name, you get types to go along with the input parameter names. You can see at a glance whether there is a return value and what type that is going to be. The pesky open ended “options” object now had a proper description of the keys and values and expect types of all of their possible contents. All very useful things. And of course the tools (that are really only showing up in the last year or so) allows you to generate decent dependency graphs…etc so you can plan refactors and see hotspots pretty quickly.
Since there is a much richer source code to draw from documentation generation should becomes a lot easier. In the case of JSDocs, you have to keep those comment sections in sync with the code section and since there is nothing to properly enforce that relationship things go out of sync quickly and the comment become a lie. Ultimately I didn’t find any decent documentation generation tools for TypeScript, I tried getting a TSDoc style system up and running but with little success.
The Bad Parts
As yes, and after all that positively comes the pain.
Tooling took a long time
The primary reason we went with TypeScript was “tooling” which is hilarious when you consider we took it up when it was [v0.8]. The only tooling that existed was Visual Studio, which at the time we couldn’t get to work for us. At that stage Visual Studio and TypeScript needed to be taken up at the same time, we had begun writing TypeScript code before then. We didn’t have a huge code base but it was big enough for someone to say, screw it, when it didn’t instantly work. When I was leaving the team was looking at Webstorm which looks like a fantastic tool and would be a major boon to anyone looking at development environments for any kind of web development.
The reason VS didn’t work for us was because of the “reference” paths. For the compiler to be able stitch things together properly you’d have to include a reference to the paths of other files that it needed (later we found the “reference file” method to be easier and more maintainable). For some reason I think that screwed with VS and it probably didn’t help that we had separate modules going and we weren’t using VS to build our code.
Obviously though, that was not the languages fault.
Sourcemaps
Source maps, though they sound like a nice idea are a nightmare. The technology was made in hell and deserves to burn there, forever.
Hyperbola out of the way, the experience in Chrome, which was the main browser I was debugging in, was terrible. Not initially though, initially it was great. Even having the Sublime Text style “Ctrl+P” open a file with fuzzy search was brilliant. The problem was when you placed breakpoints and subsequently edited your code. This is where things went to hell. I never quite figure out the set of steps to reproduce it (to be honest I turn off Sourcemaps pretty quickly) but when you had break points and were refreshing the page after doing an edit you could find yourself debugging code out of sync with the new edits. Sometimes I found I was looking at a piece of code that wasn’t the code I thought I was debugging and wondering what in the hell was going on, why were some things nil when they should have a value. All manner of strange things would happen with Sourcemaps and me (other develops never seem to run into issues) so I had to give up on them.
Obviously though, that was not the languages fault.
Inheritance and third party libraries issues
Handing over a substantial amount of your code (the generated and manage inheritance system for instance) to TypeScript has some trade offs. For one, it depended on the library makers to be sane individual and to not do weird JavaScripty things (read normal JavaScripty things) when enhancing some core library you depended on. For instance I found myself integrating a few third party Backbone plugins, with Backbone and the code we had written for TypeScript. At the time I could not get TypeScript to agree with what was happening to it. I’m pretty sure the code would of run if I tried it in the browser, but the compiler freaked out so I just left it be.
Integrating with third party libraries has a tendency to be a pain in any language at times. I found it to be more so with TypeScript. Now if the library was written in TypeScript then that’s a different story, but 99% of libraries for the web were written in JavaScript so…
MaybeTyped and probably okay
You know that thing I said about “you need to know a library inside out before being able to use it” and how TypeScript/DefinitelyTyped solved that, yeah only kinda. First off the bat, not all the libraries are there. Lot of them are, I grant you that, but not all of the ones you might need (for instance there is lots of Angular and very little Backbone). If you need a library that is not a part of that repo, then tough luck, go write your own. Which if you have to do that and are under time constraints can be kind of tough.
There are a few issues in a real production application that you have to account for when using a library. You want to make sure not to be too quick to update libraries, sometimes things change pretty radically (it’s usually okay if they use semantic versioning). That radically different stuff might mean you can’t upgrade right now. That’s an issue.
Sometimes DefinitelyTyped definitions can be written badly. It’s just a fact of life that a piece of code can be written badly, this is a community project after all. These people aren’t doing this for money, it’s on their spare time. With that in mind it makes sense to keep up to date with a d.ts because I’ve seen bugs from a d.ts having an “any” where it should have a more solid type and engineers wasted a bunch of time wondering why in the hell the compiler didn’t catch something.
Sometimes you can’t move up compiler version, that can be an issue if someone in the community re-write huge swatches of a d.ts to use some new compiler feature (which is the correct thing to do in my view).
So you have a few vectors… the third party library version can increment, the d.ts version can increment at its own pace as well, the compiler version can increment too. Frankly it was a nightmare to manage and keep an eye on it. What would be needed would be a package management system for those DefinitelyTyped files, which does exist now, but that is still in its early days. Frankly it would of been better for the TypeScript team to weight in on the design and production of those kind of support systems because it was sorely lacking during my time with the language.
The Build
Having a build system was a problem, for reason you’d think of and reasons you wouldn’t.
From a pure JavaScripty development style it means there is an extra step between you and the result of what you’ve written, though I think that is an okay thing. Putting the breaks on can help in a design and logic process. It makes you a better engineer to tease things out instead of just throwing code at a wall and seeing what sticks. However, after a while the compile step becomes pretty heavy (20 -30 seconds).
Now frankly that could of been our fault. We may have not been taken full advantage of the module system, or were using it incorrectly. When I was there we had three modules with about 30kloc~. Not massive when you consider TouchDevelop looks to be in or around 160kloc~. I’m not sure what we were doing wrong but I found development to be slower even then what I had experienced with Java.
The other issue with that was we had to re-compile our whole code base when any file (TypeScript) changed. I reckon if the compiler allowed you to compile individual files yet somehow was still able to figure out how to stitch them together without having to consume and read the whole code base again, that would produce an enormous build performance improvement. But that is just speculation. I’m sure the builds could be acceptable if it we had the right type of modules/build setup.
When the build fails
My single biggest issue with TypeScript is that it produces artifacts even when the compiler booms out and blows up with errors about your crappy (mine in this case) source code. The worst thing was since we had this kind of cascading build system (we used rake and task dependencies) it would find these artefact’s on a second build and then just assume everything was find. That I see as an genuine issue with the compiler. But I’m sure it’s probably a feature not a bug.
Conclusions
In the end, despite the warts, despites the complexity I enjoyed working with TypeScript. TypeScript or transpiled languages make a lot of sense for large scale JavaScript application development. JavaScript is great with a small team, it’s great for making things at the 10k lines of code, but once you start hitting seriously big teams and LOC you see in enterprise code bases you need the extra expression. You need the types. You need the structure.
The biggest disappointment was the Chrome debugging. I ended up having to debug in JavaScript all the time if I want to have consistent and reliably experience. The tooling in general made things very hard. IDE weren’t reliable for a long time, plugins for editors like Sublime Text only went so far. Additional tooling like documentation generation was fragile and didn’t work without breaking your back (I broke my back and it still didn’t work). The tooling situation has changed and improved massively in the mean time, but I’m talking about my experience, so.
I found there was a great deal of verbosity in the code I was writing. I don’t blame the language for that, more and more features appeared (even long before I left my last job) that was going to make the code we had written far simpler, it just we didn’t have time to look at upgrading the compiler right then.
Ultimately the root cause to all of my pain with TypeScript was that we adopted it too early. It was too early for good tools, any tools. It was too early for the community, it was too early to find out what would be the best set of “best practices”. We spent a lot of time wrangling with technology instead of writing the tool we were building. But that is the nature of being on the bleeding edge and having gone through that pain the project was in a better place and on a better trajectory as a result.