Syntax: import name from "module-name";
doesnt work in my project with Typescript 1.6. The imported variable is undefined.
Example:
import createLogger from "redux-logger"; //createLogger is undefined
This syntax works
import createLogger = require("redux-logger");
You need to write it as a namespace import (as opposed to a default import):
import * as createLogger from "redux-logger";
Thanks @ahejlsberg , I understand that the defaults syntax works in my TS internal files but it doesn't work when I want import something from node_modules (external modules)?
I have one more problem. When I do like you said
import * as createLogger from "redux-logger";
indeed in runtime createLogger keeps a createLogger() function but it is impossible to use it in my code because Typescript compiler doesn't allow me to do it.
For example:
`
import * as createLogger from "redux-logger";
...
applyMiddleware(createLogger()),`
this gives me an error error TS2349: Cannot invoke an expression whose type lacks a call signature.
What I'm doing wrong?
Oh, I see. Then you need to either do this:
import * as reduxLogger from "redux-logger";
applyMiddleware(reduxLogger.createLogger());
Or this:
import { createLogger } from "redux-logger";
applyMiddleware(createLogger());
I suspect the latter one is what you're looking for.
@ahejlsberg In both cases I have undefined in my runtime. The only import which works is
const createLogger = require("redux-logger")
but with this syntax I don't have typescript support.
Redux-Logger library exports only one default function which is createLogger.
Update 1:
This one started working when I removed word "default" from function in my module definition
import createLogger = require("redux-logger");
Are you using the SystemJS module loader? This looks like it might be the same problem as #5285 which has to do with SystemJS's "auto-magic" promotion of the default export.
@ahejlsberg I'm using commonjs module loader. I compared my compiled code with babel code and here are two versions:
Typescript:
var redux_logger_1 = __webpack_require__(678);
var test = redux_logger_1.default;
Babel:
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
var _reduxLogger = require("redux-logger");
var _reduxLogger2 = _interopRequireDefault(_reduxLogger);
and in the runtime redux_logger_1 is a function and redux_logger_1.default is undefined. Babel code works because the condition inside function is true so it returns obj
To sum up what I've said earlier:
My goal is to import a node_module which exports one default function. In pure es6 javascript I would do that with import module from "module"
. In typescript I tried to do it in the same way with the same syntax but my module variable was undefined during runtime. What is more weird for me this syntax was working when I was trying to link one .ts file to another .ts file.
I discovered that Babel compiles default export to another syntax that Typescript and Typescript can't handle babel output. For example I have the following code:
export default function defaultFunction() {}
In babel it will be
exports.__esModule = true;
exports['default'] = defaultFunction;
module.exports = exports['default'];
In typescript
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = defaultFunction
As I said the first output is incompatible with Typescript. The imports that don't work:
import defaultFunction from "module"
- defaultFunction is undefined
import { defaultFunction } from "module"
- It doesn't compile if the function is declared as default in definitions
import * as defaultFunction from "module"
- calling defaultFunction()
causes compiler error error TS2349: Cannot invoke an expression whose type lacks a call signature.
import defaultFunction = require("module");
- calling defaultFunction()
causes compiler error error TS2349: Cannot invoke an expression whose type lacks a call signature.
const defaultFunction = require("module");
- works without typescript support but you can add casting to get some intellisense
With the second output I can just do import defaultFunction from "module"
Ok, I think I see what is going on.
It appears the "redux-logger" module was transpiled with Babel. When Babel transpiles a module whose _only_ export is an export default
it injects a module.exports = exports["default"];
into the generated code, causing the exported object to become the function itself (instead of a module object that has the function as the default
property). When paired with the _interopRequireDefault
magic that Babel generates for imports the net effect is that a fake module object with a default
property is created and the function can now be accessed as _reduxLogger2.default
.
TypeScript doesn't do any of this magic (see here for more details). In order for TypeScript to consume the module you need to change the redux-logger.d.ts declaration file to actually reflect what is going on:
/// <reference path="../redux/redux.d.ts" />
declare module 'redux-logger' {
function createLogger(options?: createLogger.ReduxLoggerOptions): Redux.Middleware;
namespace createLogger {
interface ReduxLoggerOptions {
actionTransformer?: (action: any) => any;
collapsed?: boolean;
duration?: boolean;
level?: string;
logger?: any;
predicate?: (getState: Function, action: any) => boolean;
timestamp?: boolean;
transformer?: (state:any) => any;
}
}
export = createLogger;
}
Once you do that you should be able to import the module with a namespace import:
import * as createLogger from "redux-logger";
or with the equivalent:
import createLogger = require("redux-logger");
@ahejlsberg Thanks!! Works perfectly!
@niba Good to hear.
BTW, it appears that Babel is killing off the export default
magic in their next release (https://github.com/babel/babel/issues/2212). Once they do, and once redux-logger is re-transpiled, the redux-logger.d.ts declaration file on Definitely Typed will actually be correct. So, instead of using the modified declaration file you could fix redux-logger yourself by deleting the last line in node_modules/redux-logger/lib/index.js
that reads
module.exports = exports["default"];
and continue to use the declaration file you already have. In that case your import should be:
import createLogger from "redux-logger";
@ahejlsberg Good to know that. Thank you for the information!
I know this was closed a while ago, I just wanted to pop in an give a HUGE THANK YOU to @ahejlsberg for that explanation and remedy remove module.exports = exports["default"];
.
You just made some things click for me with that. I literally wasted probably 4 hours trying to understand why my type definition, using export default
was returning undefined. Then I stumbled upon export = Thing
and was further scratching my head. Why on earth does import * as Thing from 'thing'
work with export = Thing
in my definition and then when I export default Thing
I get undefined with import Thing from 'thing'
. Babel.. that's why. hahahahaha oh man I am so glad I understand this now.
I would offer that it's probably worth adding to the documentation here:
https://typescript.codeplex.com/wikipage?title=Writing%20Definition%20%28.d.ts%29%20Files
As a special note for type definitions for Babel generated code.
@aventurella Thanks. The complexities of ES6 module downlevel transpilation and interop is truly the gift that keeps on giving!
Is this still a problem?
It depends, for my money I have found that when I have an issue with a 3rd party lib I check to see if that lib was compiled with Babel. Usually it was so I just look at it's compiled source to see how it's exporting it and adjust my import accordingly
@aventurella thanks for the quick response - can the problem be fixed by updating the typing file?
The only way I have been able to "address" it is by understanding how the target 3rd party module has been exported. And you adjust things accordingly once you see how it's been exported by whatever transpiled it. Once you know that, if you are adding typings you just need to be sure you add them to describe the situation you observe in the 3rd party module.
@aventurella - I really appreciate your detailed explanation - if you have a chance, could you give me a simple example please?
What 3rd party module are you having an issue with?
@aventurella - sorry for the late response, but here is the package that I use:
@types/quill: 1.3.0
quill: 1.3.1
The definition file (types/quill v1.3.0) looks like this: code link - note that there is no default export in the definition file.
And here is my code in *.ts file:
import { Quill } from 'quill';
new Quill('foo');
The compiled JS code is:
const quill_1 = require("quill");
new quill_1.Quill("foo");
The problem here is that quill_1.Quill
is undefined - the compiled JS code should look like new Quill("foo")
- so that it uses the external library properly but as you see, it is like quill_1.Quill("foo")
.
Now the definition file has been updated a few days ago, and it has the default export - but in v1.3.0, there is no default export. In this case, how can I fix the problem? Please advise...!!
I encountered the same issue @niba was encountered with another library: cytoscape and it's type definition file. Actually I helped push the type definition to DefinitelyTyped, but I'm not so sure how to fix this after it's been merged.
When I import the module: import * as cytoscape from "cytoscape";
, and call cytoscape()
, compiler tell me Cannot invoke an expression whose type lacks a call signature. Type 'typeof "./node_modules/@types/cytoscape/index"' has no compatible call signatures.
. But the compile result is right in this condition.
When I call with cytoscape.cytoscape()
, ts compiler doesn't show an error but compile result is cytoscape_1.cytoscape()
which is incorrect because the library supposed to be called directly.
cytoscape type source on DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d9ca7d7efff765abe162023a9aa47f51f473bfd7/types/cytoscape/index.d.ts
@wy193777 the _named_ export is the problem.
I believe you want
+export = cytoscape;
+export as namespace cytoscape;
```diff
- export function cytoscape(options?: cytoscape.CytoscapeOptions): cytoscape.Core;
+ function cytoscape(options?: cytoscape.CytoscapeOptions): cytoscape.Core;
- export function cytoscape(extensionName: string, foo: string, bar: any): cytoscape.Core;
+ function cytoscape(extensionName: string, foo: string, bar: any): cytoscape.Core;
```diff
- export namespace cytoscape {
+ namespace cytoscape {
based on the package's source code
Then you would import it via either
import cytoscape = require('cytoscape');
or
import cytoscape from 'cytoscape'; // --allowSyntheticDefaultImports true
or referencing the global when not using modules, depending on your environment.
Thanks @aluanhaddad. I just submitted a new pull request to fix it.
Most helpful comment
Ok, I think I see what is going on.
It appears the "redux-logger" module was transpiled with Babel. When Babel transpiles a module whose _only_ export is an
export default
it injects amodule.exports = exports["default"];
into the generated code, causing the exported object to become the function itself (instead of a module object that has the function as thedefault
property). When paired with the_interopRequireDefault
magic that Babel generates for imports the net effect is that a fake module object with adefault
property is created and the function can now be accessed as_reduxLogger2.default
.TypeScript doesn't do any of this magic (see here for more details). In order for TypeScript to consume the module you need to change the redux-logger.d.ts declaration file to actually reflect what is going on:
Once you do that you should be able to import the module with a namespace import:
or with the equivalent: