Devious Fish
Music daemons & more

TypeScript 2.0 Notes

Why Typescript

  • It adds types to variables, parameters and returned values. When provided, assignments and function call parameters are checked at compile-time. Typos, stupids, and brainfarts cause compiler errors instead of subtle misbehaviors, saving development time through reduced run-time debugging.
  • It provides a far cleaner, modern syntax to ease comprehension and maintenance of object-oriented code: you can declare classes and inheritance with a syntax reminiscent of C++ or C#, instead of assigning functions to JavaScript object prototypes directly.
  • Compile-time validation eliminates fear of breaking things through refactoring. Refactoring helps eliminate spaghetti code, making the code-base more legible and reliable.
  • It fixes deficiencies of JavaScript, such as adding an array iterator for for loops (for (let f of foos)), which turns (almost) into the code you’d write yourself.
  • It fixes problems of JavaScript: Arrow functions retain their this in all contexts, including when called from event handlers.
  • It’s a superset of JavaScript, so you can keep using existing JavaScript in much the way that C++ allows using existing C code.
  • Although TypeScript has new syntax for new elements, it isn’t yet another entirely new and different language.

Impressions

I’ve been using TypeScript for about 2 months, with mixed impressions:

  • TypeScript itself is easy to install.
  • TypeScript’s quick evolution creates trouble. In particular, methods of referencing other modules have changed several times over the past few years, and Internet searches for help often yield more confusion because of conflicting information. (TypeScript 2 uses an import statement instead of comments; these turn into require() in compiled code.)
  • That said, Microsoft’s official TypeScript site does provide reasonable documentation that’s worth reading through.
  • The readability improvements and compile-time checks TypeScript provides, once you get it all going, are pretty darn sweet.
  • TypeScript compiles to JavaScript, which means dealing with Node.js toolchains, which seem haphazardly documented. Documentation tends to be man-page style, covering a lot of range but without going into detail, nor providing a cohesive introduction suitable for a neophyte. It is expert friendly and newbie-hostile.
  • The Node.js tools don’t look coherently planned. Browserify, for example, relies a lot on transformation plug-ins. On the positive side, plug-ins are probably a good, extensible approach. On the negative, Browserify lacks certain obvious options: there’s no easy way to depend on a module and make it available to the page via ‘require’, too.
  • The tools are also fragile and unforgiving. If you don’t do things exactly the way they expect, they either don’t work or provide wrong results.

Overall, I have no regrets moving to TypeScript. JavaScript was never meant to do the things we are doing with it, and its syntax is horrid for anything more than a few event triggers. TypeScript provides a better, safer, richer language via an evolutionary approach, enabling reuse of existing JavaScript code and adaptation as time permits. Setting up the build process sucks, but it’s a one-time hit that provides a ton of return.

Installation

Use Node.js to install the TypeScript compiler. npm is the node package manager, and the -g means global installation (as opposed to installation in the current directory):

npm install -g typescript

And the library declarations needed:

npm install -g @types/jquery
npm install -g @types/assert

Type header files originate from the DefinitelyTyped website, but npm is the best way to install them.

And toolchains to package product to browser use:

npm install -g browserify
npm install -g browserify-shim
npm install -g unassertify
npm install -g uglifyjs

To update packages to the latest version in the future:

npm update -g

The compiler is tsc. Use the --baseUrl option to indicate where the type files are; probably something like /usr/local/lib/node_modules/@types.

Using the compiler

tsc is a smart compiler; if you point it at your main, it’ll compile all the dependencies automatically.

While compiling your code, the --declaration option will write declarations for all modules being compiled too. This can be hazardous with Make if you set up dependencies on these declaration files:

  • A depends on C.
  • B depends on C.
  • make a does tsc --declaration a.ts and touches c.d.ts
  • make a again, make says a is up date.
  • make b does tsc --declaration b.ts and touches c.d.ts
  • make a again, make rebuilds because c.d.ts was touched.

It seems like the “right” way is to make your one compilation depend on all the source files and any hand-cranked declaration files, and let tsc sort it out.

Using TypeScript code on the browser

Standalone

The output of tsc is a Node.js-style module. These modules hide their private variables and functions within closures or other structures, only making export members available for outside use.

Node.js is meant for use on the server, not the client. To use its modules on the client, they must be fed through Browserify, which:

  • Starts at a main module, and bundles imported/required .js files together into one file.
  • Provides an under-the-hood require() function, which is what TypeScript import statements turn into, allowing modules to link to eachother.

If your JavaScript is fully encapsulated, this is straightforward and you’re good. But this is rarely the case.

Working with other libraries

In practice, you are likely have files you don’t want bundled and would like to load independently (say, jQuery). In this case you need browserify-shim. The shim is configured through a magic file named package.json that goes where you browserify; you don’t need to specify any options or the filename or anything, browserify just notices it’s there and reads it.

{
  "browserify-shim": {
    "jquery": "global:$",
    "./translate": "global:translate"
  },
  "browserify": {
    "transform": [
      "/opt/local/lib/node_modules/browserify-shim"
    ]
  },
  "devDependencies": {
    "browserify": "~2.36.1",
    "browserify-shim": "~3.0.0"
  }
}

In this file, browserify.transform lists transformations to perform, in this case browserify-shim. Normally you’d just put in browserify-shim in there, but it can’t be found on my system despite being installed in the same node_modules directory as browserify, and a full path works. (See note above about JavaScript tools being fragile.)

The browserify-shim entry lists module names. If you’re shimming a module from your project, you must include the path even if it’s ./. The path is relative to the project.json file (Is that right?). The global: entry specifies a global variable in which the module will be found. There are other ways to do this, indicating places to find modules other than a global variable. See the shim documentation for more information (and good luck). (See note above about haphazard documentation.)

If using globals, make sure the globals are set up before your <script> tag.

Exporting modules

By default, browserify generates a self-contained module that invokes itself but does not export any symbols. If you want to interact with it, use the --require option:

browserify --require './client.js' -o client.lib.js

After the <script> tag, you can use require to get the library:

Client = require ('/client');

Note that --require strips the path and the module shows up at the root. Unfortunately, you can’t also apply this to any required modules: the path change will prevent them being found at run-time by modules that depend on it. (See note above about fragile tools.) There may be a way to use browserify-shim to alias the path so both work, but I haven’t worked that out yet.

Assertions

Node.js provides an assert module with several assertion functions. Unfortunately, the declaration files for these haven’t been updated for TypeScript 2 yet.

Once that’s working, these is an additional Browserify transformation unassertify which strips assertions.