In order to use TypeScript alongside Angular 2, there are two major considerations: module interoperability and compilation. You'll need to handle both in order to take your application's .ts
files, mix them with external library files, and output the files that would be compatible with your target device.
TypeScript comes ready as an npm
package, but you will need to tell it how to interact with the files and modules you've written, and with files from other packages that you want to use in your modules.
The code, links, and a live example of this are available at .
You should first complete the instructions mentioned in the preceding recipe. This will give you the framework necessary to define your TypeScript configuration.
To configure TypeScript, you'll need to add declaration files to incompatible modules and generate a configuration file that will specify how the compiler should work.
TypeScript declaration files exist to specify the shape of a library. These files can be identified by a .d.ts
suffix. The majority of npm
packages and other JavaScript libraries already include these files in a standardized location, so that TypeScript can locate them and learn about how the library should be interpreted. Libraries that don't include these need to be given the files, and fortunately the open source community already provides a lot of them.
Two libraries that this project uses don't have declaration files: node
and core-js
. As of TypeScript 2.0, you are able to natively install the declaration files for these libraries directly through npm.
The -S
flag is a shorthand for saving them to package.json
:
npm install -S @types/node @types/core-js
A sensible place for this is inside the postinstall
script.
The TypeScript compiler will look for the tsconfig.json
file to determine how it should compile the TypeScript files in this directory. This configuration file isn't required, as TypeScript will fall back to the compiler defaults; however, you want to manage exactly how the *.js
and *.map.js
files are generated. Modify the tsconfig.json
file to appear as follows:
[tsconfig.json] { "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "noImplicitAny": false } }
The compilerOptions
property, as you might expect, specifies the settings the compiler should use when the compiling process finds TypeScript files. In the absence of a files property, TypeScript will traverse the entire project directory structure searching for *.ts
and *.tsx
files.
All the compilerOptions
properties can be specified equivalently as command-line flags, but doing so in tsconfig.json
is a more organized way of going about your project.
target
specifies the ECMAScript version that the compiler should output. For broad browser compatibility, ES5 is a sensible default here. Recall that ECMAScript is the specification upon which JavaScript is built. The newest finished specification is ES6 (also called ES2015), but many browsers do not fully support this specification yet. The TypeScript compiler will compile ES6 constructs, such as class
and Promise
, to non-native implementations.module
specifies how the output files will handle the modules in the output files. Since you cannot assume that browsers are able to handle ES6 modules, the TypeScript compiler will have to convert them into a module system that browsers are able to handle. CommonJS is a sensible choice here. The CommonJS module style involves defining all the exports in a single module as properties of a single "exports" object. The TypeScript compiler also supports AMD modules (require.js
style), UMD modules, SystemJS modules, and of course, leaving the modules as their existing ES6 module style. It's out of the scope of this recipe to dive deep into modules.moduleResolution
defines how module paths will be resolved. It's not critical that you understand the exact details of the resolution strategy, but the node setting will give you the proper output format.emitDecoratorMetadata
and experimentalDecorators
enable TypeScript to handle Angular 2's use of decorators. Recall the addition of the reflect-metadata
library to support experimental decorators. These flags are the point where it is able to tie into the TypeScript compiler.noImplicitAny
controls whether or not TypeScript files must be typed. When set to true
, this will throw an error if there is any missed typing in your project. There is an ongoing discussion regarding whether or not this flag should be set, as forcing objects to be typed is obviously useful to prevent errors that may arise from ambiguity in codebases. If you'd like to see an example of the compiler throwing an error, set noImplicitAny
to true and add constructor (foo) {}
inside AppComponent
. You should see the compiler complain about foo
being untyped.Running the following command will start up the TypeScript compiler from the command line at the root level of your project directory:
npm run tsc
The compiler will look for tsconfig.json
if it is there and fall back to its defaults otherwise. The settings within direct the compiler how to handle and validate the files, which is where everything you just set up comes into play.
The TypeScript compiler doesn't run the code or meaningfully understand what it does, but it can detect when different pieces of the application are trying to interact in a way that doesn't make sense. The .d.ts
declaration file for a module gives TypeScript a way to inspect the interface that the module will make available for consumption when it is imported.
For example, suppose that auth
is an external module that contains a User
class. This would then be imported via the following:
import {User} from './auth';
By adding the declaration file to the imported module, TypeScript is able to check that the User
class exists; it also behaves in the way you are attempting to in the local module. If it sees a mismatch, it will throw an error at compilation.
Depending on your framework experience, this may be something you have or have not had experience with previously. Angular 2 (among many frameworks) operates under the notion that JavaScript, as it currently exists, is insufficient for writing good code. The definition of "good" here is subjective, but all frameworks that require compilation want to extend or modify JavaScript in some form or another.
However, all platforms that these applications need to run on—for your purposes, web browsers—only have a JavaScript execution environment that executes from uncompiled code. It isn't feasible for you to extend how the browser handles payloads or delivers a compiled binary, so the files that you send to the client must play by its rules.
TypeScript, by definition and design, is a strict superset of ES6, but these extensions can't be used natively in a browser. Even today, the majority of browsers still do not fully support ES6. Therefore, a sensible objective is to convert TypeScript into ES5.1, which is the ECMAScript standard that is supported on all modern browsers. How you arrive at this output can occur in one of two ways:
When you look at the compiled JavaScript that results from compiling TypeScript, it can appear awfully brutal and unreadable. Don't worry! The browser does not care how mangled the JavaScript files are as long as they can be executed.
With the compiler options you've specified in this recipe, the TypeScript compiler will output a .js
file of the same name right next to its source, the .ts
file.
By no means is the TypeScript compiler limited to a one-off .ts
file generation. If offers you a broad range of tooling functions for specifying exactly how your output files should appear.
The TypeScript compiler is also capable of generating source maps to go along with output files. If you're not familiar with them, the utility of source maps stems from the nature of compilation and minification: files being debugged in the browsers are not the files that you have written. What's more, when using a compiled TypeScript, the compiled files won't even be in the same language.
Source maps are indexes that pair with compiled files to describe how they originally appeared before they were compiled. More specifically, the .js.map
files contain an encoding scheme that associates the compiled and/or minified tokens with their original name and structure in the uncompiled.ts
file. Browsers that understand how to use source maps can reconstruct how the original file appeared and allow you to set breakpoints, step through, and inspect lexical constructs inside it as if it were the original.
Source maps can be specified with a special token added to the compiled file://# sourceMappingURL=/dist/example.js.map
If you want to generate source map files for the output, you can specify this in the configuration file as well by adding "sourceMap": true
. By default, the .js.map
files will be created in the same place as the output .js
files; alternatively, you can direct the compiler to create the source maps inside the .js
file itself.
Even though extraneous map files won't affect the resultant application behavior, adding them inline may be undesirable if you don't want to bloat your .js
payload size unnecessarily. This is because clients that don't want or need the map files can't decline to request them.
Since TypeScript checks all the linked modules against their imports and exports, there's no reason you need to have all the compiled files exist as 1:1 mappings to their input files. TypeScript is perfectly happy to combine the compiled files into a single file if the output module format supports it. Specify the single file where you wish all the modules to be compiled with "outFile": "/dist/bundle.js"
.
Certain output module formats, such as CommonJS, won't work as concatenated modules in a single file, so using them in conjunction with outFile
will not work. As of the TypeScript 1.8 release, AMD and system output formats are supported.
If you plan on using SystemJS, this compiler option can potentially help you, as System works with virtually any module format. If, however, you're using a CommonJS-based bundler, such as Webpack, it's best to delegate the file combination to the bundler.