ReactiveX / rxjs

A reactive programming library for JavaScript
https://rxjs.dev
Apache License 2.0
30.62k stars 3k forks source link

Monorepo and Splitting Out To Different Packages #6786

Open benlesh opened 2 years ago

benlesh commented 2 years ago

Goals

  1. We've wanted to move to a monorepo for quite some time. This is so we can move the docs app out of the project for core components, and possible move things into our monorepo to ensure compatability — for example chrome tools or the eslint plugin.
  2. We've discussed many times splitting Observable off to be a standalone package.
  3. We may want to consider splitting rxjs/testing, rxjs/ajax, etc, off into their own packages as well.
  4. We want to do this in the slowest, most easy to migrate to way possible.

Monorepo and multiple packages

Standalone Observable

Long Term:

  1. We wouldn't want to force people to move over to @rxjs/observable et al until version 9. That should be a long ways off.
  2. Operator creation needs to be consumerized. We have our out internal way of creating operators, and we need to come up with a way of making that consumable and understandable to the masses. It has some advantages, like properly unsubscribing from firehose inner observables. (Currently it's impossible to do this properly with plan observer implementations) (A token/signal approach would help this)
  3. Considerations around using abort signals, etc.
benlesh commented 2 years ago

Core Team:

  1. Monorepo - 👍
  2. Creating "mini-packages" like @rxjs/observable, et al - 👍 a. Level of granularity? What do we do with things like mergeInternals? What about operators derived from other operators (mergeMap and concatMap for example)? ❓
PowerKiKi commented 2 years ago

I've posted this on #6367, but I feel it should also be seen here, sorry for duplicate:

Wouldn't publishing multiple packages for what is essentially the same code actually increase risk of non tree shakable use-case and thus increase bundle size ?

As seen with lodash, if one of my dependency decides to depends on the future @rxjs/observable, but my app depends on the current rxjs, then all the code from @rxjs/observable will essentially be bundled twice, won't it ?

And of course it would be much worse if we actually publish a package for each operator. Then a single operator could be bundled three times if my deps happened to depend on rxjs, @rxjs/operators and say @rxjs/operators-map.

I could also imagine compatibility issues if one of my dependency depends on an old version of @rxjs/observable and my app pass one of those object to a new, incompatible version of an operator coming from the latest version of rxjs. This kind of issue already happened with the graphql package where they had to add manual check in their code to throw error if it happens.

That seems a lot of tricky tradeoffs to attempt to solve the issue of bundle size that is already properly solved by tree shaking.

Is that really worth it ? :thinking:

PowerKiKi commented 2 years ago

@benlesh would you please share the rxjs team opinions about the risks mentioned in my previous post?

How could we avoid fragmenting the ecosystem and actually increase the bundle size if things are available from multiple packages at once ?

How can we solve incompatibilities across future version of rxjs, that are transient dependencies, and that will still have to collaborate with each other?

Also what do you think about the fact that lodash seems to be going into the opposite direction, single ESM tree-shakable package, https://github.com/lodash/lodash/issues/5107#issuecomment-1106683823 ?

jollytoad commented 2 years ago

(Sorry originally posted this in the roadmap issue, before realising this issue is probably more appropriate)

I just wanted to contribute to the discussion of packaging ... I've been successfully using rxjs 7.5.x in Deno via esm.sh, but the way things are imported isn't ideal, ie. everything is import { ... } from 'rxjs' (where rxjs is actually mapped to an esm.sh url in an import map) ... treeshaking isn't an option in this situation, so the whole of rxjs ends up being loaded whether used or not.

I'm not bothered really whether ops are individually packaged or not, but would prefer a situation where they are at least exposed publicly as individual modules, so I can do something like: import { concatMap } from 'rxjs/op/concatMap'; and in turn that imports only on what it needs (not the whole of rxjs) ... I know rxjs modules already exist like this, but they are all under an internal module structure.