TypeScript: TS4023: Exported variable 'X' has or is using name 'Y' from external module 'a/file/path' but cannot be named

I’m attempting to create a set of external TypeScript modules as follows:

// ClassA.ts
export default class ClassA {
  public method() { return true; } 
}

// ModuleB.ts
import ClassA from './ClassA';
var moduleB = {
  ClassA: ClassA
};
export default moduleB;

// ModuleA.ts
import moduleB from './ModuleB';
var moduleA = {
  moduleB: moduleB
}
export default moduleA;

The idea is that this will be compiled down and supplied as a library. The plan is that the consumer of the library will instantiate an instance of ClassA as follows:

import moduleA from './ModuleA';
var anInstance = moduleA.moduleB.ClassA();

This looks as though it is compiling down to the expected JS, but I’m getting an error compiler error which I’m struggling to find further information about.

Using tsc v1.6.2
.../src/ModuleA.ts(3,5): error TS4023: Exported variable 'moduleA' has or is using name 'ClassA' from external module ".../src/ClassA" but cannot be named.

Can anyone here shed any light on this issue? Does what I’m trying to do make sense?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 34
  • Comments: 24 (10 by maintainers)

Commits related to this issue

Most upvoted comments

FWIW you’ll usually get faster answers on Stack Overflow, but we do field well-phrased questions here.

The problem here is that you’re using the --declaration flag, but have not provided a way for the compiler to do its job.

When trying to emit ModuleA.d.ts, the compiler needs to write an object type literal (e.g. { moduleB: { classA: *mumble?* } }) representing the shape of the module. But there isn’t a name in scope that refers directly to classA, so it the type “cannot be named” and there’s an error.

If you add an import of ClassA to ModuleA.ts, the error should go away.

@mhegazy said:

The compiler will not add dependencies (i.e. import statements) that did not exist in the user code when it emits declarations.

The problem is that I don’t always need the import statements in the code (obviously, since it works without --declarations), and including them is noisy, causing things like tslint to complain about the “unused import”. I can see why you wouldn’t want the compiler to add dependencies to emitted javascript, but what’s the problem with adding them to emitting declarations?

The problem with adding the import is that with noUnusedLocals the compiler will complain about the type not being used. I could add an explicit type annotation, but then I don’t get inference. Example:

class Whatever {
  fetch(uri: string): Promise<void> { }
  ensureFetched = MemoizedFunction<(uri: string) => Promise<void>> = memoize((uri: string) => this.fetch(uri))
}

I would like to omit type annotation for ensureFetched

This could have deeper consequences.

Consider moduleA -> moduleB -> moduleC -> moduleD.

moduleB is the one that needs to do import { ABC } from 'moduleA' to get around this issue.

When moduleC uses moduleB and export its signature, it again fails to compile because moduleB needs ABC from moduleA but this time moduleB didn’t export it.

This means either:

  1. moduleC needs to have a hard dependency of moduleA and import ABC
  2. moduleB needs to not just import but also re-export ABC and then moduleC imports it.

If moduleD does similar things, then basically you need to know the whole chain of dependencies.

~~I wasn’t able to test this to prove that it is the case because I’m using typings and there is an issue blocking me so: https://github.com/typings/typings/issues/625~~

EDIT: I am able to reproduce it and indeed my moduleD needs to reference moduleA to do import. In my example:

  • moduleA => redux
  • moduleB => redux-thunk
  • moduleC => custom code on top of redux-thunk
  • moduleD => some library
  • ABC => Dispatch interface in redux

The compiler will not add dependencies (i.e. import statements) that did not exist in the user code when it emits declarations. the error you are getting means that the compiler is trying to write a type annotation for an exported declaration but could not. this can have one of two reasons, either the name is not accessible, i.e. not imported in the current module, or there is a declaration that is shadowing the original declaration.

in both cases, your work around is to add explicit type annotation, if you add any type annotation, it will be emitted verbatim in the output; the other option is to make sure the name is accessible, i.e. you have an import to the module, and you understand what that means for your users that are importing your module.

My current workaround, which feels like a total pain to me,

import {SomeInterface} from "./SomeFile";

const _dummySomeInterface : undefined|SomeInterface = undefined;
_dummySomeInterface;

//Code that implicitly uses SomeInterface

Avoids noUnusedLocals, allows type inference for generic interfaces, too, where possible.

@mhegazy the problem has reproduced in next scenario:

import * as Foo from "./Foo";

export class Bar {
    baz = new Foo.Baz(); // Compiler forgot "Foo." prefix in the type, and throws this error, because "Baz" without perfix is not imported.
    getBaz() { // All the same
       return new Foo.Baz();
    }
}

Solution is specify type explicit:

import * as Foo from "./Foo";

export class Bar {
    baz: Foo.Baz = new Foo.Baz(); // ok
    getBaz(): Foo.Baz { // ok
       return new Foo.Baz();
    }
}

Please reopen this issue, the issues outlined by @unional are very realistic and this makes it very hard to add any intermediate/helper libs on top of libraries while using the same types as the original module we are re-exporting.

I found a workaround for this: in tsconfig: include: [ ..., "node_modules/@your_scope/your_library" ] good luck and have fun 😃

@pelotom yeah this is completely my fault,

I was thinking on one thing and I wrote something else.

Did Typescript fixed the issue between noUnusedLocals and the declarations?

Did anyone figured out how to deal with noUnusedLocals ?

Issue https://github.com/Microsoft/TypeScript/issues/9944 tracks adding the import at the declaration emit phase.

I have just faced this issue when using:

export { IMyInterface } from './file'

The solution was to do this:

import { IMyInterface } from './file'
export { IMyInterface }

But this really shouldn’t be necessary tbh.

@salim7 this slows down your compilation times (because you’re recompiling a library that should’ve already been compiled) and forces you to use the least common denominator of strictness settings with the target library.

Oh - if this isn’t a suitable place to ask this question. Please let me know and I’d be happy to post it on another medium.