Fix dead code elimination for feature flags (#11453)

* Fix dead code elimination for feature flags

Turning flags into named exports fixes dead code elimination.

This required some restructuring of how we verify that flag types match up. I used the Check<> trick combined with import typeof, as suggested by @calebmer.

For www, we can no longer re-export `require('ReactFeatureFlags')` directly, and instead destructure it. This means flags have to be known at init time. This is already the case so it's not a problem. In fact it may be better since it removes extra property access in tight paths.

For things that we *want* to be dynamic on www (currently, only performance flag) we can export a function to toggle it, and then put it on the secret exports. In fact this is better than just letting everyone mutate the flag at arbitrary times since we can provide, e.g., a ref counting interface to it.

* Record sizes
This commit is contained in:
Dan Abramov
2017-11-06 14:14:48 +00:00
committed by GitHub
parent b6a7beefe4
commit 46f7b0d945
18 changed files with 189 additions and 139 deletions

View File

@@ -2,7 +2,15 @@
<PROJECT_ROOT>/fixtures/.*
<PROJECT_ROOT>/build/.*
<PROJECT_ROOT>/scripts/.*
<PROJECT_ROOT>/scripts/bench/.*
# These shims are copied into external projects:
<PROJECT_ROOT>/scripts/rollup/shims/facebook-www/.*
<PROJECT_ROOT>/scripts/rollup/shims/react-native/.*
# Note: intentionally *don't* ignore /scripts/rollup/shims/rollup/
# because it is part of the build and isn't external.
<PROJECT_ROOT>/.*/node_modules/y18n/.*
<PROJECT_ROOT>/node_modules/chrome-devtools-frontend/.*
<PROJECT_ROOT>/node_modules/devtools-timeline-model/.*

View File

@@ -7,17 +7,21 @@
* @flow
*/
import type {FeatureFlags} from 'shared/ReactFeatureFlags';
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
import typeof * as CSFeatureFlagsType from './ReactNativeCSFeatureFlags';
var ReactNativeCSFeatureFlags: FeatureFlags = {
enableAsyncSubtreeAPI: true,
enableAsyncSchedulingByDefaultInReactDOM: false,
enableReactFragment: false,
enableCreateRoot: false,
// React Native CS uses persistent reconciler.
enableMutatingReconciler: false,
enableNoopReconciler: false,
enablePersistentReconciler: true,
};
export const enableAsyncSubtreeAPI = true;
export const enableAsyncSchedulingByDefaultInReactDOM = false;
export const enableReactFragment = false;
export const enableCreateRoot = false;
export default ReactNativeCSFeatureFlags;
// React Native CS uses persistent reconciler.
export const enableMutatingReconciler = false;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = true;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y=_X> = null;
// eslint-disable-next-line no-unused-expressions
(null: Check<CSFeatureFlagsType, FeatureFlagsType>);

View File

@@ -23,7 +23,10 @@ import * as EventPluginHub from 'events/EventPluginHub';
import * as EventPluginRegistry from 'events/EventPluginRegistry';
import * as EventPropagators from 'events/EventPropagators';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {
enableAsyncSchedulingByDefaultInReactDOM,
enableCreateRoot,
} from 'shared/ReactFeatureFlags';
import ReactVersion from 'shared/ReactVersion';
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -631,7 +634,7 @@ const DOMRenderer = ReactFiberReconciler({
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
useSyncScheduling: !ReactFeatureFlags.enableAsyncSchedulingByDefaultInReactDOM,
useSyncScheduling: !enableAsyncSchedulingByDefaultInReactDOM,
});
ReactGenericBatching.injection.injectFiberBatchedUpdates(
@@ -944,7 +947,7 @@ const ReactDOM: Object = {
},
};
if (ReactFeatureFlags.enableCreateRoot) {
if (enableCreateRoot) {
ReactDOM.createRoot = function createRoot(
container: DOMContainer,
options?: RootOptions,

View File

@@ -21,7 +21,7 @@ import type {UpdateQueue} from 'react-reconciler/src/ReactFiberUpdateQueue';
import ReactFiberInstrumentation
from 'react-reconciler/src/ReactFiberInstrumentation';
import ReactFiberReconciler from 'react-reconciler';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import emptyObject from 'fbjs/lib/emptyObject';
import expect from 'expect';
@@ -213,7 +213,7 @@ var NoopRenderer = ReactFiberReconciler({
},
});
var PersistentNoopRenderer = ReactFeatureFlags.enablePersistentReconciler
var PersistentNoopRenderer = enablePersistentReconciler
? ReactFiberReconciler({
...SharedHostConfig,
persistence: {

View File

@@ -14,7 +14,7 @@ import type {
ExpirationTime,
} from 'react-reconciler/src/ReactFiberExpirationTime';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {enableReactFragment} from 'shared/ReactFeatureFlags';
import {NoEffect, Placement, Deletion} from 'shared/ReactTypeOfSideEffect';
import {
FunctionalComponent,
@@ -1426,7 +1426,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
if (
ReactFeatureFlags.enableReactFragment &&
enableReactFragment &&
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&

View File

@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import {Update} from 'shared/ReactTypeOfSideEffect';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {enableAsyncSubtreeAPI} from 'shared/ReactFeatureFlags';
import {isMounted} from 'shared/ReactFiberTreeReflection';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import emptyObject from 'fbjs/lib/emptyObject';
@@ -450,7 +450,7 @@ export default function(
instance.context = getMaskedContext(workInProgress, unmaskedContext);
if (
ReactFeatureFlags.enableAsyncSubtreeAPI &&
enableAsyncSubtreeAPI &&
workInProgress.type != null &&
workInProgress.type.prototype != null &&
workInProgress.type.prototype.unstable_isAsyncReactComponent === true

View File

@@ -10,7 +10,11 @@
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {
enableMutatingReconciler,
enableNoopReconciler,
enablePersistentReconciler,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
HostRoot,
@@ -218,12 +222,9 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
if (ReactFeatureFlags.enableMutatingReconciler && mutation) {
if (enableMutatingReconciler && mutation) {
unmountHostComponents(current);
} else if (
ReactFeatureFlags.enablePersistentReconciler &&
persistence
) {
} else if (enablePersistentReconciler && persistence) {
emptyPortalContainer(current);
}
return;
@@ -324,10 +325,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// Noop
};
}
if (
ReactFeatureFlags.enablePersistentReconciler ||
ReactFeatureFlags.enableNoopReconciler
) {
if (enablePersistentReconciler || enableNoopReconciler) {
return {
commitResetTextContent(finishedWork: Fiber) {},
commitPlacement(finishedWork: Fiber) {},
@@ -660,7 +658,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
resetTextContent(current.stateNode);
}
if (ReactFeatureFlags.enableMutatingReconciler) {
if (enableMutatingReconciler) {
return {
commitResetTextContent,
commitPlacement,

View File

@@ -15,7 +15,11 @@ import type {HostContext} from './ReactFiberHostContext';
import type {HydrationContext} from './ReactFiberHydrationContext';
import type {FiberRoot} from './ReactFiberRoot';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {
enableMutatingReconciler,
enablePersistentReconciler,
enableNoopReconciler,
} from 'shared/ReactFeatureFlags';
import {
IndeterminateComponent,
FunctionalComponent,
@@ -180,7 +184,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
let updateHostComponent;
let updateHostText;
if (mutation) {
if (ReactFeatureFlags.enableMutatingReconciler) {
if (enableMutatingReconciler) {
// Mutation mode
updateHostContainer = function(workInProgress: Fiber) {
// Noop
@@ -217,7 +221,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
invariant(false, 'Mutating reconciler is disabled.');
}
} else if (persistence) {
if (ReactFeatureFlags.enablePersistentReconciler) {
if (enablePersistentReconciler) {
// Persistent host tree mode
const {
cloneInstance,
@@ -354,7 +358,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
invariant(false, 'Persistent reconciler is disabled.');
}
} else {
if (ReactFeatureFlags.enableNoopReconciler) {
if (enableNoopReconciler) {
// No host operations
updateHostContainer = function(workInProgress: Fiber) {
// Noop

View File

@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {ReactNodeList} from 'shared/ReactTypes';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {enableAsyncSubtreeAPI} from 'shared/ReactFeatureFlags';
import {
findCurrentHostFiber,
findCurrentHostFiberWithNoPortals,
@@ -322,7 +322,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// treat updates to the root as async. This is a bit weird but lets us
// avoid a separate `renderAsync` API.
if (
ReactFeatureFlags.enableAsyncSubtreeAPI &&
enableAsyncSubtreeAPI &&
element != null &&
element.type != null &&
element.type.prototype != null &&

View File

@@ -7,7 +7,7 @@
import assign from 'object-assign';
import ReactVersion from 'shared/ReactVersion';
import ReactFeatureFlags from 'shared/ReactFeatureFlags';
import {enableReactFragment} from 'shared/ReactFeatureFlags';
import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
import {forEach, map, count, toArray, only} from './ReactChildren';
@@ -58,7 +58,7 @@ var React = {
},
};
if (ReactFeatureFlags.enableReactFragment) {
if (enableReactFragment) {
React.Fragment = REACT_FRAGMENT_TYPE;
}

View File

@@ -7,35 +7,16 @@
* @flow
*/
export type FeatureFlags = {|
enableAsyncSubtreeAPI: boolean,
enableAsyncSchedulingByDefaultInReactDOM: boolean,
enableMutatingReconciler: boolean,
enableNoopReconciler: boolean,
enablePersistentReconciler: boolean,
enableReactFragment: boolean,
enableCreateRoot: boolean,
|};
export const enableAsyncSubtreeAPI = true;
export const enableAsyncSchedulingByDefaultInReactDOM = false;
// Exports React.Fragment
export const enableReactFragment = false;
// Exports ReactDOM.createRoot
export const enableCreateRoot = false;
var ReactFeatureFlags: FeatureFlags = {
enableAsyncSubtreeAPI: true,
enableAsyncSchedulingByDefaultInReactDOM: false,
// Mutating mode (React DOM, React ART, React Native):
enableMutatingReconciler: true,
// Experimental noop mode (currently unused):
enableNoopReconciler: false,
// Experimental persistent mode (CS):
enablePersistentReconciler: false,
// Exports React.Fragment
enableReactFragment: false,
// Exports ReactDOM.createRoot
enableCreateRoot: false,
};
if (__DEV__) {
if (Object.freeze) {
Object.freeze(ReactFeatureFlags);
}
}
export default ReactFeatureFlags;
// Mutating mode (React DOM, React ART, React Native):
export const enableMutatingReconciler = true;
// Experimental noop mode (currently unused):
export const enableNoopReconciler = false;
// Experimental persistent mode (CS):
export const enablePersistentReconciler = false;

View File

@@ -12,3 +12,8 @@
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
inject: ?((stuff: Object) => void)
};*/
// ReactFeatureFlags rollup shim for www imports the www implementation.
declare module 'ReactFeatureFlags' {
declare module.exports: any;
}

View File

@@ -1,12 +1,9 @@
'use strict';
// We want to globally mock this but jest doesn't let us do that by default
// for a file that already exists. So we have to explicitly mock it.
jest.mock('shared/ReactFeatureFlags', () => {
const flags = require.requireActual('shared/ReactFeatureFlags').default;
return Object.assign({}, flags, {
disableNewFiberFeatures: true,
});
// We can alter flags based on environment here
// (e.g. for CI runs with different flags).
return require.requireActual('shared/ReactFeatureFlags');
});
// Error logging varies between Fiber and Stack;

View File

@@ -1,52 +1,52 @@
{
"bundleSizes": {
"react.development.js (UMD_DEV)": {
"size": 57960,
"gzip": 15055
"size": 57612,
"gzip": 14938
},
"react.production.min.js (UMD_PROD)": {
"size": 6672,
"gzip": 2772
},
"react.development.js (NODE_DEV)": {
"size": 48341,
"gzip": 12749
"size": 47993,
"gzip": 12640
},
"react.production.min.js (NODE_PROD)": {
"size": 5471,
"gzip": 2349
},
"React-dev.js (FB_DEV)": {
"size": 45254,
"gzip": 11809
"size": 45761,
"gzip": 11884
},
"React-prod.js (FB_PROD)": {
"size": 25804,
"gzip": 6870
"size": 26012,
"gzip": 6885
},
"react-dom.development.js (UMD_DEV)": {
"size": 589183,
"gzip": 133823
"size": 589114,
"gzip": 133729
},
"react-dom.production.min.js (UMD_PROD)": {
"size": 96544,
"gzip": 31188
"size": 94334,
"gzip": 30564
},
"react-dom.development.js (NODE_DEV)": {
"size": 570160,
"gzip": 129121
"size": 569900,
"gzip": 129020
},
"react-dom.production.min.js (NODE_PROD)": {
"size": 94731,
"gzip": 30346
"size": 92521,
"gzip": 29735
},
"ReactDOM-dev.js (FB_DEV)": {
"size": 571472,
"gzip": 129612
"size": 572245,
"gzip": 129735
},
"ReactDOM-prod.js (FB_PROD)": {
"size": 405312,
"gzip": 90089
"size": 405997,
"gzip": 90194
},
"react-dom-test-utils.development.js (NODE_DEV)": {
"size": 37876,
@@ -61,8 +61,8 @@
"gzip": 10182
},
"react-dom-unstable-native-dependencies.development.js (UMD_DEV)": {
"size": 66641,
"gzip": 16671
"size": 66832,
"gzip": 16686
},
"react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": {
"size": 11349,
@@ -85,8 +85,8 @@
"gzip": 12586
},
"react-dom-server.browser.development.js (UMD_DEV)": {
"size": 100894,
"gzip": 26193
"size": 101085,
"gzip": 26194
},
"react-dom-server.browser.production.min.js (UMD_PROD)": {
"size": 14713,
@@ -117,64 +117,64 @@
"gzip": 6016
},
"react-art.development.js (UMD_DEV)": {
"size": 361363,
"gzip": 78801
"size": 361168,
"gzip": 78675
},
"react-art.production.min.js (UMD_PROD)": {
"size": 84853,
"gzip": 26004
"size": 82786,
"gzip": 25335
},
"react-art.development.js (NODE_DEV)": {
"size": 285762,
"gzip": 59878
"size": 285376,
"gzip": 59744
},
"react-art.production.min.js (NODE_PROD)": {
"size": 48726,
"gzip": 15050
"size": 46664,
"gzip": 14576
},
"ReactART-dev.js (FB_DEV)": {
"size": 284015,
"gzip": 59653
"size": 284412,
"gzip": 59714
},
"ReactART-prod.js (FB_PROD)": {
"size": 209267,
"gzip": 43024
"size": 209350,
"gzip": 43045
},
"ReactNativeRenderer-dev.js (RN_DEV)": {
"size": 264248,
"gzip": 45270
"size": 263840,
"gzip": 45183
},
"ReactNativeRenderer-prod.js (RN_PROD)": {
"size": 202770,
"gzip": 34504
"size": 202457,
"gzip": 34443
},
"ReactRTRenderer-dev.js (RN_DEV)": {
"size": 195970,
"gzip": 32882
"size": 195570,
"gzip": 32792
},
"ReactRTRenderer-prod.js (RN_PROD)": {
"size": 143566,
"gzip": 23595
"size": 143253,
"gzip": 23524
},
"ReactCSRenderer-dev.js (RN_DEV)": {
"size": 189548,
"gzip": 31563
"size": 189107,
"gzip": 31467
},
"ReactCSRenderer-prod.js (RN_PROD)": {
"size": 139382,
"gzip": 22608
"size": 138973,
"gzip": 22523
},
"react-test-renderer.development.js (NODE_DEV)": {
"size": 284199,
"gzip": 58944
"size": 283813,
"gzip": 58810
},
"react-test-renderer.production.min.js (NODE_PROD)": {
"size": 47325,
"gzip": 14489
"size": 45266,
"gzip": 13979
},
"ReactTestRenderer-dev.js (FB_DEV)": {
"size": 282462,
"gzip": 58722
"size": 282859,
"gzip": 58787
},
"react-test-renderer-shallow.development.js (NODE_DEV)": {
"size": 10657,
@@ -189,16 +189,16 @@
"gzip": 2515
},
"react-noop-renderer.development.js (NODE_DEV)": {
"size": 280181,
"gzip": 57920
"size": 279777,
"gzip": 57797
},
"react-reconciler.development.js (NODE_DEV)": {
"size": 264938,
"gzip": 54474
"size": 264552,
"gzip": 54331
},
"react-reconciler.production.min.js (NODE_PROD)": {
"size": 40581,
"gzip": 12611
"size": 38523,
"gzip": 12008
},
"react-call-return.development.js (NODE_DEV)": {
"size": 3048,

View File

@@ -1 +1,8 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export default require('ReactCurrentOwner');

View File

@@ -1 +1,28 @@
export default require('ReactFeatureFlags');
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
import typeof * as FeatureFlagsShimType from './ReactFeatureFlags-www';
// Re-export all flags from the www version.
export const {
enableAsyncSubtreeAPI,
enableAsyncSchedulingByDefaultInReactDOM,
enableReactFragment,
enableCreateRoot,
enableMutatingReconciler,
enableNoopReconciler,
enablePersistentReconciler,
} = require('ReactFeatureFlags');
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y=_X> = null;
// eslint-disable-next-line no-unused-expressions
(null: Check<FeatureFlagsShimType, FeatureFlagsType>);

View File

@@ -1,3 +1,12 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import React from 'react';
const ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

View File

@@ -1 +1,8 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export default require('lowPriorityWarning');