Skip to content

Commit 7032362

Browse files
committed
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.
1 parent cf259a4 commit 7032362

14 files changed

+222
-111
lines changed

Gruntfile.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,31 @@ module.exports = function(grunt) {
104104
'build-modules',
105105
'browserify:addonsMin',
106106
]);
107+
grunt.registerTask('build:dom', [
108+
'build-modules',
109+
'version-check',
110+
'browserify:dom',
111+
]);
112+
grunt.registerTask('build:dom-min', [
113+
'build-modules',
114+
'version-check',
115+
'browserify:domMin',
116+
]);
117+
grunt.registerTask('build:dom-server', [
118+
'build-modules',
119+
'version-check',
120+
'browserify:domServer',
121+
]);
122+
grunt.registerTask('build:dom-server-min', [
123+
'build-modules',
124+
'version-check',
125+
'browserify:domServerMin',
126+
]);
107127
grunt.registerTask('build:npm-react', [
108128
'version-check',
109129
'build-modules',
110130
'npm-react:release',
111131
]);
112-
grunt.registerTask('build:react-dom', require('./grunt/tasks/react-dom'));
113132

114133
var jestTasks = require('./grunt/tasks/jest');
115134
grunt.registerTask('jest:normal', jestTasks.normal);
@@ -128,7 +147,10 @@ module.exports = function(grunt) {
128147
'browserify:addons',
129148
'browserify:min',
130149
'browserify:addonsMin',
131-
'build:react-dom',
150+
'browserify:dom',
151+
'browserify:domMin',
152+
'browserify:domServer',
153+
'browserify:domServerMin',
132154
'npm-react:release',
133155
'npm-react:pack',
134156
'npm-react-dom:release',

grunt/config/browserify.js

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

1213
var envifyDev = envify({NODE_ENV: process.env.NODE_ENV || 'development'});
1314
var envifyProd = envify({NODE_ENV: process.env.NODE_ENV || 'production'});
1415

16+
var SECRET_INTERNALS_NAME = 'React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED';
17+
var SECRET_DOM_INTERNALS_NAME = 'ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED';
18+
19+
var shimSharedModules = globalShim.configure({
20+
'./ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner',
21+
'./ReactComponentTreeDevtool': SECRET_INTERNALS_NAME + '.ReactComponentTreeDevtool',
22+
});
23+
24+
// Shim references to ReactDOM internals from addons.
25+
var shimDOM = globalShim.configure({
26+
'./ReactDOM': 'ReactDOM',
27+
'./ReactInstanceMap': SECRET_DOM_INTERNALS_NAME + '.ReactInstanceMap',
28+
29+
// TestUtils pulls in a bunch of internals.
30+
'./EventConstants': SECRET_DOM_INTERNALS_NAME + '.EventConstants',
31+
'./EventPluginHub': SECRET_DOM_INTERNALS_NAME + '.EventPluginHub',
32+
'./EventPluginRegistry': SECRET_DOM_INTERNALS_NAME + '.EventPluginRegistry',
33+
'./EventPropagators': SECRET_DOM_INTERNALS_NAME + '.EventPropagators',
34+
'./ReactDefaultInjection': SECRET_DOM_INTERNALS_NAME + '.ReactDefaultInjection',
35+
'./ReactDOMComponentTree': SECRET_DOM_INTERNALS_NAME + '.ReactDOMComponentTree',
36+
'./ReactBrowserEventEmitter': SECRET_DOM_INTERNALS_NAME + '.ReactBrowserEventEmitter',
37+
'./ReactCompositeComponent': SECRET_DOM_INTERNALS_NAME + '.ReactCompositeComponent',
38+
'./ReactInstrumentation': SECRET_DOM_INTERNALS_NAME + '.ReactInstrumentation',
39+
'./ReactReconciler': SECRET_DOM_INTERNALS_NAME + '.ReactReconciler',
40+
'./ReactUpdates': SECRET_DOM_INTERNALS_NAME + '.ReactUpdates',
41+
'./SyntheticEvent': SECRET_DOM_INTERNALS_NAME + '.SyntheticEvent',
42+
'./findDOMNode': SECRET_DOM_INTERNALS_NAME + '.findDOMNode',
43+
});
44+
1545
var SIMPLE_TEMPLATE =
1646
grunt.file.read('./grunt/data/header-template-short.txt');
1747

@@ -87,6 +117,7 @@ var addons = {
87117
debug: false,
88118
standalone: 'React',
89119
packageName: 'React (with addons)',
120+
transforms: [shimDOM],
90121
globalTransforms: [envifyDev],
91122
plugins: [collapser],
92123
after: [derequire, simpleBannerify],
@@ -100,7 +131,72 @@ var addonsMin = {
100131
debug: false,
101132
standalone: 'React',
102133
packageName: 'React (with addons)',
103-
transforms: [envifyProd, uglifyify],
134+
transforms: [shimDOM, envifyProd, uglifyify],
135+
globalTransforms: [envifyProd],
136+
plugins: [collapser],
137+
// No need to derequire because the minifier will mangle
138+
// the "require" calls.
139+
140+
after: [minify, bannerify],
141+
};
142+
143+
// The DOM Builds
144+
var dom = {
145+
entries: [
146+
'./build/modules/ReactDOMUMDEntry.js',
147+
],
148+
outfile: './build/react-dom.js',
149+
debug: false,
150+
standalone: 'ReactDOM',
151+
// Apply as global transform so that we also envify fbjs and any other deps
152+
transforms: [shimSharedModules],
153+
globalTransforms: [envifyDev],
154+
plugins: [collapser],
155+
after: [derequire, simpleBannerify],
156+
};
157+
158+
var domMin = {
159+
entries: [
160+
'./build/modules/ReactDOMUMDEntry.js',
161+
],
162+
outfile: './build/react-dom.min.js',
163+
debug: false,
164+
standalone: 'ReactDOM',
165+
// Envify twice. The first ensures that when we uglifyify, we have the right
166+
// conditions to exclude requires. The global transform runs on deps.
167+
transforms: [shimSharedModules, envifyProd, uglifyify],
168+
globalTransforms: [envifyProd],
169+
plugins: [collapser],
170+
// No need to derequire because the minifier will mangle
171+
// the "require" calls.
172+
173+
after: [minify, bannerify],
174+
};
175+
176+
var domServer = {
177+
entries: [
178+
'./build/modules/ReactDOMServerUMDEntry.js',
179+
],
180+
outfile: './build/react-dom-server.js',
181+
debug: false,
182+
standalone: 'ReactDOMServer',
183+
// Apply as global transform so that we also envify fbjs and any other deps
184+
transforms: [shimSharedModules],
185+
globalTransforms: [envifyDev],
186+
plugins: [collapser],
187+
after: [derequire, simpleBannerify],
188+
};
189+
190+
var domServerMin = {
191+
entries: [
192+
'./build/modules/ReactDOMServerUMDEntry.js',
193+
],
194+
outfile: './build/react-dom-server.min.js',
195+
debug: false,
196+
standalone: 'ReactDOMServer',
197+
// Envify twice. The first ensures that when we uglifyify, we have the right
198+
// conditions to exclude requires. The global transform runs on deps.
199+
transforms: [shimSharedModules, envifyProd, uglifyify],
104200
globalTransforms: [envifyProd],
105201
plugins: [collapser],
106202
// No need to derequire because the minifier will mangle
@@ -114,4 +210,8 @@ module.exports = {
114210
min: min,
115211
addons: addons,
116212
addonsMin: addonsMin,
213+
dom: dom,
214+
domMin: domMin,
215+
domServer: domServer,
216+
domServerMin: domServerMin,
117217
};

grunt/tasks/react-dom.js

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"babel-traverse": "^6.9.0",
3333
"babylon": "6.8.0",
3434
"browserify": "^13.0.0",
35+
"browserify-global-shim": "^1.0.3",
3536
"bundle-collapser": "^1.1.1",
3637
"coffee-script": "^1.8.0",
3738
"core-js": "^2.2.1",

src/addons/ReactWithAddons.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,20 @@ React.addons = {
3434
};
3535

3636
if (__DEV__) {
37-
React.addons.Perf = require('ReactPerf');
38-
React.addons.TestUtils = require('ReactTestUtils');
37+
// We need to lazily require these modules for the browser build since they
38+
// will depend on DOM internals which hasn't loaded yet.
39+
Object.defineProperty(React.addons, 'Perf', {
40+
enumerable: true,
41+
get: function() {
42+
return require('ReactPerf');
43+
},
44+
});
45+
Object.defineProperty(React.addons, 'TestUtils', {
46+
enumerable: true,
47+
get: function() {
48+
return require('ReactTestUtils');
49+
},
50+
});
3951
}
4052

4153
module.exports = React;

src/addons/transitions/ReactCSSTransitionGroupChild.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
'use strict';
1313

1414
var React = require('React');
15-
var ReactDOM = require('ReactDOM');
15+
// For the browser build we need to lazily load this since the DOM package
16+
// instantiates after the addons package.
17+
var ReactDOM = null;
1618

1719
var CSSCore = require('CSSCore');
1820
var ReactTransitionEvents = require('ReactTransitionEvents');
@@ -124,6 +126,9 @@ var ReactCSSTransitionGroupChild = React.createClass({
124126
},
125127

126128
componentWillMount: function() {
129+
if (!ReactDOM) {
130+
ReactDOM = require('ReactDOM');
131+
}
127132
this.classNameAndNodeQueue = [];
128133
this.transitionTimeouts = [];
129134
},

src/addons/transitions/ReactTransitionGroup.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
'use strict';
1313

1414
var React = require('React');
15-
var ReactInstanceMap = require('ReactInstanceMap');
15+
// We need to lazily require this for the browser build because the DOM
16+
// renderer gets initialized after the main React bundle.
17+
var ReactInstanceMap = null;
1618
var ReactTransitionChildMapping = require('ReactTransitionChildMapping');
1719

1820
var emptyFunction = require('emptyFunction');
@@ -45,6 +47,9 @@ var ReactTransitionGroup = React.createClass({
4547
},
4648

4749
componentWillMount: function() {
50+
if (!ReactInstanceMap) {
51+
ReactInstanceMap = require('ReactInstanceMap');
52+
}
4853
this.currentlyTransitioningKeys = {};
4954
this.keysToEnter = [];
5055
this.keysToLeave = [];

src/umd/ReactDOMServerUMDEntry.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 ReactDOMServerUMDEntry
10+
*/
11+
12+
'use strict';
13+
14+
var ReactDOMServer = require('ReactDOMServer');
15+
16+
module.exports = ReactDOMServer;

src/umd/ReactDOMUMDEntry.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 ReactDOMUMDEntry
10+
*/
11+
12+
'use strict';
13+
14+
var ReactDOM = require('ReactDOM');
15+
16+
var ReactDOMUMDEntry = Object.assign({
17+
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
18+
ReactInstanceMap: require('ReactInstanceMap'),
19+
},
20+
}, ReactDOM);
21+
22+
if (__DEV__) {
23+
// These are used by ReactTestUtils in ReactWithAddons. Ugh.
24+
Object.assign(
25+
ReactDOMUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
26+
{
27+
EventConstants: require('EventConstants'),
28+
EventPluginHub: require('EventPluginHub'),
29+
EventPluginRegistry: require('EventPluginRegistry'),
30+
EventPropagators: require('EventPropagators'),
31+
ReactDefaultInjection: require('ReactDefaultInjection'),
32+
ReactDOMComponentTree: require('ReactDOMComponentTree'),
33+
ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'),
34+
ReactCompositeComponent: require('ReactCompositeComponent'),
35+
ReactInstrumentation: require('ReactInstrumentation'),
36+
ReactReconciler: require('ReactReconciler'),
37+
ReactUpdates: require('ReactUpdates'),
38+
SyntheticEvent: require('SyntheticEvent'),
39+
findDOMNode: require('findDOMNode'),
40+
}
41+
);
42+
}
43+
44+
module.exports = ReactDOMUMDEntry;

0 commit comments

Comments
 (0)