Skip to content

Commit 7ae3bbe

Browse files
sebmarkbagezpao
authored andcommitted
Bundle DOM renderers into their individual UMD bundles (#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)
1 parent bada298 commit 7ae3bbe

20 files changed

+287
-140
lines changed

Gruntfile.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,31 @@ module.exports = function(grunt) {
117117
'build-modules',
118118
'browserify:addonsMin',
119119
]);
120+
grunt.registerTask('build:dom', [
121+
'build-modules',
122+
'version-check',
123+
'browserify:dom',
124+
]);
125+
grunt.registerTask('build:dom-min', [
126+
'build-modules',
127+
'version-check',
128+
'browserify:domMin',
129+
]);
130+
grunt.registerTask('build:dom-server', [
131+
'build-modules',
132+
'version-check',
133+
'browserify:domServer',
134+
]);
135+
grunt.registerTask('build:dom-server-min', [
136+
'build-modules',
137+
'version-check',
138+
'browserify:domServerMin',
139+
]);
120140
grunt.registerTask('build:npm-react', [
121141
'version-check',
122142
'build-modules',
123143
'npm-react:release',
124144
]);
125-
grunt.registerTask('build:react-dom', require('./grunt/tasks/react-dom'));
126145

127146
var jestTasks = require('./grunt/tasks/jest');
128147
grunt.registerTask('jest:normal', jestTasks.normal);
@@ -141,7 +160,10 @@ module.exports = function(grunt) {
141160
'browserify:addons',
142161
'browserify:min',
143162
'browserify:addonsMin',
144-
'build:react-dom',
163+
'browserify:dom',
164+
'browserify:domMin',
165+
'browserify:domServer',
166+
'browserify:domServerMin',
145167
'npm-react:release',
146168
'npm-react:pack',
147169
'npm-react-dom:release',

grunt/config/browserify.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,30 @@ var grunt = require('grunt');
77
var UglifyJS = require('uglify-js');
88
var uglifyify = require('uglifyify');
99
var derequire = require('derequire');
10+
var aliasify = require('aliasify');
11+
var globalShim = require('browserify-global-shim');
1012
var collapser = require('bundle-collapser/plugin');
1113

1214
var envifyDev = envify({NODE_ENV: process.env.NODE_ENV || 'development'});
1315
var envifyProd = envify({NODE_ENV: process.env.NODE_ENV || 'production'});
1416

17+
var SECRET_INTERNALS_NAME = 'React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED';
18+
19+
var shimSharedModules = globalShim.configure({
20+
'./ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner',
21+
'./ReactComponentTreeHook': SECRET_INTERNALS_NAME + '.ReactComponentTreeHook',
22+
// All these methods are shared are exposed.
23+
'./ReactElement': 'React',
24+
'./ReactPropTypes': 'React.PropTypes',
25+
'./ReactChildren': 'React.Children',
26+
});
27+
28+
var shimDOMModules = aliasify.configure({
29+
'aliases': {
30+
'./ReactAddonsDOMDependencies': './build/modules/ReactAddonsDOMDependenciesUMDShim.js',
31+
},
32+
});
33+
1534
var SIMPLE_TEMPLATE =
1635
grunt.file.read('./grunt/data/header-template-short.txt');
1736

@@ -87,6 +106,7 @@ var addons = {
87106
debug: false,
88107
standalone: 'React',
89108
packageName: 'React (with addons)',
109+
transforms: [shimDOMModules],
90110
globalTransforms: [envifyDev],
91111
plugins: [collapser],
92112
after: [derequire, simpleBannerify],
@@ -100,7 +120,72 @@ var addonsMin = {
100120
debug: false,
101121
standalone: 'React',
102122
packageName: 'React (with addons)',
103-
transforms: [envifyProd, uglifyify],
123+
transforms: [shimDOMModules, envifyProd, uglifyify],
124+
globalTransforms: [envifyProd],
125+
plugins: [collapser],
126+
// No need to derequire because the minifier will mangle
127+
// the "require" calls.
128+
129+
after: [minify, bannerify],
130+
};
131+
132+
// The DOM Builds
133+
var dom = {
134+
entries: [
135+
'./build/modules/ReactDOMUMDEntry.js',
136+
],
137+
outfile: './build/react-dom.js',
138+
debug: false,
139+
standalone: 'ReactDOM',
140+
// Apply as global transform so that we also envify fbjs and any other deps
141+
transforms: [shimSharedModules],
142+
globalTransforms: [envifyDev],
143+
plugins: [collapser],
144+
after: [derequire, simpleBannerify],
145+
};
146+
147+
var domMin = {
148+
entries: [
149+
'./build/modules/ReactDOMUMDEntry.js',
150+
],
151+
outfile: './build/react-dom.min.js',
152+
debug: false,
153+
standalone: 'ReactDOM',
154+
// Envify twice. The first ensures that when we uglifyify, we have the right
155+
// conditions to exclude requires. The global transform runs on deps.
156+
transforms: [shimSharedModules, envifyProd, uglifyify],
157+
globalTransforms: [envifyProd],
158+
plugins: [collapser],
159+
// No need to derequire because the minifier will mangle
160+
// the "require" calls.
161+
162+
after: [minify, bannerify],
163+
};
164+
165+
var domServer = {
166+
entries: [
167+
'./build/modules/ReactDOMServerUMDEntry.js',
168+
],
169+
outfile: './build/react-dom-server.js',
170+
debug: false,
171+
standalone: 'ReactDOMServer',
172+
// Apply as global transform so that we also envify fbjs and any other deps
173+
transforms: [shimSharedModules],
174+
globalTransforms: [envifyDev],
175+
plugins: [collapser],
176+
after: [derequire, simpleBannerify],
177+
};
178+
179+
var domServerMin = {
180+
entries: [
181+
'./build/modules/ReactDOMServerUMDEntry.js',
182+
],
183+
outfile: './build/react-dom-server.min.js',
184+
debug: false,
185+
standalone: 'ReactDOMServer',
186+
// Envify twice. The first ensures that when we uglifyify, we have the right
187+
// conditions to exclude requires. The global transform runs on deps.
188+
transforms: [shimSharedModules, envifyProd, uglifyify],
104189
globalTransforms: [envifyProd],
105190
plugins: [collapser],
106191
// No need to derequire because the minifier will mangle
@@ -114,4 +199,8 @@ module.exports = {
114199
min: min,
115200
addons: addons,
116201
addonsMin: addonsMin,
202+
dom: dom,
203+
domMin: domMin,
204+
domServer: domServer,
205+
domServerMin: domServerMin,
117206
};

grunt/tasks/react-dom.js

Lines changed: 0 additions & 30 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"private": true,
44
"version": "15.3.2",
55
"devDependencies": {
6+
"aliasify": "^2.0.0",
67
"art": "^0.10.1",
78
"async": "^1.5.0",
89
"babel-cli": "^6.6.5",
@@ -33,6 +34,7 @@
3334
"babel-traverse": "^6.9.0",
3435
"babylon": "6.8.0",
3536
"browserify": "^13.0.0",
37+
"browserify-global-shim": "^1.0.3",
3638
"bundle-collapser": "^1.1.1",
3739
"coffee-script": "^1.8.0",
3840
"core-js": "^2.2.1",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactAddonsDOMDependencies
10+
*/
11+
12+
'use strict';
13+
14+
var ReactDOM = require('ReactDOM');
15+
var ReactInstanceMap = require('ReactInstanceMap');
16+
17+
exports.getReactDOM = function() {
18+
return ReactDOM;
19+
};
20+
21+
exports.getReactInstanceMap = function() {
22+
return ReactInstanceMap;
23+
};
24+
25+
if (__DEV__) {
26+
var ReactPerf = require('ReactPerf');
27+
var ReactTestUtils = require('ReactTestUtils');
28+
29+
exports.getReactPerf = function() {
30+
return ReactPerf;
31+
};
32+
33+
exports.getReactTestUtils = function() {
34+
return ReactTestUtils;
35+
};
36+
}

src/addons/ReactWithAddons.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
var LinkedStateMixin = require('LinkedStateMixin');
1515
var React = require('React');
16+
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
1617
var ReactComponentWithPureRenderMixin =
1718
require('ReactComponentWithPureRenderMixin');
1819
var ReactCSSTransitionGroup = require('ReactCSSTransitionGroup');
@@ -34,8 +35,20 @@ React.addons = {
3435
};
3536

3637
if (__DEV__) {
37-
React.addons.Perf = require('ReactPerf');
38-
React.addons.TestUtils = require('ReactTestUtils');
38+
// For the UMD build we get these lazily from the global since they're tied
39+
// to the DOM renderer and it hasn't loaded yet.
40+
Object.defineProperty(React.addons, 'Perf', {
41+
enumerable: true,
42+
get: function() {
43+
return ReactAddonsDOMDependencies.getReactPerf();
44+
},
45+
});
46+
Object.defineProperty(React.addons, 'TestUtils', {
47+
enumerable: true,
48+
get: function() {
49+
return ReactAddonsDOMDependencies.getReactTestUtils();
50+
},
51+
});
3952
}
4053

4154
module.exports = React;

src/addons/transitions/ReactCSSTransitionGroupChild.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'use strict';
1313

1414
var React = require('React');
15-
var ReactDOM = require('ReactDOM');
15+
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
1616

1717
var CSSCore = require('CSSCore');
1818
var ReactTransitionEvents = require('ReactTransitionEvents');
@@ -54,7 +54,7 @@ var ReactCSSTransitionGroupChild = React.createClass({
5454
},
5555

5656
transition: function(animationType, finishCallback, userSpecifiedDelay) {
57-
var node = ReactDOM.findDOMNode(this);
57+
var node = ReactAddonsDOMDependencies.getReactDOM().findDOMNode(this);
5858

5959
if (!node) {
6060
if (finishCallback) {

src/addons/transitions/ReactTransitionGroup.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'use strict';
1313

1414
var React = require('React');
15-
var ReactInstanceMap = require('ReactInstanceMap');
15+
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
1616
var ReactTransitionChildMapping = require('ReactTransitionChildMapping');
1717

1818
var emptyFunction = require('emptyFunction');
@@ -64,7 +64,7 @@ var ReactTransitionGroup = React.createClass({
6464
if (__DEV__) {
6565
nextChildMapping = ReactTransitionChildMapping.getChildMapping(
6666
nextProps.children,
67-
ReactInstanceMap.get(this)._debugID
67+
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
6868
);
6969
} else {
7070
nextChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -137,7 +137,7 @@ var ReactTransitionGroup = React.createClass({
137137
if (__DEV__) {
138138
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
139139
this.props.children,
140-
ReactInstanceMap.get(this)._debugID
140+
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
141141
);
142142
} else {
143143
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -177,7 +177,7 @@ var ReactTransitionGroup = React.createClass({
177177
if (__DEV__) {
178178
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
179179
this.props.children,
180-
ReactInstanceMap.get(this)._debugID
180+
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
181181
);
182182
} else {
183183
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -218,7 +218,7 @@ var ReactTransitionGroup = React.createClass({
218218
if (__DEV__) {
219219
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
220220
this.props.children,
221-
ReactInstanceMap.get(this)._debugID
221+
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
222222
);
223223
} else {
224224
currentChildMapping = ReactTransitionChildMapping.getChildMapping(

src/renderers/dom/client/ReactMount.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function mountComponentIntoNode(
100100
) {
101101
var markerName;
102102
if (ReactFeatureFlags.logTopLevelRenders) {
103-
var wrappedElement = wrapperInstance._currentElement.props;
103+
var wrappedElement = wrapperInstance._currentElement.props.child;
104104
var type = wrappedElement.type;
105105
markerName = 'React mount: ' + (
106106
typeof type === 'string' ? type :
@@ -275,8 +275,7 @@ if (__DEV__) {
275275
TopLevelWrapper.displayName = 'TopLevelWrapper';
276276
}
277277
TopLevelWrapper.prototype.render = function() {
278-
// this.props is actually a ReactElement
279-
return this.props;
278+
return this.props.child;
280279
};
281280

282281
/**
@@ -450,14 +449,9 @@ var ReactMount = {
450449
'for your app.'
451450
);
452451

453-
var nextWrappedElement = ReactElement(
452+
var nextWrappedElement = ReactElement.createElement(
454453
TopLevelWrapper,
455-
null,
456-
null,
457-
null,
458-
null,
459-
null,
460-
nextElement
454+
{ child: nextElement }
461455
);
462456

463457
var nextContext;
@@ -472,7 +466,7 @@ var ReactMount = {
472466

473467
if (prevComponent) {
474468
var prevWrappedElement = prevComponent._currentElement;
475-
var prevElement = prevWrappedElement.props;
469+
var prevElement = prevWrappedElement.props.child;
476470
if (shouldUpdateReactComponent(prevElement, nextElement)) {
477471
var publicInst = prevComponent._renderedComponent.getPublicInstance();
478472
var updatedCallback = callback && function() {

0 commit comments

Comments
 (0)