TypeScript 2.0 Notes
Contents
- 1. Why Typescript
- 2. Impressions
- 3. Installation
- 4. Using the compiler
- 5. Targeting ES6 and later on the browser
- 6. Targeting ES5 on the browser
- 7. Assertions
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 intorequire()
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
doestsc --declaration a.ts
and touches c.d.tsmake a
again, make says a is up date.make b
doestsc --declaration b.ts
and touches c.d.tsmake 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 oftsc
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 TypeScriptimport
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 anassert
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.