rxjs: Using more than one form of imports causes multiple copies/identities of Observable

alternative title for SEO: “all operators are undefined, not on the prototype”

RxJS version: 5.5.0 Code to reproduce: Use two different styles of imports in the same project.

import 'rxjs';
import { Observable } from 'rxjs/Observable';

console.log(Observable.prototype.map);
// undefined

Or

import * as Rx from 'rxjs';
import { Observable } from 'rxjs/Observable';

console.log(Rx.Observable === Observable);
// false

Expected behavior:

They use the same copy of rxjs, so that there is only one Observable identity. Adding operators individually via import 'rxjs/add/operator/map' or import 'rxjs' both patch the same Observable.prototype as import { Observable } from 'rxjs/Observable'

Actual behavior:

They read from different copies of rxjs in the same package so adding operators to one doesn’t add them to the other.

This becomes a major issue when dealing with libraries like angular, redux-observable, etc which use import { Observable } from 'rxjs/Observable' but the consuming devs use import 'rxjs'. None of the operators get added because they are not the same Observable.

This was first reported in https://github.com/redux-observable/redux-observable/issues/341 by several users, but I’m also experiencing it as well.

Additional information:

You won’t reproduce this using CJS or with Babel. You need a bundler that supports tree shaking via "module". See https://github.com/ReactiveX/rxjs/issues/2984#issuecomment-338351370 for my working theory on the cause.

Workaround

The workaround is to use "rxjs/Rx" instead of "rxjs".

import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';

console.log(Observable.prototype.map);
// [object Function]

This works because then you won’t using the ES modules copy, instead using the default CJS code–this also means you won’t get what limited tree shaking you might have gotten, but you didn’t get that before 5.5.0 and because operators are on the prototype they are not shaken, so the difference is modest. So using this workaround will behavior identical to how it was in 5.4

Cc/ @jasonaden @IgorMinar @benlesh

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 22 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Wasn’t this fixed in 5.5.1?

On Thu, Oct 26, 2017, 4:01 PM Roman Vasilev notifications@github.com wrote:

// RxJS since 5.5+ import { Observable as Observable1 } from ‘rxjs’; import { Observable as Observable2 } from ‘rxjs/Observable’; console.log(Observable2 === Observable1); // => false, but expected true

Fix on webpack side:

resolve: { alias: { rxjs$: ‘rxjs/_esm5/Rx.js’, rxjs: ‘rxjs/_esm5’ } },

Full config https://github.com/unlight/webpack-resolve-alias-playground/blob/master/webpack.config.ts and repo example https://github.com/unlight/webpack-resolve-alias-playground

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ReactiveX/rxjs/issues/2984#issuecomment-339675892, or mute the thread https://github.com/notifications/unsubscribe-auth/AANM6CHjtoBdmuHJ4gjLvI4pgMtuSVHJks5swJDPgaJpZM4QBQvp .

@cartant Good point on the related Angular issue. I was looking at it from the context of a single project, not the combination of different projects importing different ways.

I’ll push a commit to remove the “module” key.

This issue is likely the cause of the problems in #2977, as the project in the related Angular issue includes the following imports (in addition to deep imports):

import { Observable, Subscription } from 'rxjs';
import { Observable } from 'rxjs';
import { Subscription } from 'rxjs';
import { Subscription, BehaviorSubject } from 'rxjs';
import { Observable, Subscription } from 'rxjs';

Reproduction: https://github.com/jayphelps/rxjs-issue-2984

I figured out the issue though: bundlers that understand the "module" flag in package.json will use a different copy of rxjs than if you use import * as Rx vs operator patching/direct import. i.e. there’s a node_modules/rxjs/Observable.js and also a node_modules/rxjs/_esm5/Observable.js.

"module": "./_esm5/Rx.js"

https://github.com/ReactiveX/rxjs/commit/988e1af3c74a1b1865fd0e7a1d8ac93892608aee#diff-e7586ccec757a94ea1ab5c539aa69c3dR35

This is great for tree shaking and obviously done intentionally, but it means you have to use one or the other (or you’re stuck with whatever your dependencies like redux-observable choose). Presumably the "es2015" flag + code has the same problem.

It’s unclear what the right “fix” or more accurately, choice, should be made. This is definitely a breaking change obviously, so we can revert the behavior for 5.5.1. But for v6 it’s unclear if there’s a way to make both work. I don’t think so.

I’ve also updated my OP with the workaround of importing from "rxjs/Rx" instead which won’t use the esm code.