Relationship between ES6 and TypeScript modules
The default export is special:
// foo.ts
export default function factoryFunction() {
}
and can be imported under a different name easily:
// bar.ts
import myFactory from './foo'
Given
// mymodule.ts
export function foo() { return "Hello" }
export function bar() { return "World" }
You can import both functions this way:
import {foo, bar} from './mymodule'
console.log(foo(), bar())
Or you can import everything from mymodule as a namespace:
import * as mymodule from './mymodule'
console.log(mymodule.foo(), mymodule.bar())
When compiling to a CommonJS target, the default export is simply exports.default
. Its not possible to modify module.exports
using ES6 compatible syntax, however export = something
still works.
For now, CommonJS modules can be imported either:
-
as namespaces (only tested with .d.ts files)
import * as Promise from 'bluebird'
-
using old style syntax (not tested for target ES6)
import Promise = require('bluebird')
The first method could break at any time, therefore the 2nd method is recommended. Here is the reason:
CommonJS module loading isn't defined by ES6. TypeScript chooses to treat module.exports
as an ES6 module, however ES6 modules cannot be treated as constructors or functions. For example, when importing bluebird as above, all calls in your code such as:
```typescript
var p = new Promise((resolve, reject) => ...)
```
would break. The reason: Promise is an ES6 module and cannot be invoked as a constructor
The safe option is therefore to always use import x = require(...)
for CommonJS modules and ambient external definitions that contain export = X
syntax.
ES6 supports custom module loaders, which means that theoretically, modules could be loaded from anywhere. By default paths are relative to the current module in the same directory.
In TypeScript however, this is not the case and the behaviour will depend on both the target module system and module bundler/loader. For example, if you compile
import test from 'mymodule'
with the options -m commonjs
, and you bundle your commonjs modules with browserify, it will look for 'mymodule' in the node_modules
directory. Other bundlers such as webpack will give you fine control over the directories used for module lookup.
In most cases with both TS and ES6:
-
the path will be interpreted as relative to the current module if you prepend './' to the name:
import test from './mymodule'
-
the path will be resolved by the bundler as a module installed by some package manager (e.g. npm, bower, jspm etc) if you don't prepend './'
import test from 'mymodule' // installed via npm
When presented with a relative path, for example:
// foo.ts
import X from './x'
the TypeScript compiler will try to use the types in the file x.ts
found in the same path as foo.ts
.
When presented with a bundler-relative module id that doesn't begin with './'
, e.g.
// foo.ts
import split from 'x'
Then typescript will first attempt to locate a so called "ambient external module declaration" in your source files. For example, if you include typings/x-definition.d.ts
in the compiler files, and it contains the following:
declare module 'x' {
export default function split(s:string):Array<string>;
}
then TypeScript will use this declaration and assume that split
is a function that takes a string
and returns an array of strings. However, it will be up to the bundler (browserify, webpack etc) to find and bundle the appropriate JS module.
If an ambient external module declaration is not provided, then TypeScript's lookup will depend on the target module system. For CommonJS targets, it works similarly to node modules as explained in this document. For AMD and other module types there currently are no rules, but there is work in progress to add support.