New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6 Modules default exports interop with CommonJS #2719

teppeis opened this Issue Apr 11, 2015 · 37 comments


None yet

teppeis commented Apr 11, 2015

CommonJS cannot requires a package transpiled from TypeScript ES6 Modules using default export well.

TS 1.5-alpha transpiles default exports:

// foo.ts
export default foo;


// foo.js ("main" in this package.json)
exports.default = foo;

So users of this package foo need to add .default property to require().

var foo = require('foo').default;

It's a little messy...

Babel resolves this issue.


In order to encourage the use of CommonJS and ES6 modules, when exporting a default
export with no other exports module.exports will be set in addition to exports["default"].

export default test;
exports["default"] = test;
module.exports = exports["default"];

If you don't want this behaviour then you can use the commonStrict module formatter.

It sounds pretty nice and actually works for me.


This comment has been minimized.


ahejlsberg commented Apr 13, 2015

We considered that, but it only works if you also emit an __esModule marker property and emit a dynamic check for such a marker on every default import (which is what Babel does). Since you can already get the desired effect with TypeScript's existing export =, and since you can import that with the existing import x = require(...) syntax, we felt that it was best to stick with pure ES6 module semantics and keep the code generation as clean as possible.

Adding a bit more detail, Babel's compatibility mode generates a default export as an assignment to module.exports only when the module exports nothing but a default. The minute you add any other export, the default export now "snaps back" and becomes an export named default. Hence you need an __esModule marker to indicate which mode the module is in, and you need a dynamic check of that marker on every import. It gets rather messy.


This comment has been minimized.

mizchi commented Apr 13, 2015

This behaviour is my blocker to adopt 1.5.
I expected module.exports = with --module commonjs when I use export default but TypeScript didn't so.

export default class Foo {}

commonjs users have to write below now with 1.5. (not for typescript, raw js for node.js)

let Foo = require('./foo').default

But this problem may be common.js/require spec's...

*added * I wrote typescript as a part of my project, and provide common.js module to be required.


This comment has been minimized.


ahejlsberg commented Apr 13, 2015

@mizchi You're right, if you use export default then CommonJS consumers will have to access the default property. This is the one big difference between default exports in ES6 and assigning to module.exports in CommonJS. There is really no clean way to solve this problem. Babel supports a compatibility mode and a magic __esModule marker that you dynamically have to check, but I'm not convinced that's any better. My feeling is we're better off just going with pure ES6 semantics.


This comment has been minimized.

teppeis commented Apr 13, 2015

@ahejlsberg yes, there is no clean way. I see your policy for interoperability with CommonJS.

So, how about interop with other ES6 Module transpilers?
Babel, Traceur, SystemJS and some others cannot import a module from TypeScript 1.5-alpha as default properly.

// foo.ts (main of package "foo")
export default 'foo';
// An ES6 module to be compiled by Babel, Traceur, SystemJS
import foo from 'foo';
console.log(foo); // { default: 'foo' } instead of just 'foo'

If TypeScript sets exports.__esModule = true as a transpiled ES6 module, they import it as a default.


This comment has been minimized.


vvakame commented Apr 14, 2015


4 similar comments

This comment has been minimized.

niemyjski commented Apr 22, 2015



This comment has been minimized.

scottwio commented Apr 27, 2015



This comment has been minimized.

okunokentaro commented May 1, 2015



This comment has been minimized.

EddyLane commented Jun 4, 2015



This comment has been minimized.

vlkosinov commented Jun 4, 2015



This comment has been minimized.

benlesh commented Jun 10, 2015

I'm trying to redevelop RxJS in TypeScript, and this is a requirement, as RxJS needs to support CJS very cleanly. Any idea of where this sits as a priority?


This comment has been minimized.


chicoxyzzy commented Jun 11, 2015

this is must-have feature. our team is really missing it and this is a stopper.


This comment has been minimized.

benlesh commented Jun 11, 2015

@chicoxyzzy Thus far my solution is to transpile to es6, then use Babel to get to AMD and CJS. Finally Browserify to create a global, bundled file. It's a little wonky, but it works. (I'm authoring a library though)


This comment has been minimized.


guybedford commented Jun 11, 2015

Just to clarify on the interop discussion - Babel's handling of making the default export the primary module.exports is a convenience unrelated to Babel's handling of CommonJS interop - they are orthogonal concerns. The handling of CommonJS interop is exactly the flag system though.


This comment has been minimized.

benlesh commented Jun 11, 2015

@guybedford nonetheless, if you want CJS interop without having to add __esModule all over your code, for now the answer is tsc -> es6 then babel -> cjs


This comment has been minimized.

ghost commented Jun 17, 2015

Must have feature.


This comment has been minimized.

almilo commented Jun 24, 2015

While evaluating TS for a new big project I am working on, we stumbled upon this issue which is a blocker.


This comment has been minimized.


tinganho commented Jul 8, 2015

I'm not sure why everyone is +1:ing. Either everyone is TypeScript library owners or they think it solves the opposite problem.

I'm actually more annoyed about the opposite problem. How to import a default export from CommonJS in ES6. I want to write everything using the ES6 module syntax and not having to resolve to import module = require('./module').

Many libraries have a default exported function defined in CommonJS like below:

module.exports = function() { ... }

And the best solution is to use the old TS syntax to import;

import module_ = require('./module');

Because you can't import it using ES6 module syntax. So my code base is filled with mixed module syntaxes. If we decided to emit the __esModule marker. Why can't we go the whole way and emit a check for the marker too?

Then my code base can be a lot cleaner:

import something from 'commonjs'

This comment has been minimized.

ORESoftware commented Mar 24, 2017

Hey all, as a newcomer to this thread, I am looking for "the answer" here. My thinking is that this has been resolved in favor of the askers, but I am not sure.

I have a TS module that does this:

export default {foo: 'bar'};

in my Node.js version 0.12 code, I have

var x = require('ts-module').default;

how exactly, can I avoid using the default property when using older versions of Node?

in other words, I cannot do this:

const {default as x}  = require('ts-module');

because my library needs to support older versions of Node.

It would be nice to know in simple terms what the resolution to this issue was. Thanks all.

I know that AVA solved the problem this way

like so:

module.exports = runner.test;

// TypeScript imports the `default` property for
// an ES2015 default import (`import test from 'ava'`)
// See:
module.exports.default = runner.test;

but I am not sure if they generated that file with TS though...

I am looking for the way to generate/declare that kind of code with TS


This comment has been minimized.

trusktr commented Mar 26, 2017

The official solution is to use TypeScript's special syntax, for example:

export = {foo: 'bar'};

then in your old Node code you can do

var x = require('ts-module');

without default.

However, this has serious limitations when trying to interop with other module formats or module bundlers because (for example) TypeScript compiles those statements to nothing (they just disappear), and then you'll get errors in (for example) Webpack.

That special format basically only works for app authors, not for library authors, and not for app authors doing more complex things like needing intermediate module processing steps (tools like Babel, Webpack, etc, don't understand TypeScript's special export =/import =format, and when you compile to the format that they understand then TypeScript just deletes the statements).


This comment has been minimized.

trusktr commented Mar 26, 2017

Basically the downside of ES6 is that it isn't designed to be backwards compatible with previous module formats, and there isn't any standard on how to transpile ES6 modules for older formats.


This comment has been minimized.

ORESoftware commented Mar 27, 2017


thanks, yeah that solution unfortunately won't work for me, because I am exporting another TS interface from the same file, so TS will complain when I useexport = syntax:

screenshot 2017-03-26 17 17 00

Maybe there is no solution to this one at the moment :(

my personal solution is to do this:

const x = {foo:'bar'}
x.default = x;
module.exports = x;

not my fav thing in the world LOL


This comment has been minimized.

emirotin commented Mar 30, 2017

Have the similar problem. I'm writing typedef file for an existing module. The module is CommonJS that exports a function (module.exports = function () {})

Alongside with it I also have to define and export some interfaces.

So the export = syntax works but only until I want to also export the types.


This comment has been minimized.


aluanhaddad commented Mar 30, 2017

@emirotin If you want to export types, you should consider placing them in a merged namespace. Here is an example

export = configure;

function configure(options: configure.Options) {
  return { ...options, key: 'value' };

namespace configure {
  export interface Options {
    pluginName: string;
    inject?: true;


import * as configure from './configure'; // or import = require ..., or import configure from...

const options: configure.Options = {
  pluginName: 'abstraction helper gizmo'

This comment has been minimized.

emirotin commented Mar 30, 2017

Right @aluanhaddad thanks a lot, have just discovered it 15 minutes ago :)


This comment has been minimized.

FallenMax commented Jun 26, 2017

@aluanhaddad Thanks for solution!

What if I need to write a module that exports an object, and exports some interfaces (for ts dependencies)? like:

export interface Option{
export = {
  fun(option:Option) {
    // do stuff

Some annoying limitations:

  • I'm now incrementally integrating TypeScript to an existing js project, so interop with other commonjs modules is important (i.e. we avoid export default).

  • The exported object is sort of an instance, so I rather not use separate exports like export function fun(){}

  • The namespace method also doesn't work here because:

  1. exported object may not have a name
  2. we can't export object & namespace with same name

Thanks for any idea!


This comment has been minimized.


kitsonk commented Jun 26, 2017

You just need to name the exported object:

const exportObject = {
  fun(option:exportObject.Option) {
    // do stuff

export = exportObject;

namespace exportObject {
    export interface Option{
        key1: string

But of course when you use the top level export, it exports an object anyways. So why wouldn't you do this?

export interface Option{
export function fun(option:Option) {
  // do stuff

As that would have exactly the same shape of an export as what you originally had.


This comment has been minimized.

FallenMax commented Jun 26, 2017

The namespace solution works and satisfies my need. I didn't know so well about namespace :)
The export separately solution is not desirable here, as my intention is to export an instance which contains methods and properties. Separate exports will "break" this instance and make its properties read-only.

My current approach is to just export this instance as a named object (export const instance = {}).

Thanks a lot!

bryphe added a commit to onivim/oni that referenced this issue May 16, 2018

Enable use of middle click to close tabs (#2190)
* Add middle click tab closing (#2069)

by adding a onMouseDown directive to both tab file icon and
tab name and checking the resulting React.MouseEvent for a
middle click

* Fix unused imports and whitespace in Tabs ui test (#2069)

* Update Tabs component test snapshot (#2069)

* Fix Tabs ui test to correctly retrieve children (#2069)

* Differentiate between single und multiple tabs in test (#2069)

* Use mousedown event for tab selection and closing (#2069)

by middle clicking and let the tab itself handle the logic
for it by checking React.MouseEvent.button value

* Update classnames dependency to lastest master branch

and write own @types module declaration so that it can be used in
testing nstead of using a mock. The mock does not suffice as the
way the actual module previously had to be imported was
"import * as classNames" where classNames was the actual function.

It is not possible to build a module in typescript/es6 imports, which
will directly return a function in the same way the dependency did
in commonjs module syntax. Instead when defining a function to be
returned as default export it is returned as an Object like this
"{ default: [Function] }", which is correctly resolved when importing
with "import classNames from 'classnames'".

This only previously worked in production as webpacks ts-loader,
handles this issue, whereas when testing the sources are only compiled
with tsc.

There is an update to the classnames dependency on the current master,
but there hasn't been a release since 2006. So options were to setup
webpack for tests as well or add updated classnames dependency which
sets a "default" value on its commonjs exports.

Links for reference:

* Fix tab click onMouseDown callback

* Test tab clicks to select/close trigger callbacks

* Mock child react components directly to test smaller unit

* Reset calls to callback mocks in each test case

* Add tests for tabs interaction with FileIcon/Sneakable

@Microsoft Microsoft locked and limited conversation to collaborators Jun 18, 2018

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