Devious Fish
Music daemons & more
profile for Perette at Stack Overflow, Q&A for professional and enthusiast programmers

TypeScript 2.0 Notes

Contents

1. 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.

2. 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 can compile to ES5 JavaScript, which means dealing with Node.js tool chains, 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.
  • On the other hand, you can compile ES6 and later. Browsers with support are commonplace but not ubiquitous as of this writing, so there’s a compatibility tradeoff. But it gets rid of the Node.js nightmares.

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.

3. 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.

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

3.1. Targeting ES5 & earlier

If you want to target older, EcmaScript 5 and earlier, it gets much messier. You’ll need tool chains to repackage ES5 output for 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

4. 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.

5. Targeting ES6 and later on the browser

If you’re transpiling to ES6 or a later version of JavaScript (see tsc --target option), the compiler is enough. To utilized generated code, you’ll need a snippet of JavaScript in your HTML, and tags must include a type="module" attribute. In that code, include a modern import statement: import { MyClassName } from './myfile.js'. In that snippet, you can then instantiate an instance of something you imported to kick things off. The class will not be seen outside this snippet of JavaScript.

<script type="module">
  import { SlideShow } from './slideshow.js'; // Curly brackets are necessary
  let slideshow = new SlideShow ("content", "slideshow");
</script>

You don’t need <SCRIPT> tags to import the individual modules; the import statements will trigger their loading. Modules can in turn provide additional imports, however, Typescript leaves the .js extension off, so add some sed to the Makefile to append .js or .jsm to names. .jsm has the advantage you can write the modified files to file.jsm, not having to rename back to the input file. (.jsm is an extension intended specifically for Javascript modules.)

Browser support is growing but there’s always stragglers; I haven’t yet found working minifiers/uglifiers for ES6 or later, though I haven’t looked much either. But if the change to ES6 and modern-day modules sound like a nuisance, then read on. The remainder of this document deals with ES5, legacy “modules”, and making them browser-ready.

6. Targeting ES5 on the browser

6.1. Standalone

In ES5 mode, 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.

6.2. 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.

6.3. 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.

7. 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.