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.
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
@jayphelps What package.json file are you referring to? The one that's distributed with rxjs points to './Rx.js. And the index.js file you're referring to is there in the source code version of Rx. This is what you would get if you did npm install @reactivex/rxjs.
I believe you are depending on rxjs though, right? Not building from source?
Can you send a simple repro case? I'll take a look, and hopefully we can get it fixed quick enough for 5.5.1.
What package.json file are you referring to? The one that's distributed with rxjs points to './Rx.js. And the index.js file you're referring to is there in the source code version of Rx. This is what you would get if you did npm install @reactivex/rxjs
Sorry for jumping the gun. I was quickly checking "what changed" recently and that was just one thing I noticed that seemed highly suspicious. I haven't had the time to truly confirm whether it's related. If your change only relates to @reactivex/rxjs then it's unrelated indeed, assuming it didn't have any unintended effects with the rxjs package. I've reworded my thoughts to not sound as confident in the cause š
I believe you are depending on rxjs though, right? Not building from source?
Yup. Standard rxjs package.
Can you send a simple repro case? I'll take a look, and hopefully we can get it fixed quick enough for 5.5.1.
I'll put something together later tonight. I was able to reproduce it in two my apps (and three people in https://github.com/redux-observable/redux-observable/issues/341) but admittedly I haven't had time to try it in a complete empty project with nothing but rxjs. It's also possible this requires using ES2015 module syntax to reproduce, but I doubt it.
@jayphelps Okay, sounds good. Send it over when you have that ready and I'll take a look. If a problem surfaced here, I definitely want to get it fixed ASAP. There were some renamed operators, but we should be exporting anything renamed as the old name.
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.
IIRC, this would not have been a problem in beta.0 as package.json files were used at every level for redirection to _esm2015, etc. However, that effected a case-related problem as the previous version's observable directory and Observable.d.ts file both needed to be represented as directories.
If these conflicts could be addressed, the mechanism used in beta.0 should be fine for v6.
@jayphelps Yes, that makes sense now.
So Iām terms of the breaking change, we could remove the module reference from package.json. But I guess a question would be why both are being used? Deep imports and importing the ākitchen sinkā version should generally be mutually exclusive.
Also, considering these two types of imports generally wouldnāt be used together, how would you feel about the recommendation being to just adjust Webpack module loading sequence to go straight to āmainā for rxjs?
The problem with removing āmoduleā is it makes it much harder for people using the kitchen sink import to get the benefits of ESM (significant size reduction).
Using both import patterns has been the status quo since v5. Itās been how libraries like angular, redux-observable, ngrx, etc can import Observable directly without also bringing in all of rxjs. Then consumers can choose between patching individual operators or patching the entire kitchen sink. So unfortunately for this issue, they have not been mutually exclusive; in fact, having both was the intent from the get go.
Also, considering these two types of imports generally wouldnāt be used together, how would you feel about the recommendation being to just adjust Webpack module loading sequence to go straight to āmainā for rxjs
Iām not sure what you mean, can you clarify?
While we discuss future options, is there any objections by anyone to removing āmoduleā and releasing 5.5.1?
I think that some consideration needs to be given to the fact not only is this a breaking change, but it's also one that's hard to diagnose. It's only going to effect strange, runtime behaviour.
Just my two cents, but I'd vote for removing it.
So my argument for not using both is that by importing rxjs/Rx youāre going to get the entire library. Nothing will be treeshakable no matter what you do.
That import should only be used in situations like sample apps where you donāt care about bundle size.
The issue applies to operator patching in general, even individually. This is my understanding: since traditionally rxjs has used prototype-based operators which are not treeshakeable by current bundlers (ignoring Closure Compiler with annotations and unsafe removals) normal tree shaking hasnāt been a priority because the only things it can shake are classes (and likely some random things like some of the schedulers. Removing those kb is meaningful, but most people asking for shaking wanted it to be done on the operators. This is one reason lettable operators were conceived, because then theyāre shakeable.
Angular and other libs import Observable directly and then those libraries let you in your app patch all operators with import ārxjs' or they might advocate devs patch operators individually. The āpatch allā way just calls imports all the individuals for you, but not letting you control size, obviously.
That said, I havenāt been on the core team for a while so Iāll defer to them to elaborate further and add any new details I havenāt heard of. š
Generally thatās correct. The problem is that by importing rxjs/Rx or rxjs directly, you get every static method for observable, every operator, every scheduler, etc. Essentially you get the entire weight of the library.
Therefore thereās never a scenario where importing from rxjs/Rx as well as going deep imports to pull in operators gives any value.
Iāll ping Ben and OJ on this to weigh in. Iām fine either way. It just means people importing through rxjs/Rx wonāt get the advantage of a smaller bundle.
Also, to be clear, even when using the āmoduleā import for the ESM version, nothing is treeshakable. However, thereās a large size difference between CJS modules (the āmainā key) and ESM to the tune of 10ās of kb with Webpack 2 and over a hundred with Webpack 3ās scope hoisting. So those are āfreeā bytes by keeping the āmoduleā key there.
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';
@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.
@jasonaden unfortunately this is a breaking change. We'll need to fix it. The nature of the patching operators has always meant that the expectation is if you import from rxjs/Observable you should get the same reference as you would get from rxjs or rxjs/Rx. They are definitely not mutually exclusive, as I have seen an unfortunate amount of people using both.
We are definitely going to need to fix this.
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.
lol this is what I was trying to say. Sorry I wasnāt clear! š
Guys, thank you very much for reviewing my issue in Angular project. I hope it helped to find the issue.
@jayphelps Yeah, sorry... I realized eventually ehats what you were saying. I missed it reading all this on my phone. Iāll send the PR in a few minutes.
// 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'
}
},
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 trueFix 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
.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.