93

I have some TypeScript files:

MyClass.ts

class MyClass {
  constructor() {
  }
}
export = MyClass;

MyFunc.ts

function fn() { return 0; }
export = fn;

MyConsumer.ts

import * as MC from './MyClass';
import * as fn from './MyFunc';
fn();

This gives me errors when trying to use new

Module "MyClass" resolves to a non-module entity and cannot be imported using this construct.

and when trying to call fn()

Cannot invoke an expression whose type lacks a call signature.

What gives?

Ryan Cavanaugh
  • 164,706
  • 43
  • 239
  • 219
  • 2
    Thanks for sharing an answer. I would suggest to remove `javascript` as a primary tag and leave `ecmascript-6`, because the primary tag here is `typescript`. The question wrongly assumes that `export =` (a TS feature) may be paired with `import ... from`, while [it should be paired with `import =`](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require). It is basically ES6 module import/export vs CJS/AMD. – Estus Flask Sep 09 '16 at 16:38

4 Answers4

160

Why it doesn't work

import * as MC from './MyClass';

This is ES6/ES2015-style import syntax. The exact meaning of this is "Take the module namespace object loaded from ./MyClass and use it locally as MC". Notably, the "module namespace object" consists only of a plain object with properties. An ES6 module object cannot be invoked as a function or with new.

To say it again: An ES6 module namespace object cannot be invoked as a function or with new.

The thing you import using * as X from a module is defined to only have properties. In downleveled CommonJS this might not be fully respected, but TypeScript is telling you what the behavior defined by the standard is.

What does work?

You'll need to use the CommonJS-style import syntax to use this module:

import MC = require('./MyClass');

If you control both modules, you can use export default instead:

MyClass.ts

export default class MyClass {
  constructor() {
  }
}

MyConsumer.ts

import MC from './MyClass';

I'm Sad About This; Rules are Dumb.

It would have been nice to use ES6 import syntax, but now I have to do this import MC = require('./MyClass'); thing? It's so 2013! Lame! But grief is a normal part of programming. Please jump to stage five in the Kübler-Ross model: Acceptance.

TypeScript here is telling you this doesn't work, because it doesn't work. There are hacks (adding a namespace declaration to MyClass is a popular way to pretend this works), and they might work today in your particular downleveling module bundler (e.g. rollup), but this is illusory. There aren't any ES6 module implementations in the wild yet, but that won't be true forever.

Picture your future self, trying to run on a neato native ES6 module implementation and finding that you've set yourself up for major failure by trying to use ES6 syntax to do something that ES6 explicitly doesn't do.

I want to take advantage of my non-standard module loader

Maybe you have a module loader that "helpfully" creates default exports when none exist. I mean, people make standards for a reason, but ignoring standards is fun sometimes and we can think that's a cool thing to do.

Change MyConsumer.ts to:

import A from './a';

And specify the allowSyntheticDefaultImports command-line or tsconfig.json option.

Note that allowSyntheticDefaultImports doesn't change the runtime behavior of your code at all. It's just a flag that tells TypeScript that your module loader creates default exports when none exist. It won't magically make your code work in nodejs when it didn't before.

Ryan Cavanaugh
  • 164,706
  • 43
  • 239
  • 219
  • Doesn't the commonjs style require a commonjs target? Is there a way to make this work when you are targeting es6/es2015? – Steve Buzonas Jun 13 '17 at 21:50
  • You can't make it work targeting ES6 *because it doesn't work in ES6*... – Ryan Cavanaugh Jun 13 '17 at 21:54
  • Am I correct in understanding that if I am targeting ES2015 modules, there is no way to reference a CommonJS module that has `export = MyClass`? My only option is to set my module to `commonjs` and continue to make the world a worse place by not using modern ES? – Micah Zoltu Mar 06 '18 at 06:01
  • @MicahZoltu If you're mixing ES2015 and CJS then by definition you have some interop-providing loader/bundler, so the general recommendation there would be to turn on synthetic defaults in the loader/bundler configuration – Ryan Cavanaugh Mar 06 '18 at 17:25
  • 6
    In [2.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html), under `--esModuleInterop`, it says "We highly recommend applying it both to new and existing projects". In my opinion, this answer (and the [FAQ entry/policy](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#should-i-add-an-empty-namespace-to-a-package-that-doesnt-export-a-module-to-use-es6-style-imports) in `DefinitelyTyped` that links here) should be modified to reflect the new stance. – Alec Mev Jun 01 '18 at 08:43
  • @OlegsJeremejevs thanks for your comment enabling that has allowed to me to continue targeting es2015 modules without changing it to commonjs. Reposted as answer so it's easier to spot. – Michael Aug 09 '18 at 03:30
  • @RyanCavanaugh. I'm experimenting with two packages typings, escape-string-regexp and lodash. [escape-string-regexp declares the type as a function](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/escape-string-regexp/index.d.ts#L9) and the `import *` doesn't complain. However, for lodash/uniqueId, the [type](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/uniqueId.d.ts#L1) gets imported from a [method](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/common/util.d.ts#L1447) and causes the error. Why does declare work? – dosentmatter Aug 16 '18 at 23:53
  • Does it mean that `import *` can import objects that can be invoked as a function? But it can't import lodash/uniqueId because it is a property of a namespace? – dosentmatter Aug 17 '18 at 00:05
  • 1
    So so very sassy. – jmealy Dec 13 '18 at 03:25
25

TypeScript 2.7 introduces support by emitting new helper methods: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#support-for-import-d-from-cjs-form-commonjs-modules-with---esmoduleinterop

So in tsconfig.json add these two settings:

{
    // Enable support for importing CommonJS modules targeting es6 modules
    "esModuleInterop": true,

    // When using above interop will get missing default export error from type check since
    // modules use "export =" instead of "export default", enable this to ignore errors.
    "allowSyntheticDefaultImports": true
}

And now you can use:

import MyClass from './MyClass';
Michael
  • 9,761
  • 3
  • 57
  • 56
  • Instead of using `esModuleInterop` I used `resolveJsonModule` along side your other suggestion of using `allowSyntheticDefaultImports` and it worked for me. – Edgar Quintero Apr 20 '20 at 17:19
6

Adding my 2 cents here incase someone else have this issue.

My way of working around the issue without modifying tsconfig.json (which can be problematic in some projects), I've gone with simply disabling the rule for oneline.

import MC = require('./MyClass'); // tslint:disable-line

Shahar Hadas
  • 1,894
  • 1
  • 22
  • 24
5

I got this error when trying to include a npm debounce package in my project.

When I tried the accepted solution above I got an exception:

Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.

This ended up working:

import debounce from 'debounce' 
NSjonas
  • 7,200
  • 5
  • 42
  • 78