tl;dr Typescript can generate efficient Javascript code and I decided to switch. I approached the code conversion by simply rename the files from *.js to *.ts and then slowly migrating the code base file by file and case by case. During all phases I had a working build and a running game.

About

I switched my game code from JavaScript to Typescript. I like to share some of my reasons and how the process went.

This must be blog-entry number 1,000,000 with that topic. I hope, though, it’s only number 1,000 when it comes to 60FPS game projects.

Why switch?

Working on my game GhostJump[working title] I really enjoyed writing vanilla Javascript. Using the AMD module concept and IntelliJ really helps to organize the project. I finished most of the game in that environment. It’s okay. However, while working on another project using Typescript combined with IntelliJ I started to wonder if I switch.

If I say IntelliJ I am talking about the ultimate edition. It is basically Webstorm. The community edition won’t do (at least as far I know). And no, I’m not paid by them to name them here. Visual Studio Code should be equally good in all this.

Writing the code in Typescript definitely has its advantages. Especially with a proper IDE. The slightly stricter rules of Typescript is just that little extra information the IDE needs to make many things possible. For example: shift-clicking on a variable and always jump to the right source, being able to safely refactor classes and finally have a handy solution for Javascript enums.

But it’s not just the better IDE support. After all Typescript is a transpiler and in the end it generates Javascript code. Before Typescript I already used Babel as a transpiler to replace my modern Javascript code with old Javascript code. The question for me was: which one generates better Javascript code for my game?

I started to compare the generated Javascript from my Typescript files and what Babel generates. From my observations I assume that, during the transpile process, Babel has only limited knowledge about the codes’ intention. It always generates compatible code, yes but it seems that Typescript can do a better job here. Let me give an example:

JavaScript ➡️ JavaScript (using babel)

Input

// JavaScript code
function someFunc(someObj) {
  for (let i of someObj) { // for-of loop
    console.log(i);
  }
}
const someArray = [1, 2, 3];
someFunc(someArray);

Babel output

"use strict";

function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

function someFunc(someObj) {
  var _iterator = _createForOfIteratorHelper(someObj),
      _step;

  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var i = _step.value;
      console.log(i);
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }
}

var someArray = [1, 2, 3];
someFunc(someArray);

Typescript ➡️ JavaScript

Input

function someFunc(someObj:number[]) {
  for (let i of someObj) { // for-of loop
    console.log(i);
  }
}

const someArray = [1, 2, 3];
someFunc(someArray);

Typescript output

"use strict";
function someFunc(someObj) {
    for (var _i = 0, someObj_1 = someObj; _i < someObj_1.length; _i++) {
        var i = someObj_1[_i];
        console.log(i);
    }
}
var someArray = [1, 2, 3];
someFunc(someArray);

The Javascript code generated by Typescript in this example just looks better. It’s what I would like to have when iterating over an array of numbers (given, that garbage collection is something you need to consider).

I know that I can tweak how Bable generates code. I’m not familiar enough with Babel to understand why they need to transpile it in that fashion. I assume that Babel cannot know the object type of the parameter and to 100% fulfill the requirements of a for-of-loop they need to make all those checks. But all those checks could make a simple and efficient thing like iterating over an array of numbers less efficient.

Working in an environment where you target 60 frames per second you try to avoid any unnecessary CPU load. If we compare the outputs here it’s obvious that the Typescript result is leaner and faster to execute. It does not impose the overhead of try-catch and does not create any unneeded objects that have to be handled by the Garbage Collector.

In the end the better IDE support and the seemingly more efficient Javascript code made my decision. So let’s switch to Typescript.

The switch

The game project is composed of two major parts: the game engine (TTjs) and the game itself. The code base is pretty big and there are many classes.

When I started the conversion I wasn’t sure how much Typescript I really wanted. Fully convert all Javascript code to Typescript would been too much work and I would likely break many things without noticing it. Luckily Typescript was designed to slowly migrate an existing code base. I did it in the following steps:

  1. Rename all files from *.js to *.ts
  2. Switch to the Typescript module system
  3. Make use of types and Typescript classes

Step 1: From *.js to *.ts

Typescript claims to be JavaScript with types. And yes, it actually is. It really took me some time to make full sense of what that means. When I started to think about the conversion I had this vague idea of Oh no, I need to touch every line in my code to make it ts-compatible before I can run that game again. Which isn’t true at all. All I needed to do was to setup the Typescript tool chain. In my tsconfig I said I needed ES3 as output and AMD modules. I also said it’s okay to have implicit any and that implicit null is fine too.

Once that was set up I started to simply change the file type of each file from *.js to *.ts. I really expected that to fail because I assumed that Typescript requires the usage of their own Module system, but no. As long as I do not use the keywords export or import in my ts-file it is treated as a module free Typescript file.

In 90% of the cases I didn’t need to change a single line of code in a given file. Only in some cases I got errors. Something like this:

switch(someState) {
  case "state1": return 1;
  case "state2": return 2;
  case "state2": return 3; // << issues Typescript detected
}

Aside from this level of programming errors I had nothing to change. In all cases I was happy about the errors because in really all cases they were actual errors in my code.

Once all files were renamed and the minor issues resolved I actually had the game running again. Removing Babel from the build process I could do a full deployment to my device.

Step 2: Typescript Module System

After all that renaming the entire project consisted of Typescript files. However, they still contained the hand written AMD module management.

A typical class looked like this:

// MyClass1.ts
define([
  'some/path/MyClass2'
], function(
  MyClass2
) {
  function MyClass1() {
    // do constructor things
  }
  MyClass1.prototype = {
      someFunc() {
        const someVar = new MyClass2();
      },
  };
  return MyClass1;
});

In the tsconfig I configured Typescript to generate AMD modules but as long as there are no import or export in a given file Typescript does not touch that part. That was cool because I had a running build of the game and I could already continue to work on the game itself. However, written like this, the IDE still had problems to provide me with proper navigation and it wasn’t able to do things like automatic import. For proper IDE support in a Typescript project one really needs to make use of Typescript modules.

To do that I had to change a file to this:

import {MyClass2} from 'some/path/MyClass2'

export function MyClass1() {
  // do constructor things
}
MyClass1.prototype = {
    someFunc() {
      const someVar = new MyClass2();
    },
};

Unfortunately the AMD modules generated by Typescript differ from the way I defined them by hand. The Javascript modules generated by Typescript all make use of a variable called exports. As I didn’t use that variable in my handwritten Javascript code it rendered my files incompatible. I couldn’t mix my AMD-Module-class with an already converted Typescript-module-class.

This failed:

// MyGame.ts
require([                //
  'some/path/MyClass1'   // MyClass already changed to 
], function(             // Typescript module definition
  MyClass1               //
) {                     
  const myVar = new MyClass1(); // ERROR!
});

So, other than before, I could not change the game file by file and run it from time to time. Instead I was forced to actually convert all modules in one big step.

I shared that great experience in this little tweet.

Eventually, after all files were changed, the game ran again. I had to change some more details on how the game engine loads the component classes and in a later stage I needed to rework parts of my in-browser-level-editor but I got that running too.

Having Typescript modules in place the IDE support improved drastically.

Step 3: Make use of types and Typescript classes

All files are Typescript now and they all make use of the Typescript module system. However, most of the code is still the vanilla Javascript code I originally wrote. So, it’s kind of: Javascript in Typescript.

The next logical step is to translate the original Javascript functions, classes and handwritten enums to their Typescript counter parts. However, there are so many files on both sides (game + engine) that I don’t change them right away. I decided to make that an ongoing process. I’d like to concentrate on the first release of the game and update the classes while working on new features.

Final thoughts.

Yeah and that’s basically it. Obviously in detail I had to solve more issues but nothing worth mentioning here.

I really like how the tool chain of Typescript allows me to use my old code in a new environment. The usage of the Typescript module system alone allows the IDE to provide a wide range of features: proper code navigation, refactoring of class names and auto import. It’s just so much more efficient to just write a class type and let the IDE go to the top of the file and add the import line.

The generated Javascript code is clean and in most cases efficient. I still have to check the generated code from time to time and decide if it is something I want to have in a 60FPS main loop. Sometimes it’s better to avoid modern code and pretty patterns just to reduce the load on the garbage collector in a frame. But overall it’s fine.

For new parts of the game and engine I now can make full use of everything Typescript has to offer. That way I can slowly approach a better code base with full IDE support.