-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Investigate tree-shaking #1470
Comments
Line 14 in 1186016
Cannot be tree-shaken (unless a particular minifier goes deeper and figures out that this is needed only if set react-redux/src/utils/shallowEqual.js Line 1 in 1186016
Top-level getter also cannot be tree-shaken, arguably this is a minor thing as well - but ideally, you should inline this into the function.
Similarly this - also a short leftover, but cannot be removed. A recommendation here would be making it a function, and put const isHopefullyDomEnvironment = () =>
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
export const useIsomorphicLayoutEffect = /* #__PURE__ */ isHopefullyDomEnvironment()
? useLayoutEffect
: useEffect react-redux/src/hooks/useDispatch.js Line 41 in 1186016
Top-level function call prevents this from being tree-shaked. The recommendation would be a PURE annotation before the call. react-redux/src/hooks/useSelector.js Line 2 in 1186016
Most likely this is not written in ESM and this might prevent tree-shaking it. The recommendation would be to just migrate to using if+throw explicitly without somewhat quirky API wrapper - the React team is doing the same thing from what I read (migrating away from this pattern). react-redux/src/hooks/useSelector.js Line 134 in 1186016
Similar to react-redux/src/hooks/useStore.js Line 37 in 1186016
The same thing, just for the react-redux/src/components/Context.js Lines 3 to 5 in 1186016
Both of those (createContext call and displayName assignment) cannot be removed. The first one should be fixable with a PURE annotation and for the latter, I would recommend wrapping it with a NODE_ENV check - it's not that much needed in prod build anyway, right? react-redux/src/components/Provider.js Line 36 in 1186016
Statics assignment is one of the biggest tree-shaking offenders. I would recommend wrapping this with NODE_ENV check as well - as those are most certainly not needed at all in production.
Bundlers might have a little bit hard time of pruning this as its CJS - I've recommended providing ESM ("module") build there, but it was ignored/declined. Maybe they could at least add
react-is is also CJS-only and rather not treeshakeable right now. There is an open PR for improving the situation in this regard (facebook/react#13321) but it got stale - I've offered help to finish it sometime this week but I haven't received any answer yet.
react-redux/src/connect/connect.js Line 104 in 1186016
Yet Another Top Level Call 😉 PURE annotation should help. Summary There are also few other, minor, things that could be improved - for those that I consider easy and no-brainers I can prepare a PRs with numbers before/after, just let me know if you want it. |
@Andarist : fantastic analysis, thank you! Yes, I'd be interested in any PRs that can improve the tree-shakability overall. It would be helpful if we could have some sample projects set up somewhere that would demonstrate the before-and-after results to confirm the behavior improvements, as well. |
IMHO the best way to test tree-shakeability is to literally try to build this with webpack & rollup: import 'redux' I can prepare a simple repro that would contain setup for both of those and we could observe on it how we improve the situation with each change - starting from less controversial ones to those a little bit more controversial and finally landing with a list of unresolved stuff that we could discuss further. Expect a first PR over the weekend. |
I set up a quick bash script which makes 3 create-react-apps, installs redux and react-redux, and creates 3 version of the sample todo app, connect only, hooks only, and connect+hooks. |
Fixed.
Fixed.
These will never get removed anyways. You cannot make use of this library without context (all the hooks use it).
Yup, we should add dev mode checks. We do this over on React Router with a
Nothing we can really do about external packages. If there are lighter alternatives available, we can switch. |
I've set up my tree-shaking playground for This yields tremendous improvements (%-speaking). useIsomorphicLayoutEffect@timdorr this was not quite the change I had in mind. The deopting pattern is getter used during script's initialization and not assigning to a one-time variable. This lone stays in the bundle after changes in case of webpack:
Yes, you might not be able to use this library without context BUT the library might get referenced indirectly and might stay unused - in such case, in ideal world, it would just vanish completely, along with this
Those points were rather a general remark that as long as they stay as your dependencies they might not get removed (because of how they are written/bundled - not because it's impossible to remove their code if it would be structured other way). EDIT:// After looking closer into current results - when using Rollup there is barely any react-redux code left in the bundle 🎉 it's mostly |
So my other main question about this, besides all these smaller optimizations: Do |
Right now (based on the
My PR here - #1471 - removes further:
Leaving you with:
And not-shaked code from your CJS deps. |
How would it get referenced indirectly? I'm trying to think of the practical implications here. No one is doing I think the more practical example here is to check sizes when you do |
Like when you have a dependency A depending on
IMHO that's a separate test. Solely for testing tree-shaking, it's just better to check that
What you are proposing is a bundle size check but with such, it would be harder to assess if there is in the bundle anything that shouldn't be there because you would have to read outputs carefully. With my simple test we only have to inspect the output and see if anything is still there. |
No, I am not talking about testing different import combinations. I'm only talking about testing Hooks-only usage. That was the reason Mark opened this issue. I don't want to waste your time testing an unrealistic scenario where someone imports the library but doesn't use it. That's not really going to benefit anyone if the deopts kick in as soon as you import something for real. We've basically got two sections of the library: HOC and Hooks. If you're a HOC-only user, Hooks shouldn't show up in your code. And vice versa for Hooks-only users. While there are going to be a lot of mixed users, we should still provide a clean export for those that can keep it to one or the other. It looks like we've made some progress by hinting that the Hook factory exports are pure. And those other PRs have helped too. So can we see where we're at for either the Hooks- and/or HOC-only use case? |
My point is that this really doesn't matter here - if we e.g. All of this is based on static analysis of the code, side effects created by expressions and dependencies between particular values. Dependencies conceptually form a graph and if we are able to prune a particular subtree of it and if we include a separate branch of that tree by importing stuff it doesn't change anything for that pruned subtree. We can prepare another test that would actually import something but that just wouldn't change the outcome, could only act as last step verification if we understand how this all works like we think it does. In such a case its purpose would be, in my opinion, different than testing if tree-shaking works here, because that is already showcased by existing "test" (https://github.com/Andarist/react-redux-tree-shaking-playground). |
I believe the changes we've made have improved things quite a bit. There may be some micro-optimization to be done, but I don't think we need to track this anymore. |
Bringing this back up: did we ever confirm that "you only get Also, it doesn't look like we have |
Is there a way to do a deep import (eg. |
@wilsonpage could u prepare a repro case? I could take a look at it then. Keep in mind that bundle analyzers are often misleading in this regard |
UPDATE: I found a way of 'deep' importing only the I created an abstraction module that does the import { useSelector as _useSelector } from 'react-redux/es/hooks/useSelector';
import { useDispatch as _useDispatch } from 'react-redux/es/hooks/useDispatch';
import { useStore as _useStore } from 'react-redux/es/hooks/useStore';
import _Provider from 'react-redux/es/components/Provider';
import shallowEqual from 'react-redux/es/utils/shallowEqual';
import {
useSelector as UseSelector,
useDispatch as UseDispatch,
useStore as UseStore,
Provider as ProviderType,
} from 'react-redux';
export const useSelector = _useSelector as typeof UseSelector;
export const useDispatch = _useDispatch as typeof UseDispatch;
export const useStore = _useStore as typeof UseStore;
export const Provider = _Provider as typeof ProviderType;
export { shallowEqual }; In summary this probably means that tree shaking isn't working out of the box yet. |
I don't have time I'm afraid, but this project is using nextjs v10.3 with webpack 5. Previously using the documented |
The problem is that This screenshot has been taken from a quick demo I've created. It reports that Moral of the story: double-check tool reports 😉 |
Yeah, while I don't have a repro on hand atm, some comparisons with a standard CRA project definitely showed a noticeable difference in size between importing I realize that tree shaking is highly fragile and very dependent on multiple factors, but as far as I know this should be working correctly on our end. Would love to see some better repro/example projects, though! |
As hooks continue to gain adoption, it's possible that some folks might want to write apps that only use our hooks API. I'm unsure whether
connect
actually tree-shakes or not.It would be helpful if someone could do some testing to see if
connect
tree-shakes when only the hooks are used, and if it doesn't, figure out why not and what changes are needed to enable that. (On the flip side, I'd also be curious if the hooks tree-shake when you only useconnect
.)The text was updated successfully, but these errors were encountered: