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 errorCannot 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.
The default parameter in TypeScript is used to assign some default value to the variable. In JavaScript, we have this provision not to pass the required argument to the called function because it internally manages, But in TypeScript, we cannot do this.
I prefer to use default exports when the exported component is only going to be imported once, or if a file exports one thing. A good example would be a Router component.
Export Default is used to export only one value from a file which can be a class, function, or object. The default export can be imported with any name.
You can do this by using the new keyword, followed by a syntax similar to that of an arrow function, where the parameter list contains the parameters expected by the constructor and the return type is the class instance this constructor returns. The TypeScript compiler now will correctly compile your code.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With