Importing Victor.js in TypeScript?

39,822

Solution 1

In Brief

You're trying to use victor as if it were an es6 module, but it is not. I see two options:

  1. Let tsc convert your modules to a format like commonjs, in which case tsc will provide necessary glue logic between victor and your code

  2. Or you need to load your application through a module loader that provides the glue.

Detailed Explanation

When I run the latest tsc with the import that you show, the error I get is:

This module can only be referenced with ECMAScript imports/exports by turning on the 'esModuleInterop' flag and referencing its default export.

When I turn on esModuleInterop, then it works just fine. Here is the test code I've used:

import Victor from "victor";

const foo = new Victor(1, 2);
console.log(foo.y);

And the tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

The issue originates due to the fact that when you do import Victor from "victor" you are asking for the value that would be exported through an export default... statement, which is a syntax provided by es6 modules. However, victor does export anything that corresponds to export default.... So something has to bridge the gap. With what I've shown above, when you compile, tsc emits this:

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var victor_1 = __importDefault(require("victor"));
var foo = new victor_1["default"](1, 2);
console.log(foo.y);

Note the __importDefault helper function. It is used whenever the TS code wants to access what a module exports as export default... What it does is check whether the module claims to be an es6 module. An es6 module that wants to export a default value is already correctly structured so there's nothing to do if the module is an es6 module. If the module is not an es6 module, then the helper creates a kind of fake module whose default exported value is the value of the original module.

There's an important caveat since you mention "targeting ecmascript modules". If you use, this tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "module": "es6"
  }
}

Then the emitted code is:

import Victor from "victor";
var foo = new Victor(1, 2);
console.log(foo.y);

Note that there is no longer any helper function. It is up to the module loader which will load the modules for your application to provide the same logic as provided by __importDefault. If I rename the file to have the mjs extension and run:

$ node --experimental-modules test.mjs

I get this output:

(node:18394) ExperimentalWarning: The ESM module loader is experimental.
2

When using Node with the experimental module support, it provides the same functionality as __importDefault.


When you just use allowSyntheticDefaultImports without using esModuleInterop you are telling the compiler to assume that there will be something in your toolchain that will do the work of __importDefault. So the compiler does not provide a helper. It allows the compilation to proceed, but you are responsible later to use a module loader that will perform the same work as __importDefault.

Solution 2

I see there has already been excellent answers but would like to add this shorter answer.

Error message: This module can only be referenced with ECMAScript imports/exports by turning on the 'esModuleInterop' flag and referencing its default export.ts(2497)

I had this problem with importing when moving from es5 to es6 (and javascript to typescript) when converting my own javascript file to typescript.

Importing like import * as File from "./MyFile" in OtherFile.ts .

In MyFile.ts file I had export = {funcName} at the end.

The solution was to remove = like this export {funcName} from the MyFile.ts file.

(Hope this helps someone, first time trying to make an answer for a an error/problem)

Solution 3

I feel your heartache, In that I spent a large amount of time debugging various errors on how to write typescript definition files for existing javascript modules and finally got to what I thought was the final hurdle when I got stuck on the same error:

This module can only be referenced with ECMAScript imports/exports by turning on the 'allowSyntheticDefaultImports' flag and referencing its default export

The javascript in question here:

module.exports = class AthenaExpress { ...more code.. }

tsconfig.json for the compiling/"Working version" 1:

{
  "compilerOptions": {
    "outDir": "dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es6",
    "jsx": "react"
  },
  "baseUrl": "./src",
  "include": [
    "**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

"Working version" of the d.ts file with some import differences 2:

declare module 'athena-express' {
    import * as aws from "aws-sdk";
    interface ConnectionConfigInterface {
        aws: typeof aws,
        s3: string,
        getStats: boolean
    }
    interface QueryResultsInterface {
        Items: any[],
        DataScannedInMB: number,
        QueryCostInUSD: number,
        EngineExecutionTimeInMillis: number,
        Count: number,
    }

    interface QueryInterface {
        sql: string,
        db: string,
    }

    type QueryResult = QueryResultsInterface

    interface AthenaExpressInterface {
        new: (config: ConnectionConfigInterface) => any,
        query: (query: QueryInterface) => QueryResult,
    }

    class AthenaExpress {
        new: (config: ConnectionConfigInterface) => any;
        constructor(config: ConnectionConfigInterface);
        query: (query: QueryInterface) => QueryResult;
    }
}

Version of the d.ts file that received the same error, even when esModuleInterop was enabled, I also fiddled around with module and target to no avail. With import statement differences 3:

import * as aws from "aws-sdk";
interface ConnectionConfigInterface {
    aws: typeof aws,
    s3: string,
    getStats: boolean
}
interface QueryResultsInterface {
    Items: any[],
    DataScannedInMB: number,
    QueryCostInUSD: number,
    EngineExecutionTimeInMillis: number,
    Count: number,
}

interface QueryInterface {
    sql: string,
    db: string,
}

type QueryResult = QueryResultsInterface

interface AthenaExpressInterface {
    new: (config: ConnectionConfigInterface) => any,
    query: (query: QueryInterface) => QueryResult,
}

declare class AthenaExpress {
    new: (config: ConnectionConfigInterface) => any;
    constructor(config: ConnectionConfigInterface);
    query: (query: QueryInterface) => QueryResult;
}

export = AthenaExpress

notes:

The definition file location and the file I was trying to get working with the definition:

 tree src/backend/js
    src/backend/js
        ├── athena-express.d.ts
        └── helloworld.ts
  1. "Working Version" meaning tsc seemed to compile without complaint
  2. In helloworld.ts import {AthenaExpress} from "athena-express";
  3. In helloworld.ts import * as AthenaExpress from "./athena-express";
Share:
39,822
radman
Author by

radman

Experienced with a multitude of languages and able to adapt to new technical environments swiftly and seamlessly. Consistently works on personal projects to stay up to date with industry best practice and for the enjoyment of conquering technical challenges.

Updated on July 09, 2022

Comments

  • radman
    radman almost 2 years

    I'm trying to use the victor.js library in a TypeScript project (3.0.1) and I'm having real heartache trying to import and use it. I've installed it from npm along with it's typings (victor @types/victor). I've tried to import it a myriad of ways but can't seem to get it to import along with type resolution in my IDE.

    I've tried these:

    import { Victor} from 'victor';  
    import * as v from 'victor'; 
    

    (This module can only be referenced with ECMAScript imports/exports by turning on the 'allowSyntheticDefaultImports' flag and referencing its default export)

    import Victor = require('victor');  
    

    (works but not compatible when targeting ecmascript modules)

    const Victor = require("victor");  
    

    (Imports validly and I can construct objects but none of the typings are present)

    I'm sure someone out there has run into a similar situation to this before. If it helps the top of the index.js of victor has the line:

    exports = module.exports = Victor;
    
  • radman
    radman about 5 years
    Thanks for the comprehensive answer! Both options for a solution are minimally palatable though haha. As a final question, do you know what module format Victor.js is using that triggers this situation? Commonjs with no default export maybe?
  • Louis
    Louis about 5 years
    My pleasure. Yes, it is plain CommonJS. CommonJS does not distinguish a "default" export like es6 modules do. And the authors of Victor.js did not code the glue necessary to make Victor.js look like an es6 module (export an __esModule set to true, etc).
  • Khateeb321
    Khateeb321 almost 4 years
    What a nice explanation! Beautiful
  • Jmb
    Jmb over 2 years
    This does not really answer the question. If you have a different question, you can ask it by clicking Ask Question. To get notified when this question gets new answers, you can follow this question. Once you have enough reputation, you can also add a bounty to draw more attention to this question. - From Review