11

I have a TypeScript class that's part of an npm package. For maintenance, I broke up the class into multiple classes, and I built up the ultimate exported class through inheritance. I don't think this matters to my question, but I figure it's better to disclose this bit of information. I defined the class in ChildClass.ts like this:

export default ChildClass extends ParentClass{…}

Tsc has an outDir of "build."
The package.json file has a property "main": "build/ChildClass.js"

Using both npm link and npm pack and I can deploy the package and consume it with no problem in a TypeScript demo package. However, if I try to use the package in a JavaScript demo,

const ChildClass = require('my-package')
const childClass = new ChildClass()

I get the error
Cannot use 'new' with an expression whose type lacks a call or construct signature.ts(2351)

If I change the new statement by adding .default like so:

const childClass = new ChildClass.default()

It works. I figured this out by looking at the compiled Javascript. Having to use .default struck me as an unreasonable thing to expect (the hypothetical) JavaScript consumers of my package to know. I also discovered that if I avoided export default and just used export then the package works more predictably, so that's what I did. Now I can use

const {ChildClass} = require('my-package')
const childClass = new ChildClass()

and the similar syntax

import {ChildClass} from 'my-package'
const childClass = new ChildClass()

in typescript.

Still, I would like to know what this magic .default property is and why I need it.

Also, all other references I found to this error didn't seem relevant to what I was seeing; I thought documenting this might help someone else that runs across a similar error.

1
  • 6
    Because CommonJS (Node's require() and exports) is not compatible with ES6 default exports. The solution to down-compile but still allow ES6 spec compliant module loaders to work is to emit __esModule on the exported value. Babel does the same thing. IMO just avoid default exports, they aren't very useful and create problems. Commented May 1, 2019 at 17:38

1 Answer 1

13

Background:

The ECMAScript Language Specification specified that the exported name of export default is just "default". So you can think about it as an ordinary export named "default".

Compiling a ES6 module to CommonJS only has a de facto standard, as the ECMAScript Language Specification never mentioned about it. Currently transpilers (TypeScript and Babel) just put all exports to module.exports, where default export becomes not special anymore.

Only when the consumers also understand the above "standard" (e.g. TypeScript, Babel and Webpack), they will transform import ChildClass from 'my-package statements to const ChildClass = require('my-package').default for you. Of course, Node.js deals with CommonJS and ES6 modules separately, and not doing this.

(Actually it's more complicated because Babel mixed import DefaultExport from 'some-package' and import * as AllExports from 'some-package, modern module consumers has to try both)


Solutions:

TypeScript has a special syntax export = to work with tranditional CommonJS consumers (It's not in JavaScript). If you change your export default class ChildClass... to export = class ChildClass..., it should work as you expected.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.