TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸

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

May 6, 2021

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

code
{
  "compileOnSave": true,
  "enableAutoDiscovery": true,
  "compilerOptions": {
    "target": "ES2015",
    "module": "amd",
    "sourceMap": true,
    "lib": ["ESNext", "dom"]
  }
}

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

Method 1

code
export class Awesomesauce {
  // code goes here
}

Result is exports.Awesomesauce = Awesomesauce;

code
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.Awesomesauce = void 0;
  class Awesomesauce {}
  exports.Awesomesauce = Awesomesauce;
});
//# sourceMappingURL=module1.js.map

Method 2

code
class Awesomesauce {
  // code goes here
}

export = Awesomesauce;

Result is return Awesomesauce;

code
define(["require", "exports"], function (require, exports) {
  "use strict";
  class Awesomesauce {}
  return Awesomesauce;
});
//# sourceMappingURL=module2.js.map

Method 3

code
export default class Awesomesauce {
  // code goes here
}

Result is exports.default = Awesomesauce

code
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  class Awesomesauce {}
  exports.default = Awesomesauce;
});
//# sourceMappingURL=module3.js.map

Method 4

code
class Awesomesauce {
  // code goes here
}

export default Awesomesauce;

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

code
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  class Awesomesauce {}
  exports.default = Awesomesauce;
});
//# sourceMappingURL=module4.js.map