Skip to content

Instantly share code, notes, and snippets.

@spion
Last active February 8, 2016 19:22
Show Gist options
  • Save spion/0da65549d2aa3ed3ab4a to your computer and use it in GitHub Desktop.
Save spion/0da65549d2aa3ed3ab4a to your computer and use it in GitHub Desktop.

Relationship between ES6 and TypeScript modules

Default export

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'

Other imports

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())

Default export vs CommonJS module.exports

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:

  1. as namespaces (only tested with .d.ts files)

    import * as Promise from 'bluebird'
  2. 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.

Relative paths vs bundler-relative module ids

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:

  1. the path will be interpreted as relative to the current module if you prepend './' to the name:

    import test from './mymodule'
  2. 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

Module resolution and types in TypeScript

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment