Typescript: Duplicate type declarations with npm link

Created on 15 Jan 2016  ·  147Comments  ·  Source: microsoft/TypeScript

Using TypeScript 1.7.3.

Suppose I have the below npm packages.
The declaration files are generated by TypeScript compiler, and referred to from the other packages by means of the way described here.

package-a

ts src:

export default class ClassA {
  private foo: string;
  bar: number;
}

ts declaration:

declare class ClassA {
  private foo;
  bar: number;
}
export default ClassA;

package-b (depends on package-a):

ts src:

import ClassA from 'package-a';

namespace ClassAFactory {
  export function create(): ClassA {
    return new ClassA();
  }
}
export default ClassAFactory;

ts declaration:

import ClassA from 'package-a';

declare namespace ClassAFactory {
  function create(): ClassA;
}
export default ClassAFactory;

package-c (depends on package-a and package-b):

ts src:

import ClassA from 'package-a';
import ClassAFactory from 'package-b';

let classA: ClassA;
classA = ClassAFactory.create(); // error!!

The last line causes an error during compilation:

error TS2322: Type 'ClassA' is not assignable to type 'ClassA'.
Types have separate declarations of a private property 'foo'.

When I remove the line private foo; from the declaration of package-a, TypeScript does not emit any error.
However this workaround is a bit painful.

I understand that exposing private properties to declaration is by design (https://github.com/Microsoft/TypeScript/issues/1532).
I think TypeScript should ignore private properties when compiling variable assignment.
Or is there any better workaround for this?

@types Bug Fixed

Most helpful comment

I've just merged a change which will attempt to detect duplicate packages based on their name and version, and use only one. Please try it out using typescript@next when that is next published.

All 147 comments

There's only one root declaration of ClassA here, so this error shouldn't occur.

Well, sorry I found that this is related to npm link.

When I use npm link, packages are installed as below, as it simply creates symbolic links.

package-c
|
-- node_modules
    |
    -- package-a
    |   |
    |   -- index.d.ts
    |   |
    |   ...
    |
    -- package-b
        |
        -- index.d.ts
        |
        -- node_modules
        |   |
        |   -- package-a
        |       |
        |       -- index.d.ts
        |       |
        |       ...
        |
        ...

As shown, it looks like there are two different declaration files for package-a.
If I install packages normally by using npm install, this does not happen because the declaration of package-a is not included in package-b in this case.

I hope there would be some solution for this anyway, but it might be difficult and low priority.

I ended up not using npm link, and this does not matter any more for me.

Fair enough, but someone else might :wink:

there are actually two files on disk with two declarations of ClassA. so the error is correct. but we need to consider node modules when we compare these types. this issue has been reported before in https://github.com/Microsoft/TypeScript/issues/4800, for Enums we changed the rule to a semi-nominal check. possibly do the same for classes.

+1 on this with TS 1.7.5 with all relevant packages NPM-linked. I tried to construct a testcase that exhibits the issue but could not. No matter what I tried, TS was fine with the scenario I see failing with TS2345 in my application, and as far as I could tell, all copies of the problematic .d.ts file were symlinks to the same file, so there should not have been differing declarations within the type. It would be nice however if the error emitted by Typescript referenced the files which declared the two incompatible types, as that might shed light on something I'm not considering. Right now it says there are two definitions but does nothing to help the developer pinpoint the issue.

As a workaround you can use <any> on the conflicting expression to skip the type check. Obviously this might require you to do another type annotation where you might not have had to before. I hope someone can isolate this issue at some point.

EDIT: made it clear that NPM link is at play in my case

Noticed TS 1.8 is available, upgraded and the issue still exists in that version as well.

Thanks for all the work in analyzing and documenting this issue. We're having the same problem in some of our code bases. We ported some projects to properly use package.json dependencies but are now seeing this when using npm link during development.

Is there anything I can help to solve this issue?

I'm using Lerna which symlinks packages around and seeing the issue there as well. Typescript version 2.0.3.

Unfortunately Lerna and its symlinks are a hard requirement, so I used this nasty workaround to get this to compile fine while still being properly type-checkable by consumers:

export class MyClass {
  constructor(foo: Foo) {
    (this as any)._foo = foo;
  }

  get foo() {
    return (this as any)._foo as Foo;
  }
}

The class is very small so it wasn't that arduous, and I don't expect it to change really ever, which is why I consider this an acceptable workaround.

FYI, I've also ended up here as a result of using npm link and getting this error. Has anybody found a workaround for this?

@xogeny can you elaborate on how npm link is causing this issue for you?

@mhegazy Well I started getting these errors like the one above (except I was using Observable from rxjs, i.e., "Type 'Observable' is not assignable to type 'Observable'). This, of course, seemed odd because the two I was referencing Observable from exactly the same version of rxjs in both modules. But where the types "met", I got an error. I dug around and eventually found this issue where @kimamula pointed out that if you use npm link, you'll get this error. I, like others, worked around this (in my case, I created a duplicate interface of just the functionality I needed in one module, rather than references rxjs).

Does that answer your question? I ask because I don't think my case appears any different than the others here so I'm not sure if this helps you.

We have done work in TS2.0 specifically to enable npm link scenarios (see https://github.com/Microsoft/TypeScript/pull/8486 and #8346). Do you have a sample where i can look at where npm link is still not working for you?

Huh. I'm running 2.0.3 (I checked). I'll try to create a reproducible case.

By the way, you should follow up on these threads since they imply that this is still an issue as of TS 2.0:

https://github.com/ReactiveX/rxjs/issues/1858
https://github.com/ReactiveX/rxjs/issues/1744

The issue I'm seeing in my Lerna repo is somewhat involved, so I made a stripped-down version of it at https://github.com/seansfkelley/typescript-lerna-webpack-sadness. It might even be webpack/ts-loader's fault, so I've filed https://github.com/TypeStrong/ts-loader/issues/324 over there as well.

I'm using typescript 2.0.3 and I'm seeing this error with Observable as described above, e.g.

Type 'Observable<Location[]>' is not assignable to type 'Observable<Location[]>'. Property 
            'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

I am hitting this in a Lerna monorepo package as well. It feels like most but not all parts of the type system are using the realpath to uniquely identify files. If you travel down the branch that is using the symlink path rather than the realpath, you'll end up with identical-but-different types.

This is a pretty brutal problem that will only affect more complex codebases, and it seems impossible to work around without taking drastic measures, so I hope I can convince you all to give it the attention it deserves. 😄

It's most noticeable in cases where you have an app that depends on Dependency A, Dependency A depends on Dependency B and vends objects that contain types from Dependency B. The app and Dependency A both npm link Dependency B and expect to be able to import types from it and have them describe the same thing.

This results in deep error messages, and I'm on the verge of going through and eliminating all of the private and protected properties in my libraries because I've already lost so much time to this:

TSError: ⨯ Unable to compile TypeScript
tests/helpers/test-application.ts (71,11): Argument of type '{ initializers: Initializer[]; rootPath: string; }' is not assignable to parameter of type 'ConstructorOptions'.
  Types of property 'initializers' are incompatible.
    Type 'Initializer[]' is not assignable to type 'Initializer[]'.
      Type 'Application.Initializer' is not assignable to type 'Application.Initializer'.
        Types of property 'initialize' are incompatible.
          Type '(app: Application) => void' is not assignable to type '(app: Application) => void'.
            Types of parameters 'app' and 'app' are incompatible.
              Type 'Application' is not assignable to type 'Application'.
                Types of property 'container' are incompatible.
                  Type 'Container' is not assignable to type 'Container'.
                    Types of property 'resolver' are incompatible.
                      Type 'Resolver' is not assignable to type 'Resolver'.
                        Types of property 'ui' are incompatible.
                          Type 'UI' is not assignable to type 'UI'.
                            Property 'logLevel' is protected but type 'UI' is not a class derived from 'UI'. (2345)

Really appreciate you all looking into this; thank you!

@tomdale are you using Webpack, tsc or another build tool? My issue seems to only happen when compiled via Webpack (see the linked repo from my previous comment).

@seansfkelley That looks like https://github.com/TypeStrong/ts-node.

That's right, it's using ts-node (for the root application). The dependencies, however, are packages compiled with tsc.

I just ran into this issue and it is a major problem for us because we use try to split up our back end into many small libraries. During development, we often need to npm link our repos. The specific issue I ran into which prompted me to find this is the use of rxjs Observables and interfaces:


// in repo A
export class HttpAdapter {
    request(url: string, options?: HttpRequestOptionsArgs): Observable<HttpResponse> {
        return Observable.of({});
    }
}

// in repo B
export class HttpRequestAdapter implements HttpAdapter {
    request(url: string, options?: HttpRequestOptionsArgs): Observable<HttpResponse> {
        return Observable.of({});
    }
}

This works if I don't npm link, but when I do, I get:

Error:(10, 14) TS2420:Class 'HttpRequestAdapter' incorrectly implements interface 'HttpAdapter'.
  Types of property 'request' are incompatible.
    Type '(url: string, options?: HttpRequestOptionsArgs) => Observable<HttpResponse>' is not assignable to type '(url: string, options?: HttpRequestOptionsArgs) => Observable<HttpResponse>'.
      Type 'Observable<HttpResponse>' is not assignable to type 'Observable<HttpResponse>'.
        Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

The only suggestion I can make is to avoid private. I don't publish any packages with private anymore because of this issue and just use JavaScript-style _ prefixes instead. I run into it with https://github.com/Microsoft/TypeScript/issues/7755 which is a similar discussion around why private kicks into a nominal type system instead of structural, and have hence banned it on my own projects because it's too easy to end up with version differences (E.g. NPM 2 or using npm link).

@blakeembrey when you say avoid private, are you suggestion that I can change something in my code? I am assuming that the Observable type definition is the problem, no?

@jeffwhelpley Yes, sorry, your not at fault. It's Observable. Unfortunately the avoid private advice is very slim and wasn't entirely applicable to you 😄 Maybe you can make an issue on, I'm assuming, rxjs about the use of private in their public interfaces?

Edit: I mostly commented because I had followed the issue earlier and avoided join with my own experiences, but figured I could write my thoughts down again too instead they're similar to https://github.com/Microsoft/TypeScript/issues/6496#issuecomment-255232592 (where @tomdale suggests eliminating private and protected, I did the same a while back).

I got the impression from @mhegazy that he felt there was no issue with npm link. But it still seems to be plaguing us and others. So I'm not sure where this issue stands? Is it an acknowledged issue with TS 2.0+ or am I just missing a workaround somewhere?!?

I'm getting this same issue and it doesn't appear to be caused by npm link. I still get it if I install it using npm install file.tar.gz. Here's the error:

app/app.component.ts(46,5): error TS2322: Type 'Observable<boolean | Account>' is not assignable to type 'Observable<boolean | Account>'.
  Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

Here's what my app.component.ts looks like:

export class AppComponent implements OnInit {
  private user$: Observable<Account | boolean>;
  private loggedIn$: Observable<boolean>;
  private login: boolean;
  private register: boolean;

  constructor(public stormpath: Stormpath) {}

  ngOnInit() {
    this.login = true;
    this.register = false;
    this.user$ = this.stormpath.user$;
    this.loggedIn$ = this.user$.map(user => !!user);
  }

It's complaining about the this.user$ line. Stormpath has user$ defined as the following:

@Injectable()
export class Stormpath {

  user$: Observable<Account | boolean>;

@xogeny Odd, my understanding was the definition identity was tied to file location which would mean they are always going to cause issues using npm link (because the npm linked dependency would have it's own dependencies installed). Perhaps definition identity has been changed - using file hashes might be a good workaround in TypeScript. Unfortunately there's just a dozen different ways to end up with duplicate modules in JavaScript (npm install from GitHub, npm install, manual clones, version conflicts can even result in the same version landing in different locations because of how node's module resolution algorithm works, etc).

@blakeembrey Perhaps. But then what was this about?

Note, I'm not complaining. I'm just trying to figure out if there is some hope of this being resolved or not. It is a serious thorn in our side for all the reasons @jeffwhelpley mentioned.

@xogeny I know, I'm trying too, I'd love to see it resolved correctly 😄 I read the linked issues, but it they are all designed to resolve the realpath of a symlink which implies if you have two (real) files they'll still conflict because they'll resolve to different locations. Which is what happens when you npm link from one project into another as both would have their own dependencies that can differ with re-exported symbols from the npm linked package.

Edit: I can confirm, all the issues are because of two files. npm link would trigger it because it's simple to have a dependency in a repo that you just linked that is the same dependency as in the project you linked to. A simple repro would be to do an npm install of the same dependency at two different levels of an application and watch them error out.

image

To anybody following this thread...I tried the workaround described here and it seems to work (so far).

I've reproduced this error.

mkdir a; cd a
npm install rxjs
echo 'import * as rx from "rxjs"; export const myObservable: rx.Observable<number>;' > index.d.ts
echo '{ "name": "a" }' > package.json

cd ..; mkdir b; cd b
npm install rxjs
npm link ../a
echo 'import * as rx from "rxjs"; import * as a from "a"; const x: rx.Observable<number> = a.myObservable;' > index.ts
tsc index.ts --target es6 --moduleResolution node

Since there are two installations of rxjs, we get:

index.ts(1,59): error TS2322: Type 'Observable<number>' is not assignable to type 'Observable<number>'.
  Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

i have a workaround which works great for commandline, but visual studio still gets all messed up: https://github.com/Microsoft/TypeScript/issues/11107#issuecomment-254003380

my new workaround for Windows + Visual Studio 2015 is to robocopy my xlib Library src and dist folder into the node_modules\xlib\src and node_modules\xlib\dist folders of the consuming project.

here is the significant part of my robocopy batch file script if anyone wants it:

:rerunloop
    @echo watching for changes to project files..............  (Ctrl-C to cancel)

    @rem xlib --> blib and slib
    @robocopy .\xlib\src .\blib\node_modules\xlib\src *.*  /MIR /NJH /NJS /NDL /XD .git
    @if NOT "%errorlevel%" == "0" (
        @rem copy occured, so copy both

        @robocopy .\xlib\dist .\blib\node_modules\xlib\dist *.*  /MIR /NJH /NJS /NDL /XD .git   
        @robocopy .\xlib\src .\slib\node_modules\xlib\src *.*  /MIR /NJH /NJS /NDL /XD .git     
        @robocopy .\xlib\dist .\slib\node_modules\xlib\dist *.*  /MIR /NJH /NJS /NDL /XD .git

        @rem  set the src dirs readonly
        @attrib +R .\blib\node_modules\xlib\src\*  /S /D
        @attrib +R .\slib\node_modules\xlib\src\*  /S /D
    )
    @timeout /t 1 /nobreak > NUL
@goto rerunloop

Sorry for bugging again with this issue, but it is a serious drag on our project not being able to do npm link while we are making changes. I would love to help out with a PR if one of the current TypeScript contributors could give me a little guidance on where to start looking in the codebase.

I'm also struggling with this one. We have adopted TS starting from a small app, now we have split it in submodules and linked them and… BOOM. TS won't compile anymore. Is this still an issue in all TS dist-tags? I'm currently experiencing this in @rc (2.1.1).

@heruan and @jeffwhelpley can you give typescript@next a try, we have fixed a few related issues. and if you are still seeing the issue, please provide more information about your project setup.

@mhegazy I am on Version 2.2.0-dev.20161129 and still having the issue. The specific issue is that I have one project (let's call it ProjectA) that contains an "interface" (using a class, but that is so I can use the class as a token for Angular 2 DI) as follows:

export class ServerAdapter {
    start(opts: ServerOptions): Observable<any> {
        return null;
    }
}

Then in a completely separate project (let's call it ProjectB) that has a class which implements the interface from the first project like this:

export class RestifyServerAdapter implements ServerAdapter {
    start(opts: ServerOptions): Observable<any> {
        let server = restify.createServer();
        this.addPreprocessors(server);
        this.addRequestHandler(server, opts);
        return this.startServer(server, opts);
    }

   // more stuff here that is not relevant to this issue
}

When I do a normal typescript compile for ProjectB it works fine. But if I npm link ProjectA from the ProjectB root directory and then run tsc again I get:

Types of property 'start' are incompatible.
    Type '(opts: ServerOptions) => Observable<any>' is not assignable to type '(opts: ServerOptions) => Observable<any>'. Two different types with this name exist, but they are unrelated.
      Type 'Observable<any>' is not assignable to type 'Observable<any>'. Two different types with this name exist, but they are unrelated.
        Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

I couldn't replicate in a mock project, I guess I'm missing the cause of the problem hence I'm unable to replicate it. @jeffwhelpley can you publish a mock project replicating the issue? I think a Lerna project should be fine and easily testable.

@heruan I will try to set that up.

One, FYI, though. I think I may have found a work around. The problem is resolved if I npm link rxjs in both ProjectA and ProjectB. This sort of makes sense because in that case, both ProjectA and ProjectB are using the same exact rxjs files. Without that, they are technically using different files (even though the same version):

If you just npm link ProjectA from ProjectB, then:

  • ProjectB is pointing to node_modules/rxjs
  • ProjectA exists as a symlink in node_modules/ProjectA and the rxjs it references is at node_modules/ProjectA/node_modules/rxjs

But if you npm link rxjs in both then both of those rxjs references will be symlinked to the same exactly global npm location.

Anyways, this is obviously still not ideal, but at least something that can move us forward.

Also...not sure if this is relevant or matters (will see once I set up the test project), but my two libs (i.e. ProjectA and ProjectB) are actually private npm repos.

Thank you @jeffwhelpley for the hint, but since I'm using Lerna all the modules are already linked to each other so they read the same file, but I think the TS compiler is taking into account the link path and not the real one. I may be wrong, since I cannot reproduce on a mock project, and I'm really driving out mad…

Anyone here able to resolve this in an elegant manner?

Also, note that this is not just a problem with npm link, you will get this problem too on production builds, when your shared dependencies point to a different version.

ie. ProjectA needs [email protected] and ProjectB uses [email protected]

When you install ProjectA as a dependency in ProjectB you will too have duplicated types, since there will be for example two Observable declarations, one in node_modules/rxjs and one in node_modules/project_a/node_modules/rxjs

You can get around this by allowing rxjs version in ProjectA to be something like ~4.9.0, so that npm install doesn't need to download its own version, and will instead use ProjectB version. But keep in mind that this is not only a development workflow issue.

Posting here as per @andy-ms's suggestion. Tried it again yesterday with latest 2.0.x and still had it.

I'm getting this with the typings for Angular 1: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/10082#issuecomment-253023107

Ran into this again today, specifically the problem with symlinks. My setup is something like this:

node_modules/
folder
  another_folder
    node_modules/ (symlinked to ../../node_modules)
    app/ (angular1 app in typescript)
    tsconfig.json
    (other build files)

If I just have @types/angular, tsc runs fine. If I have the whole suite (@types/angular-{animate,cookies,mocks,resource,route,sanitize}) then I start getting loads of type errors:

$ npm run tsc

> [email protected] tsc D:\work\angular.io\public\docs\_examples\upgrade-phonecat-1-typescript\ts
> tsc

../../node_modules/@types/angular/index.d.ts(17,21): error TS2300: Duplicate identifier 'angular'.
../../node_modules/@types/angular/index.d.ts(18,21): error TS2300: Duplicate identifier 'ng'.
app/app.animations.ts(5,3): error TS2339: Property 'animation' does not exist on type 'IModule'.
app/app.config.ts(6,45): error TS2305: Module 'angular' has no exported member 'route'.
app/core/checkmark/checkmark.filter.spec.ts(5,22): error TS2339: Property 'mock' does not exist on type 'IAngularStatic'.
app/core/phone/phone.service.spec.ts(18,22): error TS2339: Property 'mock' does not exist on type 'IAngularStatic'.
app/core/phone/phone.service.spec.ts(23,18): error TS2339: Property 'expectGET' does not exist on type 'IHttpBackendService'.
app/core/phone/phone.service.spec.ts(30,18): error TS2339: Property 'verifyNoOutstandingExpectation' does not exist on type 'IHttpBackendService'.
app/core/phone/phone.service.spec.ts(31,18): error TS2339: Property 'verifyNoOutstandingRequest' does not exist on type 'IHttpBackendService'.
app/core/phone/phone.service.spec.ts(39,18): error TS2339: Property 'flush' does not exist on type 'IHttpBackendService'.
app/core/phone/phone.service.ts(5,33): error TS2305: Module 'angular' has no exported member 'resource'.
app/phone-detail/phone-detail.component.spec.ts(5,22): error TS2339: Property 'mock' does not exist on type 'IAngularStatic'.
app/phone-detail/phone-detail.component.spec.ts(18,46): error TS2305: Module 'angular' has no exported member 'route'.
app/phone-detail/phone-detail.component.spec.ts(20,20): error TS2339: Property 'expectGET' does not exist on type 'IHttpBackendService'.
app/phone-detail/phone-detail.component.spec.ts(32,20): error TS2339: Property 'flush' does not exist on type 'IHttpBackendService'.
app/phone-detail/phone-detail.component.ts(7,37): error TS2305: Module 'angular' has no exported member 'route'.
app/phone-list/phone-list.component.spec.ts(6,22): error TS2339: Property 'mock' does not exist on type 'IAngularStatic'.
app/phone-list/phone-list.component.spec.ts(15,20): error TS2339: Property 'expectGET' does not exist on type 'IHttpBackendService'.
app/phone-list/phone-list.component.spec.ts(26,20): error TS2339: Property 'flush' does not exist on type 'IHttpBackendService'.
node_modules/@types/angular-resource/index.d.ts(192,40): error TS2305: Module 'angular' has no exported member 'resource'.
node_modules/@types/angular/index.d.ts(17,21): error TS2300: Duplicate identifier 'angular'.
node_modules/@types/angular/index.d.ts(18,21): error TS2300: Duplicate identifier 'ng'.

I fixed it by adding the base ../../node_modules/@types as typeRoots to my tsconfig.json:

    "typeRoots": [
      "../../node_modules/@types/"
    ]

I tried adding the local node_modules/@types, but that did not work.

@heruan one hack I started to do is to implement my own npm script for linking/unlinking instead of using the lerna functionality. So, you could do something like lerna run link and then in all your package.json files you have an npm script called link which does all the npm linking including (in my case) npm link rxjs. It seems to work OK, not definitely not ideal.

@jeffwhelpley can you share your solution here?

@yvoronen I can't share all my code, but my solution is described above. At a high level, the key I have found is being sure to npm link not only all local projects you are working on, but also npm link the external libs that may be causing the issue (in my case rxjs is the problem because of the private vars in the Observable object). So, I use lerna to manage all my local projects and then run lerna run link. Under the scenes this calls npm run link within each of my project's root folders. So, you need to have the link script defined in your package.json like this:

  "scripts": {
    "link": "npm link my-local-project1 && npm link my-local-project2 && npm link rxjs || true",
    "unlink": "npm unlink my-local-project1 && npm unlink my-local-project2 && npm unlink rxjs && npm i || true"
  }

Hopefully that makes sense, but let me know if you have any questions.

I wanted to provide an update. From an offhand exchange I had with @mhegazy, here's what we're thinking.

  • One solution that we're considering the the concept of distinguishing packages based on their version as well as their resolution name. I don't have full details for what would be involved in this.
  • Another is to just fully expand the path to get the "true" identity of a symlink. I believe this is simpler, but more limited when it comes to dealing with the same package at different versions, but helps solve a decent number of cases.

Are you sure your comment belongs in this issue? It sounds completely
different.

On Thu, Jan 12, 2017, 3:14 AM Nikos notifications@github.com wrote:

Note, I'm not using types or npm link AFAIK.

[image: image]
https://cloud.githubusercontent.com/assets/216566/21887548/451d059c-d8b8-11e6-86d1-50afae4e5c2f.png


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/6496#issuecomment-272137732,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAUAmcMXodOvU7coymMqGzTofD4pMagpks5rRgsogaJpZM4HFcWl
.

@dobesv it's actually the entire reason why you see the issue in the first place. TypeScript recognizes the class declaration as two separate declarations because it couldn't distinguish the symlinked path from the true path. The solution is either to

  1. Expand the symlink.
  2. Check to see if the containing package is the same.

@DanielRosenwasser Sorry my comment was a response to someone else's comment "On Thu, Jan 12, 2017, 3:14 AM Nikos @.*> wrote" ... who that is and what they said I no longer recall, I believe they were asking about some problem in this comment thread, which had nothing to do with npm link.

But while I'm here I should mention that there's something funny about the way the filename plays a role in types. Today I had an issue with RxJS where it complained that such-and-such method wasn't defined on Observable. The reason was that the HTTP library had its own private copy of rxjs that was different from everyone else. In fact, I found four different copies of rxjs in my node_modules tree.

This seems to be a problematic approach that will just continue to be problematic and confusing.

If this whole concept of the filename somehow playing a role in the identity of a type went away I think this npm link issue would disappear as well.

I'm still a bit fuzzy on how that would work ... I'm new to TypeScript. But that's kind of the impression I am getting, is that this "filename matters" thing has caused me quite some confusion in conjunction with the libraries I am using (ionic2 and angular2 and rxjs).

Well the problem is that the filename is still the identity of a module in Javascript, so it can't go away completely. Can someone expand on what the problems are with using the canonical path (resolution of the symlink) with regards to multiple package versions? If there are multiple package versions within a tree, they will have multiple canonical absolute paths, right?

EDIT: I realized this after posting, and this comment sums it up well:
https://github.com/Microsoft/TypeScript/issues/6496#issuecomment-257016094

exclude and compilerOptions.typeRoots are completely ignored by tsc. I tried all the combinations for it to ignore the symlinked paths, but it just won't
it's seeing the module as a completely different path, like it's resolving the symlink and it isn't understanding the excluded patterns.

for example, I have G:\www\cim-service-locator npm linked with npm link cim-service-locator. on my project path in G:\www\cim-backend, the errors are showing like this:

crop

i tried everything combination possible of excludes/includes/typesRoot but couldn't make tsc ignore them. using 2.2-dev.20170131

We encountered this issue both using "npm link" (via an SPFx issue reported by @waldekmastykarz) and also without "npm link" (see bug #11436).

I eventually realized that the TypeScript compiler's strictness is due to incompatibilities that can realistically occur due to the wacky design of the node_modules folder. Consider this example:

A
+---B@1
+---C
|   +---B@2   <--- first copy of ClassB extends ClassE version 3.4
|   \[email protected]
+---D
|   \---B@2   <--- second copy of ClassB extends ClassE version 3.5
\[email protected]

In this example, B@2 has to be installed in subfolders to avoid a conflict with A's dependency on B@1. Now, suppose that ClassB extends from ClassE, and we have something like this:

B/package.json

{
  "name": "B",
  "version": "2.0.0",
  "dependencies": {
    "E": "^3.0.0",
    ...
}

If C's package.json asks for [email protected], then the two copies of ClassB can end up with different base class implementations, which means they actually are incompatible. The code might fail at runtime if you tried to use them interchangeably.

In this example, TS2345 would prevent that mistake, which is nice. However, it is NOT essential: if the compiler treated the two ClassB copies as equivalent, its type system would still be internally consistent and have deterministic behavior.

This is important because for us TS2345 mostly produces false alarms. These false alarms force people to write "as any" wherever they share types between NPM packages, which causes other mistakes. So the strictness is creating more problems than it solves.

I'd like to propose the following fix:

If ALL of these conditions are true, then do NOT report TS2345 for classes with private members:

  1. The public signatures are compatible according to the usual TypeScript duck-typing
  2. The classes are defined in NPM package folders with the same name and version (according to package.json)
  3. The classes have the same relative path for their module (e.g. "./lib/blah/B.d.ts")

If any of these criteria are not met, then it's okay to report TS2345.

@iclanton @nickpape-msft

@seansfkelley Webpack error is tracked by TypeStrong/ts-loader#468

Hi guys,
Someone found a solution ??
I have the same issue when I link a project with another with RxJS.
Thanks ;)

Hi, a temporary work-around is to wrap Observables returned by a dependency with Observable#from.

No solution yet . ?

There seems to be two issues here.

Users of ts-loader getting erroneous errors for duplicate definitions, seems to be caused by invalid input to the compiler API. A fix for that is available in TypeStrong/ts-loader#468.

The other issue is if you have the same package (same npm package + version) installed twice locally on the file system (not using npm link) in two nested folders, with either Enums or classes with private members, comparing the types from these two packages would fail as incompatible.
This issue is a bit more involved and will require additional work for the compiler to "deduplicate" the packages before processing them.

If neither of these two categories apply to you, then please file a new issue and provide us with enough information to reproduce the problem locally, and we would be happy to investigate further.

A known workaround is to use a multi-project solution that will eliminate the duplicate folders, i.e. ensuring that both node_modules subfolders end up symlinked to the same target.

Rush (which we use) and Lerna are examples.

@smcatala You can explain your solution. I need to fix it urgent. Thanks.

@leovo2708
module 'foo':

import { Observable } from 'rxjs'
export function foo() {
  return Observable.of('foo')
}

client code in other module, that depends on module 'foo':

import { Observable } from 'rxjs'
import { foo } from 'foo'

Observable.of(foo()) // wrap the returned Observable
.forEach(res => console.log(res))

@leovo2708 the simple solution is not to use npm link.

using ts 2.1, I have been able to use npm link with a single library (lets call it xlib, which is my real example) but you will need to make sure that the modules that your library (xlib) loads are not duplicated in the consuming project's node_modules folder.

I do this by the following workflow

  1. delete node_modules
  2. npm link xlib which creates the sym_link to xlib in my consuming project's node_modules
  3. npm install which installs the remainder of my consumign project's dependencies

I haven't really been following this conversation or checked if this issue has changed/modified since 2.2 but just though i'd share my workaround for ts 2.1

In case it's useful, I put together a minimal repro of the multiple definitions of enums case here: https://github.com/rictic/repro-npm-link-typescript-issue

@mhegazy We do see the above issue when using npm link, though I don't think that affects the rest of your analysis.

Thanks a lot, hope that it will be fixed soon. However, I used a new solution, duplicate any files using Observable. It's not good but only a temp solution.

tsconfig.json exposes a path mapping, add duplicated dependencies into paths, so it will be loaded from the right node_modules instead of linked one.

{
  "compilerOptions": {
    "baseUrl": ".", // This must be specified if "paths" is.
    "paths": {
      "@angular/common": ["../node_modules/@angular/common"],
      "@angular/compiler": ["../node_modules/@angular/compiler"],
      "@angular/core": ["../node_modules/@angular/core"],
      "@angular/forms": ["../node_modules/@angular/forms"],
      "@angular/platform-browser": ["../node_modules/@angular/platform-browser"],
      "@angular/platform-browser-dynamic": ["../node_modules/@angular/platform-browser-dynamic"],
      "@angular/router": ["../node_modules/@angular/router"],
      "@angular/http": ["../node_modules/@angular/http"],
      "rxjs/Observable": ["../node_modules/rxjs/Observable"]
    }
  }
}

Not sure if mentioned here already, but this solution worked for me: https://github.com/Microsoft/TypeScript/issues/11916#issuecomment-257130001

It's basically the same as @charpeni's solution above, except without the "../" prefix in the paths. (which seems odd, since wouldn't that mean the tsconfig.json file would be in a subfolder of the project root?)

That's a great idea @charpeni. I have used that paths setting to work around various other similar issues, but it also appears ideal for this one. In fact, I wonder if the right paths setting in the consuming project (rather than in every consumed project) might distract the TypeScript compiler away from following node resolution. If that works, it would be an O(1) hack instead of an O(n) hack. Essentially such a scheme would run node resolution statically, then stuff the results into tsconfig.

I am experimenting with these things semi-publicly if anyone wants to watch (or help...). I will probably attempt the above idea next.

https://github.com/OasisDigital/many-to-many-angular

For easier usage, you can also set the paths like this:

{
    "compilerOptions": {
        "baseUrl": ".", // This must be specified if "paths" is.
        "paths": {
            "@angular/*": ["../node_modules/@angular/*"],
            "rxjs/*": ["../node_modules/rxjs/*"]
        }
    }
}

npm link is a crucial part of the workflow when working with multiple packages as opposed to a mono-repo architecture. TS should be able to see that the two packages are identical and not error

@charpeni I couldn't get the workaround working. Just to be clear, to which tsconfig.json does this have to be added? The root package, or the linked package?

I'm also experiencing this issue. It suddenly started happening on Monday, and I'm not sure why. This is really hampering development especially when it comes to developing packages for our projects. I've tried numerous workarounds to fix this. Running mklink /j in Windows still causes this issue to occur, so it's not an npm link issue. If anyone has a workaround or a fix this would be great help.

My workaround is to npm link every single "duplicate" package in both the root package and the dependency, because then they refer to the same files again.

Btw, this issue has nothing to do with @types

We ended up using the glob pattern.

@felixfbecker For the implementation, it's available here: https://github.com/sherweb/ng2-materialize/blob/master/demo-app/tsconfig.json#L19-L22

I have a similar issue except it is entirely related to @types/node.

libA depends on @node/types
libB depends on libA and @node/types
libC depends on libA, libB and @node/types

libA builds fine.
libB npm linked to libA builds file.
libC npm linked to libA and libB fails typechecking with errors like

libC/node_modules/@types/node/index.d.ts(102,6): Duplicate identifier 'BufferEncoding'.
libC/node_modules/libB/node_modules/@types/node/index.d.ts(102,6): Duplicate identifier 'BufferEncoding'.
libC/node_modules/libB/node_modules/libA/node_modules/@types/node/index.d.ts(102,6): Duplicate identifier 'BufferEncoding'.

I tried messing around with "types" and "typeRoots" with no luck.

@charpeni It looks like your project also depends on @types/node. Do you have any insight into the solution to my problem or a workaround?

@nicksnyder that might be because they are different versions, and @types/node is a global declaration, meaning you cannot define the same names twice. This is the reason why it's best for packages to _not_ depend on environmental typings, these should be provided by the user:
https://github.com/Microsoft/types-publisher/issues/107

@nicksnyder I had a similar problem and was able to work around it by npm linking all three projects to a single @types/node install. Since the definitions are then defined in the same file they are no longer duplicates

@felixfbecker I did verify that all projects depend on the same version of node.

FWIW libA is https://github.com/Microsoft/vscode-languageserver-node/tree/master/jsonrpc, libB is https://github.com/Microsoft/vscode-languageserver-node/tree/master/client and libC is my own VS Code extension.

@uncleramsay how exactly did you npm link @types/node?

???
cd libA; npm link @types/node
cd libB; npm link @types/node
cd libC; npm link @types/node

I would like to do the linking solution, but I have also hacked around this by deleting the @types/node dependency in libA and libB, and added a refs.d.ts that contains a local references to libC's copy of node.

/// <reference path='../../../path/to/libC/node_modules/@types/node/index.d.ts'/>

I found I was able to resolve issues I was having from the description in the comment in https://github.com/Microsoft/TypeScript/issues/9091#issuecomment-225303098

we only added support for symlink resolution for modules...

In separate places in the codebase when we were using two different reference styles:

/// <reference path="../node_modules/@types/library" />
/// <reference types="library" />

These two are incompatible in the case where node_modules are symlinked, as module resolution (with types=) is expanded to the realpath, but path= is not. This results in two different files referenced when compiling with tsc --listFiles, the symlink and the realpath.

The solution we went with was to use one or the other, but never both. It also helped to specify the typeRoots: [] in the tsconfig to avoid having the compiler automatically load the types from node_modules/@types in the case of using the reference path= style.

I believe the preference going forward (at least for us) is to prefer the types= style.

Ideally though, expanding to the realpath in the reference path= would be preferred, and checking for duplicate matching paths to avoid such issues.

Hope that helps someone.

@nicksnyder you should be able to do something like this:

cd libA/node_modules/@types/node; npm link
cd libB; npm link @types/node
cd libC; npm link @types/node

That way, B & C are pointing at the exact same files that A is.

@uncleramsay be aware that they are then all the same versions, which they may not have been before

Peer dependencies were invented for a situation where a plug-in wants to say what it goes with. They sort of work okay for that case. But they cause a lot of problems if people start converting all their dependencies to peer dependencies, hoping to avoid "npm install" duplication.

We spent months banishing them from our internal git repos. When used as I think you're suggesting, peer dependencies allow you to solve a side-by-side versioning problem by trading it for several new problems:

  • the entire tree is inverted, i.e. every package now becomes responsible for taking a hard dependency on packages that used to be indirect dependencies, in many cases without any idea what it is for

  • if a peer dependency is removed, these hard dependencies will probably never get removed

  • package authors are tempted to use broad ranges for their peer version patterns, claiming to work with versions that they never actually tested against; broken builds suddenly become the consumer's problem

Well said.

workarounds

Here are 3 options for workarounds I used:

option 1: use vscode not visual studio

The tsc compiler is a lot more forgiving than VS2017, probably because VS2017 crawls the code (including symlinked node_modules) to provide nice intellisense and thus gets confused. However I have big complex multi-npm-module projects so use VS2017... so read option 2 and 3 for if you have similar needs....

option 2: emit d.ts

if you use outDir and rootDir (in your tsconfig.json) your symlinked lib-module must emit d.ts declarations and those declarations must be in the lib-module's package.json types property.

here is an example of what your lib-module's tsconfig.json should look like

{
  "compileOnSave": true,
  "compilerOptions": {
    "module": "commonjs",
    "sourceMap": true,
     "declaration": true,
    "jsx": "react",
    "newLine": "LF",
    "pretty": true,
    "stripInternal": true,
    "diagnostics": true,
    "target": "es5",
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
   //workaround for npm linking projects and associated dupe identifier bugs: https://github.com/Microsoft/TypeScript/issues/9566#issuecomment-287633339
    "baseUrl": "./",
    "paths": {
      "*": [
        "node_modules/@types/*",
        "*",
        "custom-dts/*"
      ]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

and your lib-module's package.json should include something like the following:

  "main": "./dist/_index.js",
  "types": "./dist/_index.d.ts",

This option works well, the main problem is that in visual studio when debugging or "go-to-definition" or "show all references" it will show you the d.ts not the actual typescript source files, which defeats the main benefit of visual studio (navigation in big projects)

if you want to see an actual example of this in the wild, look at the npm module xlib v8.5.x

option 3: emit .js inline with source (my favorite choice)

You can use your .ts files directly for typing in symlinked lib-modules! but only if you don't use outDir`` androotDirin yourtsconfig.json``` file. That will let VS2017 referencing work properly. Here are the config settings you need:

{
  "compileOnSave": true,
  "compilerOptions": {
    "module": "commonjs",
    "sourceMap": true,
     //"declaration": true,
    "jsx": "react",
    "newLine": "LF",
    "pretty": true,
    "stripInternal": true,
    "diagnostics": true,
    "target": "es5",
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    //"outDir": "./dist",
    //"rootDir": "./src",
   //workaround for npm linking projects and associated dupe identifier bugs: https://github.com/Microsoft/TypeScript/issues/9566#issuecomment-287633339
    "baseUrl": "./",
    "paths": {
      "*": [
        "node_modules/@types/*",
        "*",
        "custom-dts/*"
      ]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

and your lib-module's package.json should be changed to:

  "main": "./src/_index.js",
  "types": "./src/_index.ts",

caveats (things that won't work with option 3)

The above workarounds only work if each lib-module does NOT emit redundant type information. For example I used to have my xlib library expose the @types/async type definitions. But then I had another library that also independently referenced @types/async. Using the .ts files directly for typing causes tsc to import async from two lib-modules, which causes it to barf with various forms of duplicate identifier issues. to solve this you need to either not import the same @types from multiple lib-modules, or use the option 2 .d.ts workaround

summary

Hope this can save you the hours it took me.... Overall this is extremely painful, and I hope the typescript team can fix symlinked modules. But at least it kind of works now (prior to 2.x it was basically impossible)

The workaround posted by @mhegazy in https://github.com/Microsoft/TypeScript/issues/11916#issuecomment-257130001 (a variation of workaround 2 @jasonswearingen) solved this for us when using linked modules in a lerna project!

I just thought I would share my experience...trying to create/extract a lib from an existing project so that lib can be shared across other projects.

Windows 10 Enterprise
VS 2015 Update 3
Tools for VS2015 2.2.2
VS Code 1.12.2
Typescript 2.2.2
NPM: 3.10.9
Node: 6.9.2

We have a VS 2015 ASP.MVC project with an Angular 4.1.x front end that we've started extracting some components out of to make a common library so that they can be used in other projects.

The library project is built using VS Code and uses rollup to build es2015, es5 and umd versions into the dist folder along with appropriate d.ts files.

The end result looks something like this:

-- dist
    |
    -- mylib
        |-- @myscope
            |-- mylib.es5.js
            |-- mylib.js
        |-- bundles
            |-- mylib.umd.js
        |-- src
            |-- [all the d.ts folders/files]
        |-- index.d.ts
        |-- package.json
        |-- public_api.d.ts

I've npm linked the dist/mylib folder to folder to my VS 2015/Angular 4.1.x project.

When I attempt to compile the project in VS 2015, I get the same type of messages as described in some of the situations above for Subscription, Observable, etc.

Ex: Build: Argument of type 'Subscription' is not assignable to parameter of type 'Subscription'.

If I temporarily delete the node_modules directory from the library project I get new errors complaining about not being able to find modules:

ex: Build: Cannot find module 'rxjs/Observable'.

Which leads me to the conclusion that when the typescript compiler compiles the application it does indeed look in the node_modules folder of the my linked library package for the module definitions for my library and not the applications node_modules folder.

It's not clear to me which solution(s) proposed above will provide a workaround for this issue, could someone please help me out?

Thanks!

@mikehutter In your project which uses your library (not in the library itself), do something like this in your tsconfig:

    "paths": {
      "rxjs/*": ["../node_modules/rxjs/*"]
    },

You may need some slight adjustment to this depending on where your tsconfig and source code result.

(Like others in this thread, I'd love to see some TypeScript improvement which makes this unnecessary.)

@mikehutter we've recently added some guidance in Angular CLI for working with linked libs, and it seems like in your case you're missing the TypeScript paths configuration for RxJs in your consumer app. See https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/linked-library.md for more info.

@kylecordes is on point 👍

Thank you @kylecordes and @filipesilva! That was all I needed...

Hi!

@mhegazy, do you believe this will make it to 2.4 or odds are it will be delayed again?
This is really painful to work around it, forcing each project to always define path mappings for things they really shouldn't be caring about.

Really hoping this time this makes it into the release!

Best,

Don't think this has been mentioned yet, but the super-shorthand way of working around this issue is:

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "*": ["node_modules/*", "*"]
        }
    }
}

The paths entry above basically says: for any module, first look it up in the node_modules folder in the root of the project, then fallback to the normal rule (recursive walk up the directories from where the import has occurred).

(Please correct me if I'm wrong, I'm still a relative newcomer to TS).

The entry in paths appears to be relative to the baseUrl. So, if you set baseUrl to be in a subfolder, you need to change your paths definition accordingly. E.g.:

{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "*": ["../node_modules/*", "*"]
        }
    }
}

@fiznool fyi that's part of my workarounds options 2 and 3 (in my above post). I've seen that just setting the paths property isn't enough if you have symlinked projects that use the same typings.

@jasonswearingen your post is much more comprehensive than mine... thanks for clearing that up. I'll be sure to refer back to it if I run into any more issues. 😄

We're seeing this in Google projects as well. As described here:
https://github.com/Microsoft/TypeScript/issues/9091#issuecomment-306969543

The workarounds described above using baseUrl and paths don't appear to work, tsc still picks up the duplicate definitions and no combination of paths/baseUrl/include/exclude/etc. has been able to convince it otherwise.

I'm curious how this works normally, if you have a project that depends on X, and also depends on Y which transitively depends on X, how does tsc avoid loading the type definitions twice?

@esprehn I strongly recommend you read my workaround post here: https://github.com/Microsoft/TypeScript/issues/6496#issuecomment-302886203

regarding that link, both my solutions (2 and 3) require more than just setting the baseUrl and paths, so if that's all you are doing, please re-read. the "easy" way is solution 2, but my preference is solution 3 as it allows proper "navigate to definition" via visual studio. the problem with solution 3 is that you need to only have external .d.ts definitions loaded once in the dependency chain.

hope that helps.

+1 I'm using lerna and a subdependency with included typings is flagged as "duplicate" when included through linked dependencies.

I can share code if needed.

With WebPack as in Angular-CLI, have that Angular-CLI package.json reference the RXJS package, then remove the `RXJS' package from any other project's package.json which you don't need because of the WebPack.

This situation may happen not just with symlinks, but with npm-shrinkwrap.json in a dependency with @types/node in dependencies. Shrinkwrap makes npm install two identical versions of @types/node into parent package and into the shrinkwrapped one. Please see https://github.com/KingHenne/custom-tslint-formatters/issues/5

Also ran into this when using a react project that has @types/react installed and linking it to an app that also uses @types/react

I was having the same issue while working w/ yarn link-ed packages.
Rx.js was throwing error: Type 'Observable<Location[]>' is not assignable to type 'Observable<Location[]>'. Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'

Luckily, I've resolved it by using same Rxj.s version in parent and linked packages.
So, in my case, two different versions of Rx.js caused the problem.

This is a huge blocker when working with monorepos. Really hope this will no longer delayed and finds its way into 2.5.

Edit: 😮

image

Interesting. The monorepo is the one scenario where my team does NOT encounter this issue, because the symlinks point everything back to a common package folder.

My team has hit this problem pretty frequently when linking our own internal libraries during development.

Recently we found a tool named yalc which mitigates this really well and actually makes for a pretty good dev loop. (As a tl;dr instead of linking the whole package it runs the prepublish scripts and copies the result to a linked folder).

This problem seems to be continually pushed back. Because this is really disruptive to so many people's workflows, could we potentially have some sort of stopgap fix, such as a 'disable comment' as suggested in #9448 ?

import { baz$ } from './qux';
function foo (bar$: Observable<any>) {}

foo(baz$);
> Duplicate identifier

// Makes foo's `bar$` type equal to that of `baz$`
foo(/*typescript:identicalIdentifier*/ baz$);

The "disable comment" is not much better than simply doing type casts everywhere, which is pretty painful in a large project. Above I proposed the following compiler change:

If ALL of these conditions are true, then do NOT report TS2345 for classes with private members:

  1. The public signatures are compatible according to the usual TypeScript duck-typing
  2. The classes are defined in NPM package folders with the same name and version (according to package.json)
  3. The classes have the same relative path for their module (e.g. "./lib/blah/B.d.ts")

If any of these criteria are not met, then it's okay to report TS2345.

A simplified fix would also still be better than the current situation, e.g. only consider # 1, maybe only if people opt-in via tsconfig. It should be pretty cheap to implement.

This approach has the advantage of merely disabling false alarms, but retaining the existing semantics. Whereas the node_modules remapping hacks change the NPM module resolution in a potentially incorrect way (e.g. causing code to malfunction in the hypothetical example I gave).

To be honest, I'm not even sure if this should be "fixed" in Typescript, as there also a problem with plain JavaScript: The instanceof operator will return false if the object's class differs from the provided class.

Thus, I'd rather look forward to a better alternative to npm link that maybe hooks into require(), first.

That's a good insight. Have you looked at pnpm? It is a small step forward from the current node_modules design, but a very effective one if I understand it correctly.

It's not just about npm link which can be replaced or not used, but about having multiple versions of the same @types/xxx package installed at the same time, often pulled in from dependencies which provide TypeScript declarations that depend on standard declarations, as me and other people reported. The number of such packages will grow with more adoption of TypeScript in the Node community.

@Yogu I think that's confusing identity and equality. Even though imports of the same package/version from two locations might represent two different instances of a type, they're still equal in type.

The following (valid) code comes to mind:

type a = { c: string };
type b = { c: string };

function foo (bar: a) {}

const baz: b = { c: 'd' };
foo(baz);

The same should apply for the libraries we import.

The types imported from the different versions of the libraries (and the corresponding @types packages) can be both equal and different (if the library updates, the type definition should update as well to reflect the changes).

I've just merged a change which will attempt to detect duplicate packages based on their name and version, and use only one. Please try it out using typescript@next when that is next published.

Hurray, thanks a ton for getting this fixed! :-)

@pgonzal Thanks for the pointer to pnpm, this definitely looks interesting!

@sompylasar The @types/node is a bit of a special case, but for @types/react, here is my suggestion: Put all dependencies required for the API of a module into the peerDependencies, not into dependencies. This makes it clear that the versions of the dependencies must match (or at least be compatible) between parent and child module. If parent and child both use react but don't exchange objects of react types, this issue here won't arise in the first place.

@harangue If a class is declared twice in two different files, with the same name and same structure, there are still two distinct classes which are not regarded as equal by JavaScript. As I mentioned, the instanceof operator will still distinguish instances of the two classes.

See this fiddle for a demonstration.

That's the reason I would rather solve this on the NPM level (so we don't have multiple declarations in the first place if that's not intended) than in TypeScript.

@Yogu :+1: so if @types/node is a special case, i.e. you cannot execute a program using two different node versions at the same time, it'll be one version, and most likely the one specified in the top-level package, maybe TypeScript should treat it as a special case, e.g. always use the top-level @types/node for every dependency?

@andy-ms Thank you for this!

Please try it out using typescript@next when that is next published.

I assume that hasn't happened yet. I've tried it with a couple of projects but no luck so far. Would be great to see an update in this thread as soon as next is available. @andy-ms

It looks like the daily builds have stopped again. The last was: 2.5.0-dev.20170808.

cc/ @DanielRosenwasser

I am not sure if this is related, but when trying 2.5.1 I seem to only see one aspect of a module, even when it has extending d.ts elsewhere.

if I have a @types for something lets say @types/mongodb and I have a custom declaration which extends that module, i.e adding promisification to methods, so I now have the @types/mongodb which has the module mongodb and I have a d.ts file in my project called something like mongo-promisification.d.ts which contains a mongodb module.

So then with that above scenario I have a file which has import {somethingFromTypes} from "mongodb" it complains that the data from the types module cant be found, even though its there and valid, but if I did import {somethingFromMyExtendingDts} from "mongodb" that works fine.

When compiling I just get told the module doesnt export that stuff, when it does, so is this desired behaviour and I just need to re-export on my custom d.ts or should it take in both the types and the extension d.ts I have?

I'm running the latest test build (Windows 10 64 bit) and this doesn't seem to be fixed for me.

Reproduction

Structure

a/
  index.ts
  package.json
b/
  index.ts
  package.json

Run

cd a
npm link
cd ../b
npm link a

a/index.ts

import { Observable } from 'rxjs/Observable';

export class Foo {
  public bar: Observable<any>;
}

b/index.ts

import { Foo } from '@rxjs-test/a';
import { Observable } from 'rxjs/Observable';

const baz = new Foo();

function qux (quux: Observable<any>) {}

// TypeError
qux(baz.bar);

Run

b>tsc -v
Version 2.6.0-dev.20170826

b>tsc index.ts
index.ts(11,5): error TS2345: Argument of type 'Observable<any>' is not assignable to parameter of type 'Observable<any>'.
  Property 'source' is protected but type 'Observable<T>' is not a class derived from 'Observable<T>'.

@grofit I think the problem in your situation is that you have two declarations of a module, where what you need is a module and an augmentation.
You should be able to do mongo-promisification.d.ts like this:

import * as mongodb from "mongodb"; // import the real mongo

// now augment it
declare module "mongodb" {
    // new stuff...
}

Without the import, you are in an ambient context and are writing a new declaration, not an augmentation.

(Handbook ref)

Can confirm this is working for me now with [email protected]. Big thanks to @andy-ms - this is a game changer!

I had the same issue, when I was importing Observable through library Typescript to angular-cli project and the functions were using the code snippet

```function getitems():Observable
http.get().map(response : Response) =>{
return response.json();
}


and when I removed the Observable<T> from function getitems() to not return anything the error disappear.
```function getitems()
http.get().map(response : Response) =>{
return <T> response.json();
}

@Basel78 I would need a complete example to be able to reproduce your error. Also, make sure you're using typescript@next -- angular-cli may have you on an older version which doesn't have the deduplication feature.

I've tried [email protected] but still getting lots of error TS2300: Duplicate identifier.
Unfortunately I can't share the whole project. But here's some details:
npm-link-ed package's tsconfig:

    "compilerOptions": {
        "module": "amd",
        "target": "es3",
        "sourceMap": true,
        "noEmitHelpers": true,
        "experimentalDecorators": true,
        "baseUrl": ".", // This must be specified if "paths" is.
        "paths": {
            "lib/*": [ "src/lib/*" ],
            "modules/*": [ "src/modules/*" ],
            "vendor/*": [ "src/vendor/*" ]
        },
        "typeRoots" : ["src/typings"]
    },
    "include": [
        "src/**/*.ts",
        "src/**/.*.ts", // TS ignores file names starting with dot by default
        "tests/**/*.ts",
        "tests/**/.*.ts"
    ]

the main project's tsconfig (@croc/webclient is the linked package):

    "extends": "./node_modules/@croc/webclient/tsconfig",
    "include": [
        "src/**/*.ts",
        "src/**/.*.ts",
        "node_modules/@croc/webclient/src/**/*.ts",
        "node_modules/@croc/webclient/src/**/.*.ts"
    ],
    "compilerOptions": {
      "baseUrl": ".",
      "typeRoots" : ["node_modules/@croc/webclient/src/typings"],
      "paths": {
        // map runtime paths to compile-time paths
        "lib/*": [ "node_modules/@croc/webclient/src/lib/*" ],
        "modules/*": [ "node_modules/@croc/webclient/src/modules/*" ],
        "vendor/*": [ "node_modules/@croc/webclient/src/vendor/*" ]
      }
    }

@evil-shrike can you reduce your project to a simple example that demonstrates the issue?

@mhegazy here it's
https://github.com/evil-shrike/typescript-npmlink-issue
in propject root:

cd lib
npm link
cd ../main
npm link tstest-lib

then in main:

npm run tsc

Hi @evil-shrike, I've narrowed the problem down to:
lib/tsconfig.json

{
    "compilerOptions": {
        "typeRoots" : ["src/typings"]
    }
}

main/tsconfig.json

{
    "extends": "./node_modules/tstest-lib/tsconfig",
    "include": [
        "node_modules/tstest-lib/src/**/*.ts"
    ]
}

So we end up including both (from tsc --listFiles):

/main/node_modules/tstest-lib/src/typings/jquery/index.d.ts
/lib/src/typings/jquery/index.d.ts

Since the node_modules comes from include and not from module resolution, I don't think we call realpath on it, so it ends up as two different included files.
It's best to not include your node_modules anyway, and to instead compile them separately and import them the way you would any other external package.

@andy-ms
It's not easy to do. The referenced lib in my case has lots of ambient d.ts which for example extends global interfaces. Like JQueryStatic:

interface JQueryStatic {
    cleanData (elems);
}

or declare prefixes for loaded (requirejs):

declare module "i18n!*" {
    //const m: { [key: string]: string };
    const m;
    export = m;
}

Then somewhere in lib's source it uses them:

const oldCleanData = $.cleanData;
$.cleanData = function (elems: JQuery) {
..
    oldCleanData(elems);
};

When such a module (from lib) will be imported somewhere in main it will fail compiling.

In your lib's source you should use /// <reference types="" /> (or /// <reference path="" />) directives to ensure that the needed typings are present whenever your library is imported. That way it won't have to be in "include"; when it is imported the referenced types will automatically be added to the project.

@andy-ms thanks, I've refactored my lib and now a project where it npm-link-ed is being compiled fine.

Not sure if these are too large reproductions but I'm getting the same thing from a barebones installation of CLI with angular@next and typescript@next to do with Observable<T> !== Observable<T>:

https://github.com/intellix/angular-cli-red
https://github.com/intellix/angular-cli-blue

Blue imports and uses a component and service from Red

@intellix Could you get an example that errors using only the tsc command line? Hard to tell what version of typescript ng is using.

@andy-ms I'm still having this issue. This occurs for me when attempting to use npm link between any vscode extensions I'm building. They all import vscode which causes duplicate identifier errors.
TS Version 2.7.0-dev.20171118

For me it worked map for example "rxjs/*" to a specific rxjs folder within node_modules in paths section of tsconfig file.
Now everything works fine with npm link.

I've also seen this happen when going from symlinked to non-symlinked from a version update link to a package.

See this SO question too: https://stackoverflow.com/questions/38168581/observablet-is-not-a-class-derived-from-observablet

@dakaraphi @JoshuaKGoldberg Could you provide instructions to reproduce these scenarios?

With the new behavior we should not include a package if we've already seen another package with the same "version" value in its package.json. If you have multiple installations with different versions, you will get two different copies of the module. That might explain why a version update would break this, if it only affected one of two installations.

@andy-ms The example I have are the following repositories:

  1. https://github.com/dakaraphi/vscode-extension-fold
  2. https://github.com/dakaraphi/vscode-extension-common

I use local npm install to reference vscode-extension-common from vscode-extension-fold

If you checkout these repositories, they currently work because I have a path mapping workaround in the package.json of vscode-extension-fold. However, if I understand correctly, I should not need that workaround.

@dakaraphi Thanks! It looks like the error is due to vscode.d.ts being written as a global, ambient declaration rather than as an external module. I've created Microsoft/vscode-extension-vscode#90.

This still doesn't work for me when I'm trying to link 2 packages, each having a dependency on rxjs. I'm using [email protected] and [email protected]. Both packages are using the exact same version. Does anyone have a workaround for this?

@SamVerschueren Could you provide specific instructions to reproduce the error? Also test with typescript@next.

@andy-ms I'll see what I can do!

@andy-ms Here's a small reproduction repository https://github.com/SamVerschueren/ts-link-6496. I used [email protected] to reproduce this.

  1. Install both the dependencies for mod-a and mod-b
  2. Compile mod-b with yarn build
  3. Compile mod-a with yarn build

Step 3 will fail with the following error

src/index.ts(7,15): error TS2345: Argument of type 'UnaryFunction<Observable<string>, Observable<string>>' is not assignable to parameter of type 'UnaryFunction<Observable<string>, Observable<string>>'.
  Types of parameters 'source' and 'source' are incompatible.
    Type 'Observable<string>' is not assignable to type 'Observable<string>'. Two different types with this name exist, but they are unrelated.
      Property 'buffer' is missing in type 'Observable<string>'.
src/index.ts(7,47): error TS7006: Parameter 'result' implicitly has an 'any' type.

Still running into this issue with [email protected]. Also tried typescript@next and had the same issue.

The root of the problem seems to be that linked packages still reference the type definitions in their own local node_modules rather than using the typings from the node_modules into which they are linked, when possible. That combined with the fact that:

  1. globals cannot be re-defined

    • this causes the compiler to complain when the global is defined both in the parent project's node_modules as well as the node_modules in the linked package

  2. classes that are otherwise identical cannot be assigned to one another

    • this causes the compiler to complain when trying to assign a variable whose type is a class from the parent project's node_modules to a value returned by the linked package whose type is defined in its own node_modules

I've been able to work around this issue using the paths config variable. For modules whose definitions come from @types/*, as suggested here, you can simply use:

"paths": {
  "*": ["node_modules/@types/*", "*"]
}

In the case where you run into this issue with a package that comes bundled with type definitions which define classes or globals, you have to add them manually. For example, rxjs:

"paths": {
  "rxjs": ["node_modules/rxjs"],
  "rxjs/*": ["node_modules/rxjs/*"]
}

I also experience problems with symlinks when I add a local package, using TS 2.8.3:

},
"devDependencies": {
    "@types/MyLib": "file:../MyLib/bin/npm/@types"
},

Since v3, npm are apparently installing these with symlinks instead of copying the files.

However, when I try to compile, the compiler sees the linked definition file as two separate and conflicting files:

node_modules\@types\MyLib\index.d.ts(3,11): error TS2300: Duplicate identifier 'Foo'.
C:/MySolution/MyLib/bin/npm/@types/index.d.ts(3,11): error TS2300: Duplicate identifier 'Foo'.

If I manually copy the files instead, it works as expected. I can work around this by setting typeRoots: ["./node_modules/**/"]

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DanielRosenwasser picture DanielRosenwasser  ·  3Comments

uber5001 picture uber5001  ·  3Comments

Roam-Cooper picture Roam-Cooper  ·  3Comments

siddjain picture siddjain  ·  3Comments

fwanicka picture fwanicka  ·  3Comments