-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Code splitting in large webapps #37
Comments
I don't care right now about bundles in my production app so this solution looks good because it should be implemented on app-level. |
Having the ability to lazily load stores is pretty nice for code splitting optimizations. At Optimizely we organize everything in modules. Currently in production we have over 50 modules that are spread out through several JS bundles. Here is an excerpt from NuclearJS's README: ModulesThe prescribed way of code organization in NuclearJS is to group all stores, actions and getters of the same domain in a module. Example Module File StructureFor the flux-chat example we will create a chat module that holds all of the domain logic for the chat aspect. For smaller projects there may only need to be one module, but for larger projects using many modules can decouple your codebase and make it much easier to manage.
modules/chat/index.js
By grouping everything together in the module and registering stores as a side effect of requiring the modules entry point then components / other parts of the system are free to require whatever modules they need and they are guaranteed that all stores needed will be registered. This allows us to code split however we'd like which is usually based around components and not flux. Not saying you should adopt 100% this route but really reinforcing the concept of organizing by domain and not by type. |
By grouping stores and actions in a module then the dependency between components <==> modues and modules <==> modules is made explicit. If a module needs to invoke another modules actions or wants its store to handle another modules action then it must explicitly require the other module and the stores will always be loaded. |
@jordangarcia, thanks for chiming in! NuclearJS is an inspiration :-) Yeah. Dividing source by “domains” and registering sources as a side effect of relevant domain root module execution makes sense to me. That's what I suggested in the original post, too. (Although via a slightly different mechanism.) |
<3 this idea! Another production (not-ready-to-use-yet) use case that could help.
We use DI in all modules (except for Currently we are using a classical Flux implementation, so we have one instance of the Now if I use
Now, with this proposal, I can imagine to just inject Am I thinking correct or am I missing some fundamental point? Thanks :) |
So this is of interest to me as well as we do a lot of code splitting in our Big App. We actually have two concerns that are being addressed by our current set up
One, is fairly easy to fix with the current redux api, we just have a function createReducer(reducer){
globalReducers[reducer.name] = reducer
redex.replaceDispatcher(reCreateDispatcher())
return reducer
} This has the effect of letting reducers register themselves, a la traditional flux. That isn't partcularly necessary for code splitting but it helps us with number 2 on the list. We created a custom @connect(userReducer) we could also use reducer string names, but using the module instance lets webpack build the bundles a lot easier. All in all the setup is working for us, and has the benefit of when you move, rename, or remove a reducer you get a compile time error for views that depend on it. None of this is to suggest that this is a good idea for a redux core api, but I though i'd chime in with our use case. things that are still annoying:
|
These are great points, thanks for raising them. I think it will be easier to brainstorm solutions if somebody's up for making Wanna give it a try? |
@gaearon I'd be happy to try. Fair warning it may not happy super quickly. you want a PR or should I just build an example repo? |
A separate repo would be better. |
Superseded by #350. |
FYI today I'd probably code split reducers like this: https://gist.github.com/gaearon/0a2213881b5d53973514 |
I've got a working example of reducer splitting at my webapp example in case anyone is interested. Using a different approach in which the innermost route's component must export the complete reducer. That way the reducer is predictable and doesn't depend on browsing history, although that's a detail. Description of the approach is here. I've improved that example in the meantime, but haven't pushed the changes yet. The reducer splitting hasn't changed though. |
I've heard from several people they already want to use this in production, which is a pretty crazy idea, if you ask me 😉 . Still, it's better to consider production use cases early. One of such use cases is code splitting.
Large apps don't want to carry all the code in one JS bundle. Webpack and Browserify allow you to split your app into several parts. One core part loads first, the rest is loaded on demand. We want to support this.
Currently Redux forces you to declare all Stores at the root. This is sensible because if you register them lazily as components subscribe to them, some Stores might miss some actions. This is totally not obvious and fragile.
However, defining all Stores at the root robs us of some of the benefits of code splitting, as all Stores will have to be included in the application's entry point bundle.
So here's my proposal. (I haven't thought it through at all; tell me if it's silly.)
I want to support several<Root>
s throughout the application. Whenever a new<Root>
is mounted under an existing one, instead of initializing a new dispatcher, it adds itsstores
to the parent dispatcher. They do not receive the actions they missed—fair game IMO.Open questions:Is the Store state ever destroyed?Should it be destroyed when a child<Root>
unmounts?Should it be destroyed when a particular store key is removed from thestores
prop?Is it time to rename<Root>
to something like<Dispatcher>
?I don't know if it's a good design or not, just something to get the ball rolling.I want to have some consistent state lifecycle story that works with big apps.I don't like this idea. If the code loads, it should start handling the actions immediately; not when some view mounts. And what if the new view mounts, and then unmounts? Its
<Root>
will be gone, poof! But we don't want to erase its state.Perhaps, a better idea is to rely on React! We got
<Root>
at the top. (Yo, let's call it<Dispatcher>
;-). Okay, so we got<Dispatcher>
at the top. And it has astores
prop. (Not in the decorator version, but we're looking at an advanced use case.) And we got React. And React lets you change props. Get it?Thoughts?
cc @vslinko
The text was updated successfully, but these errors were encountered: