ES6 modules specification explicitly states import/export statements may only be used at the top level of files.

Static imports like this bring about a number of benefits such as first-class support for circular dependencies between modules and (potential) support for macros but it also means you cannot conditionally import modules. Which can be a fairly big issue if you're using e.g. Meteor, as code-sharing between server and client is one of its core propositions.

Now, this is not reallly a problem when using vanilla JS as you can simply switch to raw require. However, if you're using TypeScript (and some smart people say you should), this doesn't really cut it because, hey, where did my types go?

So that's the question - with require which is just a regular function, how do I tell compiler the type of what I'm requiring?

Reference Elision

Enter reference elision. See, in TypeScript, when you import a module, the compiler only emits code into the JavaScript output if you use the import in a value position in the body of the module. In other words, if you import a module but never use any of the imported values other than as TypeScript types, the resulting javascript file will look as if you never imported the module at all.

Which means these two files will output the same JS code:

b.ts

import * as ModuleA from './a';  
const modA: typeof ModuleA = require('./a');  
console.log('modA.foo', modA.foo);  

c.ts

const modA = require('./a');  
console.log('modA.foo', modA.foo);  

Conditional Imports

This is incredibly useful because if we combine the typeof keyword (which returns the type of its argument) with what we know of reference elision, we can achieve conditional imports in TypeScript while maintaining type safety:

import * as ServerOnly from './top-secret';

let serverMod: typeof ServerOnly;  
if (Meteor.isServer) {  
  serverMod = require('./top-secret');
}

if (Meteor.isServer) {  
  console.log('topSecret:', serverMod.topSecret);
}

Note that this won't prevent you from accidentally calling the (undefined) module from your client code; that would require something like preprocessor directives which TS doesn't currently support. But at least you'll have types when calling it from the server.

Happy importing! I'm @tomas_brambora.


Sidenote: There was a proposal to make reference elision explicit via the type keyword but it was rejected.

Sidenote2: Once TypeScript supports the ES dynamic import proposal this will be a non-issue.

Sidenote3: Kudos to TypeScript handbook which is where I've found info on reference elision and optional imports.

Sidenote4: I've submitted a pull-request to the Meteor guide.