Compare commits
35 Commits
v16.6.1
...
v16.7.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
906b9fa597 | ||
|
|
da863c380c | ||
|
|
2790634a5a | ||
|
|
43dd968939 | ||
|
|
8354ff81a8 | ||
|
|
85f5a81eb7 | ||
|
|
f55795c8ee | ||
|
|
d204747bef | ||
|
|
b98adb648a | ||
|
|
f8bfd58680 | ||
|
|
961eb65b4b | ||
|
|
1a6ab1e9b5 | ||
|
|
8ae867e6b5 | ||
|
|
d5e1bf07d0 | ||
|
|
2dd4ba11e0 | ||
|
|
9cc631a539 | ||
|
|
1034e26fe5 | ||
|
|
5618da49d8 | ||
|
|
9fb9199455 | ||
|
|
c174f85924 | ||
|
|
02e4848e3a | ||
|
|
4b163fee1c | ||
|
|
e58ecda9a2 | ||
|
|
7fd1661f80 | ||
|
|
f9e9913f0e | ||
|
|
7c560131bf | ||
|
|
3d8bda70e5 | ||
|
|
051272f201 | ||
|
|
3ff2c7ccd4 | ||
|
|
5afa1c4eda | ||
|
|
be63473004 | ||
|
|
e27720d7f5 | ||
|
|
aa1ffe4e77 | ||
|
|
e3a7b96455 | ||
|
|
ff29de4029 |
@@ -40,3 +40,6 @@ jobs:
|
||||
key: v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
- store_artifacts:
|
||||
path: ./node_modules.tgz
|
||||
48
.eslintrc.js
48
.eslintrc.js
@@ -1,5 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
es5Paths,
|
||||
esNextPaths,
|
||||
} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
|
||||
const OFF = 0;
|
||||
const ERROR = 2;
|
||||
|
||||
@@ -16,6 +21,15 @@ module.exports = {
|
||||
'react-internal',
|
||||
],
|
||||
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2017,
|
||||
sourceType: 'script',
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
},
|
||||
|
||||
// We're stricter than the default config, mostly. We'll override a few rules
|
||||
// and then enable some React specific ones.
|
||||
rules: {
|
||||
@@ -44,6 +58,13 @@ module.exports = {
|
||||
'space-before-function-paren': OFF,
|
||||
'valid-typeof': [ERROR, {requireStringLiterals: true}],
|
||||
|
||||
// We apply these settings to files that should run on Node.
|
||||
// They can't use JSX or ES6 modules, and must be in strict mode.
|
||||
// They can, however, use other ES6 features.
|
||||
// (Note these rules are overridden later for source files.)
|
||||
'no-var': ERROR,
|
||||
strict: ERROR,
|
||||
|
||||
// React & JSX
|
||||
// Our transforms set this automatically
|
||||
'react/jsx-boolean-value': [ERROR, 'always'],
|
||||
@@ -71,6 +92,33 @@ module.exports = {
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
// We apply these settings to files that we ship through npm.
|
||||
// They must be ES5.
|
||||
files: es5Paths,
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
sourceType: 'script',
|
||||
},
|
||||
rules: {
|
||||
'no-var': OFF,
|
||||
strict: ERROR,
|
||||
},
|
||||
},
|
||||
{
|
||||
// We apply these settings to the source files that get compiled.
|
||||
// They can use all features including JSX (but shouldn't use `var`).
|
||||
files: esNextPaths,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'no-var': ERROR,
|
||||
strict: OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/__tests__/*.js'],
|
||||
rules: {
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
Changes that have landed in master but are not yet released.
|
||||
Click to see more.
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.6.1 (November 6, 2018)
|
||||
|
||||
### React DOM
|
||||
|
||||
@@ -17,8 +19,6 @@
|
||||
|
||||
* Switch from deadline object to `shouldYield` API. ([@acdlite](https://github.com/acdlite) in [#14025](https://github.com/facebook/react/pull/14025))
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 16.6.0 (October 23, 2018)
|
||||
|
||||
|
||||
@@ -860,7 +860,7 @@ class App extends React.Component {
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error('Switch statement should be exhuastive');
|
||||
throw new Error('Switch statement should be exhaustive');
|
||||
}
|
||||
|
||||
// Sort
|
||||
@@ -887,7 +887,7 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new Error('Switch statement should be exhuastive');
|
||||
throw new Error('Switch statement should be exhaustive');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Follow these steps:
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom-client,core,react-cache,scheduler --type=NODE
|
||||
yarn build react-dom/index,react/index,react-cache,scheduler --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/suspense/
|
||||
|
||||
@@ -18,7 +18,7 @@ There are also known bugs and inefficiencies in master so **don't use this fixtu
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom-client,core,react-cache,scheduler --type=NODE
|
||||
yarn build react-dom/index,react/index,react-cache,scheduler --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/time-slicing/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -10,7 +10,7 @@
|
||||
"cjs/"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0"
|
||||
"react": "^16.3.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rxjs": "^5.5.6"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "0.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0-alpha.1",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -16,7 +16,7 @@
|
||||
"homepage": "https://reactjs.org/",
|
||||
"peerDependencies": {
|
||||
"jest": "^23.0.1",
|
||||
"react": "^16.0.0",
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1",
|
||||
"react-test-renderer": "^16.0.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
@@ -23,10 +23,10 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"umd/"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0-alpha.1"
|
||||
"react": "^16.3.0-alpha.1 || 16.7.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"error-stack-parser": "^2.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -16,10 +16,10 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -146,6 +146,141 @@ describe('ReactDOMComponent', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn for conflicting CSS shorthand updates', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<div style={{font: 'foo', fontStyle: 'bar'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{font: 'foo'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (fontStyle) ' +
|
||||
'when a conflicting property is set (font) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// These updates are OK and don't warn:
|
||||
ReactDOM.render(
|
||||
<div style={{font: 'qux', fontStyle: 'bar'}} />,
|
||||
container,
|
||||
);
|
||||
ReactDOM.render(
|
||||
<div style={{font: 'foo', fontStyle: 'baz'}} />,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<div style={{font: 'qux', fontStyle: 'baz'}} />,
|
||||
container,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Updating a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{fontStyle: 'baz'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// A bit of a special case: backgroundPosition isn't technically longhand
|
||||
// (it expands to backgroundPosition{X,Y} but so does background)
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{background: 'yellow'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender ' +
|
||||
'(backgroundPosition) when a conflicting property is set ' +
|
||||
"(background) can lead to styling bugs. To avoid this, don't mix " +
|
||||
'shorthand and non-shorthand properties for the same value; ' +
|
||||
'instead, replace the shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
container,
|
||||
);
|
||||
// But setting them at the same time is OK:
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'green', backgroundPosition: 'top'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{backgroundPosition: 'top'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (background) ' +
|
||||
'when a conflicting property is set (backgroundPosition) can lead ' +
|
||||
"to styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// A bit of an even more special case: borderLeft and borderStyle overlap.
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '1px solid red'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<div style={{borderLeft: '1px solid red'}} />,
|
||||
container,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dashed', borderLeft: '1px solid red'}} />,
|
||||
container,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Updating a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
// But setting them at the same time is OK:
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '2px solid red'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{borderStyle: 'dotted'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (borderLeft) ' +
|
||||
'when a conflicting property is set (borderStyle) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn for unknown prop', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
|
||||
@@ -249,5 +249,195 @@ describe('ReactDOMServerIntegration', () => {
|
||||
expect(e.querySelector('#language2').textContent).toBe('sanskrit');
|
||||
expect(e.querySelector('#language3').textContent).toBe('french');
|
||||
});
|
||||
|
||||
itRenders(
|
||||
'should warn with an error message when using Context as consumer in DEV',
|
||||
async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<Theme.Provider value="light">
|
||||
<Language.Provider value="english">
|
||||
<Theme.Provider value="dark">
|
||||
<Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
|
||||
</Theme.Provider>
|
||||
</Language.Provider>
|
||||
</Theme.Provider>
|
||||
</div>
|
||||
);
|
||||
// We expect 1 error.
|
||||
await render(<App />, 1);
|
||||
},
|
||||
);
|
||||
|
||||
// False positive regression test.
|
||||
itRenders(
|
||||
'should not warn when using Consumer from React < 16.6 with newer renderer',
|
||||
async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
// React 16.5 and earlier didn't have a separate object.
|
||||
Theme.Consumer = Theme;
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<Theme.Provider value="light">
|
||||
<Language.Provider value="english">
|
||||
<Theme.Provider value="dark">
|
||||
<Theme>{theme => <div id="theme1">{theme}</div>}</Theme>
|
||||
</Theme.Provider>
|
||||
</Language.Provider>
|
||||
</Theme.Provider>
|
||||
</div>
|
||||
);
|
||||
// We expect 0 errors.
|
||||
await render(<App />, 0);
|
||||
},
|
||||
);
|
||||
|
||||
itRenders(
|
||||
'should warn with an error message when using nested context consumers in DEV',
|
||||
async render => {
|
||||
const App = () => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Theme.Provider value="light">
|
||||
<Language.Provider value="english">
|
||||
<Theme.Provider value="dark">
|
||||
<Theme.Consumer.Consumer>
|
||||
{theme => <div id="theme1">{theme}</div>}
|
||||
</Theme.Consumer.Consumer>
|
||||
</Theme.Provider>
|
||||
</Language.Provider>
|
||||
</Theme.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// We expect 1 error.
|
||||
await render(<App />, 1);
|
||||
},
|
||||
);
|
||||
|
||||
itRenders(
|
||||
'should warn with an error message when using Context.Consumer.Provider DEV',
|
||||
async render => {
|
||||
const App = () => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Theme.Provider value="light">
|
||||
<Language.Provider value="english">
|
||||
<Theme.Consumer.Provider value="dark">
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme1">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
</Theme.Consumer.Provider>
|
||||
</Language.Provider>
|
||||
</Theme.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// We expect 1 error.
|
||||
await render(<App />, 1);
|
||||
},
|
||||
);
|
||||
|
||||
it('does not pollute parallel node streams', () => {
|
||||
const LoggedInUser = React.createContext();
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
<footer>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</footer>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const streamAmy = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
const streamBob = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Bob'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// Testing by filling the buffer using internal _read() with a small
|
||||
// number of bytes to avoid a test case which needs to align to a
|
||||
// highWaterMark boundary of 2^14 chars.
|
||||
streamAmy._read(20);
|
||||
streamBob._read(20);
|
||||
streamAmy._read(20);
|
||||
streamBob._read(20);
|
||||
|
||||
expect(streamAmy.read()).toBe('<header>Amy</header><footer>Amy</footer>');
|
||||
expect(streamBob.read()).toBe('<header>Bob</header><footer>Bob</footer>');
|
||||
});
|
||||
|
||||
it('does not pollute parallel node streams when many are used', () => {
|
||||
const CurrentIndex = React.createContext();
|
||||
|
||||
const NthRender = index => (
|
||||
<CurrentIndex.Provider value={index}>
|
||||
<header>
|
||||
<CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
|
||||
</header>
|
||||
<footer>
|
||||
<CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
|
||||
</footer>
|
||||
</CurrentIndex.Provider>
|
||||
);
|
||||
|
||||
let streams = [];
|
||||
|
||||
// Test with more than 32 streams to test that growing the thread count
|
||||
// works properly.
|
||||
let streamCount = 34;
|
||||
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i] = ReactDOMServer.renderToNodeStream(
|
||||
NthRender(i % 2 === 0 ? 'Expected to be recreated' : i),
|
||||
).setEncoding('utf8');
|
||||
}
|
||||
|
||||
// Testing by filling the buffer using internal _read() with a small
|
||||
// number of bytes to avoid a test case which needs to align to a
|
||||
// highWaterMark boundary of 2^14 chars.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i]._read(20);
|
||||
}
|
||||
|
||||
// Early destroy every other stream
|
||||
for (let i = 0; i < streamCount; i += 2) {
|
||||
streams[i].destroy();
|
||||
}
|
||||
|
||||
// Recreate those same streams.
|
||||
for (let i = 0; i < streamCount; i += 2) {
|
||||
streams[i] = ReactDOMServer.renderToNodeStream(
|
||||
NthRender(i),
|
||||
).setEncoding('utf8');
|
||||
}
|
||||
|
||||
// Read a bit from all streams again.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i]._read(20);
|
||||
}
|
||||
|
||||
// Assert that all stream rendered the expected output.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
expect(streams[i].read()).toBe(
|
||||
'<header>' + i + '</header><footer>' + i + '</footer>',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,16 +43,48 @@ describe('ReactDOMServerSuspense', () => {
|
||||
resetModules();
|
||||
});
|
||||
|
||||
it('should always render the fallback when a placeholder is encountered', async () => {
|
||||
const Suspended = props => {
|
||||
throw new Promise(() => {});
|
||||
};
|
||||
function Text(props) {
|
||||
return <div>{props.text}</div>;
|
||||
}
|
||||
|
||||
function AsyncText(props) {
|
||||
throw new Promise(() => {});
|
||||
}
|
||||
|
||||
it('should render the children when no promise is thrown', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<div />}>
|
||||
<Suspended />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<Text text="Children" />
|
||||
</React.Suspense>,
|
||||
);
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Children');
|
||||
});
|
||||
|
||||
it('should render the fallback when a promise thrown', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>,
|
||||
);
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Fallback');
|
||||
});
|
||||
|
||||
it('should work with nested suspense components', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<div>
|
||||
<Text text="Children" />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</React.Suspense>,
|
||||
);
|
||||
|
||||
expect(e.innerHTML).toBe('<div>Children</div><div>Fallback</div>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
ReactCache = require('react-cache');
|
||||
Suspense = React.Suspense;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
@@ -38,6 +39,10 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
}, ([text, ms]) => text);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
function advanceTimers(ms) {
|
||||
// Note: This advances Jest's virtual time but not React's. Use
|
||||
// ReactNoop.expire for that.
|
||||
@@ -157,4 +162,64 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14188
|
||||
it('can call findDOMNode() in a suspended component commit phase', async () => {
|
||||
const log = [];
|
||||
const Lazy = React.lazy(
|
||||
() =>
|
||||
new Promise(resolve =>
|
||||
resolve({
|
||||
default() {
|
||||
return 'lazy';
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
class Child extends React.Component {
|
||||
componentDidMount() {
|
||||
log.push('cDM ' + this.props.id);
|
||||
ReactDOM.findDOMNode(this);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
log.push('cDU ' + this.props.id);
|
||||
ReactDOM.findDOMNode(this);
|
||||
}
|
||||
render() {
|
||||
return 'child';
|
||||
}
|
||||
}
|
||||
|
||||
const buttonRef = React.createRef();
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
suspend: false,
|
||||
};
|
||||
handleClick = () => {
|
||||
this.setState({suspend: true});
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<React.Suspense fallback="Loading">
|
||||
<Child id="first" />
|
||||
<button ref={buttonRef} onClick={this.handleClick}>
|
||||
Suspend
|
||||
</button>
|
||||
<Child id="second" />
|
||||
{this.state.suspend && <Lazy />}
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, container);
|
||||
|
||||
expect(log).toEqual(['cDM first', 'cDM second']);
|
||||
log.length = 0;
|
||||
|
||||
buttonRef.current.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
await Lazy;
|
||||
expect(log).toEqual(['cDU first', 'cDU second']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -766,6 +766,12 @@ export function diffProperties(
|
||||
}
|
||||
}
|
||||
if (styleUpdates) {
|
||||
if (__DEV__) {
|
||||
CSSPropertyOperations.validateShorthandPropertyCollisionInDev(
|
||||
styleUpdates,
|
||||
nextProps[STYLE],
|
||||
);
|
||||
}
|
||||
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
|
||||
}
|
||||
return updatePayload;
|
||||
|
||||
@@ -18,11 +18,15 @@ class ReactMarkupReadableStream extends Readable {
|
||||
this.partialRenderer = new ReactPartialRenderer(element, makeStaticMarkup);
|
||||
}
|
||||
|
||||
_destroy() {
|
||||
this.partialRenderer.destroy();
|
||||
}
|
||||
|
||||
_read(size) {
|
||||
try {
|
||||
this.push(this.partialRenderer.read(size));
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
this.destroy(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,12 @@ import ReactPartialRenderer from './ReactPartialRenderer';
|
||||
*/
|
||||
export function renderToString(element) {
|
||||
const renderer = new ReactPartialRenderer(element, false);
|
||||
const markup = renderer.read(Infinity);
|
||||
return markup;
|
||||
try {
|
||||
const markup = renderer.read(Infinity);
|
||||
return markup;
|
||||
} finally {
|
||||
renderer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,6 +29,10 @@ export function renderToString(element) {
|
||||
*/
|
||||
export function renderToStaticMarkup(element) {
|
||||
const renderer = new ReactPartialRenderer(element, true);
|
||||
const markup = renderer.read(Infinity);
|
||||
return markup;
|
||||
try {
|
||||
const markup = renderer.read(Infinity);
|
||||
return markup;
|
||||
} finally {
|
||||
renderer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ThreadID} from './ReactThreadIDAllocator';
|
||||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {
|
||||
ReactProvider,
|
||||
ReactConsumer,
|
||||
ReactContext,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactProvider, ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import React from 'react';
|
||||
import invariant from 'shared/invariant';
|
||||
@@ -20,7 +17,6 @@ import getComponentName from 'shared/getComponentName';
|
||||
import lowPriorityWarning from 'shared/lowPriorityWarning';
|
||||
import warning from 'shared/warning';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
@@ -43,6 +39,12 @@ import {
|
||||
REACT_MEMO_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import {
|
||||
emptyObject,
|
||||
processContext,
|
||||
validateContextBounds,
|
||||
} from './ReactPartialRendererContext';
|
||||
import {allocThreadID, freeThreadID} from './ReactThreadIDAllocator';
|
||||
import {
|
||||
createMarkupForCustomAttribute,
|
||||
createMarkupForProperty,
|
||||
@@ -54,6 +56,8 @@ import {
|
||||
finishHooks,
|
||||
Dispatcher,
|
||||
DispatcherWithoutHooks,
|
||||
currentThreadID,
|
||||
setCurrentThreadID,
|
||||
} from './ReactPartialRendererHooks';
|
||||
import {
|
||||
Namespaces,
|
||||
@@ -93,6 +97,7 @@ let validatePropertiesInDevelopment = (type, props) => {};
|
||||
let pushCurrentDebugStack = (stack: Array<Frame>) => {};
|
||||
let pushElementToDebugStack = (element: ReactElement) => {};
|
||||
let popCurrentDebugStack = () => {};
|
||||
let hasWarnedAboutUsingContextAsConsumer = false;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
@@ -179,7 +184,6 @@ const didWarnAboutBadClass = {};
|
||||
const didWarnAboutDeprecatedWillMount = {};
|
||||
const didWarnAboutUndefinedDerivedState = {};
|
||||
const didWarnAboutUninitializedState = {};
|
||||
const didWarnAboutInvalidateContextType = {};
|
||||
const valuePropNames = ['value', 'defaultValue'];
|
||||
const newlineEatingTags = {
|
||||
listing: true,
|
||||
@@ -327,65 +331,6 @@ function flattenOptionChildren(children: mixed): ?string {
|
||||
return content;
|
||||
}
|
||||
|
||||
const emptyObject = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(emptyObject);
|
||||
}
|
||||
|
||||
function maskContext(type, context) {
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
}
|
||||
const maskedContext = {};
|
||||
for (const contextName in contextTypes) {
|
||||
maskedContext[contextName] = context[contextName];
|
||||
}
|
||||
return maskedContext;
|
||||
}
|
||||
|
||||
function checkContextTypes(typeSpecs, values, location: string) {
|
||||
if (__DEV__) {
|
||||
checkPropTypes(
|
||||
typeSpecs,
|
||||
values,
|
||||
location,
|
||||
'Component',
|
||||
getCurrentServerStackImpl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function processContext(type, context) {
|
||||
const contextType = type.contextType;
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
if (__DEV__) {
|
||||
if (contextType.$$typeof !== REACT_CONTEXT_TYPE) {
|
||||
let name = getComponentName(type) || 'Component';
|
||||
if (!didWarnAboutInvalidateContextType[name]) {
|
||||
didWarnAboutInvalidateContextType[type] = true;
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return contextType._currentValue;
|
||||
} else {
|
||||
const maskedContext = maskContext(type, context);
|
||||
if (__DEV__) {
|
||||
if (type.contextTypes) {
|
||||
checkContextTypes(type.contextTypes, maskedContext, 'context');
|
||||
}
|
||||
}
|
||||
return maskedContext;
|
||||
}
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
const STYLE = 'style';
|
||||
const RESERVED_PROPS = {
|
||||
@@ -456,6 +401,7 @@ function validateRenderResult(child, type) {
|
||||
function resolve(
|
||||
child: mixed,
|
||||
context: Object,
|
||||
threadID: ThreadID,
|
||||
): {|
|
||||
child: mixed,
|
||||
context: Object,
|
||||
@@ -475,7 +421,7 @@ function resolve(
|
||||
|
||||
// Extra closure so queue and replace can be captured properly
|
||||
function processChild(element, Component) {
|
||||
let publicContext = processContext(Component, context);
|
||||
let publicContext = processContext(Component, context, threadID);
|
||||
|
||||
let queue = [];
|
||||
let replace = false;
|
||||
@@ -710,6 +656,7 @@ type Frame = {
|
||||
type: mixed,
|
||||
domNamespace: string,
|
||||
children: FlatReactChildren,
|
||||
fallbackFrame?: Frame,
|
||||
childIndex: number,
|
||||
context: Object,
|
||||
footer: string,
|
||||
@@ -720,12 +667,14 @@ type FrameDev = Frame & {
|
||||
};
|
||||
|
||||
class ReactDOMServerRenderer {
|
||||
threadID: ThreadID;
|
||||
stack: Array<Frame>;
|
||||
exhausted: boolean;
|
||||
// TODO: type this more strictly:
|
||||
currentSelectValue: any;
|
||||
previousWasTextNode: boolean;
|
||||
makeStaticMarkup: boolean;
|
||||
suspenseDepth: number;
|
||||
|
||||
contextIndex: number;
|
||||
contextStack: Array<ReactContext<any>>;
|
||||
@@ -748,11 +697,13 @@ class ReactDOMServerRenderer {
|
||||
if (__DEV__) {
|
||||
((topFrame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.threadID = allocThreadID();
|
||||
this.stack = [topFrame];
|
||||
this.exhausted = false;
|
||||
this.currentSelectValue = null;
|
||||
this.previousWasTextNode = false;
|
||||
this.makeStaticMarkup = makeStaticMarkup;
|
||||
this.suspenseDepth = 0;
|
||||
|
||||
// Context (new API)
|
||||
this.contextIndex = -1;
|
||||
@@ -763,6 +714,13 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.exhausted) {
|
||||
this.exhausted = true;
|
||||
freeThreadID(this.threadID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: We use just two stacks regardless of how many context providers you have.
|
||||
* Providers are always popped in the reverse order to how they were pushed
|
||||
@@ -776,7 +734,9 @@ class ReactDOMServerRenderer {
|
||||
pushProvider<T>(provider: ReactProvider<T>): void {
|
||||
const index = ++this.contextIndex;
|
||||
const context: ReactContext<any> = provider.type._context;
|
||||
const previousValue = context._currentValue;
|
||||
const threadID = this.threadID;
|
||||
validateContextBounds(context, threadID);
|
||||
const previousValue = context[threadID];
|
||||
|
||||
// Remember which value to restore this context to on our way up.
|
||||
this.contextStack[index] = context;
|
||||
@@ -787,7 +747,7 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
|
||||
// Mutate the current value.
|
||||
context._currentValue = provider.props.value;
|
||||
context[threadID] = provider.props.value;
|
||||
}
|
||||
|
||||
popProvider<T>(provider: ReactProvider<T>): void {
|
||||
@@ -813,7 +773,9 @@ class ReactDOMServerRenderer {
|
||||
this.contextIndex--;
|
||||
|
||||
// Restore to the previous value we stored as we were walking down.
|
||||
context._currentValue = previousValue;
|
||||
// We've already verified that this context has been expanded to accommodate
|
||||
// this thread id, so we don't need to do it again.
|
||||
context[this.threadID] = previousValue;
|
||||
}
|
||||
|
||||
read(bytes: number): string | null {
|
||||
@@ -821,6 +783,8 @@ class ReactDOMServerRenderer {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prevThreadID = currentThreadID;
|
||||
setCurrentThreadID(this.threadID);
|
||||
const prevDispatcher = ReactCurrentOwner.currentDispatcher;
|
||||
if (enableHooks) {
|
||||
ReactCurrentOwner.currentDispatcher = Dispatcher;
|
||||
@@ -828,16 +792,19 @@ class ReactDOMServerRenderer {
|
||||
ReactCurrentOwner.currentDispatcher = DispatcherWithoutHooks;
|
||||
}
|
||||
try {
|
||||
let out = '';
|
||||
while (out.length < bytes) {
|
||||
// Markup generated within <Suspense> ends up buffered until we know
|
||||
// nothing in that boundary suspended
|
||||
let out = [''];
|
||||
let suspended = false;
|
||||
while (out[0].length < bytes) {
|
||||
if (this.stack.length === 0) {
|
||||
this.exhausted = true;
|
||||
freeThreadID(this.threadID);
|
||||
break;
|
||||
}
|
||||
const frame: Frame = this.stack[this.stack.length - 1];
|
||||
if (frame.childIndex >= frame.children.length) {
|
||||
if (suspended || frame.childIndex >= frame.children.length) {
|
||||
const footer = frame.footer;
|
||||
out += footer;
|
||||
if (footer !== '') {
|
||||
this.previousWasTextNode = false;
|
||||
}
|
||||
@@ -851,28 +818,60 @@ class ReactDOMServerRenderer {
|
||||
) {
|
||||
const provider: ReactProvider<any> = (frame.type: any);
|
||||
this.popProvider(provider);
|
||||
} else if (frame.type === REACT_SUSPENSE_TYPE) {
|
||||
this.suspenseDepth--;
|
||||
const buffered = out.pop();
|
||||
|
||||
if (suspended) {
|
||||
suspended = false;
|
||||
// If rendering was suspended at this boundary, render the fallbackFrame
|
||||
const fallbackFrame = frame.fallbackFrame;
|
||||
invariant(
|
||||
fallbackFrame,
|
||||
'suspense fallback not found, something is broken',
|
||||
);
|
||||
this.stack.push(fallbackFrame);
|
||||
// Skip flushing output since we're switching to the fallback
|
||||
continue;
|
||||
} else {
|
||||
out[this.suspenseDepth] += buffered;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush output
|
||||
out[this.suspenseDepth] += footer;
|
||||
continue;
|
||||
}
|
||||
const child = frame.children[frame.childIndex++];
|
||||
|
||||
let outBuffer = '';
|
||||
if (__DEV__) {
|
||||
pushCurrentDebugStack(this.stack);
|
||||
// We're starting work on this frame, so reset its inner stack.
|
||||
((frame: any): FrameDev).debugElementStack.length = 0;
|
||||
try {
|
||||
// Be careful! Make sure this matches the PROD path below.
|
||||
out += this.render(child, frame.context, frame.domNamespace);
|
||||
} finally {
|
||||
}
|
||||
try {
|
||||
outBuffer += this.render(child, frame.context, frame.domNamespace);
|
||||
} catch (err) {
|
||||
if (enableSuspenseServerRenderer && typeof err.then === 'function') {
|
||||
suspended = true;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} finally {
|
||||
if (__DEV__) {
|
||||
popCurrentDebugStack();
|
||||
}
|
||||
} else {
|
||||
// Be careful! Make sure this matches the DEV path above.
|
||||
out += this.render(child, frame.context, frame.domNamespace);
|
||||
}
|
||||
if (out.length <= this.suspenseDepth) {
|
||||
out.push('');
|
||||
}
|
||||
out[this.suspenseDepth] += outBuffer;
|
||||
}
|
||||
return out;
|
||||
return out[0];
|
||||
} finally {
|
||||
ReactCurrentOwner.currentDispatcher = prevDispatcher;
|
||||
setCurrentThreadID(prevThreadID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,7 +895,7 @@ class ReactDOMServerRenderer {
|
||||
return escapeTextForBrowser(text);
|
||||
} else {
|
||||
let nextChild;
|
||||
({child: nextChild, context} = resolve(child, context));
|
||||
({child: nextChild, context} = resolve(child, context, this.threadID));
|
||||
if (nextChild === null || nextChild === false) {
|
||||
return '';
|
||||
} else if (!React.isValidElement(nextChild)) {
|
||||
@@ -963,13 +962,25 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
case REACT_SUSPENSE_TYPE: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const nextChildren = toArray(
|
||||
// Always use the fallback when synchronously rendering to string.
|
||||
const fallbackChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.fallback,
|
||||
);
|
||||
const frame: Frame = {
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
const fallbackFrame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: fallbackChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
out: '',
|
||||
};
|
||||
const frame: Frame = {
|
||||
fallbackFrame,
|
||||
type: REACT_SUSPENSE_TYPE,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
@@ -977,8 +988,10 @@ class ReactDOMServerRenderer {
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
((fallbackFrame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
this.suspenseDepth++;
|
||||
return '';
|
||||
} else {
|
||||
invariant(false, 'ReactDOMServer does not yet support Suspense.');
|
||||
@@ -1061,9 +1074,37 @@ class ReactDOMServerRenderer {
|
||||
return '';
|
||||
}
|
||||
case REACT_CONTEXT_TYPE: {
|
||||
const consumer: ReactConsumer<any> = (nextChild: any);
|
||||
const nextProps: any = consumer.props;
|
||||
const nextValue = consumer.type._currentValue;
|
||||
let reactContext = (nextChild: any).type;
|
||||
// The logic below for Context differs depending on PROD or DEV mode. In
|
||||
// DEV mode, we create a separate object for Context.Consumer that acts
|
||||
// like a proxy to Context. This proxy object adds unnecessary code in PROD
|
||||
// so we use the old behaviour (Context.Consumer references Context) to
|
||||
// reduce size and overhead. The separate object references context via
|
||||
// a property called "_context", which also gives us the ability to check
|
||||
// in DEV mode if this property exists or not and warn if it does not.
|
||||
if (__DEV__) {
|
||||
if ((reactContext: any)._context === undefined) {
|
||||
// This may be because it's a Context (rather than a Consumer).
|
||||
// Or it may be because it's older React where they're the same thing.
|
||||
// We only want to warn if we're sure it's a new React.
|
||||
if (reactContext !== reactContext.Consumer) {
|
||||
if (!hasWarnedAboutUsingContextAsConsumer) {
|
||||
hasWarnedAboutUsingContextAsConsumer = true;
|
||||
warning(
|
||||
false,
|
||||
'Rendering <Context> directly is not supported and will be removed in ' +
|
||||
'a future major release. Did you mean to render <Context.Consumer> instead?',
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reactContext = (reactContext: any)._context;
|
||||
}
|
||||
}
|
||||
const nextProps: any = (nextChild: any).props;
|
||||
const threadID = this.threadID;
|
||||
validateContextBounds(reactContext, threadID);
|
||||
const nextValue = reactContext[threadID];
|
||||
|
||||
const nextChildren = toArray(nextProps.children(nextValue));
|
||||
const frame: Frame = {
|
||||
|
||||
104
packages/react-dom/src/server/ReactPartialRendererContext.js
vendored
Normal file
104
packages/react-dom/src/server/ReactPartialRendererContext.js
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ThreadID} from './ReactThreadIDAllocator';
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
|
||||
let ReactDebugCurrentFrame;
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
}
|
||||
|
||||
const didWarnAboutInvalidateContextType = {};
|
||||
|
||||
export const emptyObject = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(emptyObject);
|
||||
}
|
||||
|
||||
function maskContext(type, context) {
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
}
|
||||
const maskedContext = {};
|
||||
for (const contextName in contextTypes) {
|
||||
maskedContext[contextName] = context[contextName];
|
||||
}
|
||||
return maskedContext;
|
||||
}
|
||||
|
||||
function checkContextTypes(typeSpecs, values, location: string) {
|
||||
if (__DEV__) {
|
||||
checkPropTypes(
|
||||
typeSpecs,
|
||||
values,
|
||||
location,
|
||||
'Component',
|
||||
ReactDebugCurrentFrame.getCurrentStack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateContextBounds(
|
||||
context: ReactContext<any>,
|
||||
threadID: ThreadID,
|
||||
) {
|
||||
// If we don't have enough slots in this context to store this threadID,
|
||||
// fill it in without leaving any holes to ensure that the VM optimizes
|
||||
// this as non-holey index properties.
|
||||
for (let i = context._threadCount; i <= threadID; i++) {
|
||||
// We assume that this is the same as the defaultValue which might not be
|
||||
// true if we're rendering inside a secondary renderer but they are
|
||||
// secondary because these use cases are very rare.
|
||||
context[i] = context._currentValue2;
|
||||
context._threadCount = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function processContext(
|
||||
type: Function,
|
||||
context: Object,
|
||||
threadID: ThreadID,
|
||||
) {
|
||||
const contextType = type.contextType;
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
if (__DEV__) {
|
||||
if (contextType.$$typeof !== REACT_CONTEXT_TYPE) {
|
||||
let name = getComponentName(type) || 'Component';
|
||||
if (!didWarnAboutInvalidateContextType[name]) {
|
||||
didWarnAboutInvalidateContextType[name] = true;
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
validateContextBounds(contextType, threadID);
|
||||
return contextType[threadID];
|
||||
} else {
|
||||
const maskedContext = maskContext(type, context);
|
||||
if (__DEV__) {
|
||||
if (type.contextTypes) {
|
||||
checkContextTypes(type.contextTypes, maskedContext, 'context');
|
||||
}
|
||||
}
|
||||
return maskedContext;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,13 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ThreadID} from './ReactThreadIDAllocator';
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
import areHookInputsEqual from 'shared/areHookInputsEqual';
|
||||
|
||||
import {validateContextBounds} from './ReactPartialRendererContext';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
@@ -139,7 +143,9 @@ function readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
return context._currentValue;
|
||||
let threadID = currentThreadID;
|
||||
validateContextBounds(context, threadID);
|
||||
return context[threadID];
|
||||
}
|
||||
|
||||
function useContext<T>(
|
||||
@@ -147,7 +153,9 @@ function useContext<T>(
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return context._currentValue;
|
||||
let threadID = currentThreadID;
|
||||
validateContextBounds(context, threadID);
|
||||
return context[threadID];
|
||||
}
|
||||
|
||||
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
|
||||
@@ -334,6 +342,12 @@ function dispatchAction<A>(
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export let currentThreadID: ThreadID = 0;
|
||||
|
||||
export function setCurrentThreadID(threadID: ThreadID) {
|
||||
currentThreadID = threadID;
|
||||
}
|
||||
|
||||
export const Dispatcher = {
|
||||
readContext,
|
||||
useContext,
|
||||
|
||||
58
packages/react-dom/src/server/ReactThreadIDAllocator.js
vendored
Normal file
58
packages/react-dom/src/server/ReactThreadIDAllocator.js
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Allocates a new index for each request. Tries to stay as compact as possible so that these
|
||||
// indices can be used to reference a tightly packaged array. As opposed to being used in a Map.
|
||||
// The first allocated index is 1.
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
export type ThreadID = number;
|
||||
|
||||
let nextAvailableThreadIDs = new Uint16Array(16);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
nextAvailableThreadIDs[i] = i + 1;
|
||||
}
|
||||
nextAvailableThreadIDs[15] = 0;
|
||||
|
||||
function growThreadCountAndReturnNextAvailable() {
|
||||
let oldArray = nextAvailableThreadIDs;
|
||||
let oldSize = oldArray.length;
|
||||
let newSize = oldSize * 2;
|
||||
invariant(
|
||||
newSize <= 0x10000,
|
||||
'Maximum number of concurrent React renderers exceeded. ' +
|
||||
'This can happen if you are not properly destroying the Readable provided by React. ' +
|
||||
'Ensure that you call .destroy() on it if you no longer want to read from it, ' +
|
||||
'and did not read to the end. If you use .pipe() this should be automatic.',
|
||||
);
|
||||
let newArray = new Uint16Array(newSize);
|
||||
newArray.set(oldArray);
|
||||
nextAvailableThreadIDs = newArray;
|
||||
nextAvailableThreadIDs[0] = oldSize + 1;
|
||||
for (let i = oldSize; i < newSize - 1; i++) {
|
||||
nextAvailableThreadIDs[i] = i + 1;
|
||||
}
|
||||
nextAvailableThreadIDs[newSize - 1] = 0;
|
||||
return oldSize;
|
||||
}
|
||||
|
||||
export function allocThreadID(): ThreadID {
|
||||
let nextID = nextAvailableThreadIDs[0];
|
||||
if (nextID === 0) {
|
||||
return growThreadCountAndReturnNextAvailable();
|
||||
}
|
||||
nextAvailableThreadIDs[0] = nextAvailableThreadIDs[nextID];
|
||||
return nextID;
|
||||
}
|
||||
|
||||
export function freeThreadID(id: ThreadID) {
|
||||
nextAvailableThreadIDs[id] = nextAvailableThreadIDs[0];
|
||||
nextAvailableThreadIDs[0] = id;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ function escapeHtml(string) {
|
||||
|
||||
let escape;
|
||||
let html = '';
|
||||
let index = 0;
|
||||
let index;
|
||||
let lastIndex = 0;
|
||||
|
||||
for (index = match.index; index < str.length; index++) {
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {shorthandToLonghand} from './CSSShorthandProperty';
|
||||
|
||||
import dangerousStyleValue from './dangerousStyleValue';
|
||||
import hyphenateStyleName from './hyphenateStyleName';
|
||||
import warnValidStyle from './warnValidStyle';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
/**
|
||||
* Operations for dealing with CSS properties.
|
||||
@@ -78,3 +81,75 @@ export function setValueForStyles(node, styles) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isValueEmpty(value) {
|
||||
return value == null || typeof value === 'boolean' || value === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Given {color: 'red', overflow: 'hidden'} returns {
|
||||
* color: 'color',
|
||||
* overflowX: 'overflow',
|
||||
* overflowY: 'overflow',
|
||||
* }. This can be read as "the overflowY property was set by the overflow
|
||||
* shorthand". That is, the values are the property that each was derived from.
|
||||
*/
|
||||
function expandShorthandMap(styles) {
|
||||
const expanded = {};
|
||||
for (const key in styles) {
|
||||
const longhands = shorthandToLonghand[key] || [key];
|
||||
for (let i = 0; i < longhands.length; i++) {
|
||||
expanded[longhands[i]] = key;
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* When mixing shorthand and longhand property names, we warn during updates if
|
||||
* we expect an incorrect result to occur. In particular, we warn for:
|
||||
*
|
||||
* Updating a shorthand property (longhand gets overwritten):
|
||||
* {font: 'foo', fontVariant: 'bar'} -> {font: 'baz', fontVariant: 'bar'}
|
||||
* becomes .style.font = 'baz'
|
||||
* Removing a shorthand property (longhand gets lost too):
|
||||
* {font: 'foo', fontVariant: 'bar'} -> {fontVariant: 'bar'}
|
||||
* becomes .style.font = ''
|
||||
* Removing a longhand property (should revert to shorthand; doesn't):
|
||||
* {font: 'foo', fontVariant: 'bar'} -> {font: 'foo'}
|
||||
* becomes .style.fontVariant = ''
|
||||
*/
|
||||
export function validateShorthandPropertyCollisionInDev(
|
||||
styleUpdates,
|
||||
nextStyles,
|
||||
) {
|
||||
if (!nextStyles) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expandedUpdates = expandShorthandMap(styleUpdates);
|
||||
const expandedStyles = expandShorthandMap(nextStyles);
|
||||
const warnedAbout = {};
|
||||
for (const key in expandedUpdates) {
|
||||
const originalKey = expandedUpdates[key];
|
||||
const correctOriginalKey = expandedStyles[key];
|
||||
if (correctOriginalKey && originalKey !== correctOriginalKey) {
|
||||
const warningKey = originalKey + ',' + correctOriginalKey;
|
||||
if (warnedAbout[warningKey]) {
|
||||
continue;
|
||||
}
|
||||
warnedAbout[warningKey] = true;
|
||||
warning(
|
||||
false,
|
||||
'%s a style property during rerender (%s) when a ' +
|
||||
'conflicting property is set (%s) can lead to styling bugs. To ' +
|
||||
"avoid this, don't mix shorthand and non-shorthand properties " +
|
||||
'for the same value; instead, replace the shorthand with ' +
|
||||
'separate values.',
|
||||
isValueEmpty(styleUpdates[originalKey]) ? 'Removing' : 'Updating',
|
||||
originalKey,
|
||||
correctOriginalKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
192
packages/react-dom/src/shared/CSSShorthandProperty.js
vendored
Normal file
192
packages/react-dom/src/shared/CSSShorthandProperty.js
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// List derived from Gecko source code:
|
||||
// https://github.com/mozilla/gecko-dev/blob/4e638efc71/layout/style/test/property_database.js
|
||||
export const shorthandToLonghand = {
|
||||
animation: [
|
||||
'animationDelay',
|
||||
'animationDirection',
|
||||
'animationDuration',
|
||||
'animationFillMode',
|
||||
'animationIterationCount',
|
||||
'animationName',
|
||||
'animationPlayState',
|
||||
'animationTimingFunction',
|
||||
],
|
||||
background: [
|
||||
'backgroundAttachment',
|
||||
'backgroundClip',
|
||||
'backgroundColor',
|
||||
'backgroundImage',
|
||||
'backgroundOrigin',
|
||||
'backgroundPositionX',
|
||||
'backgroundPositionY',
|
||||
'backgroundRepeat',
|
||||
'backgroundSize',
|
||||
],
|
||||
backgroundPosition: ['backgroundPositionX', 'backgroundPositionY'],
|
||||
border: [
|
||||
'borderBottomColor',
|
||||
'borderBottomStyle',
|
||||
'borderBottomWidth',
|
||||
'borderImageOutset',
|
||||
'borderImageRepeat',
|
||||
'borderImageSlice',
|
||||
'borderImageSource',
|
||||
'borderImageWidth',
|
||||
'borderLeftColor',
|
||||
'borderLeftStyle',
|
||||
'borderLeftWidth',
|
||||
'borderRightColor',
|
||||
'borderRightStyle',
|
||||
'borderRightWidth',
|
||||
'borderTopColor',
|
||||
'borderTopStyle',
|
||||
'borderTopWidth',
|
||||
],
|
||||
borderBlockEnd: [
|
||||
'borderBlockEndColor',
|
||||
'borderBlockEndStyle',
|
||||
'borderBlockEndWidth',
|
||||
],
|
||||
borderBlockStart: [
|
||||
'borderBlockStartColor',
|
||||
'borderBlockStartStyle',
|
||||
'borderBlockStartWidth',
|
||||
],
|
||||
borderBottom: ['borderBottomColor', 'borderBottomStyle', 'borderBottomWidth'],
|
||||
borderColor: [
|
||||
'borderBottomColor',
|
||||
'borderLeftColor',
|
||||
'borderRightColor',
|
||||
'borderTopColor',
|
||||
],
|
||||
borderImage: [
|
||||
'borderImageOutset',
|
||||
'borderImageRepeat',
|
||||
'borderImageSlice',
|
||||
'borderImageSource',
|
||||
'borderImageWidth',
|
||||
],
|
||||
borderInlineEnd: [
|
||||
'borderInlineEndColor',
|
||||
'borderInlineEndStyle',
|
||||
'borderInlineEndWidth',
|
||||
],
|
||||
borderInlineStart: [
|
||||
'borderInlineStartColor',
|
||||
'borderInlineStartStyle',
|
||||
'borderInlineStartWidth',
|
||||
],
|
||||
borderLeft: ['borderLeftColor', 'borderLeftStyle', 'borderLeftWidth'],
|
||||
borderRadius: [
|
||||
'borderBottomLeftRadius',
|
||||
'borderBottomRightRadius',
|
||||
'borderTopLeftRadius',
|
||||
'borderTopRightRadius',
|
||||
],
|
||||
borderRight: ['borderRightColor', 'borderRightStyle', 'borderRightWidth'],
|
||||
borderStyle: [
|
||||
'borderBottomStyle',
|
||||
'borderLeftStyle',
|
||||
'borderRightStyle',
|
||||
'borderTopStyle',
|
||||
],
|
||||
borderTop: ['borderTopColor', 'borderTopStyle', 'borderTopWidth'],
|
||||
borderWidth: [
|
||||
'borderBottomWidth',
|
||||
'borderLeftWidth',
|
||||
'borderRightWidth',
|
||||
'borderTopWidth',
|
||||
],
|
||||
columnRule: ['columnRuleColor', 'columnRuleStyle', 'columnRuleWidth'],
|
||||
columns: ['columnCount', 'columnWidth'],
|
||||
flex: ['flexBasis', 'flexGrow', 'flexShrink'],
|
||||
flexFlow: ['flexDirection', 'flexWrap'],
|
||||
font: [
|
||||
'fontFamily',
|
||||
'fontFeatureSettings',
|
||||
'fontKerning',
|
||||
'fontLanguageOverride',
|
||||
'fontSize',
|
||||
'fontSizeAdjust',
|
||||
'fontStretch',
|
||||
'fontStyle',
|
||||
'fontVariant',
|
||||
'fontVariantAlternates',
|
||||
'fontVariantCaps',
|
||||
'fontVariantEastAsian',
|
||||
'fontVariantLigatures',
|
||||
'fontVariantNumeric',
|
||||
'fontVariantPosition',
|
||||
'fontWeight',
|
||||
'lineHeight',
|
||||
],
|
||||
fontVariant: [
|
||||
'fontVariantAlternates',
|
||||
'fontVariantCaps',
|
||||
'fontVariantEastAsian',
|
||||
'fontVariantLigatures',
|
||||
'fontVariantNumeric',
|
||||
'fontVariantPosition',
|
||||
],
|
||||
gap: ['columnGap', 'rowGap'],
|
||||
grid: [
|
||||
'gridAutoColumns',
|
||||
'gridAutoFlow',
|
||||
'gridAutoRows',
|
||||
'gridTemplateAreas',
|
||||
'gridTemplateColumns',
|
||||
'gridTemplateRows',
|
||||
],
|
||||
gridArea: ['gridColumnEnd', 'gridColumnStart', 'gridRowEnd', 'gridRowStart'],
|
||||
gridColumn: ['gridColumnEnd', 'gridColumnStart'],
|
||||
gridColumnGap: ['columnGap'],
|
||||
gridGap: ['columnGap', 'rowGap'],
|
||||
gridRow: ['gridRowEnd', 'gridRowStart'],
|
||||
gridRowGap: ['rowGap'],
|
||||
gridTemplate: [
|
||||
'gridTemplateAreas',
|
||||
'gridTemplateColumns',
|
||||
'gridTemplateRows',
|
||||
],
|
||||
listStyle: ['listStyleImage', 'listStylePosition', 'listStyleType'],
|
||||
margin: ['marginBottom', 'marginLeft', 'marginRight', 'marginTop'],
|
||||
marker: ['markerEnd', 'markerMid', 'markerStart'],
|
||||
mask: [
|
||||
'maskClip',
|
||||
'maskComposite',
|
||||
'maskImage',
|
||||
'maskMode',
|
||||
'maskOrigin',
|
||||
'maskPositionX',
|
||||
'maskPositionY',
|
||||
'maskRepeat',
|
||||
'maskSize',
|
||||
],
|
||||
maskPosition: ['maskPositionX', 'maskPositionY'],
|
||||
outline: ['outlineColor', 'outlineStyle', 'outlineWidth'],
|
||||
overflow: ['overflowX', 'overflowY'],
|
||||
padding: ['paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop'],
|
||||
placeContent: ['alignContent', 'justifyContent'],
|
||||
placeItems: ['alignItems', 'justifyItems'],
|
||||
placeSelf: ['alignSelf', 'justifySelf'],
|
||||
textDecoration: [
|
||||
'textDecorationColor',
|
||||
'textDecorationLine',
|
||||
'textDecorationStyle',
|
||||
],
|
||||
textEmphasis: ['textEmphasisColor', 'textEmphasisStyle'],
|
||||
transition: [
|
||||
'transitionDelay',
|
||||
'transitionDuration',
|
||||
'transitionProperty',
|
||||
'transitionTimingFunction',
|
||||
],
|
||||
wordWrap: ['overflowWrap'],
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"regenerator-runtime": "^0.11.0",
|
||||
"react-reconciler": "*"
|
||||
"react-reconciler": "* || 0.18.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-reconciler",
|
||||
"description": "React package for creating custom renderers.",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.0-alpha.1",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -22,13 +22,13 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
257
packages/react-reconciler/src/ReactFiberBeginWork.js
vendored
257
packages/react-reconciler/src/ReactFiberBeginWork.js
vendored
@@ -66,7 +66,12 @@ import {
|
||||
} from './ReactChildFiber';
|
||||
import {processUpdateQueue} from './ReactUpdateQueue';
|
||||
import {NoWork, Never} from './ReactFiberExpirationTime';
|
||||
import {ConcurrentMode, StrictMode, NoContext} from './ReactTypeOfMode';
|
||||
import {
|
||||
ConcurrentMode,
|
||||
NoContext,
|
||||
ProfileMode,
|
||||
StrictMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
shouldSetTextContent,
|
||||
shouldDeprioritizeSubtree,
|
||||
@@ -245,6 +250,9 @@ function updateMemoComponent(
|
||||
// to a SimpleMemoComponent to allow fast path updates.
|
||||
workInProgress.tag = SimpleMemoComponent;
|
||||
workInProgress.type = type;
|
||||
if (__DEV__) {
|
||||
validateFunctionComponentInDev(workInProgress, type);
|
||||
}
|
||||
return updateSimpleMemoComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
@@ -283,6 +291,8 @@ function updateMemoComponent(
|
||||
);
|
||||
}
|
||||
}
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
let newChild = createWorkInProgress(
|
||||
currentChild,
|
||||
nextProps,
|
||||
@@ -741,7 +751,7 @@ function mountLazyComponent(
|
||||
) {
|
||||
if (_current !== null) {
|
||||
// An lazy component only mounts if it suspended inside a non-
|
||||
// concurrent tree, in an inconsistent state. We want to tree it like
|
||||
// concurrent tree, in an inconsistent state. We want to treat it like
|
||||
// a new mount, even though an empty version of it already committed.
|
||||
// Disconnect the alternate pointers.
|
||||
_current.alternate = null;
|
||||
@@ -804,7 +814,7 @@ function mountLazyComponent(
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// This message intentionally doesn't metion ForwardRef or MemoComponent
|
||||
// This message intentionally doesn't mention ForwardRef or MemoComponent
|
||||
// because the fact that it's a separate type of work is an
|
||||
// implementation detail.
|
||||
invariant(
|
||||
@@ -827,7 +837,7 @@ function mountIncompleteClassComponent(
|
||||
) {
|
||||
if (_current !== null) {
|
||||
// An incomplete component only mounts if it suspended inside a non-
|
||||
// concurrent tree, in an inconsistent state. We want to tree it like
|
||||
// concurrent tree, in an inconsistent state. We want to treat it like
|
||||
// a new mount, even though an empty version of it already committed.
|
||||
// Disconnect the alternate pointers.
|
||||
_current.alternate = null;
|
||||
@@ -884,7 +894,7 @@ function mountIndeterminateComponent(
|
||||
) {
|
||||
if (_current !== null) {
|
||||
// An indeterminate component only mounts if it suspended inside a non-
|
||||
// concurrent tree, in an inconsistent state. We want to tree it like
|
||||
// concurrent tree, in an inconsistent state. We want to treat it like
|
||||
// a new mount, even though an empty version of it already committed.
|
||||
// Disconnect the alternate pointers.
|
||||
_current.alternate = null;
|
||||
@@ -983,71 +993,75 @@ function mountIndeterminateComponent(
|
||||
// Proceed under the assumption that this is a function component
|
||||
workInProgress.tag = FunctionComponent;
|
||||
value = finishHooks(Component, props, value, context);
|
||||
if (__DEV__) {
|
||||
if (Component) {
|
||||
warningWithoutStack(
|
||||
!Component.childContextTypes,
|
||||
'%s(...): childContextTypes cannot be defined on a function component.',
|
||||
Component.displayName || Component.name || 'Component',
|
||||
);
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
let info = '';
|
||||
const ownerName = ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull();
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
|
||||
let warningKey = ownerName || workInProgress._debugID || '';
|
||||
const debugSource = workInProgress._debugSource;
|
||||
if (debugSource) {
|
||||
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
|
||||
}
|
||||
if (!didWarnAboutFunctionRefs[warningKey]) {
|
||||
didWarnAboutFunctionRefs[warningKey] = true;
|
||||
warning(
|
||||
false,
|
||||
'Function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.%s',
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof Component.getDerivedStateFromProps === 'function') {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
|
||||
if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s: Function components do not support getDerivedStateFromProps.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof Component.contextType === 'object' &&
|
||||
Component.contextType !== null
|
||||
) {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
|
||||
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s: Function components do not support contextType.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
reconcileChildren(null, workInProgress, value, renderExpirationTime);
|
||||
if (__DEV__) {
|
||||
validateFunctionComponentInDev(workInProgress, Component);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
|
||||
function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
|
||||
if (Component) {
|
||||
warningWithoutStack(
|
||||
!Component.childContextTypes,
|
||||
'%s(...): childContextTypes cannot be defined on a function component.',
|
||||
Component.displayName || Component.name || 'Component',
|
||||
);
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
let info = '';
|
||||
const ownerName = ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull();
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
|
||||
let warningKey = ownerName || workInProgress._debugID || '';
|
||||
const debugSource = workInProgress._debugSource;
|
||||
if (debugSource) {
|
||||
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
|
||||
}
|
||||
if (!didWarnAboutFunctionRefs[warningKey]) {
|
||||
didWarnAboutFunctionRefs[warningKey] = true;
|
||||
warning(
|
||||
false,
|
||||
'Function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.%s',
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof Component.getDerivedStateFromProps === 'function') {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
|
||||
if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s: Function components do not support getDerivedStateFromProps.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof Component.contextType === 'object' &&
|
||||
Component.contextType !== null
|
||||
) {
|
||||
const componentName = getComponentName(Component) || 'Unknown';
|
||||
|
||||
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s: Function components do not support contextType.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
@@ -1127,11 +1141,7 @@ function updateSuspenseComponent(
|
||||
progressedState !== null
|
||||
? (workInProgress.child: any).child
|
||||
: (workInProgress.child: any);
|
||||
reuseProgressedPrimaryChild(
|
||||
workInProgress,
|
||||
primaryChildFragment,
|
||||
progressedPrimaryChild,
|
||||
);
|
||||
primaryChildFragment.child = progressedPrimaryChild;
|
||||
}
|
||||
|
||||
const fallbackChildFragment = createFiberFromFragment(
|
||||
@@ -1175,7 +1185,6 @@ function updateSuspenseComponent(
|
||||
currentPrimaryChildFragment.pendingProps,
|
||||
NoWork,
|
||||
);
|
||||
primaryChildFragment.effectTag |= Placement;
|
||||
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoContext) {
|
||||
// Outside of concurrent mode, we commit the effects from the
|
||||
@@ -1186,14 +1195,23 @@ function updateSuspenseComponent(
|
||||
? (workInProgress.child: any).child
|
||||
: (workInProgress.child: any);
|
||||
if (progressedPrimaryChild !== currentPrimaryChildFragment.child) {
|
||||
reuseProgressedPrimaryChild(
|
||||
workInProgress,
|
||||
primaryChildFragment,
|
||||
progressedPrimaryChild,
|
||||
);
|
||||
primaryChildFragment.child = progressedPrimaryChild;
|
||||
}
|
||||
}
|
||||
|
||||
// Because primaryChildFragment is a new fiber that we're inserting as the
|
||||
// parent of a new tree, we need to set its treeBaseDuration.
|
||||
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
|
||||
// treeBaseDuration is the sum of all the child tree base durations.
|
||||
let treeBaseDuration = 0;
|
||||
let hiddenChild = primaryChildFragment.child;
|
||||
while (hiddenChild !== null) {
|
||||
treeBaseDuration += hiddenChild.treeBaseDuration;
|
||||
hiddenChild = hiddenChild.sibling;
|
||||
}
|
||||
primaryChildFragment.treeBaseDuration = treeBaseDuration;
|
||||
}
|
||||
|
||||
// Clone the fallback child fragment, too. These we'll continue
|
||||
// working on.
|
||||
const fallbackChildFragment = (primaryChildFragment.sibling = createWorkInProgress(
|
||||
@@ -1201,7 +1219,6 @@ function updateSuspenseComponent(
|
||||
nextFallbackChildren,
|
||||
currentFallbackChildFragment.expirationTime,
|
||||
));
|
||||
fallbackChildFragment.effectTag |= Placement;
|
||||
child = primaryChildFragment;
|
||||
primaryChildFragment.childExpirationTime = NoWork;
|
||||
// Skip the primary children, and continue working on the
|
||||
@@ -1213,27 +1230,26 @@ function updateSuspenseComponent(
|
||||
// and remove the intermediate fragment fiber.
|
||||
const nextPrimaryChildren = nextProps.children;
|
||||
const currentPrimaryChild = currentPrimaryChildFragment.child;
|
||||
const currentFallbackChild = currentFallbackChildFragment.child;
|
||||
const primaryChild = reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentPrimaryChild,
|
||||
nextPrimaryChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
// Delete the fallback children.
|
||||
reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentFallbackChild,
|
||||
null,
|
||||
renderExpirationTime,
|
||||
);
|
||||
|
||||
// If this render doesn't suspend, we need to delete the fallback
|
||||
// children. Wait until the complete phase, after we've confirmed the
|
||||
// fallback is no longer needed.
|
||||
// TODO: Would it be better to store the fallback fragment on
|
||||
// the stateNode?
|
||||
|
||||
// Continue rendering the children, like we normally do.
|
||||
child = next = primaryChild;
|
||||
}
|
||||
} else {
|
||||
// The current tree has not already timed out. That means the primary
|
||||
// children are not wrapped in a fragment fiber.
|
||||
const currentPrimaryChild: Fiber = (current.child: any);
|
||||
const currentPrimaryChild = current.child;
|
||||
if (nextDidTimeout) {
|
||||
// Timed out. Wrap the children in a fragment fiber to keep them
|
||||
// separate from the fallback children.
|
||||
@@ -1246,9 +1262,12 @@ function updateSuspenseComponent(
|
||||
NoWork,
|
||||
null,
|
||||
);
|
||||
primaryChildFragment.effectTag |= Placement;
|
||||
primaryChildFragment.child = currentPrimaryChild;
|
||||
currentPrimaryChild.return = primaryChildFragment;
|
||||
|
||||
// Even though we're creating a new fiber, there are no new children,
|
||||
// because we're reusing an already mounted tree. So we don't need to
|
||||
// schedule a placement.
|
||||
// primaryChildFragment.effectTag |= Placement;
|
||||
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoContext) {
|
||||
// Outside of concurrent mode, we commit the effects from the
|
||||
@@ -1258,11 +1277,20 @@ function updateSuspenseComponent(
|
||||
progressedState !== null
|
||||
? (workInProgress.child: any).child
|
||||
: (workInProgress.child: any);
|
||||
reuseProgressedPrimaryChild(
|
||||
workInProgress,
|
||||
primaryChildFragment,
|
||||
progressedPrimaryChild,
|
||||
);
|
||||
primaryChildFragment.child = progressedPrimaryChild;
|
||||
}
|
||||
|
||||
// Because primaryChildFragment is a new fiber that we're inserting as the
|
||||
// parent of a new tree, we need to set its treeBaseDuration.
|
||||
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
|
||||
// treeBaseDuration is the sum of all the child tree base durations.
|
||||
let treeBaseDuration = 0;
|
||||
let hiddenChild = primaryChildFragment.child;
|
||||
while (hiddenChild !== null) {
|
||||
treeBaseDuration += hiddenChild.treeBaseDuration;
|
||||
hiddenChild = hiddenChild.sibling;
|
||||
}
|
||||
primaryChildFragment.treeBaseDuration = treeBaseDuration;
|
||||
}
|
||||
|
||||
// Create a fragment from the fallback children, too.
|
||||
@@ -1298,49 +1326,6 @@ function updateSuspenseComponent(
|
||||
return next;
|
||||
}
|
||||
|
||||
function reuseProgressedPrimaryChild(
|
||||
workInProgress: Fiber,
|
||||
primaryChildFragment: Fiber,
|
||||
progressedChild: Fiber | null,
|
||||
) {
|
||||
// This function is only called outside concurrent mode. Usually, if a work-
|
||||
// in-progress primary tree suspends, we throw it out and revert back to
|
||||
// current. Outside concurrent mode, though, we commit the suspended work-in-
|
||||
// progress, even though it didn't complete. This function reuses the children
|
||||
// and transfers the effects.
|
||||
let child = (primaryChildFragment.child = progressedChild);
|
||||
while (child !== null) {
|
||||
// Ensure that the first and last effect of the parent corresponds
|
||||
// to the children's first and last effect.
|
||||
if (primaryChildFragment.firstEffect === null) {
|
||||
primaryChildFragment.firstEffect = child.firstEffect;
|
||||
}
|
||||
if (child.lastEffect !== null) {
|
||||
if (primaryChildFragment.lastEffect !== null) {
|
||||
primaryChildFragment.lastEffect.nextEffect = child.firstEffect;
|
||||
}
|
||||
primaryChildFragment.lastEffect = child.lastEffect;
|
||||
}
|
||||
|
||||
// Append all the effects of the subtree and this fiber onto the effect
|
||||
// list of the parent. The completion order of the children affects the
|
||||
// side-effect order.
|
||||
if (child.effectTag > PerformedWork) {
|
||||
if (primaryChildFragment.lastEffect !== null) {
|
||||
primaryChildFragment.lastEffect.nextEffect = child;
|
||||
} else {
|
||||
primaryChildFragment.firstEffect = child;
|
||||
}
|
||||
primaryChildFragment.lastEffect = child;
|
||||
}
|
||||
child.return = primaryChildFragment;
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
workInProgress.firstEffect = primaryChildFragment.firstEffect;
|
||||
workInProgress.lastEffect = primaryChildFragment.lastEffect;
|
||||
}
|
||||
|
||||
function updatePortalComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
|
||||
@@ -558,6 +558,16 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
|
||||
} else {
|
||||
unhideTextInstance(instance, node.memoizedProps);
|
||||
}
|
||||
} else if (
|
||||
node.tag === SuspenseComponent &&
|
||||
node.memoizedState !== null
|
||||
) {
|
||||
// Found a nested Suspense component that timed out. Skip over the
|
||||
// primary child fragment, which should remain hidden.
|
||||
const fallbackChildFragment: Fiber = (node.child: any).sibling;
|
||||
fallbackChildFragment.return = node;
|
||||
node = fallbackChildFragment;
|
||||
continue;
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
Update,
|
||||
NoEffect,
|
||||
DidCapture,
|
||||
Deletion,
|
||||
} from 'shared/ReactSideEffectTags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
@@ -700,12 +701,33 @@ function completeWork(
|
||||
if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
|
||||
// Something suspended. Re-render with the fallback children.
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
workInProgress.firstEffect = workInProgress.lastEffect = null;
|
||||
// Do not reset the effect list.
|
||||
return workInProgress;
|
||||
}
|
||||
|
||||
const nextDidTimeout = nextState !== null;
|
||||
const prevDidTimeout = current !== null && current.memoizedState !== null;
|
||||
|
||||
if (current !== null && !nextDidTimeout && prevDidTimeout) {
|
||||
// We just switched from the fallback to the normal children. Delete
|
||||
// the fallback.
|
||||
// TODO: Would it be better to store the fallback fragment on
|
||||
// the stateNode during the begin phase?
|
||||
const currentFallbackChild: Fiber | null = (current.child: any).sibling;
|
||||
if (currentFallbackChild !== null) {
|
||||
// Deletions go at the beginning of the return fiber's effect list
|
||||
const first = workInProgress.firstEffect;
|
||||
if (first !== null) {
|
||||
workInProgress.firstEffect = currentFallbackChild;
|
||||
currentFallbackChild.nextEffect = first;
|
||||
} else {
|
||||
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
|
||||
currentFallbackChild.nextEffect = null;
|
||||
}
|
||||
currentFallbackChild.effectTag = Deletion;
|
||||
}
|
||||
}
|
||||
|
||||
// The children either timed out after previously being visible, or
|
||||
// were restored after previously being hidden. Schedule an effect
|
||||
// to update their visiblity.
|
||||
|
||||
118
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
118
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
@@ -180,11 +180,11 @@ let warnAboutInvalidUpdates;
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
// Provide explicit error message when production+profiling bundle of e.g. react-dom
|
||||
// is used with production (non-profiling) bundle of schedule/tracing
|
||||
// is used with production (non-profiling) bundle of scheduler/tracing
|
||||
invariant(
|
||||
__interactionsRef != null && __interactionsRef.current != null,
|
||||
'It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) ' +
|
||||
'without also replacing the `schedule/tracing` module with `schedule/tracing-profiling`. ' +
|
||||
'without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. ' +
|
||||
'Your bundler might have a setting for aliasing both modules. ' +
|
||||
'Learn more at http://fb.me/react-profiling',
|
||||
);
|
||||
@@ -993,7 +993,6 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
|
||||
if (nextUnitOfWork !== null) {
|
||||
// Completing this fiber spawned new work. Work on that next.
|
||||
nextUnitOfWork.firstEffect = nextUnitOfWork.lastEffect = null;
|
||||
return nextUnitOfWork;
|
||||
}
|
||||
|
||||
@@ -1050,9 +1049,18 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
|
||||
// Record the render duration for the fiber that errored.
|
||||
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
|
||||
|
||||
// Include the time spent working on failed children before continuing.
|
||||
let actualDuration = workInProgress.actualDuration;
|
||||
let child = workInProgress.child;
|
||||
while (child !== null) {
|
||||
actualDuration += child.actualDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
workInProgress.actualDuration = actualDuration;
|
||||
}
|
||||
|
||||
// This fiber did not complete because something threw. Pop values off
|
||||
@@ -1077,19 +1085,6 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
|
||||
}
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Include the time spent working on failed children before continuing.
|
||||
if (next.mode & ProfileMode) {
|
||||
let actualDuration = next.actualDuration;
|
||||
let child = next.child;
|
||||
while (child !== null) {
|
||||
actualDuration += child.actualDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
next.actualDuration = actualDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// If completing this work spawned new work, do that next. We'll come
|
||||
// back here again.
|
||||
// Since we're restarting, remove anything that is not a host effect
|
||||
@@ -1315,6 +1310,12 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
|
||||
didFatal = true;
|
||||
onUncaughtError(thrownValue);
|
||||
} else {
|
||||
if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) {
|
||||
// Record the time spent rendering before an error was thrown.
|
||||
// This avoids inaccurate Profiler durations in the case of a suspended render.
|
||||
stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// Reset global debug state
|
||||
// We assume this is defined in DEV
|
||||
@@ -1746,6 +1747,46 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
if (root !== null) {
|
||||
const interactions = __interactionsRef.current;
|
||||
if (interactions.size > 0) {
|
||||
const pendingInteractionMap = root.pendingInteractionMap;
|
||||
const pendingInteractions = pendingInteractionMap.get(expirationTime);
|
||||
if (pendingInteractions != null) {
|
||||
interactions.forEach(interaction => {
|
||||
if (!pendingInteractions.has(interaction)) {
|
||||
// Update the pending async work count for previously unscheduled interaction.
|
||||
interaction.__count++;
|
||||
}
|
||||
|
||||
pendingInteractions.add(interaction);
|
||||
});
|
||||
} else {
|
||||
pendingInteractionMap.set(expirationTime, new Set(interactions));
|
||||
|
||||
// Update the pending async work count for the current interactions.
|
||||
interactions.forEach(interaction => {
|
||||
interaction.__count++;
|
||||
});
|
||||
}
|
||||
|
||||
const subscriber = __subscriberRef.current;
|
||||
if (subscriber !== null) {
|
||||
const threadID = computeThreadID(
|
||||
expirationTime,
|
||||
root.interactionThreadID,
|
||||
);
|
||||
subscriber.onWorkScheduled(interactions, threadID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
|
||||
const root = scheduleWorkToRoot(fiber, expirationTime);
|
||||
if (root === null) {
|
||||
if (__DEV__) {
|
||||
switch (fiber.tag) {
|
||||
@@ -1760,49 +1801,6 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
const interactions = __interactionsRef.current;
|
||||
if (interactions.size > 0) {
|
||||
const pendingInteractionMap = root.pendingInteractionMap;
|
||||
const pendingInteractions = pendingInteractionMap.get(expirationTime);
|
||||
if (pendingInteractions != null) {
|
||||
interactions.forEach(interaction => {
|
||||
if (!pendingInteractions.has(interaction)) {
|
||||
// Update the pending async work count for previously unscheduled interaction.
|
||||
interaction.__count++;
|
||||
}
|
||||
|
||||
pendingInteractions.add(interaction);
|
||||
});
|
||||
} else {
|
||||
pendingInteractionMap.set(expirationTime, new Set(interactions));
|
||||
|
||||
// Update the pending async work count for the current interactions.
|
||||
interactions.forEach(interaction => {
|
||||
interaction.__count++;
|
||||
});
|
||||
}
|
||||
|
||||
const subscriber = __subscriberRef.current;
|
||||
if (subscriber !== null) {
|
||||
const threadID = computeThreadID(
|
||||
expirationTime,
|
||||
root.interactionThreadID,
|
||||
);
|
||||
subscriber.onWorkScheduled(interactions, threadID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
|
||||
const root = scheduleWorkToRoot(fiber, expirationTime);
|
||||
if (root === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ import {
|
||||
LOW_PRIORITY_EXPIRATION,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {findEarliestOutstandingPriorityLevel} from './ReactFiberPendingPriority';
|
||||
import {reconcileChildren} from './ReactFiberBeginWork';
|
||||
|
||||
function createRootErrorUpdate(
|
||||
fiber: Fiber,
|
||||
@@ -238,20 +237,10 @@ function throwException(
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoEffect) {
|
||||
workInProgress.effectTag |= DidCapture;
|
||||
|
||||
// Unmount the source fiber's children
|
||||
const nextChildren = null;
|
||||
reconcileChildren(
|
||||
sourceFiber.alternate,
|
||||
sourceFiber,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
sourceFiber.effectTag &= ~Incomplete;
|
||||
|
||||
// We're going to commit this fiber even though it didn't complete.
|
||||
// But we shouldn't call any lifecycle methods or callbacks. Remove
|
||||
// all lifecycle effect tags.
|
||||
sourceFiber.effectTag &= ~LifecycleEffectMask;
|
||||
sourceFiber.effectTag &= ~(LifecycleEffectMask | Incomplete);
|
||||
|
||||
if (sourceFiber.tag === ClassComponent) {
|
||||
const current = sourceFiber.alternate;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let Suspense;
|
||||
|
||||
describe('memo', () => {
|
||||
beforeEach(() => {
|
||||
@@ -23,6 +24,7 @@ describe('memo', () => {
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
({Suspense} = React);
|
||||
});
|
||||
|
||||
function span(prop) {
|
||||
@@ -38,6 +40,42 @@ describe('memo', () => {
|
||||
return {default: result};
|
||||
}
|
||||
|
||||
it('warns when giving a ref (simple)', async () => {
|
||||
// This test lives outside sharedTests because the wrappers don't forward
|
||||
// refs properly, and they end up affecting the current owner which is used
|
||||
// by the warning (making the messages not line up).
|
||||
function App() {
|
||||
return null;
|
||||
}
|
||||
App = React.memo(App);
|
||||
function Outer() {
|
||||
return <App ref={() => {}} />;
|
||||
}
|
||||
ReactNoop.render(<Outer />);
|
||||
expect(ReactNoop.flush).toWarnDev([
|
||||
'Warning: Function components cannot be given refs. Attempts to access ' +
|
||||
'this ref will fail.',
|
||||
]);
|
||||
});
|
||||
|
||||
it('warns when giving a ref (complex)', async () => {
|
||||
// defaultProps means this won't use SimpleMemoComponent (as of this writing)
|
||||
// SimpleMemoComponent is unobservable tho, so we can't check :)
|
||||
function App() {
|
||||
return null;
|
||||
}
|
||||
App.defaultProps = {};
|
||||
App = React.memo(App);
|
||||
function Outer() {
|
||||
return <App ref={() => {}} />;
|
||||
}
|
||||
ReactNoop.render(<Outer />);
|
||||
expect(ReactNoop.flush).toWarnDev([
|
||||
'Warning: Function components cannot be given refs. Attempts to access ' +
|
||||
'this ref will fail.',
|
||||
]);
|
||||
});
|
||||
|
||||
// Tests should run against both the lazy and non-lazy versions of `memo`.
|
||||
// To make the tests work for both versions, we wrap the non-lazy version in
|
||||
// a lazy function component.
|
||||
@@ -56,8 +94,6 @@ describe('memo', () => {
|
||||
function sharedTests(label, memo) {
|
||||
describe(`${label}`, () => {
|
||||
it('bails out on props equality', async () => {
|
||||
const {Suspense} = React;
|
||||
|
||||
function Counter({count}) {
|
||||
return <Text text={count} />;
|
||||
}
|
||||
@@ -93,8 +129,6 @@ describe('memo', () => {
|
||||
});
|
||||
|
||||
it("does not bail out if there's a context change", async () => {
|
||||
const {Suspense} = React;
|
||||
|
||||
const CountContext = React.createContext(0);
|
||||
|
||||
function readContext(Context) {
|
||||
@@ -142,8 +176,6 @@ describe('memo', () => {
|
||||
});
|
||||
|
||||
it('accepts custom comparison function', async () => {
|
||||
const {Suspense} = React;
|
||||
|
||||
function Counter({count}) {
|
||||
return <Text text={count} />;
|
||||
}
|
||||
@@ -184,8 +216,6 @@ describe('memo', () => {
|
||||
});
|
||||
|
||||
it('supports non-pure class components', async () => {
|
||||
const {Suspense} = React;
|
||||
|
||||
class CounterInner extends React.Component {
|
||||
static defaultProps = {suffix: '!'};
|
||||
render() {
|
||||
|
||||
@@ -779,5 +779,149 @@ describe('ReactSuspense', () => {
|
||||
expect(root).toFlushAndYield(['Child 1', 'Child 2']);
|
||||
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
|
||||
});
|
||||
|
||||
it('reuses effects, including deletions, from the suspended tree', () => {
|
||||
const {useState} = React;
|
||||
|
||||
let setTab;
|
||||
function App() {
|
||||
const [tab, _setTab] = useState(0);
|
||||
setTab = _setTab;
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText key={tab} text={'Tab: ' + tab} ms={1000} />
|
||||
<Text key={tab + 'sibling'} text=" + sibling" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Suspend! [Tab: 0]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [Tab: 0]',
|
||||
'Tab: 0',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
|
||||
|
||||
setTab(1);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Suspend! [Tab: 1]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [Tab: 1]',
|
||||
'Tab: 1',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
|
||||
|
||||
setTab(2);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Suspend! [Tab: 2]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [Tab: 2]',
|
||||
'Tab: 2',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
|
||||
});
|
||||
|
||||
it('does not warn if an mounted component is pinged', () => {
|
||||
const {useState} = React;
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
|
||||
let setStep;
|
||||
function UpdatingText({text, ms}) {
|
||||
const [step, _setStep] = useState(0);
|
||||
setStep = _setStep;
|
||||
const fullText = `${text}:${step}`;
|
||||
try {
|
||||
TextResource.read([fullText, ms]);
|
||||
ReactTestRenderer.unstable_yield(fullText);
|
||||
return fullText;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
ReactTestRenderer.unstable_yield(`Suspend! [${fullText}]`);
|
||||
} else {
|
||||
ReactTestRenderer.unstable_yield(`Error! [${fullText}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<UpdatingText text="A" ms={1000} />
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(ReactTestRenderer).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [A:0]',
|
||||
'A:0',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('A:0');
|
||||
|
||||
setStep(1);
|
||||
expect(ReactTestRenderer).toHaveYielded(['Suspend! [A:1]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
root.update(null);
|
||||
expect(root).toFlushWithoutYielding();
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
|
||||
it('#14162', () => {
|
||||
const {lazy} = React;
|
||||
|
||||
function Hello() {
|
||||
return <span>hello</span>;
|
||||
}
|
||||
|
||||
async function fetchComponent() {
|
||||
return new Promise(r => {
|
||||
// simulating a delayed import() call
|
||||
setTimeout(r, 1000, {default: Hello});
|
||||
});
|
||||
}
|
||||
|
||||
const LazyHello = lazy(fetchComponent);
|
||||
|
||||
class App extends React.Component {
|
||||
state = {render: false};
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => this.setState({render: true}));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Suspense fallback={<span>loading...</span>}>
|
||||
{this.state.render && <LazyHello />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
|
||||
root.update(<App name="world" />);
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
let React;
|
||||
let Suspense;
|
||||
let ReactTestRenderer;
|
||||
let ReactFeatureFlags;
|
||||
let Random;
|
||||
|
||||
const SEED = 0;
|
||||
|
||||
const prettyFormatPkg = require('pretty-format');
|
||||
|
||||
function prettyFormat(thing) {
|
||||
return prettyFormatPkg(thing, {
|
||||
plugins: [
|
||||
prettyFormatPkg.plugins.ReactElement,
|
||||
prettyFormatPkg.plugins.ReactTestComponent,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
describe('ReactSuspenseFuzz', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
ReactFeatureFlags.enableHooks = true;
|
||||
React = require('react');
|
||||
Suspense = React.Suspense;
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Random = require('random-seed');
|
||||
});
|
||||
|
||||
function createFuzzer() {
|
||||
const {useState, useContext, useLayoutEffect} = React;
|
||||
|
||||
const ShouldSuspendContext = React.createContext(true);
|
||||
|
||||
let pendingTasks = new Set();
|
||||
let cache = new Map();
|
||||
|
||||
function resetCache() {
|
||||
pendingTasks = new Set();
|
||||
cache = new Map();
|
||||
}
|
||||
|
||||
function Container({children, updates}) {
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (updates !== undefined) {
|
||||
const cleanUps = new Set();
|
||||
updates.forEach(({remountAfter}, i) => {
|
||||
const task = {
|
||||
label: `Remount childen after ${remountAfter}ms`,
|
||||
};
|
||||
const timeoutID = setTimeout(() => {
|
||||
pendingTasks.delete(task);
|
||||
ReactTestRenderer.unstable_yield(task.label);
|
||||
setStep(i + 1);
|
||||
}, remountAfter);
|
||||
pendingTasks.add(task);
|
||||
cleanUps.add(() => {
|
||||
pendingTasks.delete(task);
|
||||
clearTimeout(timeoutID);
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
cleanUps.forEach(cleanUp => cleanUp());
|
||||
};
|
||||
}
|
||||
},
|
||||
[updates],
|
||||
);
|
||||
|
||||
return <React.Fragment key={step}>{children}</React.Fragment>;
|
||||
}
|
||||
|
||||
function Text({text, initialDelay = 0, updates}) {
|
||||
const [[step, delay], setStep] = useState([0, initialDelay]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (updates !== undefined) {
|
||||
const cleanUps = new Set();
|
||||
updates.forEach(({beginAfter, suspendFor}, i) => {
|
||||
const task = {
|
||||
label: `Update ${beginAfter}ms after mount and suspend for ${suspendFor}ms [${text}]`,
|
||||
};
|
||||
const timeoutID = setTimeout(() => {
|
||||
pendingTasks.delete(task);
|
||||
ReactTestRenderer.unstable_yield(task.label);
|
||||
setStep([i + 1, suspendFor]);
|
||||
}, beginAfter);
|
||||
pendingTasks.add(task);
|
||||
cleanUps.add(() => {
|
||||
pendingTasks.delete(task);
|
||||
clearTimeout(timeoutID);
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
cleanUps.forEach(cleanUp => cleanUp());
|
||||
};
|
||||
}
|
||||
},
|
||||
[updates],
|
||||
);
|
||||
|
||||
const fullText = `${text}:${step}`;
|
||||
|
||||
const shouldSuspend = useContext(ShouldSuspendContext);
|
||||
|
||||
let resolvedText;
|
||||
if (shouldSuspend && delay > 0) {
|
||||
resolvedText = cache.get(fullText);
|
||||
if (resolvedText === undefined) {
|
||||
const thenable = {
|
||||
then(resolve) {
|
||||
const task = {label: `Promise resolved [${fullText}]`};
|
||||
pendingTasks.add(task);
|
||||
setTimeout(() => {
|
||||
cache.set(fullText, fullText);
|
||||
pendingTasks.delete(task);
|
||||
ReactTestRenderer.unstable_yield(task.label);
|
||||
resolve();
|
||||
}, delay);
|
||||
},
|
||||
};
|
||||
cache.set(fullText, thenable);
|
||||
ReactTestRenderer.unstable_yield(`Suspended! [${fullText}]`);
|
||||
throw thenable;
|
||||
} else if (typeof resolvedText.then === 'function') {
|
||||
const thenable = resolvedText;
|
||||
ReactTestRenderer.unstable_yield(`Suspended! [${fullText}]`);
|
||||
throw thenable;
|
||||
}
|
||||
} else {
|
||||
resolvedText = fullText;
|
||||
}
|
||||
|
||||
ReactTestRenderer.unstable_yield(resolvedText);
|
||||
return resolvedText;
|
||||
}
|
||||
|
||||
function renderToRoot(
|
||||
root,
|
||||
children,
|
||||
{shouldSuspend} = {shouldSuspend: true},
|
||||
) {
|
||||
root.update(
|
||||
<ShouldSuspendContext.Provider value={shouldSuspend}>
|
||||
{children}
|
||||
</ShouldSuspendContext.Provider>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
|
||||
let elapsedTime = 0;
|
||||
while (pendingTasks && pendingTasks.size > 0) {
|
||||
if ((elapsedTime += 1000) > 1000000) {
|
||||
throw new Error('Something did not resolve properly.');
|
||||
}
|
||||
jest.advanceTimersByTime(1000);
|
||||
root.unstable_flushAll();
|
||||
}
|
||||
|
||||
return root.toJSON();
|
||||
}
|
||||
|
||||
function testResolvedOutput(unwrappedChildren) {
|
||||
const children = (
|
||||
<Suspense fallback="Loading...">{unwrappedChildren}</Suspense>
|
||||
);
|
||||
|
||||
const expectedRoot = ReactTestRenderer.create(null);
|
||||
const expectedOutput = renderToRoot(expectedRoot, children, {
|
||||
shouldSuspend: false,
|
||||
});
|
||||
expectedRoot.unmount();
|
||||
|
||||
resetCache();
|
||||
const syncRoot = ReactTestRenderer.create(null);
|
||||
const syncOutput = renderToRoot(syncRoot, children);
|
||||
expect(syncOutput).toEqual(expectedOutput);
|
||||
syncRoot.unmount();
|
||||
|
||||
resetCache();
|
||||
const concurrentRoot = ReactTestRenderer.create(null, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
const concurrentOutput = renderToRoot(concurrentRoot, children);
|
||||
expect(concurrentOutput).toEqual(expectedOutput);
|
||||
concurrentRoot.unmount();
|
||||
concurrentRoot.unstable_flushAll();
|
||||
|
||||
ReactTestRenderer.unstable_clearYields();
|
||||
}
|
||||
|
||||
function pickRandomWeighted(rand, options) {
|
||||
let totalWeight = 0;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
totalWeight += options[i].weight;
|
||||
}
|
||||
let remainingWeight = rand.floatBetween(0, totalWeight);
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const {value, weight} = options[i];
|
||||
remainingWeight -= weight;
|
||||
if (remainingWeight <= 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateTestCase(rand, numberOfElements) {
|
||||
let remainingElements = numberOfElements;
|
||||
|
||||
function createRandomChild(hasSibling) {
|
||||
const possibleActions = [
|
||||
{value: 'return', weight: 1},
|
||||
{value: 'text', weight: 1},
|
||||
];
|
||||
|
||||
if (hasSibling) {
|
||||
possibleActions.push({value: 'container', weight: 1});
|
||||
possibleActions.push({value: 'suspense', weight: 1});
|
||||
}
|
||||
|
||||
const action = pickRandomWeighted(rand, possibleActions);
|
||||
|
||||
switch (action) {
|
||||
case 'text': {
|
||||
remainingElements--;
|
||||
|
||||
const numberOfUpdates = pickRandomWeighted(rand, [
|
||||
{value: 0, weight: 8},
|
||||
{value: 1, weight: 4},
|
||||
{value: 2, weight: 1},
|
||||
]);
|
||||
|
||||
let updates = [];
|
||||
for (let i = 0; i < numberOfUpdates; i++) {
|
||||
updates.push({
|
||||
beginAfter: rand.intBetween(0, 10000),
|
||||
suspendFor: rand.intBetween(0, 10000),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
text={(remainingElements + 9).toString(36).toUpperCase()}
|
||||
initialDelay={rand.intBetween(0, 10000)}
|
||||
updates={updates}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'container': {
|
||||
const numberOfUpdates = pickRandomWeighted(rand, [
|
||||
{value: 0, weight: 8},
|
||||
{value: 1, weight: 4},
|
||||
{value: 2, weight: 1},
|
||||
]);
|
||||
|
||||
let updates = [];
|
||||
for (let i = 0; i < numberOfUpdates; i++) {
|
||||
updates.push({
|
||||
remountAfter: rand.intBetween(0, 10000),
|
||||
});
|
||||
}
|
||||
|
||||
remainingElements--;
|
||||
const children = createRandomChildren(3);
|
||||
return React.createElement(Container, {updates}, ...children);
|
||||
}
|
||||
case 'suspense': {
|
||||
remainingElements--;
|
||||
const children = createRandomChildren(3);
|
||||
|
||||
const maxDuration = pickRandomWeighted(rand, [
|
||||
{value: undefined, weight: 1},
|
||||
{value: rand.intBetween(0, 5000), weight: 1},
|
||||
]);
|
||||
|
||||
const fallbackType = pickRandomWeighted(rand, [
|
||||
{value: 'none', weight: 1},
|
||||
{value: 'normal', weight: 1},
|
||||
{value: 'nested suspense', weight: 1},
|
||||
]);
|
||||
|
||||
let fallback;
|
||||
if (fallbackType === 'normal') {
|
||||
fallback = 'Loading...';
|
||||
} else if (fallbackType === 'nested suspense') {
|
||||
fallback = React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
...createRandomChildren(3),
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
Suspense,
|
||||
{maxDuration, fallback},
|
||||
...children,
|
||||
);
|
||||
}
|
||||
case 'return':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createRandomChildren(limit) {
|
||||
const children = [];
|
||||
while (remainingElements > 0 && children.length < limit) {
|
||||
children.push(createRandomChild(children.length > 0));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
const children = createRandomChildren(Infinity);
|
||||
return React.createElement(React.Fragment, null, ...children);
|
||||
}
|
||||
|
||||
return {Container, Text, testResolvedOutput, generateTestCase};
|
||||
}
|
||||
|
||||
it('basic cases', () => {
|
||||
// This demonstrates that the testing primitives work
|
||||
const {Container, Text, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
<Container updates={[{remountAfter: 150}]}>
|
||||
<Text
|
||||
text="Hi"
|
||||
initialDelay={2000}
|
||||
updates={[{beginAfter: 100, suspendFor: 200}]}
|
||||
/>
|
||||
</Container>,
|
||||
);
|
||||
});
|
||||
|
||||
it('hard-coded cases', () => {
|
||||
const {Text, testResolvedOutput} = createFuzzer();
|
||||
|
||||
testResolvedOutput(
|
||||
<React.Fragment>
|
||||
<Text
|
||||
initialDelay={20}
|
||||
text="A"
|
||||
updates={[{beginAfter: 10, suspendFor: 20}]}
|
||||
/>
|
||||
<Suspense fallback="Loading... (B)">
|
||||
<Text
|
||||
initialDelay={10}
|
||||
text="B"
|
||||
updates={[{beginAfter: 30, suspendFor: 50}]}
|
||||
/>
|
||||
<Text text="C" />
|
||||
</Suspense>
|
||||
</React.Fragment>,
|
||||
);
|
||||
});
|
||||
|
||||
it('generative tests', () => {
|
||||
const {generateTestCase, testResolvedOutput} = createFuzzer();
|
||||
|
||||
const rand = Random.create(SEED);
|
||||
|
||||
const NUMBER_OF_TEST_CASES = 500;
|
||||
const ELEMENTS_PER_CASE = 12;
|
||||
|
||||
for (let i = 0; i < NUMBER_OF_TEST_CASES; i++) {
|
||||
const randomTestCase = generateTestCase(rand, ELEMENTS_PER_CASE);
|
||||
try {
|
||||
testResolvedOutput(randomTestCase);
|
||||
} catch (e) {
|
||||
console.log(`
|
||||
Failed fuzzy test case:
|
||||
|
||||
${prettyFormat(randomTestCase)}
|
||||
`);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,9 @@ runPlaceholderTests('ReactSuspensePlaceholder (persistence)', () =>
|
||||
);
|
||||
|
||||
function runPlaceholderTests(suiteLabel, loadReactNoop) {
|
||||
let advanceTimeBy;
|
||||
let mockNow;
|
||||
let Profiler;
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let ReactFeatureFlags;
|
||||
@@ -27,13 +30,24 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
|
||||
describe(suiteLabel, () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
let currentTime = 0;
|
||||
mockNow = jest.fn().mockImplementation(() => currentTime);
|
||||
global.Date.now = mockNow;
|
||||
advanceTimeBy = amount => {
|
||||
currentTime += amount;
|
||||
};
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableProfilerTimer = true;
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
ReactTestRenderer.unstable_setNowImplementation(mockNow);
|
||||
ReactCache = require('react-cache');
|
||||
|
||||
Profiler = React.unstable_Profiler;
|
||||
Suspense = React.Suspense;
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
|
||||
@@ -83,15 +97,16 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
|
||||
textResourceShouldFail = false;
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
ReactTestRenderer.unstable_yield(props.text);
|
||||
return props.text;
|
||||
function Text({fakeRenderDuration = 0, text = 'Text'}) {
|
||||
advanceTimeBy(fakeRenderDuration);
|
||||
ReactTestRenderer.unstable_yield(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function AsyncText(props) {
|
||||
const text = props.text;
|
||||
function AsyncText({fakeRenderDuration = 0, ms, text}) {
|
||||
advanceTimeBy(fakeRenderDuration);
|
||||
try {
|
||||
TextResource.read([props.text, props.ms]);
|
||||
TextResource.read([text, ms]);
|
||||
ReactTestRenderer.unstable_yield(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
@@ -215,5 +230,229 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
|
||||
// given a `hidden` prop.
|
||||
expect(root).toMatchRenderedOutput('AB2C');
|
||||
});
|
||||
|
||||
describe('profiler durations', () => {
|
||||
let App;
|
||||
let onRender;
|
||||
|
||||
beforeEach(() => {
|
||||
// Order of parameters: id, phase, actualDuration, treeBaseDuration
|
||||
onRender = jest.fn();
|
||||
|
||||
const Fallback = () => {
|
||||
ReactTestRenderer.unstable_yield('Fallback');
|
||||
advanceTimeBy(10);
|
||||
return 'Loading...';
|
||||
};
|
||||
|
||||
const Suspending = () => {
|
||||
ReactTestRenderer.unstable_yield('Suspending');
|
||||
advanceTimeBy(2);
|
||||
return <AsyncText ms={1000} text="Loaded" fakeRenderDuration={1} />;
|
||||
};
|
||||
|
||||
App = ({shouldSuspend, text = 'Text', textRenderDuration = 5}) => {
|
||||
ReactTestRenderer.unstable_yield('App');
|
||||
return (
|
||||
<Profiler id="root" onRender={onRender}>
|
||||
<Suspense maxDuration={500} fallback={<Fallback />}>
|
||||
{shouldSuspend && <Suspending />}
|
||||
<Text fakeRenderDuration={textRenderDuration} text={text} />
|
||||
</Suspense>
|
||||
</Profiler>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
describe('when suspending during mount', () => {
|
||||
it('properly accounts for base durations when a suspended times out in a sync tree', () => {
|
||||
const root = ReactTestRenderer.create(<App shouldSuspend={true} />);
|
||||
expect(root.toJSON()).toEqual(['Loading...']);
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Initial mount only shows the "Loading..." Fallback.
|
||||
// The treeBaseDuration then should be 10ms spent rendering Fallback,
|
||||
// but the actualDuration should also include the 8ms spent rendering the hidden tree.
|
||||
expect(onRender.mock.calls[0][2]).toBe(18);
|
||||
expect(onRender.mock.calls[0][3]).toBe(10);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(root.toJSON()).toEqual(['Loaded', 'Text']);
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// the baseDuration should only include the 1ms re-rendering AsyncText,
|
||||
// but the treeBaseDuration should include the full 8ms spent in the tree.
|
||||
expect(onRender.mock.calls[1][2]).toBe(1);
|
||||
expect(onRender.mock.calls[1][3]).toBe(8);
|
||||
});
|
||||
|
||||
it('properly accounts for base durations when a suspended times out in a concurrent tree', () => {
|
||||
const root = ReactTestRenderer.create(<App shouldSuspend={true} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(root).toFlushAndYield([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Text',
|
||||
'Fallback',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Show the fallback UI.
|
||||
jest.advanceTimersByTime(750);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Initial mount only shows the "Loading..." Fallback.
|
||||
// The treeBaseDuration then should be 10ms spent rendering Fallback,
|
||||
// but the actualDuration should also include the 8ms spent rendering the hidden tree.
|
||||
expect(onRender.mock.calls[0][2]).toBe(18);
|
||||
expect(onRender.mock.calls[0][3]).toBe(10);
|
||||
|
||||
// Resolve the pending promise.
|
||||
jest.advanceTimersByTime(250);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
]);
|
||||
expect(root).toFlushAndYield(['Suspending', 'Loaded', 'Text']);
|
||||
expect(root).toMatchRenderedOutput('LoadedText');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 8ms re-rendering Suspending and AsyncText.
|
||||
expect(onRender.mock.calls[1][2]).toBe(8);
|
||||
expect(onRender.mock.calls[1][3]).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when suspending during update', () => {
|
||||
it('properly accounts for base durations when a suspended times out in a sync tree', () => {
|
||||
const root = ReactTestRenderer.create(
|
||||
<App shouldSuspend={false} textRenderDuration={5} />,
|
||||
);
|
||||
expect(root.toJSON()).toEqual('Text');
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Initial mount only shows the "Text" text.
|
||||
// It should take 5ms to render.
|
||||
expect(onRender.mock.calls[0][2]).toBe(5);
|
||||
expect(onRender.mock.calls[0][3]).toBe(5);
|
||||
|
||||
root.update(<App shouldSuspend={true} textRenderDuration={5} />);
|
||||
expect(root.toJSON()).toEqual(['Loading...']);
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// The suspense update should only show the "Loading..." Fallback.
|
||||
// Both durations should include 10ms spent rendering Fallback
|
||||
// plus the 8ms rendering the (hidden) components.
|
||||
expect(onRender.mock.calls[1][2]).toBe(18);
|
||||
expect(onRender.mock.calls[1][3]).toBe(18);
|
||||
|
||||
root.update(
|
||||
<App shouldSuspend={true} text="New" textRenderDuration={6} />,
|
||||
);
|
||||
expect(root.toJSON()).toEqual(['Loading...']);
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
|
||||
// If we force another update while still timed out,
|
||||
// but this time the Text component took 1ms longer to render.
|
||||
// This should impact both actualDuration and treeBaseDuration.
|
||||
expect(onRender.mock.calls[2][2]).toBe(19);
|
||||
expect(onRender.mock.calls[2][3]).toBe(19);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
// TODO Change expected onRender count to 4.
|
||||
// At the moment, every time we suspended while rendering will cause a commit.
|
||||
// This will probably change in the future, but that's why there are two new ones.
|
||||
expect(root.toJSON()).toEqual(['Loaded', 'New']);
|
||||
expect(onRender).toHaveBeenCalledTimes(5);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// the baseDuration should only include the 1ms re-rendering AsyncText,
|
||||
// but the treeBaseDuration should include the full 9ms spent in the tree.
|
||||
expect(onRender.mock.calls[3][2]).toBe(1);
|
||||
expect(onRender.mock.calls[3][3]).toBe(9);
|
||||
|
||||
// TODO Remove these assertions once this commit is gone.
|
||||
// For now, there was no actual work done during this commit; see above comment.
|
||||
expect(onRender.mock.calls[4][2]).toBe(0);
|
||||
expect(onRender.mock.calls[4][3]).toBe(9);
|
||||
});
|
||||
|
||||
it('properly accounts for base durations when a suspended times out in a concurrent tree', () => {
|
||||
const root = ReactTestRenderer.create(
|
||||
<App shouldSuspend={false} textRenderDuration={5} />,
|
||||
{
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(root).toFlushAndYield(['App', 'Text']);
|
||||
expect(root).toMatchRenderedOutput('Text');
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Initial mount only shows the "Text" text.
|
||||
// It should take 5ms to render.
|
||||
expect(onRender.mock.calls[0][2]).toBe(5);
|
||||
expect(onRender.mock.calls[0][3]).toBe(5);
|
||||
|
||||
root.update(<App shouldSuspend={true} textRenderDuration={5} />);
|
||||
expect(root).toFlushAndYield([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Text',
|
||||
'Fallback',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Text');
|
||||
|
||||
// Show the fallback UI.
|
||||
jest.advanceTimersByTime(750);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// The suspense update should only show the "Loading..." Fallback.
|
||||
// The actual duration should include 10ms spent rendering Fallback,
|
||||
// plus the 8ms render all of the hidden, suspended subtree.
|
||||
// But the tree base duration should only include 10ms spent rendering Fallback,
|
||||
// plus the 5ms rendering the previously committed version of the hidden tree.
|
||||
expect(onRender.mock.calls[1][2]).toBe(18);
|
||||
expect(onRender.mock.calls[1][3]).toBe(15);
|
||||
|
||||
// Update again while timed out.
|
||||
root.update(
|
||||
<App shouldSuspend={true} text="New" textRenderDuration={6} />,
|
||||
);
|
||||
expect(root).toFlushAndYield([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'New',
|
||||
'Fallback',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Resolve the pending promise.
|
||||
jest.advanceTimersByTime(250);
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
]);
|
||||
expect(root).toFlushAndYield(['App', 'Suspending', 'Loaded', 'New']);
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 6ms rendering Text,
|
||||
// the 2ms rendering Suspending, and the 1ms rendering AsyncText.
|
||||
expect(onRender.mock.calls[2][2]).toBe(9);
|
||||
expect(onRender.mock.calls[2][3]).toBe(9);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -937,6 +937,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
]);
|
||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||
<React.Fragment>
|
||||
<span hidden={true} prop="Step: 1" />
|
||||
<span hidden={true} prop="Sibling" />
|
||||
<span prop="Loading (1)" />
|
||||
<span prop="Loading (2)" />
|
||||
@@ -1060,6 +1061,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||
<React.Fragment>
|
||||
<span hidden={true} prop="Before" />
|
||||
<span hidden={true} prop="Async: 1" />
|
||||
<span hidden={true} prop="After" />
|
||||
<span prop="Loading..." />
|
||||
|
||||
@@ -1194,6 +1196,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||
<React.Fragment>
|
||||
<span hidden={true} prop="Before" />
|
||||
<span hidden={true} prop="Async: 1" />
|
||||
<span hidden={true} prop="After" />
|
||||
<span prop="Loading..." />
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
'use strict';
|
||||
|
||||
describe('ReactTracing', () => {
|
||||
it('should error if profiling renderer and non-profiling schedule/tracing bundles are combined', () => {
|
||||
it('should error if profiling renderer and non-profiling scheduler/tracing bundles are combined', () => {
|
||||
jest.resetModules();
|
||||
|
||||
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-test-renderer",
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"description": "React package for snapshot testing.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -17,11 +17,11 @@
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.1",
|
||||
"scheduler": "^0.11.0"
|
||||
"react-is": "^16.7.0-alpha.1",
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
"react": "^16.0.0 || 16.7.0-alpha.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "16.6.1",
|
||||
"version": "16.7.0-alpha.1",
|
||||
"homepage": "https://reactjs.org/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
@@ -24,7 +24,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.12.0-alpha.1"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -42,6 +42,9 @@ export function createContext<T>(
|
||||
// Secondary renderers store their context values on separate fields.
|
||||
_currentValue: defaultValue,
|
||||
_currentValue2: defaultValue,
|
||||
// Used to track how many concurrent renderers this context currently
|
||||
// supports within in a single renderer. Such as parallel server rendering.
|
||||
_threadCount: 0,
|
||||
// These are circular
|
||||
Provider: (null: any),
|
||||
Consumer: (null: any),
|
||||
@@ -98,6 +101,14 @@ export function createContext<T>(
|
||||
context._currentValue2 = _currentValue2;
|
||||
},
|
||||
},
|
||||
_threadCount: {
|
||||
get() {
|
||||
return context._threadCount;
|
||||
},
|
||||
set(_threadCount) {
|
||||
context._threadCount = _threadCount;
|
||||
},
|
||||
},
|
||||
Consumer: {
|
||||
get() {
|
||||
if (!hasWarnedAboutUsingNestedContextConsumers) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
(function(global, factory) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? (module.exports = factory(require('react')))
|
||||
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scheduler",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.0-alpha.1",
|
||||
"description": "Cooperative scheduler for the browser environment.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
var ImmediatePriority = 1;
|
||||
var UserBlockingPriority = 2;
|
||||
var NormalPriority = 3;
|
||||
var IdlePriority = 4;
|
||||
var LowPriority = 4;
|
||||
var IdlePriority = 5;
|
||||
|
||||
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
||||
// Math.pow(2, 30) - 1
|
||||
@@ -24,6 +25,7 @@ var IMMEDIATE_PRIORITY_TIMEOUT = -1;
|
||||
// Eventually times out
|
||||
var USER_BLOCKING_PRIORITY = 250;
|
||||
var NORMAL_PRIORITY_TIMEOUT = 5000;
|
||||
var LOW_PRIORITY_TIMEOUT = 10000;
|
||||
// Never times out
|
||||
var IDLE_PRIORITY = maxSigned31BitInt;
|
||||
|
||||
@@ -220,6 +222,7 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
|
||||
case ImmediatePriority:
|
||||
case UserBlockingPriority:
|
||||
case NormalPriority:
|
||||
case LowPriority:
|
||||
case IdlePriority:
|
||||
break;
|
||||
default:
|
||||
@@ -284,6 +287,9 @@ function unstable_scheduleCallback(callback, deprecated_options) {
|
||||
case IdlePriority:
|
||||
expirationTime = startTime + IDLE_PRIORITY;
|
||||
break;
|
||||
case LowPriority:
|
||||
expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
|
||||
break;
|
||||
case NormalPriority:
|
||||
default:
|
||||
expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
|
||||
@@ -655,6 +661,7 @@ export {
|
||||
UserBlockingPriority as unstable_UserBlockingPriority,
|
||||
NormalPriority as unstable_NormalPriority,
|
||||
IdlePriority as unstable_IdlePriority,
|
||||
LowPriority as unstable_LowPriority,
|
||||
unstable_runWithPriority,
|
||||
unstable_scheduleCallback,
|
||||
unstable_cancelCallback,
|
||||
|
||||
@@ -15,7 +15,7 @@ import invariant from 'shared/invariant';
|
||||
function shim(...args: any) {
|
||||
invariant(
|
||||
false,
|
||||
'The current renderer does not support hyration. ' +
|
||||
'The current renderer does not support hydration. ' +
|
||||
'This error is likely caused by a bug in React. ' +
|
||||
'Please file an issue.',
|
||||
);
|
||||
|
||||
@@ -59,6 +59,7 @@ export type ReactContext<T> = {
|
||||
|
||||
_currentValue: T,
|
||||
_currentValue2: T,
|
||||
_threadCount: number,
|
||||
|
||||
// DEV only
|
||||
_currentRenderer?: Object | null,
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: this is special because it gets imported during build.
|
||||
module.exports = '16.6.1';
|
||||
module.exports = '16.7.0-alpha.1';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
In most cases, the only two commands you might want to use are:
|
||||
|
||||
- `yarn start`
|
||||
- `yarn --cwd=../../ build core,dom-client --type=UMD_PROD && yarn start --skip-build`
|
||||
- `yarn --cwd=../../ build react/index,react-dom/index --type=UMD_PROD && yarn start --skip-build`
|
||||
|
||||
The first command will run benchmarks with all the default settings. A local and remote build will occur on React and ReactDOM UMD bundles, both local and remote repos will be run against all benchmarks.
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ async function buildBenchmarkBundlesFromGitRepo(
|
||||
async function buildReactBundles(reactPath = getDefaultReactPath(), skipBuild) {
|
||||
if (!skipBuild) {
|
||||
await executeCommand(
|
||||
`cd ${reactPath} && yarn && yarn build core,dom-client --type=UMD_PROD`
|
||||
`cd ${reactPath} && yarn && yarn build react/index,react-dom/index --type=UMD_PROD`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ set -e
|
||||
# the merge base by Dangerfile instead. See https://github.com/facebook/react/pull/12606.
|
||||
if [ -z "$CI_PULL_REQUEST" ]; then
|
||||
curl -o scripts/rollup/results.json http://react.zpao.com/builds/master/latest/results.json
|
||||
else
|
||||
# If build fails, cause danger to fail/abort too
|
||||
rm scripts/rollup/results.json
|
||||
fi
|
||||
|
||||
yarn build --extract-errors
|
||||
|
||||
11
scripts/circleci/pack_and_store_artifact.sh
Executable file
11
scripts/circleci/pack_and_store_artifact.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# NPM pack all modules to ensure we archive the correct set of files
|
||||
for dir in ./build/node_modules/* ; do
|
||||
npm pack "$dir"
|
||||
done
|
||||
|
||||
# Wrap everything in a single zip file for easy download by the publish script
|
||||
tar -zcvf ./node_modules.tgz ./*.tgz
|
||||
@@ -30,6 +30,7 @@ if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/danger')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/upload_build.sh')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/pack_and_store_artifact.sh')
|
||||
fi
|
||||
|
||||
if [ $((3 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
|
||||
@@ -300,5 +300,9 @@
|
||||
"298": "Hooks can only be called inside the body of a function component.",
|
||||
"299": "%s(...): Target container is not a DOM element.",
|
||||
"300": "Rendered fewer hooks than expected. This may be caused by an accidental early return statement.",
|
||||
"301": "Too many re-renders. React limits the number of renders to prevent an infinite loop."
|
||||
"301": "Too many re-renders. React limits the number of renders to prevent an infinite loop.",
|
||||
"302": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling",
|
||||
"303": "suspense fallback not found, something is broken",
|
||||
"304": "Maximum number of concurrent React renderers exceeded. This can happen if you are not properly destroying the Readable provided by React. Ensure that you call .destroy() on it if you no longer want to read from it, and did not read to the end. If you use .pipe() this should be automatic.",
|
||||
"305": "The current renderer does not support hydration. This error is likely caused by a bug in React. Please file an issue."
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const eslintrc = require('../../.eslintrc');
|
||||
|
||||
const ERROR = 2;
|
||||
|
||||
// We apply these settings to files that should run on Node.
|
||||
// They can't use JSX or ES6 modules, and must be in strict mode.
|
||||
// They can, however, use other ES6 features.
|
||||
|
||||
module.exports = Object.assign({}, eslintrc, {
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2017,
|
||||
sourceType: 'script',
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
},
|
||||
rules: Object.assign({}, eslintrc.rules, {
|
||||
'no-var': ERROR,
|
||||
strict: ERROR,
|
||||
}),
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const eslintrc = require('../../.eslintrc');
|
||||
|
||||
const ERROR = 2;
|
||||
|
||||
// We apply these settings to files that we ship through npm.
|
||||
// They must be ES5.
|
||||
|
||||
module.exports = Object.assign({}, eslintrc, {
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
sourceType: 'script',
|
||||
},
|
||||
rules: {
|
||||
strict: ERROR,
|
||||
},
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const eslintrc = require('../../.eslintrc');
|
||||
|
||||
const ERROR = 2;
|
||||
|
||||
// We apply these settings to the source files that get compiled.
|
||||
// They can use all features including JSX (but shouldn't use `var`).
|
||||
|
||||
module.exports = Object.assign({}, eslintrc, {
|
||||
rules: Object.assign({}, eslintrc.rules, {
|
||||
'no-var': ERROR,
|
||||
}),
|
||||
});
|
||||
@@ -10,7 +10,6 @@
|
||||
const minimatch = require('minimatch');
|
||||
const CLIEngine = require('eslint').CLIEngine;
|
||||
const listChangedFiles = require('../shared/listChangedFiles');
|
||||
const {es5Paths, esNextPaths} = require('../shared/pathsByLanguageVersion');
|
||||
|
||||
const allPaths = ['**/*.js'];
|
||||
|
||||
@@ -65,25 +64,10 @@ function runESLint({onlyChanged}) {
|
||||
if (typeof onlyChanged !== 'boolean') {
|
||||
throw new Error('Pass options.onlyChanged as a boolean.');
|
||||
}
|
||||
let errorCount = 0;
|
||||
let warningCount = 0;
|
||||
let output = '';
|
||||
[
|
||||
runESLintOnFilesWithOptions(allPaths, onlyChanged, {
|
||||
configFile: `${__dirname}/eslintrc.default.js`,
|
||||
ignorePattern: [...es5Paths, ...esNextPaths],
|
||||
}),
|
||||
runESLintOnFilesWithOptions(esNextPaths, onlyChanged, {
|
||||
configFile: `${__dirname}/eslintrc.esnext.js`,
|
||||
}),
|
||||
runESLintOnFilesWithOptions(es5Paths, onlyChanged, {
|
||||
configFile: `${__dirname}/eslintrc.es5.js`,
|
||||
}),
|
||||
].forEach(result => {
|
||||
errorCount += result.errorCount;
|
||||
warningCount += result.warningCount;
|
||||
output += result.output;
|
||||
});
|
||||
const {errorCount, warningCount, output} = runESLintOnFilesWithOptions(
|
||||
allPaths,
|
||||
onlyChanged
|
||||
);
|
||||
console.log(output);
|
||||
return errorCount === 0 && warningCount === 0;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,12 @@ const {execRead, execUnlessDry, logPromise} = require('../utils');
|
||||
const push = async ({cwd, dry, otp, packages, version, tag}) => {
|
||||
const errors = [];
|
||||
const isPrerelease = semver.prerelease(version);
|
||||
|
||||
let resolvedTag = tag;
|
||||
if (tag === undefined) {
|
||||
// No tag was provided. Default to `latest` for stable releases and `next`
|
||||
// for prereleases
|
||||
tag = isPrerelease ? 'next' : 'latest';
|
||||
resolvedTag = isPrerelease ? 'next' : 'latest';
|
||||
} else if (tag === 'latest' && isPrerelease) {
|
||||
throw new Error('The tag `latest` can only be used for stable versions.');
|
||||
}
|
||||
@@ -26,7 +28,7 @@ const push = async ({cwd, dry, otp, packages, version, tag}) => {
|
||||
const publishProject = async project => {
|
||||
try {
|
||||
const path = join(cwd, 'build', 'node_modules', project);
|
||||
await execUnlessDry(`npm publish --tag ${tag} ${twoFactorAuth}`, {
|
||||
await execUnlessDry(`npm publish --tag ${resolvedTag} ${twoFactorAuth}`, {
|
||||
cwd: path,
|
||||
dry,
|
||||
});
|
||||
@@ -49,7 +51,7 @@ const push = async ({cwd, dry, otp, packages, version, tag}) => {
|
||||
const status = JSON.parse(
|
||||
await execRead(`npm info ${project} dist-tags --json`)
|
||||
);
|
||||
const remoteVersion = status[tag];
|
||||
const remoteVersion = status[resolvedTag];
|
||||
|
||||
// Compare remote version to package.json version,
|
||||
// To better handle the case of pre-release versions.
|
||||
@@ -62,7 +64,9 @@ const push = async ({cwd, dry, otp, packages, version, tag}) => {
|
||||
|
||||
// If we've just published a stable release,
|
||||
// Update the @next tag to also point to it (so @next doesn't lag behind).
|
||||
if (!isPrerelease) {
|
||||
// Skip this step if we have a manually specified tag.
|
||||
// This is an escape hatch for us to interleave alpha and stable releases.
|
||||
if (tag === undefined && !isPrerelease) {
|
||||
await execUnlessDry(
|
||||
`npm dist-tag add ${project}@${packageVersion} next ${twoFactorAuth}`,
|
||||
{cwd: path, dry}
|
||||
|
||||
@@ -53,12 +53,30 @@ const {
|
||||
RN_FB_PROFILING,
|
||||
} = Bundles.bundleTypes;
|
||||
|
||||
const requestedBundleTypes = (argv.type || '')
|
||||
.split(',')
|
||||
.map(type => type.toUpperCase());
|
||||
const requestedBundleNames = (argv._[0] || '')
|
||||
.split(',')
|
||||
.map(type => type.toLowerCase());
|
||||
function parseRequestedNames(names, toCase) {
|
||||
let result = [];
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
let splitNames = names[i].split(',');
|
||||
for (let j = 0; j < splitNames.length; j++) {
|
||||
let name = splitNames[j].trim();
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
if (toCase === 'uppercase') {
|
||||
name = name.toUpperCase();
|
||||
} else if (toCase === 'lowercase') {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
result.push(name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const requestedBundleTypes = argv.type
|
||||
? parseRequestedNames([argv.type], 'uppercase')
|
||||
: [];
|
||||
const requestedBundleNames = parseRequestedNames(argv._, 'lowercase');
|
||||
const forcePrettyOutput = argv.pretty;
|
||||
const syncFBSourcePath = argv['sync-fbsource'];
|
||||
const syncWWWPath = argv['sync-www'];
|
||||
@@ -407,7 +425,12 @@ function shouldSkipBundle(bundle, bundleType) {
|
||||
}
|
||||
if (requestedBundleNames.length > 0) {
|
||||
const isAskingForDifferentNames = requestedBundleNames.every(
|
||||
requestedName => bundle.label.indexOf(requestedName) === -1
|
||||
// If the name ends with `something/index` we only match if the
|
||||
// entry ends in something. Such as `react-dom/index` only matches
|
||||
// `react-dom` but not `react-dom/server`. Everything else is fuzzy
|
||||
// search.
|
||||
requestedName =>
|
||||
(bundle.entry + '/index.js').indexOf(requestedName) === -1
|
||||
);
|
||||
if (isAskingForDifferentNames) {
|
||||
return true;
|
||||
|
||||
@@ -56,7 +56,6 @@ const NON_FIBER_RENDERER = moduleTypes.NON_FIBER_RENDERER;
|
||||
const bundles = [
|
||||
/******* Isomorphic *******/
|
||||
{
|
||||
label: 'core',
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
@@ -75,7 +74,6 @@ const bundles = [
|
||||
|
||||
/******* React DOM *******/
|
||||
{
|
||||
label: 'dom-client',
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
@@ -95,7 +93,6 @@ const bundles = [
|
||||
|
||||
//******* Test Utils *******/
|
||||
{
|
||||
label: 'dom-test-utils',
|
||||
moduleType: RENDERER_UTILS,
|
||||
bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
entry: 'react-dom/test-utils',
|
||||
@@ -105,7 +102,6 @@ const bundles = [
|
||||
|
||||
/* React DOM internals required for react-native-web (e.g., to shim native events from react-dom) */
|
||||
{
|
||||
label: 'dom-unstable-native-dependencies',
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
@@ -122,7 +118,6 @@ const bundles = [
|
||||
|
||||
/******* React DOM Server *******/
|
||||
{
|
||||
label: 'dom-server-browser',
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
@@ -138,7 +133,6 @@ const bundles = [
|
||||
},
|
||||
|
||||
{
|
||||
label: 'dom-server-node',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: NON_FIBER_RENDERER,
|
||||
entry: 'react-dom/server.node',
|
||||
@@ -147,7 +141,6 @@ const bundles = [
|
||||
|
||||
/******* React ART *******/
|
||||
{
|
||||
label: 'art',
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
@@ -169,7 +162,6 @@ const bundles = [
|
||||
|
||||
/******* React Native *******/
|
||||
{
|
||||
label: 'native-fb',
|
||||
bundleTypes: [RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-native-renderer',
|
||||
@@ -189,7 +181,6 @@ const bundles = [
|
||||
},
|
||||
|
||||
{
|
||||
label: 'native',
|
||||
bundleTypes: [RN_OSS_DEV, RN_OSS_PROD, RN_OSS_PROFILING],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-native-renderer',
|
||||
@@ -210,7 +201,6 @@ const bundles = [
|
||||
|
||||
/******* React Native Fabric *******/
|
||||
{
|
||||
label: 'native-fabric-fb',
|
||||
bundleTypes: [RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-native-renderer/fabric',
|
||||
@@ -231,7 +221,6 @@ const bundles = [
|
||||
},
|
||||
|
||||
{
|
||||
label: 'native-fabric',
|
||||
bundleTypes: [RN_OSS_DEV, RN_OSS_PROD, RN_OSS_PROFILING],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-native-renderer/fabric',
|
||||
@@ -253,7 +242,6 @@ const bundles = [
|
||||
|
||||
/******* React Test Renderer *******/
|
||||
{
|
||||
label: 'test',
|
||||
bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-test-renderer',
|
||||
@@ -262,7 +250,6 @@ const bundles = [
|
||||
},
|
||||
|
||||
{
|
||||
label: 'test-shallow',
|
||||
bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
moduleType: NON_FIBER_RENDERER,
|
||||
entry: 'react-test-renderer/shallow',
|
||||
@@ -272,7 +259,6 @@ const bundles = [
|
||||
|
||||
/******* React Noop Renderer (used for tests) *******/
|
||||
{
|
||||
label: 'noop',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-noop-renderer',
|
||||
@@ -295,7 +281,6 @@ const bundles = [
|
||||
|
||||
/******* React Noop Persistent Renderer (used for tests) *******/
|
||||
{
|
||||
label: 'noop-persistent',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-noop-renderer/persistent',
|
||||
@@ -318,7 +303,6 @@ const bundles = [
|
||||
|
||||
/******* React Reconciler *******/
|
||||
{
|
||||
label: 'react-reconciler',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RECONCILER,
|
||||
entry: 'react-reconciler',
|
||||
@@ -328,7 +312,6 @@ const bundles = [
|
||||
|
||||
/******* React Persistent Reconciler *******/
|
||||
{
|
||||
label: 'react-reconciler-persistent',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RECONCILER,
|
||||
entry: 'react-reconciler/persistent',
|
||||
@@ -338,7 +321,6 @@ const bundles = [
|
||||
|
||||
/******* Reflection *******/
|
||||
{
|
||||
label: 'reconciler-reflection',
|
||||
moduleType: RENDERER_UTILS,
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
entry: 'react-reconciler/reflection',
|
||||
@@ -348,7 +330,6 @@ const bundles = [
|
||||
|
||||
/******* React Is *******/
|
||||
{
|
||||
label: 'react-is',
|
||||
bundleTypes: [
|
||||
NODE_DEV,
|
||||
NODE_PROD,
|
||||
@@ -365,7 +346,6 @@ const bundles = [
|
||||
|
||||
/******* React Debug Tools *******/
|
||||
{
|
||||
label: 'react-debug-tools',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'react-debug-tools',
|
||||
@@ -375,7 +355,6 @@ const bundles = [
|
||||
|
||||
/******* React Cache (experimental) *******/
|
||||
{
|
||||
label: 'react-cache',
|
||||
bundleTypes: [
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
@@ -392,7 +371,6 @@ const bundles = [
|
||||
|
||||
/******* createComponentWithSubscriptions (experimental) *******/
|
||||
{
|
||||
label: 'create-subscription',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'create-subscription',
|
||||
@@ -402,7 +380,6 @@ const bundles = [
|
||||
|
||||
/******* React Scheduler (experimental) *******/
|
||||
{
|
||||
label: 'scheduler',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'scheduler',
|
||||
@@ -412,7 +389,6 @@ const bundles = [
|
||||
|
||||
/******* Jest React (experimental) *******/
|
||||
{
|
||||
label: 'jest-react',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'jest-react',
|
||||
@@ -422,21 +398,20 @@ const bundles = [
|
||||
|
||||
/******* ESLint Plugin for Hooks (proposal) *******/
|
||||
{
|
||||
label: 'eslint-plugin-react-hooks',
|
||||
// TODO: it's awkward to create a bundle for this
|
||||
// but if we don't, the package won't get copied.
|
||||
// We also can't create just DEV bundle because
|
||||
// it contains a NODE_ENV check inside.
|
||||
// We should probably tweak our build process
|
||||
// to allow "raw" packages that don't get bundled.
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'eslint-plugin-react-hooks',
|
||||
global: 'ESLintPluginReactHooks',
|
||||
externals: [],
|
||||
},
|
||||
|
||||
{
|
||||
label: 'scheduler-tracing',
|
||||
bundleTypes: [
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
|
||||
@@ -4,29 +4,29 @@
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react",
|
||||
"size": 97676,
|
||||
"gzip": 25688
|
||||
"size": 98246,
|
||||
"gzip": 25819
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react",
|
||||
"size": 11771,
|
||||
"gzip": 4678
|
||||
"size": 11830,
|
||||
"gzip": 4700
|
||||
},
|
||||
{
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react",
|
||||
"size": 60944,
|
||||
"gzip": 16507
|
||||
"size": 61339,
|
||||
"gzip": 16608
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react",
|
||||
"size": 6223,
|
||||
"gzip": 2655
|
||||
"size": 6254,
|
||||
"gzip": 2669
|
||||
},
|
||||
{
|
||||
"filename": "React-dev.js",
|
||||
@@ -46,29 +46,29 @@
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 725515,
|
||||
"gzip": 167737
|
||||
"size": 733309,
|
||||
"gzip": 169744
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 100307,
|
||||
"gzip": 32617
|
||||
"size": 100018,
|
||||
"gzip": 32571
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 720713,
|
||||
"gzip": 166331
|
||||
"size": 728507,
|
||||
"gzip": 168337
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 100301,
|
||||
"gzip": 32146
|
||||
"size": 100013,
|
||||
"gzip": 32076
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
@@ -88,29 +88,29 @@
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 45937,
|
||||
"gzip": 12585
|
||||
"size": 45945,
|
||||
"gzip": 12592
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10199,
|
||||
"gzip": 3787
|
||||
"size": 10207,
|
||||
"gzip": 3794
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 45651,
|
||||
"gzip": 12521
|
||||
"size": 45659,
|
||||
"gzip": 12528
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 9969,
|
||||
"gzip": 3726
|
||||
"size": 9977,
|
||||
"gzip": 3733
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestUtils-dev.js",
|
||||
@@ -123,29 +123,29 @@
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 62059,
|
||||
"gzip": 16289
|
||||
"size": 62067,
|
||||
"gzip": 16297
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 11263,
|
||||
"gzip": 3892
|
||||
"size": 11271,
|
||||
"gzip": 3899
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 61723,
|
||||
"gzip": 16156
|
||||
"size": 61731,
|
||||
"gzip": 16163
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10998,
|
||||
"gzip": 3785
|
||||
"size": 11006,
|
||||
"gzip": 3791
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
|
||||
@@ -165,29 +165,29 @@
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 120524,
|
||||
"gzip": 32011
|
||||
"size": 127102,
|
||||
"gzip": 33823
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 16402,
|
||||
"gzip": 6237
|
||||
"size": 17381,
|
||||
"gzip": 6667
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 116562,
|
||||
"gzip": 31037
|
||||
"size": 123140,
|
||||
"gzip": 32865
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 16301,
|
||||
"gzip": 6233
|
||||
"size": 17289,
|
||||
"gzip": 6661
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
@@ -207,43 +207,43 @@
|
||||
"filename": "react-dom-server.node.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 118530,
|
||||
"gzip": 31584
|
||||
"size": 125215,
|
||||
"gzip": 33415
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 17126,
|
||||
"gzip": 6544
|
||||
"size": 18173,
|
||||
"gzip": 6976
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 507548,
|
||||
"gzip": 112066
|
||||
"size": 508568,
|
||||
"gzip": 112248
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 92284,
|
||||
"gzip": 28330
|
||||
"size": 91997,
|
||||
"gzip": 28283
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 437685,
|
||||
"gzip": 94610
|
||||
"size": 438705,
|
||||
"gzip": 94790
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 56431,
|
||||
"gzip": 17393
|
||||
"size": 56144,
|
||||
"gzip": 17322
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
@@ -291,29 +291,29 @@
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 450653,
|
||||
"gzip": 97378
|
||||
"size": 451673,
|
||||
"gzip": 97563
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 57674,
|
||||
"gzip": 17698
|
||||
"size": 57391,
|
||||
"gzip": 17618
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 445754,
|
||||
"gzip": 96206
|
||||
"size": 446774,
|
||||
"gzip": 96385
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 57342,
|
||||
"gzip": 17539
|
||||
"size": 57053,
|
||||
"gzip": 17471
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
@@ -326,29 +326,29 @@
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 26400,
|
||||
"gzip": 7200
|
||||
"size": 26408,
|
||||
"gzip": 7207
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 7442,
|
||||
"gzip": 2425
|
||||
"size": 7450,
|
||||
"gzip": 2431
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 20656,
|
||||
"gzip": 5736
|
||||
"size": 20664,
|
||||
"gzip": 5742
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 8141,
|
||||
"gzip": 2697
|
||||
"size": 8149,
|
||||
"gzip": 2703
|
||||
},
|
||||
{
|
||||
"filename": "ReactShallowRenderer-dev.js",
|
||||
@@ -361,57 +361,57 @@
|
||||
"filename": "react-noop-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 28829,
|
||||
"gzip": 6286
|
||||
"size": 28837,
|
||||
"gzip": 6293
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 10889,
|
||||
"gzip": 3632
|
||||
"size": 10897,
|
||||
"gzip": 3639
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 435479,
|
||||
"gzip": 93068
|
||||
"size": 436490,
|
||||
"gzip": 93261
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 57593,
|
||||
"gzip": 17242
|
||||
"size": 57292,
|
||||
"gzip": 17184
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 433889,
|
||||
"gzip": 92426
|
||||
"size": 434900,
|
||||
"gzip": 92614
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 57604,
|
||||
"gzip": 17248
|
||||
"size": 57303,
|
||||
"gzip": 17190
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 15764,
|
||||
"gzip": 4943
|
||||
"size": 15772,
|
||||
"gzip": 4950
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 2614,
|
||||
"gzip": 1153
|
||||
"size": 2622,
|
||||
"gzip": 1159
|
||||
},
|
||||
{
|
||||
"filename": "react-call-return.development.js",
|
||||
@@ -431,29 +431,29 @@
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 7691,
|
||||
"gzip": 2393
|
||||
"size": 7699,
|
||||
"gzip": 2400
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 2171,
|
||||
"gzip": 854
|
||||
"size": 2179,
|
||||
"gzip": 860
|
||||
},
|
||||
{
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 7502,
|
||||
"gzip": 2344
|
||||
"size": 7510,
|
||||
"gzip": 2351
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 2132,
|
||||
"gzip": 793
|
||||
"size": 2140,
|
||||
"gzip": 799
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-dev.js",
|
||||
@@ -487,43 +487,43 @@
|
||||
"filename": "create-subscription.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "create-subscription",
|
||||
"size": 8535,
|
||||
"gzip": 2957
|
||||
"size": 8543,
|
||||
"gzip": 2964
|
||||
},
|
||||
{
|
||||
"filename": "create-subscription.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "create-subscription",
|
||||
"size": 2886,
|
||||
"gzip": 1346
|
||||
"size": 2894,
|
||||
"gzip": 1353
|
||||
},
|
||||
{
|
||||
"filename": "React-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react",
|
||||
"size": 58338,
|
||||
"gzip": 15703
|
||||
"size": 58723,
|
||||
"gzip": 15806
|
||||
},
|
||||
{
|
||||
"filename": "React-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react",
|
||||
"size": 15346,
|
||||
"gzip": 4131
|
||||
"size": 15377,
|
||||
"gzip": 4148
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 742105,
|
||||
"gzip": 167549
|
||||
"size": 750157,
|
||||
"gzip": 169494
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 318358,
|
||||
"gzip": 58562
|
||||
"size": 317074,
|
||||
"gzip": 58435
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestUtils-dev.js",
|
||||
@@ -550,92 +550,92 @@
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 117649,
|
||||
"gzip": 30639
|
||||
"size": 124347,
|
||||
"gzip": 32492
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 41626,
|
||||
"gzip": 9808
|
||||
"size": 45482,
|
||||
"gzip": 10536
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 445159,
|
||||
"gzip": 93560
|
||||
"size": 445813,
|
||||
"gzip": 93658
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 189135,
|
||||
"gzip": 32266
|
||||
"size": 187820,
|
||||
"gzip": 32111
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_FB_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 573357,
|
||||
"gzip": 124963
|
||||
"size": 574011,
|
||||
"gzip": 125117
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_FB_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 245987,
|
||||
"gzip": 43227
|
||||
"size": 244579,
|
||||
"gzip": 43055
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_OSS_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 573046,
|
||||
"gzip": 124866
|
||||
"size": 573700,
|
||||
"gzip": 125022
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_OSS_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 231080,
|
||||
"gzip": 40077
|
||||
"size": 229672,
|
||||
"gzip": 39906
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_FB_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 563440,
|
||||
"gzip": 122415
|
||||
"size": 564094,
|
||||
"gzip": 122574
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_FB_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 225188,
|
||||
"gzip": 38730
|
||||
"size": 223518,
|
||||
"gzip": 38538
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_OSS_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 563475,
|
||||
"gzip": 122429
|
||||
"size": 564129,
|
||||
"gzip": 122588
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_OSS_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 225224,
|
||||
"gzip": 38745
|
||||
"size": 223554,
|
||||
"gzip": 38553
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 453421,
|
||||
"gzip": 95442
|
||||
"size": 454075,
|
||||
"gzip": 95541
|
||||
},
|
||||
{
|
||||
"filename": "ReactShallowRenderer-dev.js",
|
||||
@@ -676,15 +676,15 @@
|
||||
"filename": "scheduler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 22073,
|
||||
"gzip": 5976
|
||||
"size": 22300,
|
||||
"gzip": 6022
|
||||
},
|
||||
{
|
||||
"filename": "scheduler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "scheduler",
|
||||
"size": 4755,
|
||||
"gzip": 1865
|
||||
"size": 4822,
|
||||
"gzip": 1892
|
||||
},
|
||||
{
|
||||
"filename": "SimpleCacheProvider-dev.js",
|
||||
@@ -704,50 +704,50 @@
|
||||
"filename": "react-noop-renderer-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 28948,
|
||||
"gzip": 6299
|
||||
"size": 28956,
|
||||
"gzip": 6306
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 10911,
|
||||
"gzip": 3639
|
||||
"size": 10919,
|
||||
"gzip": 3645
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.profiling.min.js",
|
||||
"bundleType": "NODE_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 103137,
|
||||
"gzip": 32687
|
||||
"size": 103086,
|
||||
"gzip": 32708
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-profiling.js",
|
||||
"bundleType": "RN_OSS_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 236392,
|
||||
"gzip": 41298
|
||||
"size": 235527,
|
||||
"gzip": 41288
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-profiling.js",
|
||||
"bundleType": "RN_OSS_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 229595,
|
||||
"gzip": 40005
|
||||
"size": 228448,
|
||||
"gzip": 39999
|
||||
},
|
||||
{
|
||||
"filename": "Scheduler-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 22314,
|
||||
"gzip": 6027
|
||||
"size": 22533,
|
||||
"gzip": 6066
|
||||
},
|
||||
{
|
||||
"filename": "Scheduler-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "scheduler",
|
||||
"size": 13375,
|
||||
"gzip": 2927
|
||||
"size": 13496,
|
||||
"gzip": 2945
|
||||
},
|
||||
{
|
||||
"filename": "react.profiling.min.js",
|
||||
@@ -760,64 +760,64 @@
|
||||
"filename": "React-profiling.js",
|
||||
"bundleType": "FB_WWW_PROFILING",
|
||||
"packageName": "react",
|
||||
"size": 15346,
|
||||
"gzip": 4131
|
||||
"size": 15377,
|
||||
"gzip": 4148
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-profiling.js",
|
||||
"bundleType": "FB_WWW_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 324711,
|
||||
"gzip": 59847
|
||||
"size": 324138,
|
||||
"gzip": 59866
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-profiling.js",
|
||||
"bundleType": "RN_FB_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 251555,
|
||||
"gzip": 44455
|
||||
"size": 250690,
|
||||
"gzip": 44441
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-profiling.js",
|
||||
"bundleType": "RN_FB_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 229554,
|
||||
"gzip": 39988
|
||||
"size": 228407,
|
||||
"gzip": 39983
|
||||
},
|
||||
{
|
||||
"filename": "react.profiling.min.js",
|
||||
"bundleType": "UMD_PROFILING",
|
||||
"packageName": "react",
|
||||
"size": 13977,
|
||||
"gzip": 5211
|
||||
"size": 14036,
|
||||
"gzip": 5234
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.profiling.min.js",
|
||||
"bundleType": "UMD_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 103046,
|
||||
"gzip": 33256
|
||||
"size": 102993,
|
||||
"gzip": 33291
|
||||
},
|
||||
{
|
||||
"filename": "scheduler-tracing.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 10480,
|
||||
"gzip": 2403
|
||||
"size": 10488,
|
||||
"gzip": 2408
|
||||
},
|
||||
{
|
||||
"filename": "scheduler-tracing.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "scheduler",
|
||||
"size": 719,
|
||||
"gzip": 374
|
||||
"size": 727,
|
||||
"gzip": 379
|
||||
},
|
||||
{
|
||||
"filename": "scheduler-tracing.profiling.min.js",
|
||||
"bundleType": "NODE_PROFILING",
|
||||
"packageName": "scheduler",
|
||||
"size": 3334,
|
||||
"gzip": 991
|
||||
"size": 3342,
|
||||
"gzip": 997
|
||||
},
|
||||
{
|
||||
"filename": "SchedulerTracing-dev.js",
|
||||
@@ -844,15 +844,15 @@
|
||||
"filename": "react-cache.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-cache",
|
||||
"size": 9015,
|
||||
"gzip": 3022
|
||||
"size": 9023,
|
||||
"gzip": 3029
|
||||
},
|
||||
{
|
||||
"filename": "react-cache.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-cache",
|
||||
"size": 2204,
|
||||
"gzip": 1128
|
||||
"size": 2212,
|
||||
"gzip": 1135
|
||||
},
|
||||
{
|
||||
"filename": "ReactCache-dev.js",
|
||||
@@ -872,29 +872,29 @@
|
||||
"filename": "react-cache.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-cache",
|
||||
"size": 9246,
|
||||
"gzip": 3094
|
||||
"size": 9254,
|
||||
"gzip": 3102
|
||||
},
|
||||
{
|
||||
"filename": "react-cache.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-cache",
|
||||
"size": 2403,
|
||||
"gzip": 1219
|
||||
"size": 2411,
|
||||
"gzip": 1226
|
||||
},
|
||||
{
|
||||
"filename": "jest-react.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "jest-react",
|
||||
"size": 9286,
|
||||
"gzip": 3053
|
||||
"size": 9294,
|
||||
"gzip": 3059
|
||||
},
|
||||
{
|
||||
"filename": "jest-react.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "jest-react",
|
||||
"size": 3716,
|
||||
"gzip": 1655
|
||||
"size": 3724,
|
||||
"gzip": 1661
|
||||
},
|
||||
{
|
||||
"filename": "JestReact-dev.js",
|
||||
@@ -914,29 +914,36 @@
|
||||
"filename": "react-debug-tools.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-debug-tools",
|
||||
"size": 16717,
|
||||
"gzip": 4965
|
||||
"size": 16725,
|
||||
"gzip": 4972
|
||||
},
|
||||
{
|
||||
"filename": "react-debug-tools.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-debug-tools",
|
||||
"size": 5402,
|
||||
"gzip": 2187
|
||||
"size": 5410,
|
||||
"gzip": 2194
|
||||
},
|
||||
{
|
||||
"filename": "eslint-plugin-react-hooks.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "eslint-plugin-react-hooks",
|
||||
"size": 25592,
|
||||
"gzip": 5885
|
||||
"size": 25600,
|
||||
"gzip": 5891
|
||||
},
|
||||
{
|
||||
"filename": "eslint-plugin-react-hooks.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "eslint-plugin-react-hooks",
|
||||
"size": 4943,
|
||||
"gzip": 1815
|
||||
"size": 4951,
|
||||
"gzip": 1821
|
||||
},
|
||||
{
|
||||
"filename": "ESLintPluginReactHooks-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "eslint-plugin-react-hooks",
|
||||
"size": 27469,
|
||||
"gzip": 6036
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,9 @@ const filesize = require('filesize');
|
||||
const chalk = require('chalk');
|
||||
const join = require('path').join;
|
||||
const fs = require('fs');
|
||||
const prevBuildResults = require('./results.json');
|
||||
const prevBuildResults = fs.existsSync(__dirname + '/results.json')
|
||||
? require('./results.json')
|
||||
: {bundleSizes: []};
|
||||
|
||||
const currentBuildResults = {
|
||||
// Mutated inside build.js during a build run.
|
||||
|
||||
@@ -12,6 +12,7 @@ module.exports = {
|
||||
Proxy: true,
|
||||
Symbol: true,
|
||||
WeakMap: true,
|
||||
Uint16Array: true,
|
||||
// Vendor specific
|
||||
MSApp: true,
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ module.exports = {
|
||||
Symbol: true,
|
||||
Proxy: true,
|
||||
WeakMap: true,
|
||||
Uint16Array: true,
|
||||
// Vendor specific
|
||||
MSApp: true,
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
Symbol: true,
|
||||
Proxy: true,
|
||||
WeakMap: true,
|
||||
Uint16Array: true,
|
||||
// Vendor specific
|
||||
MSApp: true,
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
|
||||
|
||||
@@ -3382,6 +3382,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0:
|
||||
loose-envify@^1.3.1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
@@ -4021,6 +4022,7 @@ promise@^7.1.1:
|
||||
prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||
dependencies:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
Reference in New Issue
Block a user