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.
This commit is contained in:
Sebastian Markbåge
2016-08-09 12:39:03 -07:00
committed by GitHub
parent 7d57c1f0c1
commit 8ef00dbb7d
20 changed files with 287 additions and 140 deletions
+24 -2
View File
@@ -117,12 +117,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);
@@ -141,7 +160,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',
+90 -1
View File
@@ -7,11 +7,30 @@ var grunt = require('grunt');
var UglifyJS = require('uglify-js');
var uglifyify = require('uglifyify');
var derequire = require('derequire');
var aliasify = require('aliasify');
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 shimSharedModules = globalShim.configure({
'./ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner',
'./ReactComponentTreeHook': SECRET_INTERNALS_NAME + '.ReactComponentTreeHook',
// All these methods are shared are exposed.
'./ReactElement': 'React',
'./ReactPropTypes': 'React.PropTypes',
'./ReactChildren': 'React.Children',
});
var shimDOMModules = aliasify.configure({
'aliases': {
'./ReactAddonsDOMDependencies': './build/modules/ReactAddonsDOMDependenciesUMDShim.js',
},
});
var SIMPLE_TEMPLATE =
grunt.file.read('./grunt/data/header-template-short.txt');
@@ -87,6 +106,7 @@ var addons = {
debug: false,
standalone: 'React',
packageName: 'React (with addons)',
transforms: [shimDOMModules],
globalTransforms: [envifyDev],
plugins: [collapser],
after: [derequire, simpleBannerify],
@@ -100,7 +120,72 @@ var addonsMin = {
debug: false,
standalone: 'React',
packageName: 'React (with addons)',
transforms: [envifyProd, uglifyify],
transforms: [shimDOMModules, 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
@@ -114,4 +199,8 @@ module.exports = {
min: min,
addons: addons,
addonsMin: addonsMin,
dom: dom,
domMin: domMin,
domServer: domServer,
domServerMin: domServerMin,
};
-30
View File
@@ -1,30 +0,0 @@
'use strict';
var grunt = require('grunt');
var UglifyJS = require('uglify-js');
var LICENSE_TEMPLATE =
grunt.file.read('./grunt/data/header-template-extended.txt');
function build(name, filename) {
var srcFile = `vendor/${filename}.js`;
var destFile = `build/${filename}.js`;
var destFileMin = `build/${filename}.min.js`;
var templateData = {
package: name,
version: grunt.config.data.pkg.version,
};
var header = grunt.template.process(
LICENSE_TEMPLATE,
{data: templateData}
);
var src = grunt.file.read(srcFile);
var srcMin = UglifyJS.minify(src, {fromString: true}).code;
grunt.file.write(destFile, header + src);
grunt.file.write(destFileMin, header + srcMin);
}
module.exports = function() {
build('ReactDOM', 'react-dom');
build('ReactDOMServer', 'react-dom-server');
};
+2
View File
@@ -3,6 +3,7 @@
"private": true,
"version": "16.0.0-alpha",
"devDependencies": {
"aliasify": "^2.0.0",
"art": "^0.10.1",
"async": "^1.5.0",
"babel-cli": "^6.6.5",
@@ -33,6 +34,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",
+36
View File
@@ -0,0 +1,36 @@
/**
* 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 ReactAddonsDOMDependencies
*/
'use strict';
var ReactDOM = require('ReactDOM');
var ReactInstanceMap = require('ReactInstanceMap');
exports.getReactDOM = function() {
return ReactDOM;
};
exports.getReactInstanceMap = function() {
return ReactInstanceMap;
};
if (__DEV__) {
var ReactPerf = require('ReactPerf');
var ReactTestUtils = require('ReactTestUtils');
exports.getReactPerf = function() {
return ReactPerf;
};
exports.getReactTestUtils = function() {
return ReactTestUtils;
};
}
+15 -2
View File
@@ -13,6 +13,7 @@
var LinkedStateMixin = require('LinkedStateMixin');
var React = require('React');
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
var ReactComponentWithPureRenderMixin =
require('ReactComponentWithPureRenderMixin');
var ReactCSSTransitionGroup = require('ReactCSSTransitionGroup');
@@ -34,8 +35,20 @@ React.addons = {
};
if (__DEV__) {
React.addons.Perf = require('ReactPerf');
React.addons.TestUtils = require('ReactTestUtils');
// For the UMD build we get these lazily from the global since they're tied
// to the DOM renderer and it hasn't loaded yet.
Object.defineProperty(React.addons, 'Perf', {
enumerable: true,
get: function() {
return ReactAddonsDOMDependencies.getReactPerf();
},
});
Object.defineProperty(React.addons, 'TestUtils', {
enumerable: true,
get: function() {
return ReactAddonsDOMDependencies.getReactTestUtils();
},
});
}
module.exports = React;
@@ -12,7 +12,7 @@
'use strict';
var React = require('React');
var ReactDOM = require('ReactDOM');
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
var CSSCore = require('CSSCore');
var ReactTransitionEvents = require('ReactTransitionEvents');
@@ -54,7 +54,7 @@ var ReactCSSTransitionGroupChild = React.createClass({
},
transition: function(animationType, finishCallback, userSpecifiedDelay) {
var node = ReactDOM.findDOMNode(this);
var node = ReactAddonsDOMDependencies.getReactDOM().findDOMNode(this);
if (!node) {
if (finishCallback) {
@@ -12,7 +12,7 @@
'use strict';
var React = require('React');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies');
var ReactTransitionChildMapping = require('ReactTransitionChildMapping');
var emptyFunction = require('emptyFunction');
@@ -64,7 +64,7 @@ var ReactTransitionGroup = React.createClass({
if (__DEV__) {
nextChildMapping = ReactTransitionChildMapping.getChildMapping(
nextProps.children,
ReactInstanceMap.get(this)._debugID
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
);
} else {
nextChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -137,7 +137,7 @@ var ReactTransitionGroup = React.createClass({
if (__DEV__) {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
this.props.children,
ReactInstanceMap.get(this)._debugID
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
);
} else {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -177,7 +177,7 @@ var ReactTransitionGroup = React.createClass({
if (__DEV__) {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
this.props.children,
ReactInstanceMap.get(this)._debugID
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
);
} else {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
@@ -218,7 +218,7 @@ var ReactTransitionGroup = React.createClass({
if (__DEV__) {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
this.props.children,
ReactInstanceMap.get(this)._debugID
ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID
);
} else {
currentChildMapping = ReactTransitionChildMapping.getChildMapping(
+5 -11
View File
@@ -100,7 +100,7 @@ function mountComponentIntoNode(
) {
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var wrappedElement = wrapperInstance._currentElement.props;
var wrappedElement = wrapperInstance._currentElement.props.child;
var type = wrappedElement.type;
markerName = 'React mount: ' + (
typeof type === 'string' ? type :
@@ -235,8 +235,7 @@ if (__DEV__) {
TopLevelWrapper.displayName = 'TopLevelWrapper';
}
TopLevelWrapper.prototype.render = function() {
// this.props is actually a ReactElement
return this.props;
return this.props.child;
};
/**
@@ -421,14 +420,9 @@ var ReactMount = {
'for your app.'
);
var nextWrappedElement = ReactElement(
var nextWrappedElement = ReactElement.createElement(
TopLevelWrapper,
null,
null,
null,
null,
null,
nextElement
{ child: nextElement }
);
var nextContext;
@@ -443,7 +437,7 @@ var ReactMount = {
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props;
var prevElement = prevWrappedElement.props.child;
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback = callback && function() {
@@ -15,7 +15,6 @@ var DOMProperty = require('DOMProperty');
var EventPluginHub = require('EventPluginHub');
var EventPluginUtils = require('EventPluginUtils');
var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactClass = require('ReactClass');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactHostComponent = require('ReactHostComponent');
@@ -23,7 +22,6 @@ var ReactUpdates = require('ReactUpdates');
var ReactInjection = {
Component: ReactComponentEnvironment.injection,
Class: ReactClass.injection,
DOMProperty: DOMProperty.injection,
EmptyComponent: ReactEmptyComponent.injection,
EventPluginHub: EventPluginHub.injection,
@@ -18,10 +18,13 @@ var ReactErrorUtils = require('ReactErrorUtils');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactNodeTypes = require('ReactNodeTypes');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactReconciler = require('ReactReconciler');
var checkReactTypeSpec = require('checkReactTypeSpec');
if (__DEV__) {
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var checkReactTypeSpec = require('checkReactTypeSpec');
}
var emptyObject = require('emptyObject');
var invariant = require('invariant');
var shallowEqual = require('shallowEqual');
@@ -729,14 +732,16 @@ var ReactCompositeComponentMixin = {
* @private
*/
_checkContextTypes: function(typeSpecs, values, location) {
checkReactTypeSpec(
typeSpecs,
values,
location,
this.getName(),
null,
this._debugID
);
if (__DEV__) {
checkReactTypeSpec(
typeSpecs,
values,
location,
this.getName(),
null,
this._debugID
);
}
},
receiveComponent: function(nextElement, transaction, nextContext) {
@@ -161,7 +161,7 @@ function runBatchedUpdates(transaction) {
var namedComponent = component;
// Duck type TopLevelWrapper. This is probably always true.
if (
component._currentElement.props ===
component._currentElement.props.child ===
component._renderedComponent._currentElement
) {
namedComponent = component._renderedComponent;
+16
View File
@@ -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;
+34
View File
@@ -0,0 +1,34 @@
/**
* 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__) {
Object.assign(
ReactDOMUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
{
// ReactPerf and ReactTestUtils currently only work with the DOM renderer
// so we expose them from here, but only in DEV mode.
ReactPerf: require('ReactPerf'),
ReactTestUtils: require('ReactTestUtils'),
}
);
}
module.exports = ReactDOMUMDEntry;
+5 -6
View File
@@ -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'),
ReactComponentTreeHook: require('ReactComponentTreeHook'),
},
}, React);
module.exports = ReactUMDEntry;
+5 -6
View File
@@ -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'),
ReactComponentTreeHook: require('ReactComponentTreeHook'),
},
}, ReactWithAddons);
module.exports = ReactWithAddonsUMDEntry;
@@ -0,0 +1,32 @@
/**
* 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 ReactAddonsDOMDependenciesUMDShim
*/
/* globals ReactDOM */
'use strict';
exports.getReactDOM = function() {
return ReactDOM;
};
exports.getReactInstanceMap = function() {
return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactInstanceMap;
};
if (__DEV__) {
exports.getReactPerf = function() {
return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf;
};
exports.getReactTestUtils = function() {
return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactTestUtils;
};
}
-31
View File
@@ -1,31 +0,0 @@
// Based off https://github.com/ForbesLindesay/umd/blob/master/template.js
;(function(f) {
// CommonJS
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = f(require('react'));
// RequireJS
} else if (typeof define === "function" && define.amd) {
define(['react'], f);
// <script>
} else {
var g;
if (typeof window !== "undefined") {
g = window;
} else if (typeof global !== "undefined") {
g = global;
} else if (typeof self !== "undefined") {
g = self;
} else {
// works providing we're not in "use strict";
// needed for Java 8 Nashorn
// see https://github.com/facebook/react/issues/3037
g = this;
}
g.ReactDOMServer = f(g.React);
}
})(function(React) {
return React.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
});
-31
View File
@@ -1,31 +0,0 @@
// Based off https://github.com/ForbesLindesay/umd/blob/master/template.js
;(function(f) {
// CommonJS
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = f(require('react'));
// RequireJS
} else if (typeof define === "function" && define.amd) {
define(['react'], f);
// <script>
} else {
var g;
if (typeof window !== "undefined") {
g = window;
} else if (typeof global !== "undefined") {
g = global;
} else if (typeof self !== "undefined") {
g = self;
} else {
// works providing we're not in "use strict";
// needed for Java 8 Nashorn
// see https://github.com/facebook/react/issues/3037
g = this;
}
g.ReactDOM = f(g.React);
}
})(function(React) {
return React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
});