$ tsc main.ts router/index.ts
However, most projects will not need to provide an explicit file list. By default, TypeScript will locate and compile every file inside the project root and all of its subdirectories. A folder becomes a project root when it contains a tsconfig.json file. Running tsc will look for this configuration file starting in the current working directory and moving up through the parent directories until it is found.
The compiler is capable of generating a tsconfig.json file for you by using the init flag:
tsc --init
The file that is generated will contain only a handful of predefined settings. For the purposes of this guide, we will create an empty tsconfig.json by hand and then run the tsc command from within that folder:
$ echo {} > tsconfig.json && tsc
Another way to point the compiler to a specific project is the -p flag, which takes the path to the directory that contains the tsconfig.json file:
$ tsc -p /path/to/folder/with/tsconfig
At this stage, the compiler will recursively scan the entire root directory and its subdirectories to find files to compile. The scope of this search can be restricted using the files configuration option.
For instance, to restrict compilation to just main.ts and router/b.ts, leaving other files untouched, we would write:
{
"compilerOptions": { ... },
"files": [
"main.ts",
"router/b.ts"
]
}
Note: any files that are imported by the files in the
fileslist will also be compiled. For example, ifmain.tspulls in exports froma.ts, thena.tsis automatically added to the compilation.
Rather than writing out a list of individual files, the include option accepts glob-like patterns to match entire directories. This is very useful for compiling, say, all files under a router directory:
{
"compilerOptions": { ... },
"include": [
"router/*"
]
}
Note: The
rootDircompiler option does not determine which files the compiler looks at for input. It works withoutDirto control how the output directory is laid out.
To omit certain files or folders from the build, the exclude option is the way to go. It also uses glob-like patterns. If, for example, we want to compile everything in the project except the navigation folder, the configuration could look like this:
{
"compilerOptions": { ... },
"exclude": [
"navigation/*"
]
}
When there is overlap between these settings, the precedence order is:
- Files
- Exclude
- Include
So, a file that appears in files will be included even if exclude would have omitted it. Conversely, if a file is matched by both include and exclude, the exclude rule wins. Unless overridden, tsc will always filter out content from node_modules, bower_components, jspm_packages, and <outDir>. The outDir setting is the subject of the next part.
Output location
By default, the compiler places each generated file in the same folder as its source .ts file. If we prefer a different destination, the outDir option is what we need.
{
"compilerOptions": {
"outDir": "dist"
}
}
With this in place, running tsc will mirror the source tree structure into the dist folder.
Note: the settings discussed in this and the following sections live inside the
compilerOptionsobject of the configuration file, unlike the ones from the previous section which are placed at the top level.
On top of that, the compiler can also bundle everything into a single file via the outFile option. The following setup:
{
"compilerOptions": {
"outFile": "dist/bundle"
}
}
will produce a file called bundle.js in the dist directory.
Note: the
outFileoption is only respected when the output modules use eitheramdorsystem. More on module formats below.
If both outDir and outFile are present, outFile takes priority and outDir is effectively ignored.
Even with compilation errors present, a .js file will normally be written. This behavior can be suppressed via the noEmitOnError flag:
{
"compilerOptions": {
"noEmitOnError": true
}
}
Output files types
In the default configuration, only .js files are emitted. For a better debugging experience at runtime, source maps are needed. We can turn them on using the sourceMap setting:
"compilerOptions": {
"sourceMap": true
}
After a build, you will notice extra mapping files sitting next to their respective ts files. So, starting with a single main.ts file, the output will contain these three files:
main.ts
main.js
main.js.map
Inside the generated main.js, there will be a comment pointing to the mapping file:
//# sourceMappingURL=main.js.map
The URL that appears in that comment can be changed via the mapRoot option:
"compilerOptions": {
"mapRoot": "/sourcemap/directory/on/webserver",
}
which results in the path below:
//# sourceMappingURL=/sourcemap/directory/on/webserver/main.js.map
The .map file locates the original sources via these two keys:
"sourceRoot": "",
"sources": [
"/typescript/main.ts"
],
The path to the source root inside the map can be adjusted using sourceRoot:
"sourceRoot": "/path/to/sources",
yielding this output:
"sourceRoot": "/path/to/sources",
"sources": [
"main.ts"
],
If your setup requires the original sources to be embedded within the map file itself — for example, to save an extra network request in production or because your server doesn’t serve raw sources — the option below is what you need:
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true
}
}
This makes the compiler store the TS source code inside the sourcesContent field:
{
"version": 3,
"file": "main.js",
"sourceRoot": "",
"sources": [
"main.ts"
],
"names": [],
"mappings": ";AAAA;IAAA;IAAgB,CAAC;...",
"sourcesContent": [
"export class Main {}"
]
}
There is also a way to skip the separate file entirely and have the map content written into the .js file itself:
"compilerOptions": {
"inlineSourceMap": true
}
So instead of a standalone main.js.map, the contents are simply embedded in main.js:
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJza...
Here, sourceMappingURL is a Data URI.
Note:
sourceMapandinlineSourceMapare exclusive — pick one or the other. TheinlineSourcesflag, however, can be combined with either of them.
Combining inlineSourceMap with inlineSources is a neat trick to end up with a single js file that carries both the source map and the source code.
Transpiling
TypeScript is a superset of ES6, meaning your TS sources are expected to use modern ES6 syntax. But the final JS output can be designated for ES5 (or earlier). The JavaScript version for the output is chosen with the target option:
{
"compilerOptions": {
"target": "es6"
}
}
As of this writing, most browsers support well over 90% of the ES6 spec, so es6 might be a reasonable choice provided you have shims in place. Keep in mind that ES3 is the default, so setting target to at least es5 is highly recommended.
You’re writing your TS project using ES6 modules, yet as of January 2016, no browser can natively load this module format. Enter the module option, which transforms ES6 modules into a more widely supported format like CommonJS, AMD, or SystemJS. This conversion is typically handled during a build step (such as with Webpack) or at runtime by a module loader (such as SystemJS). When omitted, module falls back to ES6 if target is ES6, otherwise it uses CommonJS. Setting target to CommonJS explicitly is a common practice:
{
"compilerOptions": {
"module": "CommonJS"
}
}
From the ES7 proposal, TS supports decorators. These are extensively used in Angular2 development. The relevant flag to enable decorator syntax is:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Similarly, Angular2’s dependency injection relies on metadata to determine what to inject. If this metadata should be generated in the compiled output, turn on emitDecoratorMetadata:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
When your sources make use of classes from standard ES libraries, the lib option must list every intended library interface. To get access to Reflect or Array.from (both part of ES6) in addition to DOM, you would use:
{
"compilerOptions": {
"lib": ["es6", "dom"],
}
}
The default set of libraries is DOM,ES5,ScriptHost when targeting ES5, and DOM,ES6,DOM.Iterable,ScriptHost for ES6. Supplying a custom lib list will cause the compiler to skip its defaults, meaning you’ll have to manually include every library you depend on.
Note: Setting
libtells the compiler to stop complaining when it sees APIs from those libraries. It has no effect on the generated code, since a library is only ad.tsfile that declares interfaces.
Resolving modules
Similar to how Node’s require works in ES6 modules, TypeScript differentiates between relative and non-relative (absolute) module references. A relative reference begins with /, ./, or ../, and such imports are always resolved from the location of the importing file.
The strategy for resolving non-relative modules is controlled by the moduleResolution compiler option. A thorough explanation of the algorithm can be found in the TypeScript handbook. When this option is omitted, it defaults to node if module is set to CommonJS, and to classic for any other module target.
With the node resolution strategy in place, the compiler searches for modules inside the node_modules directory. But what if your modules live elsewhere? The paths option lets you define additional directories to search. Imagine you import a module as below:
import { jQuery} from 'jquery';
If the jquery folder is stored under libs, you could set up the configuration like this:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"jquery": [
"libs/jquery"
]
}
}
}
This instructs the compiler that whenever jquery is imported, it should check libs/jquery. The resolution process looks for jquery.[ts|d.ts] inside the lib directory first; if that fails, it moves on to libs/jquery. During this search, it also inspects any package.json for a typings field that points to the main entry file; otherwise, it reverts to index.[ts|d.ts].
The values in paths are actually patterns, and you can leverage the * wildcard to match any module name. This means the earlier configuration could be simplified as follows:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"libs/*"
]
}
}
}
Running the compiler with the --traceResolution flag would output something like this:
Module name 'jquery', matched pattern '*'.
Trying substitution 'libs/*', candidate module location: 'libs/jquery'.
Notice that the asterisk in the path gets replaced with the actual matched pattern. This flexibility becomes useful when module names don’t correspond directly to folder structure. For example, if your code references libraries like this:
import { jQuery } from 'package/vendors/jquery';
but physically the jquery library sits inside the libs directory, the following configuration enables the compiler to find it:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"package/vendors/*": [
"libs/*"
]
}
}
}
Here, the compiler binds jquery to *, turning libs/* into libs/jquery during resolution.
Remember: when you set
paths, you must also providebaseUrl. This value defines the root for resolving non-relative modules.
Once paths is defined, the compiler walks through the specified folders and only falls back to node_modules if no match is found. The first resolved module wins, and no other locations are examined. So if a module exists both in node_modules and in your custom directory, the one from the custom path takes precedence. To force the compiler to use the version from node_modules, place it in paths ahead of your custom folder:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*",
"node_modules/*",
"generated/*"
]
}
}
}
Note: the
typeRootsoption plays no role when resolving external modules (ES6 modules).
Handling declaration files
TypeScript lets you describe a variable or class that has no JavaScript output; its actual implementation is assumed to exist at runtime. This capability was introduced to bridge the gap with existing JavaScript code, such as browser APIs or third-party libraries like jQuery.
If you attempt to use an object that isn't declared in your TS project, the compiler reacts with an error:
logger.log();
Error:(2, 1) TS2304:Cannot find name 'logger'.
The remedy is to provide an explicit declaration:
declare var logger: {log: () => void};
logger.log();
This is known as an ambient declaration — it generates no output and exists solely for compile-time type checking. Ambient declarations rely on the declare keyword. To keep things organized, TypeScript offers a dedicated file format for grouping these: files with the .d.ts extension. These files may only host ambient declarations and are a staple during development. For instance, calling console.log() won't raise an error because console is already typed in lib.d.ts, which ships with the typescript npm package.
Chances are you'll need to produce and consume declaration files yourself. I've walked through an example of this in a Stack Overflow answer. As noted, these files carry no implementations but instead expose the shape of class APIs and values present at runtime. To have the compiler generate .d.ts files, enable the declaration option:
"compilerOptions": {
"declaration": true
}
Sometimes it's useful to direct these declaration files to a separate folder or even bundle them into a single file. The declarationDir option controls their output location:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "declarations"
}
}
And the outFile option (the same one used for generated .js files) can concatenate them:
{
"compilerOptions": {
"declaration": true,
"outFile": "declarations/index.d.ts"
}
}
When you open a generated .d.ts file, you might encounter this:
declare module "module1" { ... }
declare module "module2" { ... }
That syntax is a leftover from before version 1.5 for external/ES6 modules. It's retained today to support declaring multiple ES6 modules in a single file, and it's valid only in declaration files.
Be careful not to confuse this quoted module declaration with the unquoted form:
declare module module1 { ... }
declare module module2 { ... }
The unquoted name style predates 1.5 and was used for declaring namespaces. Since 1.5, this pattern has been deprecated in favor of the namespace keyword:
declare namespace module1 { ... }
declare namespace module2 { ... }
Unlike quoted module names, namespaces are valid in both regular .ts files and .d.ts declaration files.
TypeScript support in WebStorm
WebStorm offers built-in TypeScript support either via its own compiler or the integrated TypeScript Language Service.
It is crucial to keep the TypeScript version in WebStorm in sync with the one used during your build (for instance, through Webpack loaders). Otherwise, you might end up puzzled when your build succeeds while the IDE flags errors, or the other way around.
By default, WebStorm picks the TypeScript compiler from the typescript package in node_modules at the project root, or falls back to the version bundled with the IDE. You'll want to place the exact same typescript package version that your build relies on into that root node_modules.
A more robust approach is to set the custom directory option and point it to the location of typescriptServices.js and lib.d.ts. Both files live inside the typescript/lib npm package. So you could direct the IDE to use a global TypeScript install by setting the path to /path/to/nodejs/node_modules/typescript/lib.
