Friday, September 11, 2015

Hello, Typescript!

After spending some time wrestling with it, a few months ago I began porting my Dart-based codebases to Typescript. I can't say the process has been particularly easy, but ultimately it's been worth it.

The tools

The roughest part of the migrations, I think, was dealing with the current JS ecosystem. In Dart, everything is pretty straightforward -- if you need a pub package, use `pub`. If you need to format your code, use `dartfmt`. Do you need to build for the web? `pub build`. If you're writing code for the server, it looks exactly like code for the client, except you switch out different libraries.

While the ecosystem has finally consolidated around npm for packaging (sorry, bower), there's still some issues:

  • If you're targeting the browser, should use webpack or browserify? It's a hard question to answer: they're _very_ similar, with the exception of a few things here or there.
  • If you're targeting io.js, do you use the built-in ES6 support, or do you go balls to the wall and use every ES6 feature _now_ and just compile down to ES5 with Babel?
  • Modules: CommonJS? SystemJS + JSPM?

There's no clear standout: it's more like you have two mediocre solutions to pick between, with only your gut instinct to go on. This is probably more an issue of maturity: in time, just like Grunt v. Gulp, an overwhelmingly better option will appear.

Typescript

Getting into Typescript isn't very straight-forward, either. For type definitions for external libraries, you're going to rely on DefinitelyTyped: a massive, massive repository that Github won't even display the entirety of online. In order to better finagle it, there is an npm module called `tsd` that installs a command line that will quickly find and download type definitions for you.

Then there's your `.tsconfig` file, which configures a TypeScript project, and has to be created by hand if you're not using atom-typescript (hint: use atom-typescript). 

There's not a lot of documentation on typical Typescript applications.

  • What directory _should_ your *.ts files go in? lib? src? scripts? I've seen them all.
  • Type definition files: do you check them into the repo? Yes? No? If no, how do you handle custom type definition files?
  • Should you check in the generated *.js files? They're build artifacts, but also required for the application to run after checkout.

After those hurdles, though, things get a bit easier. Typescript, as a language, is great to work with; its obvious and its type syntax is very concise. It'll give you all of the ES6 features you hoped and dreamed for, as well as typing for analysis. On top of that, I think they're working on await/async support in 1.7.

I'll say this, though: I would have really appreciated comprehensive "Getting started with Typescript on io.js" and "Getting started on Typescript on the browser" guides. That's one of the things Dart does right: guides so you can hit the ground running right from the start.

But Arron, [vague reason why I should still be using Dart]!

When I first started using Dart in 2013, it was pretty great. I didn't like the language all that much, felt too much like a random and wordy hodgepodge of other languages, but everything else about it was incredibly awesome...

...in comparison to the state of 2013 web development.

Back then, Typescript was buggy, it was still Grunt vs Gulp, browserify wasn't stable yet (I think?), yadda yadda yadda, Polymer wasn't even born yet, and angular 1.x was still had a bunch of people trying to figure out the difference between a service and a factory and a provider.

So I deployed some production applications with Dart, and everything was good, for a while. Then these things happened:

1.) Clients started asking for features and integration with third party components.
2.) Heavier usage, exposing the flaw of dart2js when it comes to tracking bugs.

#1 is the thing every bitches about the most when they start using Dart: awful javascript interop. Basically you write _tons_ of proxy objects to hide how ugly `dart:js` is, or you go raw and spend your time building against the `dart:js` library directly. Both are a massive timesink, and both are hideous.

Clients don't care about either of those. There's already a JavaScript version available, why can't you just drop in a file and go to work?

Why can't I, indeed.

#2 was a sucker-punch I didn't quite see coming. When you're building an application Dart, you normally use Dartium to run the code. Dartium has a DartVM embedded inside of it. The stacktraces are pretty clear when something goes wrong. There's also good IDE integration (Dart Editor at the time, WebStorm afterward).

But when you take a Dart application to production, it gets compiled down to minified JavaScript, and when it causes a stacktrace in a browser, the data that gets reported by your error tracking service is a nightmare to go through.

Here's an example from a Dart plugin for Atom (which is awesome, by the way): https://github.com/dart-atom/dartlang/issues/277

 At /home/robert/.atom/packages/dartlang/web/entry.dart.js:15723  
 Uncaught Error: NullError: method not found: 'get$iterator' on null  
 Stack Trace:  
 TypeError: Cannot read property 'get$iterator' of null  
   at [object Object].J.get$iterator$ax (/home/robert/.atom/packages/dartlang/web/entry.dart.js:40600:41)  
   at OutlineView.dart.OutlineView._handleOutline$1 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:23413:23)  
   at [object Object]. (/home/robert/.atom/packages/dartlang/web/entry.dart.js:16917:32)  
   at _RootZone.dart._RootZone.runUnaryGuarded$2 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:30734:20)  
   at _BroadcastSubscription.dart._BufferingStreamSubscription._sendData$1 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:29878:20)  
   at _DelayedData.dart._DelayedData.perform$1 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:30072:18)  
   at _StreamImplEvents.dart._StreamImplEvents.handleNext$1 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:30145:16)  
   at _PendingEvents_schedule_closure.dart._PendingEvents_schedule_closure.call$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:30120:12)  
   at _AsyncCallbackEntry.dart._AsyncCallbackEntry.callback$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:29023:30)  
   at [object Object].dart._microtaskLoop (/home/robert/.atom/packages/dartlang/web/entry.dart.js:27728:12)  
   at [object Object].dart.wrapException (/home/robert/.atom/packages/dartlang/web/entry.dart.js:16669:17)  
   at _rootHandleUncaughtError_closure.dart._rootHandleUncaughtError_closure.call$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:30703:17)  
   at _AsyncCallbackEntry.dart._AsyncCallbackEntry.callback$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:29023:30)  
   at [object Object].dart._microtaskLoop (/home/robert/.atom/packages/dartlang/web/entry.dart.js:27728:12)  
   at [object Object].dart._microtaskLoopEntry (/home/robert/.atom/packages/dartlang/web/entry.dart.js:27734:11)  
   at TimerImpl_internalCallback0.dart.TimerImpl_internalCallback0.call$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:16100:35)  
   at invokeClosure_closure.dart.invokeClosure_closure.call$0 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:17775:41)  
   at _IsolateContext.dart._IsolateContext.eval$1 (/home/robert/.atom/packages/dartlang/web/entry.dart.js:15714:25)  
   at [object Object].dart._callInIsolate (/home/robert/.atom/packages/dartlang/web/entry.dart.js:15354:28)  
   at dart.invokeClosure (/home/robert/.atom/packages/dartlang/web/entry.dart.js:16817:18)  

The saving grace, in my production use of Dart, was that I had written all the code, so based on a stacktrace I could reasonably determine the general area that caused it. But imagine if I had a team of two or three developers alongside me?

Over time the advantages of using Dart dwindled. I'm not a dogmatic dude; I don't have any philosophical objections to Javascript, nor am I morally opposed to it because it's the spawn of Satan. 

It's just an awkward language with an awkward ecosystem that's still growing up.

And, right now, when I compare Dart to Javascript / Typescript, I don't see as many compelling reasons to use Dart anymore.  Really, the only things I'm missing from the language itself is a sane `this` semantic (yes yes, I know about function binding) and async/await, and the former is coming down the pipe via Typescript soon.

I still keep tabs on Dart, though. `dev_compiler` might be good for the community, and it seems like they're discussing pretty interesting language changes that might make Dart itself more palatable (dropping new, non nullable by default, RHS types).

Lessons Learned


Being backed by a corporation doesn't mean jack when it comes to programming or programmers. 


A healthy ecosystem is created by producers. 

An ecosystem of consumers is basically an impatient mob waiting for their corporate overlord to tell them what to do and how to do it, and by the way when will you be making the libraries for them to do it with?

Producers are the ones who make the cool shit that attracts consumers and other producers. They make Rails, Rack, Merb. Sometimes they're backed by a corporation, and sometimes they're just flying solo, but whatever the case, they're the ones that ultimately grow the ecosystem.

Dart doesn't have enough producers. There are a lot of promising pub packages that were abandoned early on after Dart went 1.0. I'm assuming Dart's familiar-to-a-fault syntax had something to do with that.

Prepare yourself for the anger, for it will flow freely.


I come from Ruby (C# before then, Java before that, C++ before that), and some things about ES5 still infuriate me.

No language is perfect. Learn to accept those flaws and mitigate them as best you can. And try not to think about the fact that setInterval accepts a function as its first parameter and not its last parameter.

Learn about FRP, and use bacon.js to do it.



FRP is awesome, and can simplify certain problems. I won't give you the salesman's pitch; check out the website and its examples, as well as tutorials, to get a better feel for how it might help you.


Language culture is almost as important as the language itself.


I like Kotlin. It is awesome. It has seamless interop with Java, while at the same time being incredibly concise and delightful to program in. 

That doesn't change the fact that when you do interop with Java, you still have to deal with, you know, Java. Not the language's syntax, but the result of that: ugly and bloated paradigms. If you're not careful, it's like an overweight boxer punching you in the face every time you try to get any work done.

It's 2015, and Javascript has a culture of being extremely straight-forward with code and its documentation, and that's awesome. 

That's it.


Sorry, felt like I needed another header just for the end. ¯\_(ツ)_/¯

1 comment:

eu4 console commands said...

Syntax of parseInt(string, radix). Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior. Different implementations produce different results when a radix is not specified.