Notes

Using Typescript to generate RequireJS (AMD) modules and export behaviour

Edit on GitHub

Typescript
3 minutes

I’m compiling to AMD modules.

  • Default exports are exported as exports.default = Awesomesauce
  • Individual exports with export class Awesomesauce output exports.Awesomesauce = Awesomesauce
  • Individual exports with export = Awesomesauce output return Awesomesauce. exports = syntax specifies a single object that is exported from the module

Default exports work fine when i am importing TS modules into TS modules manually. But when DurandalJS/KnockoutJS does the automatic ViewModel+View binding i run into weird issues. Hence this thorough checkup of what exports are like and what DurandalJS/KnockoutJS is expecting..

Answer was found in the TypeScript docs related to modules:

Both CommonJS and AMD generally have the concept of an exports object which contains all exports from a module.

They also support replacing the exports object with a custom single object. Default exports are meant to act as a replacement for this behavior; however, the two are incompatible. TypeScript supports export = to model the traditional CommonJS and AMD workflow.

The export = syntax specifies a single object that is exported from the module. This can be a class, interface, namespace, function, or enum.

When exporting a module using export =, TypeScript-specific import module = require("module") must be used to import the module.

tl;dr:

When working with CommonJS/AMD

  • Do NOT use default exports if you want CommonJS/AMD interop. Default exports and the exports object in CommonJS/AMD are incompatible. (The former is actually the replacement for the latter).
  • Use export = Class and not export Class.
  • Must use import Class = require("Class") to import all export = modules

Here’s the tsconfig.json

 1{
 2  "compileOnSave": true,
 3  "enableAutoDiscovery": true,
 4  "compilerOptions": {
 5    "target": "ES2015",
 6    "module": "amd",
 7    "sourceMap": true,
 8    "lib": ["ESNext", "dom"]
 9  }
10}

And here’s are the outputs of different export patterns:

Method 1

1export class Awesomesauce {
2  // code goes here
3}

Result is exports.Awesomesauce = Awesomesauce;

1define(['require', 'exports'], function (require, exports) {
2  'use strict'
3  Object.defineProperty(exports, '__esModule', { value: true })
4  exports.Awesomesauce = void 0
5  class Awesomesauce {}
6  exports.Awesomesauce = Awesomesauce
7})
8//# sourceMappingURL=module1.js.map

Method 2

1class Awesomesauce {
2  // code goes here
3}
4
5export = Awesomesauce

Result is return Awesomesauce;

1define(['require', 'exports'], function (require, exports) {
2  'use strict'
3  class Awesomesauce {}
4  return Awesomesauce
5})
6//# sourceMappingURL=module2.js.map

Method 3

1export default class Awesomesauce {
2  // code goes here
3}

Result is exports.default = Awesomesauce

1define(['require', 'exports'], function (require, exports) {
2  'use strict'
3  Object.defineProperty(exports, '__esModule', { value: true })
4  class Awesomesauce {}
5  exports.default = Awesomesauce
6})
7//# sourceMappingURL=module3.js.map

Method 4

1class Awesomesauce {
2  // code goes here
3}
4
5export default Awesomesauce

Result is exports.default = Awesomesauce, same as Method 3 above

1define(['require', 'exports'], function (require, exports) {
2  'use strict'
3  Object.defineProperty(exports, '__esModule', { value: true })
4  class Awesomesauce {}
5  exports.default = Awesomesauce
6})
7//# sourceMappingURL=module4.js.map