Skip to content

Commit

Permalink
Bundle DOM renderers into their individual UMD bundles
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage committed Jul 1, 2016
1 parent cf259a4 commit 7032362
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 111 deletions.
26 changes: 24 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,31 @@ module.exports = function(grunt) {
'build-modules',
'browserify:addonsMin',
]);
grunt.registerTask('build:dom', [
'build-modules',
'version-check',
'browserify:dom',
]);
grunt.registerTask('build:dom-min', [
'build-modules',
'version-check',
'browserify:domMin',
]);
grunt.registerTask('build:dom-server', [
'build-modules',
'version-check',
'browserify:domServer',
]);
grunt.registerTask('build:dom-server-min', [
'build-modules',
'version-check',
'browserify:domServerMin',
]);
grunt.registerTask('build:npm-react', [
'version-check',
'build-modules',
'npm-react:release',
]);
grunt.registerTask('build:react-dom', require('./grunt/tasks/react-dom'));

var jestTasks = require('./grunt/tasks/jest');
grunt.registerTask('jest:normal', jestTasks.normal);
Expand All @@ -128,7 +147,10 @@ module.exports = function(grunt) {
'browserify:addons',
'browserify:min',
'browserify:addonsMin',
'build:react-dom',
'browserify:dom',
'browserify:domMin',
'browserify:domServer',
'browserify:domServerMin',
'npm-react:release',
'npm-react:pack',
'npm-react-dom:release',
Expand Down
102 changes: 101 additions & 1 deletion grunt/config/browserify.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,41 @@ var grunt = require('grunt');
var UglifyJS = require('uglify-js');
var uglifyify = require('uglifyify');
var derequire = require('derequire');
var globalShim = require('browserify-global-shim');
var collapser = require('bundle-collapser/plugin');

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

var SECRET_INTERNALS_NAME = 'React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED';
var SECRET_DOM_INTERNALS_NAME = 'ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED';

var shimSharedModules = globalShim.configure({
'./ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner',
'./ReactComponentTreeDevtool': SECRET_INTERNALS_NAME + '.ReactComponentTreeDevtool',
});

// Shim references to ReactDOM internals from addons.
var shimDOM = globalShim.configure({
'./ReactDOM': 'ReactDOM',
'./ReactInstanceMap': SECRET_DOM_INTERNALS_NAME + '.ReactInstanceMap',

// TestUtils pulls in a bunch of internals.
'./EventConstants': SECRET_DOM_INTERNALS_NAME + '.EventConstants',
'./EventPluginHub': SECRET_DOM_INTERNALS_NAME + '.EventPluginHub',
'./EventPluginRegistry': SECRET_DOM_INTERNALS_NAME + '.EventPluginRegistry',
'./EventPropagators': SECRET_DOM_INTERNALS_NAME + '.EventPropagators',
'./ReactDefaultInjection': SECRET_DOM_INTERNALS_NAME + '.ReactDefaultInjection',
'./ReactDOMComponentTree': SECRET_DOM_INTERNALS_NAME + '.ReactDOMComponentTree',
'./ReactBrowserEventEmitter': SECRET_DOM_INTERNALS_NAME + '.ReactBrowserEventEmitter',
'./ReactCompositeComponent': SECRET_DOM_INTERNALS_NAME + '.ReactCompositeComponent',
'./ReactInstrumentation': SECRET_DOM_INTERNALS_NAME + '.ReactInstrumentation',
'./ReactReconciler': SECRET_DOM_INTERNALS_NAME + '.ReactReconciler',
'./ReactUpdates': SECRET_DOM_INTERNALS_NAME + '.ReactUpdates',
'./SyntheticEvent': SECRET_DOM_INTERNALS_NAME + '.SyntheticEvent',
'./findDOMNode': SECRET_DOM_INTERNALS_NAME + '.findDOMNode',
});

var SIMPLE_TEMPLATE =
grunt.file.read('./grunt/data/header-template-short.txt');

Expand Down Expand Up @@ -87,6 +117,7 @@ var addons = {
debug: false,
standalone: 'React',
packageName: 'React (with addons)',
transforms: [shimDOM],
globalTransforms: [envifyDev],
plugins: [collapser],
after: [derequire, simpleBannerify],
Expand All @@ -100,7 +131,72 @@ var addonsMin = {
debug: false,
standalone: 'React',
packageName: 'React (with addons)',
transforms: [envifyProd, uglifyify],
transforms: [shimDOM, envifyProd, uglifyify],
globalTransforms: [envifyProd],
plugins: [collapser],
// No need to derequire because the minifier will mangle
// the "require" calls.

after: [minify, bannerify],
};

// The DOM Builds
var dom = {
entries: [
'./build/modules/ReactDOMUMDEntry.js',
],
outfile: './build/react-dom.js',
debug: false,
standalone: 'ReactDOM',
// Apply as global transform so that we also envify fbjs and any other deps
transforms: [shimSharedModules],
globalTransforms: [envifyDev],
plugins: [collapser],
after: [derequire, simpleBannerify],
};

var domMin = {
entries: [
'./build/modules/ReactDOMUMDEntry.js',
],
outfile: './build/react-dom.min.js',
debug: false,
standalone: 'ReactDOM',
// Envify twice. The first ensures that when we uglifyify, we have the right
// conditions to exclude requires. The global transform runs on deps.
transforms: [shimSharedModules, envifyProd, uglifyify],
globalTransforms: [envifyProd],
plugins: [collapser],
// No need to derequire because the minifier will mangle
// the "require" calls.

after: [minify, bannerify],
};

var domServer = {
entries: [
'./build/modules/ReactDOMServerUMDEntry.js',
],
outfile: './build/react-dom-server.js',
debug: false,
standalone: 'ReactDOMServer',
// Apply as global transform so that we also envify fbjs and any other deps
transforms: [shimSharedModules],
globalTransforms: [envifyDev],
plugins: [collapser],
after: [derequire, simpleBannerify],
};

var domServerMin = {
entries: [
'./build/modules/ReactDOMServerUMDEntry.js',
],
outfile: './build/react-dom-server.min.js',
debug: false,
standalone: 'ReactDOMServer',
// Envify twice. The first ensures that when we uglifyify, we have the right
// conditions to exclude requires. The global transform runs on deps.
transforms: [shimSharedModules, envifyProd, uglifyify],
globalTransforms: [envifyProd],
plugins: [collapser],
// No need to derequire because the minifier will mangle
Expand All @@ -114,4 +210,8 @@ module.exports = {
min: min,
addons: addons,
addonsMin: addonsMin,
dom: dom,
domMin: domMin,
domServer: domServer,
domServerMin: domServerMin,
};
30 changes: 0 additions & 30 deletions grunt/tasks/react-dom.js

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"babel-traverse": "^6.9.0",
"babylon": "6.8.0",
"browserify": "^13.0.0",
"browserify-global-shim": "^1.0.3",
"bundle-collapser": "^1.1.1",
"coffee-script": "^1.8.0",
"core-js": "^2.2.1",
Expand Down
16 changes: 14 additions & 2 deletions src/addons/ReactWithAddons.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,20 @@ React.addons = {
};

if (__DEV__) {
React.addons.Perf = require('ReactPerf');
React.addons.TestUtils = require('ReactTestUtils');
// We need to lazily require these modules for the browser build since they
// will depend on DOM internals which hasn't loaded yet.
Object.defineProperty(React.addons, 'Perf', {
enumerable: true,
get: function() {
return require('ReactPerf');
},
});
Object.defineProperty(React.addons, 'TestUtils', {
enumerable: true,
get: function() {
return require('ReactTestUtils');
},
});
}

module.exports = React;
7 changes: 6 additions & 1 deletion src/addons/transitions/ReactCSSTransitionGroupChild.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
'use strict';

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

var CSSCore = require('CSSCore');
var ReactTransitionEvents = require('ReactTransitionEvents');
Expand Down Expand Up @@ -124,6 +126,9 @@ var ReactCSSTransitionGroupChild = React.createClass({
},

componentWillMount: function() {
if (!ReactDOM) {
ReactDOM = require('ReactDOM');
}
this.classNameAndNodeQueue = [];
this.transitionTimeouts = [];
},
Expand Down
7 changes: 6 additions & 1 deletion src/addons/transitions/ReactTransitionGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
'use strict';

var React = require('React');
var ReactInstanceMap = require('ReactInstanceMap');
// We need to lazily require this for the browser build because the DOM
// renderer gets initialized after the main React bundle.
var ReactInstanceMap = null;
var ReactTransitionChildMapping = require('ReactTransitionChildMapping');

var emptyFunction = require('emptyFunction');
Expand Down Expand Up @@ -45,6 +47,9 @@ var ReactTransitionGroup = React.createClass({
},

componentWillMount: function() {
if (!ReactInstanceMap) {
ReactInstanceMap = require('ReactInstanceMap');
}
this.currentlyTransitioningKeys = {};
this.keysToEnter = [];
this.keysToLeave = [];
Expand Down
16 changes: 16 additions & 0 deletions src/umd/ReactDOMServerUMDEntry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMServerUMDEntry
*/

'use strict';

var ReactDOMServer = require('ReactDOMServer');

module.exports = ReactDOMServer;
44 changes: 44 additions & 0 deletions src/umd/ReactDOMUMDEntry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMUMDEntry
*/

'use strict';

var ReactDOM = require('ReactDOM');

var ReactDOMUMDEntry = Object.assign({
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
ReactInstanceMap: require('ReactInstanceMap'),
},
}, ReactDOM);

if (__DEV__) {
// These are used by ReactTestUtils in ReactWithAddons. Ugh.
Object.assign(
ReactDOMUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
{
EventConstants: require('EventConstants'),
EventPluginHub: require('EventPluginHub'),
EventPluginRegistry: require('EventPluginRegistry'),
EventPropagators: require('EventPropagators'),
ReactDefaultInjection: require('ReactDefaultInjection'),
ReactDOMComponentTree: require('ReactDOMComponentTree'),
ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'),
ReactCompositeComponent: require('ReactCompositeComponent'),
ReactInstrumentation: require('ReactInstrumentation'),
ReactReconciler: require('ReactReconciler'),
ReactUpdates: require('ReactUpdates'),
SyntheticEvent: require('SyntheticEvent'),
findDOMNode: require('findDOMNode'),
}
);
}

module.exports = ReactDOMUMDEntry;
11 changes: 5 additions & 6 deletions src/umd/ReactUMDEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

'use strict';

var ReactDOM = require('ReactDOM');
var ReactDOMServer = require('ReactDOMServer');
var React = require('React');


// `version` will be added here by ReactIsomorphic.
// `version` will be added here by the React module.
var ReactUMDEntry = Object.assign({
__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOM,
__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOMServer,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
ReactCurrentOwner: require('ReactCurrentOwner'),
ReactComponentTreeDevtool: require('ReactComponentTreeDevtool'),
},
}, React);

module.exports = ReactUMDEntry;
11 changes: 5 additions & 6 deletions src/umd/ReactWithAddonsUMDEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

'use strict';

var ReactDOM = require('ReactDOM');
var ReactDOMServer = require('ReactDOMServer');
var ReactWithAddons = require('ReactWithAddons');


// `version` will be added here by ReactIsomorphic.
// `version` will be added here by the React module.
var ReactWithAddonsUMDEntry = Object.assign({
__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOM,
__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOMServer,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
ReactCurrentOwner: require('ReactCurrentOwner'),
ReactComponentTreeDevtool: require('ReactComponentTreeDevtool'),
},
}, ReactWithAddons);

module.exports = ReactWithAddonsUMDEntry;

0 comments on commit 7032362

Please sign in to comment.