-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
Bundle DOM renderers into their individual UMD bundles #7164
Conversation
I moved ReactComponentTreeDevtool into isomorphic because it is the only dependency from isomorphic into the renderers. I'm not really sure how Perf and other devtools will work with alternative renderers since there needs to be some way to register renderers with a global devtool instead of renderer specific tools. That's a separate discussion that we've had before though. |
78a4e46
to
7032362
Compare
Oh, this uses |
Looks pretty legit. I don't think we were totally UMD compatible before either (eg you were screwed if you required the dist file of react and react-dom). Didn't look close enough at what you have here to really access but might not need to do too much. FWIW here's the output from the compare size script:
|
I managed to cut out some more shared dependencies: b04acc6 That's 3kb less on the react-dom.min.js so down to +16kb in overlap. |
b04acc6
to
780e01f
Compare
Found a bit more unnecessary overlap. Down to 136kb for react-dom.min.js so now it is only +5kb in overlap. |
null, | ||
null, | ||
nextElement | ||
{ child: nextElement } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will break the devtools. Sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Time to port devtools to new APIs 😈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we still going to break the devtools with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't break devtools but it does start showing the top level wrapper.
I switched this to an explicit flag on the wrapper type and updated devtools to support this new flag:
Great job, looking forward to this feature, we have put dist/react.js dist/react-dom.js into our native mobile app, so web app bundle code do not contain react core code and can load react core from local filesystem. At the same time we build a standalone react-native bundle into our native mobile app too, and use https://github.com/react-component/rn-packager to bundle app code without react-native(react) framework code, so react-native app can also load react-native core code from local filesystem. Currently I notice our standalone react-native bundle has some duplication with dist/react.js, we want to reuse code and reduce overall native app size. Here is my proposal (I know it is hard for current module resolution): reactonly contains ReactElement, ReactUpdates… bundle to react.js, expose React.createElement, React._secret.ReactUpdates react-domdepends on react, entry: // use ReactUpdates
const ReactUpdates = require(‘react’)._secret.ReactUpdates;
ReactUpdates.injection.xxx
// expose ReactDOM.render react-nativedepends on react, entry: const ReactUpdates = require(‘react’)._secret.ReactUpdates;
ReactUpdates.injection.xxx
// expose ReactNative.render |
The point of this is to put as much as possible in the individual renderer packages so that they can be versioned independently, and can be replaced by better algorithms independently. The more we share, the more we lose out on that ability. It is an unusual scenario to have two renderers in the same app so we're optimizing for the case where you only have one renderer. However, it seems like this duplication would only marginally impact your application file size as soon as you add any significant logic to your app. Especially compared to the rest of React Native. |
understood. As for two render, we prefer to use web inside mobile app for most situations(high dev efficiency), but in some situation(high performance) we will use react-native. |
9e4485e
to
912cac7
Compare
Instead of exposing the entire DOM renderer on the react.js package, I only expose CurrentOwner and ComponentTreeDevtool which are currently the only two modules that share __state__ with the renderers. Then I package each renderer in its own package. That could allow us to drop more server dependencies from the client package. It will also allow us to ship fiber as a separate renderer. Unminified DEV after before react.js 123kb 696kb react-with-addons.js 227kb 774kb react-dom.js 668kb 1kb react-dom-server.js 638kb 1kb Minified PROD after before react.min.js 24kb 154kb react-with-addons.min.js 37kb 166kb react-dom.min.js 149kb 1kb react-dom-server.min.js 144kb 1kb The total size for react.min.js + react-dom.min.js is +19kb larger because of the overlap between them right now. I'd like to see what an optimizing compiler can do to this. Some of that is fbjs stuff. There shouldn't need to be that much overlap so that's something we can hunt. We should keep isomorphic absolutely minimal so there's no reason for other React clones not to use it. There will be less overlap with Fiber. However, another strategy that we could do is package the isomorphic package into each renderer bundle and conditionally initialize it if it hasn't already been initialized. That way you only pay an overlap tax when there are two renderers on the page but not without it. It's also easier to just pull in one package. The downside is the versioning stuff that the separate npm package would solve. That applies to CDNs as well. ReactWithAddons is a bit weird because it is packaged into the isomorphic package but has a bunch of DOM dependencies. So we have to load them lazily since the DOM package gets initialized after.
These files reaches into isomorphic files. The ReactElement functions are exposed on the React object anyway so I can just use those instead. I also found some files that are not shared that should be in renderers shared.
renderSubtreeIntoContainer is only used by the DOM renderer. It's not an addon. ReactClass isn't needed as a dependency since injection doesn't happen anymore.
By replacing this intermediate file we can do the lazy loading without needing any lazy requires. This set up works with ES modules. We could also replace the globalShim thing with aliased files instead for consistency.
var shimSharedModules = globalShim.configure({ | ||
'./ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner', | ||
'./ReactComponentTreeHook': SECRET_INTERNALS_NAME + '.ReactComponentTreeHook', | ||
// All these methods are shared are exposed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you fix that language?
Let's do it. |
…e test I added this in facebook/react#7164
…e test (#407) I added this in facebook/react#7164
Fixes facebook#7492. This was a build size regression introduced in facebook#7164.
* Cut out isomorphic dependencies from the renderers These files reaches into isomorphic files. The ReactElement functions are exposed on the React object anyway so I can just use those instead. I also found some files that are not shared that should be in renderers shared. * Found a few more shared dependencies renderSubtreeIntoContainer is only used by the DOM renderer. It's not an addon. ReactClass isn't needed as a dependency since injection doesn't happen anymore. * Use a shim file to load addons' dependencies on DOM By replacing this intermediate file we can do the lazy loading without needing any lazy requires. This set up works with ES modules. We could also replace the globalShim thing with aliased files instead for consistency. * Bundle DOM renderers into their individual UMD bundles Instead of exposing the entire DOM renderer on the react.js package, I only expose CurrentOwner and ComponentTreeDevtool which are currently the only two modules that share __state__ with the renderers. Then I package each renderer in its own package. That could allow us to drop more server dependencies from the client package. It will also allow us to ship fiber as a separate renderer. Unminified DEV after before react.js 123kb 696kb react-with-addons.js 227kb 774kb react-dom.js 668kb 1kb react-dom-server.js 638kb 1kb Minified PROD after before react.min.js 24kb 154kb react-with-addons.min.js 37kb 166kb react-dom.min.js 149kb 1kb react-dom-server.min.js 144kb 1kb The total size for react.min.js + react-dom.min.js is +19kb larger because of the overlap between them right now. I'd like to see what an optimizing compiler can do to this. Some of that is fbjs stuff. There shouldn't need to be that much overlap so that's something we can hunt. We should keep isomorphic absolutely minimal so there's no reason for other React clones not to use it. There will be less overlap with Fiber. However, another strategy that we could do is package the isomorphic package into each renderer bundle and conditionally initialize it if it hasn't already been initialized. That way you only pay an overlap tax when there are two renderers on the page but not without it. It's also easier to just pull in one package. The downside is the versioning stuff that the separate npm package would solve. That applies to CDNs as well. ReactWithAddons is a bit weird because it is packaged into the isomorphic package but has a bunch of DOM dependencies. So we have to load them lazily since the DOM package gets initialized after. (cherry picked from commit 8ef00db)
@xgqfrms-GitHub You can continue to use |
Since this PR made it to 15.4.0 I can't get require.js to load ReactDOM.render or anything from ReactDOM. My build process is ES6 to UMD and my import statement in each of my react components looks like: import { render } from 'react-dom'; Can confirm the fix mentioned in #8301 solves the issue |
Hooray! 🎉 |
Instead of exposing the entire DOM renderer on the react.js package, I only expose CurrentOwner and ComponentTreeDevtool which are currently the only two modules that share state with the renderers.
Then I package each renderer in its own package. That could allow us to drop more server dependencies from the client package. It will also allow us to ship Fiber as a separate renderer.
The total size for react.min.js + react-dom.min.js is +5kb larger because of the overlap between them right now. I'd like to see what an optimizing compiler can do to this. Some of that is fbjs stuff. There shouldn't need to be that much overlap so that's something we can hunt. We should keep isomorphic absolutely minimal so there's no reason for other React clones not to use it. There will be less overlap with Fiber.
I like this strategy because it encourages us to keep isomorphic really minimal.
However, another strategy that we could do is package the isomorphic package into each renderer bundle and conditionally initialize it if it hasn't already been initialized. That way you only pay an overlap tax when there are two renderers on the page but not with one. It's also easier to just pull in one package. The downside is the versioning stuff that the separate npm package would solve. That applies to CDNs as well.
ReactWithAddons is a bit weird because it is packaged into the isomorphic package but has a bunch of DOM dependencies. So we have to load them lazily since the DOM package gets initialized after.
cc @zpao @kittens @gaearon