diff --git a/docs/docs/installation.md b/docs/docs/installation.md
index 0039fc7750..15da2f4d17 100644
--- a/docs/docs/installation.md
+++ b/docs/docs/installation.md
@@ -99,6 +99,10 @@ By default, React includes many helpful warnings. These warnings are very useful
If you use [Create React App](https://github.com/facebookincubator/create-react-app), `npm run build` will create an optimized build of your app in the `build` folder.
+#### Brunch
+
+To create an optimized production build with Brunch, just add the `-p` flag to the build command. See the [Brunch docs](http://brunch.io/docs/commands) for more details.
+
#### Webpack
Include both `DefinePlugin` and `UglifyJsPlugin` into your production Webpack configuration as described in [this guide](https://webpack.js.org/guides/production-build/).
diff --git a/docs/docs/optimizing-performance.md b/docs/docs/optimizing-performance.md
index fe1d93077a..86acb8cdb7 100644
--- a/docs/docs/optimizing-performance.md
+++ b/docs/docs/optimizing-performance.md
@@ -13,6 +13,7 @@ If you're benchmarking or experiencing performance problems in your React apps,
* For Create React App, you need to run `npm run build` and follow the instructions.
* For single-file builds, we offer production-ready `.min.js` versions.
+* For Brunch, you need to add the `-p` flag to the `build` command.
* For Browserify, you need to run it with `NODE_ENV=production`.
* For Webpack, you need to add this to plugins in your production config:
diff --git a/docs/docs/refs-and-the-dom.md b/docs/docs/refs-and-the-dom.md
index eaf19e7b39..85a0429eb4 100644
--- a/docs/docs/refs-and-the-dom.md
+++ b/docs/docs/refs-and-the-dom.md
@@ -145,7 +145,7 @@ function CustomTextInput(props) {
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. See the [Lifting State Up](/react/docs/lifting-state-up.html) guide for examples of this.
-### Legacy API: String Refs
+### Legacy API: String Refs
If you worked with React before, you might be familiar with an older API where the `ref` attribute is a string, like `"textInput"`, and the DOM node is accessed as `this.refs.textInput`. We advise against it because string refs have [some issues](https://github.com/facebook/react/pull/8333#issuecomment-271648615), are considered legacy, and **are likely to be removed in one of the future releases**. If you're currently using `this.refs.textInput` to access refs, we recommend the callback pattern instead.
diff --git a/fixtures/dom/package.json b/fixtures/dom/package.json
index cc109e1001..5de1a7d4b8 100644
--- a/fixtures/dom/package.json
+++ b/fixtures/dom/package.json
@@ -6,9 +6,11 @@
"react-scripts": "0.8.4"
},
"dependencies": {
+ "classnames": "^2.2.5",
"query-string": "^4.2.3",
"react": "^15.4.1",
- "react-dom": "^15.4.1"
+ "react-dom": "^15.4.1",
+ "semver": "^5.3.0"
},
"scripts": {
"start": "react-scripts start",
diff --git a/fixtures/dom/src/components/Fixture.js b/fixtures/dom/src/components/Fixture.js
new file mode 100644
index 0000000000..1f6ef9ba09
--- /dev/null
+++ b/fixtures/dom/src/components/Fixture.js
@@ -0,0 +1,21 @@
+const React = window.React;
+
+const propTypes = {
+ children: React.PropTypes.node.isRequired,
+};
+
+class Fixture extends React.Component {
+ render() {
+ const { children } = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+Fixture.propTypes = propTypes;
+
+export default Fixture
diff --git a/fixtures/dom/src/components/FixtureSet.js b/fixtures/dom/src/components/FixtureSet.js
new file mode 100644
index 0000000000..9c6e8581eb
--- /dev/null
+++ b/fixtures/dom/src/components/FixtureSet.js
@@ -0,0 +1,28 @@
+import React from 'react';
+
+const propTypes = {
+ title: React.PropTypes.node.isRequired,
+ description: React.PropTypes.node.isRequired,
+};
+
+class FixtureSet extends React.Component {
+
+ render() {
+ const { title, description, children } = this.props;
+
+ return (
+
+
{title}
+ {description && (
+
{description}
+ )}
+
+ {children}
+
+ );
+ }
+}
+
+FixtureSet.propTypes = propTypes;
+
+export default FixtureSet
diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js
index fb9d143f0f..83f77eee31 100644
--- a/fixtures/dom/src/components/Header.js
+++ b/fixtures/dom/src/components/Header.js
@@ -1,4 +1,5 @@
import { parse, stringify } from 'query-string';
+import getVersionTags from '../tags';
const React = window.React;
const Header = React.createClass({
@@ -9,13 +10,12 @@ const Header = React.createClass({
return { version, versions };
},
componentWillMount() {
- fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
- .then(res => res.json())
+ getVersionTags()
.then(tags => {
let versions = tags.map(tag => tag.name.slice(1));
- versions = ['local', ...versions];
+ versions = [`local`, ...versions];
this.setState({ versions });
- });
+ })
},
handleVersionChange(event) {
const query = parse(window.location.search);
@@ -46,6 +46,7 @@ const Header = React.createClass({
Text Inputs
Selects
Textareas
+ Input change events
diff --git a/fixtures/dom/src/components/TestCase.js b/fixtures/dom/src/components/TestCase.js
new file mode 100644
index 0000000000..79c46a975a
--- /dev/null
+++ b/fixtures/dom/src/components/TestCase.js
@@ -0,0 +1,145 @@
+import cn from 'classnames';
+import semver from 'semver';
+import React from 'react';
+import { parse } from 'query-string';
+import { semverString } from './propTypes'
+
+const propTypes = {
+ children: React.PropTypes.node.isRequired,
+ title: React.PropTypes.node.isRequired,
+ resolvedIn: semverString,
+ resolvedBy: React.PropTypes.string
+};
+
+class TestCase extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ complete: false,
+ };
+ }
+
+ handleChange = (e) => {
+ this.setState({
+ complete: e.target.checked
+ })
+ };
+
+ render() {
+ const {
+ title,
+ description,
+ resolvedIn,
+ resolvedBy,
+ affectedBrowsers,
+ children,
+ } = this.props;
+
+ let { complete } = this.state;
+
+ const { version } = parse(window.location.search);
+ const isTestRelevant = (
+ !version ||
+ !resolvedIn ||
+ semver.gte(version, resolvedIn)
+ );
+
+ complete = !isTestRelevant || complete;
+
+ return (
+
+
+
+
+ {' '}{title}
+
+
+
+
+ {resolvedIn && (
+ First supported in: )}
+ {resolvedIn && (
+
+
+ {resolvedIn}
+
+
+ )}
+
+ {resolvedBy && (
+ Fixed by: )}
+ {resolvedBy && (
+
+
+ {resolvedBy}
+
+
+ )}
+
+ {affectedBrowsers &&
+ Affected browsers: }
+ {affectedBrowsers &&
+ {affectedBrowsers}
+ }
+
+
+
+ {description}
+
+
+
+ {!isTestRelevant &&(
+
+ Note: This test case was fixed in a later version of React.
+ This test is not expected to pass for the selected version, and that's ok!
+
+ )}
+
+ {children}
+
+
+ );
+ }
+}
+
+TestCase.propTypes = propTypes;
+
+TestCase.Steps = class extends React.Component {
+ render() {
+ const { children } = this.props;
+ return (
+
+
Steps to reproduce:
+
+ {children}
+
+
+ )
+ }
+}
+
+TestCase.ExpectedResult = class extends React.Component {
+ render() {
+ const { children } = this.props
+ return (
+
+
Expected Result:
+
+ {children}
+
+
+ )
+ }
+}
+export default TestCase
diff --git a/fixtures/dom/src/components/fixtures/index.js b/fixtures/dom/src/components/fixtures/index.js
index b0e70baf99..ea7cebbde8 100644
--- a/fixtures/dom/src/components/fixtures/index.js
+++ b/fixtures/dom/src/components/fixtures/index.js
@@ -2,7 +2,8 @@ const React = window.React;
import RangeInputFixtures from './range-inputs';
import TextInputFixtures from './text-inputs';
import SelectFixtures from './selects';
-import TextAreaFixtures from './textareas/';
+import TextAreaFixtures from './textareas';
+import InputChangeEvents from './input-change-events';
/**
* A simple routing component that renders the appropriate
@@ -19,6 +20,8 @@ const FixturesPage = React.createClass({
return ;
case '/textareas':
return ;
+ case '/input-change-events':
+ return ;
default:
return Please select a test fixture.
;
}
diff --git a/fixtures/dom/src/components/fixtures/input-change-events/InputPlaceholderFixture.js b/fixtures/dom/src/components/fixtures/input-change-events/InputPlaceholderFixture.js
new file mode 100644
index 0000000000..35419b896a
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/input-change-events/InputPlaceholderFixture.js
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import Fixture from '../../Fixture';
+
+
+
+class InputPlaceholderFixture extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ placeholder: 'A placeholder',
+ changeCount: 0,
+ };
+ }
+
+ handleChange = () => {
+ this.setState(({ changeCount }) => {
+ return {
+ changeCount: changeCount + 1
+ }
+ })
+ }
+ handleGeneratePlaceholder = () => {
+ this.setState({
+ placeholder: `A placeholder: ${Math.random() * 100}`
+ })
+ }
+
+ handleReset = () => {
+ this.setState({
+ changeCount: 0,
+ })
+ }
+
+ render() {
+ const { placeholder, changeCount } = this.state;
+ const color = changeCount === 0 ? 'green' : 'red';
+
+ return (
+
+
+ {' '}
+
+ Change placeholder
+
+
+
+ onChange{' calls: '}{changeCount}
+
+ Reset count
+
+ )
+ }
+}
+
+export default InputPlaceholderFixture;
diff --git a/fixtures/dom/src/components/fixtures/input-change-events/RadioClickFixture.js b/fixtures/dom/src/components/fixtures/input-change-events/RadioClickFixture.js
new file mode 100644
index 0000000000..fb44166b34
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/input-change-events/RadioClickFixture.js
@@ -0,0 +1,52 @@
+import React from 'react';
+
+import Fixture from '../../Fixture';
+
+class RadioClickFixture extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ changeCount: 0,
+ };
+ }
+
+ handleChange = () => {
+ this.setState(({ changeCount }) => {
+ return {
+ changeCount: changeCount + 1
+ }
+ })
+ }
+
+ handleReset = () => {
+ this.setState({
+ changeCount: 0,
+ })
+ }
+
+ render() {
+ const { changeCount } = this.state;
+ const color = changeCount === 0 ? 'green' : 'red';
+
+ return (
+
+
+
+ Test case radio input
+
+ {' '}
+
+ onChange{' calls: '}{changeCount}
+
+ Reset count
+
+ )
+ }
+}
+
+export default RadioClickFixture;
diff --git a/fixtures/dom/src/components/fixtures/input-change-events/RangeKeyboardFixture.js b/fixtures/dom/src/components/fixtures/input-change-events/RangeKeyboardFixture.js
new file mode 100644
index 0000000000..12ccd1f43a
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/input-change-events/RangeKeyboardFixture.js
@@ -0,0 +1,75 @@
+import React from 'react';
+
+import Fixture from '../../Fixture';
+
+
+class RangeKeyboardFixture extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ keydownCount: 0,
+ changeCount: 0,
+ };
+ }
+
+ componentDidMount() {
+ this.input.addEventListener('keydown', this.handleKeydown, false)
+ }
+
+ componentWillUnmount() {
+ this.input.removeEventListener('keydown', this.handleKeydown, false)
+ }
+
+ handleChange = () => {
+ this.setState(({ changeCount }) => {
+ return {
+ changeCount: changeCount + 1
+ }
+ })
+ }
+
+ handleKeydown = (e) => {
+ // only interesting in arrow key events
+ if (![37, 38, 39, 40].includes(e.keyCode))
+ return;
+
+ this.setState(({ keydownCount }) => {
+ return {
+ keydownCount: keydownCount + 1
+ }
+ })
+ }
+
+ handleReset = () => {
+ this.setState({
+ keydownCount: 0,
+ changeCount: 0,
+ })
+ }
+
+ render() {
+ const { keydownCount, changeCount } = this.state;
+ const color = keydownCount === changeCount ? 'green' : 'red';
+
+ return (
+
+ this.input = r}
+ onChange={this.handleChange}
+ />
+ {' '}
+
+
+ onKeyDown{' calls: '}{keydownCount}
+ {' vs '}
+ onChange{' calls: '}{changeCount}
+
+ Reset counts
+
+ )
+ }
+}
+
+export default RangeKeyboardFixture;
diff --git a/fixtures/dom/src/components/fixtures/input-change-events/index.js b/fixtures/dom/src/components/fixtures/input-change-events/index.js
new file mode 100644
index 0000000000..703585ed43
--- /dev/null
+++ b/fixtures/dom/src/components/fixtures/input-change-events/index.js
@@ -0,0 +1,81 @@
+import React from 'react';
+
+import FixtureSet from '../../FixtureSet';
+import TestCase from '../../TestCase';
+import RangeKeyboardFixture from './RangeKeyboardFixture';
+import RadioClickFixture from './RadioClickFixture';
+import InputPlaceholderFixture from './InputPlaceholderFixture';
+
+class InputChangeEvents extends React.Component {
+ render() {
+ return (
+
+
+
+ Focus range input
+ change value via the keyboard arrow keys
+
+
+
+ The onKeyDown call count should be equal to
+ the onChange call count.
+
+
+
+
+
+
+
+ Click on the Radio input (or label text)
+
+
+
+ The onChange call count should remain at 0
+
+
+
+
+
+
+
+ Click on the Text input
+ Click on the "Change placeholder" button
+
+
+
+ The onChange call count should remain at 0
+
+
+
+
+
+ );
+ }
+}
+
+
+export default InputChangeEvents
diff --git a/fixtures/dom/src/components/fixtures/text-inputs/index.js b/fixtures/dom/src/components/fixtures/text-inputs/index.js
index ece6397bc3..2db132bcb1 100644
--- a/fixtures/dom/src/components/fixtures/text-inputs/index.js
+++ b/fixtures/dom/src/components/fixtures/text-inputs/index.js
@@ -24,7 +24,7 @@ const TextInputFixtures = React.createClass({
return (
- {type}
+ {type}
→ {JSON.stringify(state)}
@@ -35,7 +35,7 @@ const TextInputFixtures = React.createClass({
let id = `uncontrolled_${type}`;
return (
- {type}
+ {type}
);
diff --git a/fixtures/dom/src/components/propTypes.js b/fixtures/dom/src/components/propTypes.js
new file mode 100644
index 0000000000..42f4f4cf3a
--- /dev/null
+++ b/fixtures/dom/src/components/propTypes.js
@@ -0,0 +1,16 @@
+import semver from 'semver';
+
+const React = window.React;
+
+export function semverString (props, propName, componentName) {
+ let version = props[propName];
+
+ let error = React.PropTypes.string(...arguments);
+ if (!error && version != null && !semver.valid(version))
+ error = new Error(
+ `\`${propName}\` should be a valid "semantic version" matching ` +
+ 'an existing React version'
+ );
+
+ return error || null;
+};
diff --git a/fixtures/dom/src/style.css b/fixtures/dom/src/style.css
index 6a7101c1f9..032d0c85f6 100644
--- a/fixtures/dom/src/style.css
+++ b/fixtures/dom/src/style.css
@@ -4,22 +4,28 @@
box-sizing: border-box;
}
+html {
+ font-size: 10px;
+}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+ font-size: 1.4rem;
margin: 0;
padding: 0;
}
select {
- width: 120px;
+ width: 12rem;
}
+
.header {
background: #222;
box-shadow: inset 0 -1px 3px #000;
- line-height: 32px;
+ font-size: 1.6rem;
+ line-height: 3.2rem;
overflow: hidden;
- padding: 8px 16px;
+ padding: .8rem 1.6rem;
}
.header__inner {
@@ -40,7 +46,7 @@ select {
.header__logo img {
display: inline-block;
- margin-right: 8px;
+ margin-right: 0.8rem;
vertical-align: middle;
}
@@ -63,24 +69,142 @@ select {
margin: 0 auto;
max-width: 900px;
overflow: hidden;
- padding: 20px;
+ padding: 2rem;
}
-label {
+.control-label {
display: block;
- font-size: 12px;
+ font-size: 1.2rem;
letter-spacing: 0.01em;
- margin-bottom: 4px;
+ margin-bottom: 0.4rem;
text-transform: uppercase;
}
.field {
- padding: 8px;
+ padding: 0.8rem;
}
fieldset {
border: 1px solid #aaa;
float: left;
- padding: 16px;
+ padding: 1.6rem;
width: 49%;
}
+
+.control-box {
+ overflow: hidden;
+}
+
+ul, ol {
+ margin: 0 0 2rem 0;
+}
+
+li {
+ margin-bottom: 0.4rem;
+}
+
+.type-subheading {
+ font-size: 1.8rem;
+ font-weight: 600;
+ line-height: 1.5;
+ margin: 0 0 1.6rem;
+}
+
+.hint {
+ font-style: italic;
+ line-height: 1.5;
+ text-size: 1.4rem;
+}
+
+.footnote {
+ border-left: 4px solid #aaa;
+ color: #444;
+ font-style: italic;
+ line-height: 1.5;
+ margin-bottom: 2.4rem;
+ margin-left: 0.4rem;
+ padding-left: 1.6rem;
+ text-size: 1.3rem;
+}
+
+.test-case {
+ border-radius: 0.2rem;
+ border: 1px solid #d9d9d9;
+ margin: 3.2rem 0 3.2rem;
+}
+
+.test-case__title {
+ padding: 10px 15px 8px;
+ line-height: 16px;
+ font-size: 18px;
+ border-bottom: 1px dashed #d9d9d9;
+ margin: 0 0 -1px;
+}
+
+.test-case__title__check {
+ display: inline-block;
+ margin-right: 8px;
+ vertical-align: middle;
+}
+
+.test-case__body {
+ padding: 0 15px;
+}
+
+.test-case__desc {
+ font-style: italic;
+ margin: 15px 0;
+ padding: 0 15px;
+}
+
+.test-case--complete {
+ border-color: #5cb85c;
+}
+
+.test-case--complete .test-case__title {
+ color: #5cb85c;
+}
+
+.test-case__details {
+ border-bottom: 1px dashed #d9d9d9;
+ font-size: 80%;
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+ margin: 0;
+ padding: 0 15px;
+}
+
+.test-case__details > * {
+ display: inline-block;
+}
+
+.test-case__details > dt,
+.test-case__details > dd {
+ padding: 8px 0 6px;
+}
+
+.test-case__details > dt {
+ color: #464a4c;
+ font-weight: 600;
+}
+
+.test-case__details > dd {
+ margin-left: 1rem;
+}
+
+.test-case__details > dd + dt {
+ margin-left: 1.5rem;
+}
+
+.test-case__invalid-version {
+ font-style: italic;
+ font-size: 1.6rem;
+ color: #5cb85c;
+}
+
+.test-fixture {
+ padding: 20px;
+ margin: 0 -15px; /* opposite of .test-case padding */
+ background-color: #f4f4f4;
+ border-top: 1px solid #d9d9d9;
+}
diff --git a/fixtures/dom/src/tags.js b/fixtures/dom/src/tags.js
new file mode 100644
index 0000000000..a82c3e5d29
--- /dev/null
+++ b/fixtures/dom/src/tags.js
@@ -0,0 +1,70 @@
+/**
+ * Version tags are loaded from the Github API. Since the Github API is rate-limited
+ * we attempt to save and load the tags in sessionStorage when possible. Since its unlikely
+ * that versions will change during a single session this should be safe.
+ */
+
+const TAGS_CACHE_KEY = '@react-dom-fixtures/tags';
+
+/**
+ * Its possible that users will be testing changes frequently
+ * in a browser that does not support sessionStorage. If the API does
+ * get rate limited this hardcoded fallback will be loaded instead.
+ * This way users can still switch between ~some versions while testing.
+ * If there's a specific version they need to test that is not here, they
+ * can manually load it by editing the URL (`?version={whatever}`)
+ */
+const fallbackTags = [
+ '15.4.2',
+ '15.3.2',
+ '15.2.1',
+ '15.1.0',
+ '15.0.2',
+ '0.14.8',
+ '0.13.0'
+ ].map(version => ({
+ name: `v${version}`
+ }))
+
+let canUseSessionStorage = true;
+
+try {
+ sessionStorage.setItem('foo', '')
+} catch (err) {
+ canUseSessionStorage = false;
+}
+
+/**
+ * Attempts to load tags from sessionStorage. In cases where
+ * sessionStorage is not available (Safari private browsing) or the
+ * tags are cached a fetch request is made to the Github API.
+ *
+ * Returns a promise so that the consuming module can always assume
+ * the request is async, even if its loaded from sessionStorage.
+ */
+export default function getVersionTags() {
+ return new Promise((resolve) => {
+ let cachedTags;
+ if (canUseSessionStorage) {
+ cachedTags = sessionStorage.getItem(TAGS_CACHE_KEY);
+ }
+ if (cachedTags) {
+ cachedTags = JSON.parse(cachedTags);
+ resolve(cachedTags);
+ } else {
+ fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
+ .then(res => res.json())
+ .then(tags => {
+ // A message property indicates an error was sent from the API
+ if (tags.message) {
+ return resolve(fallbackTags)
+ }
+ if (canUseSessionStorage) {
+ sessionStorage.setItem(TAGS_CACHE_KEY, JSON.stringify(tags))
+ }
+ resolve(tags)
+ })
+ .catch(() => resolve(fallbackTags))
+ }
+ })
+}
\ No newline at end of file
diff --git a/fixtures/dom/yarn.lock b/fixtures/dom/yarn.lock
index c8780701e0..6136f0a9cf 100644
--- a/fixtures/dom/yarn.lock
+++ b/fixtures/dom/yarn.lock
@@ -1136,6 +1136,10 @@ clap@^1.0.9:
dependencies:
chalk "^1.1.3"
+classnames@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
+
clean-css@3.4.x:
version "3.4.23"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.23.tgz#604fbbca24c12feb59b02f00b84f1fb7ded6d001"
diff --git a/gulpfile.js b/gulpfile.js
index 8cacc65343..91cc987fb8 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -103,6 +103,8 @@ var paths = {
},
};
+exports.paths = paths;
+
var moduleMapBase = {'object-assign': 'object-assign'};
var fbjsModules = require('fbjs/module-map');
diff --git a/package.json b/package.json
index 32e05560ee..c91e902a6c 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"babel-jest": "^19.0.0",
"babel-plugin-check-es2015-constants": "^6.5.0",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
+ "babel-plugin-transform-async-to-generator": "^6.22.0",
"babel-plugin-transform-class-properties": "^6.11.5",
"babel-plugin-transform-es2015-arrow-functions": "^6.5.2",
"babel-plugin-transform-es2015-block-scoped-functions": "^6.5.0",
@@ -51,6 +52,7 @@
"fbjs-scripts": "^0.6.0",
"flow-bin": "^0.37.0",
"glob": "^6.0.1",
+ "glob-stream": "^6.1.0",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-compare-size": "^0.4.0",
diff --git a/scripts/circleci/test_extract_errors.sh b/scripts/circleci/test_extract_errors.sh
index 5169752509..cce29cc347 100755
--- a/scripts/circleci/test_extract_errors.sh
+++ b/scripts/circleci/test_extract_errors.sh
@@ -4,3 +4,7 @@ set -e
./node_modules/.bin/gulp react:extract-errors
git checkout -- scripts/error-codes/codes.json
+
+WARNINGS=$(node scripts/error-codes/print-warnings.js)
+echo "$WARNINGS"
+test ! -z "$WARNINGS"
diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json
index e4662c0912..fb8006fdf2 100644
--- a/scripts/error-codes/codes.json
+++ b/scripts/error-codes/codes.json
@@ -142,5 +142,6 @@
"140": "Expected hook events to fire for the child before its parent includes it in onSetChildren().",
"141": "Expected onSetChildren() to fire for a container child before its parent includes it in onSetChildren().",
"142": "Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (%s has parents %s and %s).",
- "143": "React.Children.only expected to receive a single React element child."
+ "143": "React.Children.only expected to receive a single React element child.",
+ "144": "React.PropTypes type checking code is stripped in production."
}
diff --git a/scripts/error-codes/print-warnings.js b/scripts/error-codes/print-warnings.js
new file mode 100644
index 0000000000..c886e02195
--- /dev/null
+++ b/scripts/error-codes/print-warnings.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+'use strict';
+
+const babylon = require('babylon');
+const fs = require('fs');
+const through = require('through2');
+const traverse = require('babel-traverse').default;
+const gs = require('glob-stream');
+
+const evalToString = require('./evalToString');
+
+const paths = require('../../gulpfile').paths;
+
+
+const babylonOptions = {
+ sourceType: 'module',
+ // As a parser, babylon has its own options and we can't directly
+ // import/require a babel preset. It should be kept **the same** as
+ // the `babel-plugin-syntax-*` ones specified in
+ // https://github.com/facebook/fbjs/blob/master/babel-preset/configure.js
+ plugins: [
+ 'classProperties',
+ 'flow',
+ 'jsx',
+ 'trailingFunctionCommas',
+ 'objectRestSpread',
+ ],
+};
+
+const warnings = new Set();
+
+function transform(file, enc, cb) {
+ fs.readFile(file.path, 'utf8', function(err, source) {
+ if (err) {
+ cb(err);
+ return;
+ }
+
+ const ast = babylon.parse(source, babylonOptions);
+ traverse(ast, {
+ CallExpression: {
+ exit: function(astPath) {
+ if (astPath.get('callee').isIdentifier({name: 'warning'})) {
+ const node = astPath.node;
+
+ // warning messages can be concatenated (`+`) at runtime, so here's
+ // a trivial partial evaluator that interprets the literal value
+ const warningMsgLiteral = evalToString(node.arguments[1]);
+ warnings.add(JSON.stringify(warningMsgLiteral));
+ }
+ },
+ },
+ });
+
+ cb(null);
+ });
+}
+
+const sourcePaths = [].concat(
+ paths.react.src,
+ paths.reactDOM.src,
+ paths.reactNative.src,
+ paths.reactTestRenderer.src
+);
+
+gs(sourcePaths)
+ .pipe(through.obj(transform, cb => {
+ process.stdout.write(Array.from(warnings).sort().join('\n') + '\n');
+ cb();
+ }));
diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index ade485d62d..c2cfe1b935 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -157,13 +157,6 @@ src/isomorphic/children/__tests__/onlyChild-test.js
* should not fail when passed interpolated single child
* should return the only child
-src/isomorphic/children/__tests__/sliceChildren-test.js
-* should render the whole set if start zero is supplied
-* should render the remaining set if no end index is supplied
-* should exclude everything at or after the end index
-* should allow static children to be sliced
-* should slice nested children
-
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should filter out context not in contextTypes
* should pass next context to lifecycles
@@ -784,14 +777,6 @@ src/renderers/__tests__/ReactPerf-test.js
* should not print errant warnings if portal throws in componentWillMount()
* should not print errant warnings if portal throws in componentDidMount()
-src/renderers/__tests__/ReactStateSetters-test.js
-* createStateSetter should update state
-* createStateKeySetter should update state
-* createStateKeySetter is memoized
-* createStateSetter should update state from mixin
-* createStateKeySetter should update state with mixin
-* createStateKeySetter is memoized with mixin
-
src/renderers/__tests__/ReactStatelessComponent-test.js
* should render stateless component
* should update stateless component
@@ -1656,7 +1641,6 @@ src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
* should publish registration names of injected plugins
* should throw if multiple registration names collide
* should throw if an invalid event is published
-* should be able to get the plugin from synthetic events
src/renderers/shared/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js
* should do nothing when no one wants to respond
@@ -1695,6 +1679,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
* should return null if no error is thrown (development)
* can nest with same debug name (development)
* does not return nested errors (development)
+* can be shimmed (development)
* it should rethrow errors caught by invokeGuardedCallbackAndCatchFirstError (production)
* should call the callback the passed arguments (production)
* should call the callback with the provided context (production)
@@ -1702,6 +1687,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
* should return null if no error is thrown (production)
* can nest with same debug name (production)
* does not return nested errors (production)
+* can be shimmed (production)
src/renderers/shared/utils/__tests__/accumulateInto-test.js
* throws if the second item is null
diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js
index 20f372dc57..4a48cfe9e5 100644
--- a/scripts/jest/preprocessor.js
+++ b/scripts/jest/preprocessor.js
@@ -22,6 +22,7 @@ var pathToBabel = path.join(require.resolve('babel-core'), '..', 'package.json')
var pathToModuleMap = require.resolve('fbjs/module-map');
var pathToBabelPluginDevWithCode = require.resolve('../error-codes/dev-expression-with-codes');
var pathToBabelPluginModules = require.resolve('fbjs-scripts/babel-6/rewrite-modules');
+var pathToBabelPluginAsyncToGenerator = require.resolve('babel-plugin-transform-async-to-generator');
var pathToBabelrc = path.join(__dirname, '..', '..', '.babelrc');
var pathToErrorCodes = require.resolve('../error-codes/codes.json');
@@ -54,11 +55,17 @@ module.exports = {
!filePath.match(/\/node_modules\//) &&
!filePath.match(/\/third_party\//)
) {
+ // for test files, we also apply the async-await transform, but we want to
+ // make sure we don't accidentally apply that transform to product code.
+ var isTestFile = !!filePath.match(/\/__tests__\//);
return babel.transform(
src,
Object.assign(
{filename: path.relative(process.cwd(), filePath)},
- babelOptions
+ babelOptions,
+ isTestFile ? {
+ plugins: [pathToBabelPluginAsyncToGenerator].concat(babelOptions.plugins),
+ } : {}
)
).code;
}
diff --git a/src/addons/link/ReactStateSetters.js b/src/addons/link/ReactStateSetters.js
deleted file mode 100644
index 977826d301..0000000000
--- a/src/addons/link/ReactStateSetters.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactStateSetters
- */
-
-'use strict';
-
-var ReactStateSetters = {
- /**
- * Returns a function that calls the provided function, and uses the result
- * of that to set the component's state.
- *
- * @param {ReactCompositeComponent} component
- * @param {function} funcReturningState Returned callback uses this to
- * determine how to update state.
- * @return {function} callback that when invoked uses funcReturningState to
- * determined the object literal to setState.
- */
- createStateSetter: function(component, funcReturningState) {
- return function(a, b, c, d, e, f) {
- var partialState = funcReturningState.call(component, a, b, c, d, e, f);
- if (partialState) {
- component.setState(partialState);
- }
- };
- },
-
- /**
- * Returns a single-argument callback that can be used to update a single
- * key in the component's state.
- *
- * Note: this is memoized function, which makes it inexpensive to call.
- *
- * @param {ReactCompositeComponent} component
- * @param {string} key The key in the state that you should update.
- * @return {function} callback of 1 argument which calls setState() with
- * the provided keyName and callback argument.
- */
- createStateKeySetter: function(component, key) {
- // Memoize the setters.
- var cache = component.__keySetters || (component.__keySetters = {});
- return cache[key] || (cache[key] = createStateKeySetter(component, key));
- },
-};
-
-function createStateKeySetter(component, key) {
- // Partial state is allocated outside of the function closure so it can be
- // reused with every call, avoiding memory allocation when this function
- // is called.
- var partialState = {};
- return function stateKeySetter(value) {
- partialState[key] = value;
- component.setState(partialState);
- };
-}
-
-ReactStateSetters.Mixin = {
- /**
- * Returns a function that calls the provided function, and uses the result
- * of that to set the component's state.
- *
- * For example, these statements are equivalent:
- *
- * this.setState({x: 1});
- * this.createStateSetter(function(xValue) {
- * return {x: xValue};
- * })(1);
- *
- * @param {function} funcReturningState Returned callback uses this to
- * determine how to update state.
- * @return {function} callback that when invoked uses funcReturningState to
- * determined the object literal to setState.
- */
- createStateSetter: function(funcReturningState) {
- return ReactStateSetters.createStateSetter(this, funcReturningState);
- },
-
- /**
- * Returns a single-argument callback that can be used to update a single
- * key in the component's state.
- *
- * For example, these statements are equivalent:
- *
- * this.setState({x: 1});
- * this.createStateKeySetter('x')(1);
- *
- * Note: this is memoized function, which makes it inexpensive to call.
- *
- * @param {string} key The key in the state that you should update.
- * @return {function} callback of 1 argument which calls setState() with
- * the provided keyName and callback argument.
- */
- createStateKeySetter: function(key) {
- return ReactStateSetters.createStateKeySetter(this, key);
- },
-};
-
-module.exports = ReactStateSetters;
diff --git a/src/isomorphic/children/__tests__/sliceChildren-test.js b/src/isomorphic/children/__tests__/sliceChildren-test.js
deleted file mode 100644
index d09ab6757f..0000000000
--- a/src/isomorphic/children/__tests__/sliceChildren-test.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @emails react-core
- */
-
-'use strict';
-
-describe('sliceChildren', () => {
-
- var React;
-
- var sliceChildren;
-
- beforeEach(() => {
- React = require('react');
-
- sliceChildren = require('sliceChildren');
- });
-
- it('should render the whole set if start zero is supplied', () => {
- var fullSet = [
-
,
-
,
-
,
- ];
- var children = sliceChildren(fullSet, 0);
- expect(children).toEqual([
-
,
-
,
-
,
- ]);
- });
-
- it('should render the remaining set if no end index is supplied', () => {
- var fullSet = [
-
,
-
,
-
,
- ];
- var children = sliceChildren(fullSet, 1);
- expect(children).toEqual([
-
,
-
,
- ]);
- });
-
- it('should exclude everything at or after the end index', () => {
- var fullSet = [
-
,
-
,
-
,
-
,
- ];
- var children = sliceChildren(fullSet, 1, 2);
- expect(children).toEqual([
-
,
- ]);
- });
-
- it('should allow static children to be sliced', () => {
- var a = ;
- var b = ;
- var c = ;
-
- var el = {a}{b}{c}
;
- var children = sliceChildren(el.props.children, 1, 2);
- expect(children).toEqual([
- ,
- ]);
- });
-
- it('should slice nested children', () => {
- var fullSet = [
-
,
- [
-
,
-
,
- ],
-
,
- ];
- var children = sliceChildren(fullSet, 1, 2);
- expect(children).toEqual([
-
,
- ]);
- });
-
-});
diff --git a/src/isomorphic/children/sliceChildren.js b/src/isomorphic/children/sliceChildren.js
deleted file mode 100644
index 691af1da34..0000000000
--- a/src/isomorphic/children/sliceChildren.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule sliceChildren
- */
-
-'use strict';
-
-var ReactChildren = require('ReactChildren');
-
-/**
- * Slice children that are typically specified as `props.children`. This version
- * of slice children ignores empty child components.
- *
- * @param {*} children The children set to filter.
- * @param {number} start The first zero-based index to include in the subset.
- * @param {?number} end The non-inclusive last index of the subset.
- * @return {object} mirrored array with mapped children
- */
-function sliceChildren(children, start, end) {
- if (children == null) {
- return children;
- }
-
- var array = ReactChildren.toArray(children);
- return array.slice(start, end);
-}
-
-module.exports = sliceChildren;
diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js
index 07a46a8774..67842710e4 100644
--- a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js
+++ b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js
@@ -49,7 +49,7 @@ describe('ReactPropTypesProduction', function() {
'prop'
);
}).toThrowError(
- 'React.PropTypes type checking code is stripped in production.'
+ 'Minified React error #144'
);
}
diff --git a/src/renderers/__tests__/ReactStateSetters-test.js b/src/renderers/__tests__/ReactStateSetters-test.js
deleted file mode 100644
index dcc22a743b..0000000000
--- a/src/renderers/__tests__/ReactStateSetters-test.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @emails react-core
- */
-
-'use strict';
-
-var React = require('react');
-var ReactStateSetters = require('ReactStateSetters');
-var ReactTestUtils = require('ReactTestUtils');
-
-var TestComponent;
-var TestComponentWithMixin;
-
-describe('ReactStateSetters', () => {
- beforeEach(() => {
- jest.resetModules();
-
- TestComponent = class extends React.Component {
- state = {foo: 'foo'};
-
- render() {
- return
;
- }
- };
-
- TestComponentWithMixin = React.createClass({
- mixins: [ReactStateSetters.Mixin],
-
- getInitialState: function() {
- return {foo: 'foo'};
- },
-
- render: function() {
- return
;
- },
- });
- });
-
- it('createStateSetter should update state', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var setter = ReactStateSetters.createStateSetter(
- instance,
- function(a, b, c) {
- return {
- foo: a + b + c,
- bar: a * b * c,
- };
- }
- );
- expect(instance.state).toEqual({foo: 'foo'});
-
- setter(1, 2, 3);
- expect(instance.state).toEqual({foo: 6, bar: 6});
-
- setter(10, 11, 12);
- expect(instance.state).toEqual({foo: 33, bar: 1320});
- });
-
- it('createStateKeySetter should update state', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var setter = ReactStateSetters.createStateKeySetter(instance, 'foo');
-
- expect(instance.state).toEqual({foo: 'foo'});
-
- setter('bar');
- expect(instance.state).toEqual({foo: 'bar'});
-
- setter('baz');
- expect(instance.state).toEqual({foo: 'baz'});
- });
-
- it('createStateKeySetter is memoized', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var foo1 = ReactStateSetters.createStateKeySetter(instance, 'foo');
- var bar1 = ReactStateSetters.createStateKeySetter(instance, 'bar');
-
- var foo2 = ReactStateSetters.createStateKeySetter(instance, 'foo');
- var bar2 = ReactStateSetters.createStateKeySetter(instance, 'bar');
-
- expect(foo2).toBe(foo1);
- expect(bar2).toBe(bar1);
- });
-
- it('createStateSetter should update state from mixin', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var setter = instance.createStateSetter(
- function(a, b, c) {
- return {
- foo: a + b + c,
- bar: a * b * c,
- };
- }
- );
- expect(instance.state).toEqual({foo: 'foo'});
-
- setter(1, 2, 3);
- expect(instance.state).toEqual({foo: 6, bar: 6});
-
- setter(10, 11, 12);
- expect(instance.state).toEqual({foo: 33, bar: 1320});
- });
-
- it('createStateKeySetter should update state with mixin', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var setter = instance.createStateKeySetter('foo');
-
- expect(instance.state).toEqual({foo: 'foo'});
-
- setter('bar');
- expect(instance.state).toEqual({foo: 'bar'});
-
- setter('baz');
- expect(instance.state).toEqual({foo: 'baz'});
- });
-
- it('createStateKeySetter is memoized with mixin', () => {
- var instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
- expect(instance.state).toEqual({foo: 'foo'});
-
- var foo1 = instance.createStateKeySetter('foo');
- var bar1 = instance.createStateKeySetter('bar');
-
- var foo2 = instance.createStateKeySetter('foo');
- var bar2 = instance.createStateKeySetter('bar');
-
- expect(foo2).toBe(foo1);
- expect(bar2).toBe(bar1);
- });
-});
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
index daf84a2f21..afe70f87cc 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
@@ -19,41 +19,44 @@ let ReactDOMServer;
// Helper functions for rendering tests
// ====================================
+// promisified version of ReactDOM.render()
+function asyncReactDOMRender(reactElement, domElement) {
+ return new Promise(resolve =>
+ ReactDOM.render(reactElement, domElement, resolve));
+}
// performs fn asynchronously and expects count errors logged to console.error.
// will fail the test if the count of errors logged is not equal to count.
-function expectErrors(fn, count) {
+async function expectErrors(fn, count) {
if (console.error.calls && console.error.calls.reset) {
console.error.calls.reset();
} else {
spyOn(console, 'error');
}
- return fn().then((result) => {
- if (console.error.calls.count() !== count) {
- console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
- if (console.error.calls.count() > 0) {
- console.log(`We saw these warnings:`);
- for (var i = 0; i < console.error.calls.count(); i++) {
- console.log(console.error.calls.argsFor(i)[0]);
- }
+ const result = await fn();
+ if (console.error.calls.count() !== count && console.error.calls.count() !== 0) {
+ console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
+ if (console.error.calls.count() > 0) {
+ console.log(`We saw these warnings:`);
+ for (var i = 0; i < console.error.calls.count(); i++) {
+ console.log(console.error.calls.argsFor(i)[0]);
}
}
- expectDev(console.error.calls.count()).toBe(count);
- return result;
- });
+ }
+ expectDev(console.error.calls.count()).toBe(count);
+ return result;
}
// renders the reactElement into domElement, and expects a certain number of errors.
// returns a Promise that resolves when the render is complete.
function renderIntoDom(reactElement, domElement, errorCount = 0) {
return expectErrors(
- () => new Promise((resolve) => {
+ async () => {
ExecutionEnvironment.canUseDOM = true;
- ReactDOM.render(reactElement, domElement, () => {
- ExecutionEnvironment.canUseDOM = false;
- resolve(domElement.firstChild);
- });
- }),
+ await asyncReactDOMRender(reactElement, domElement);
+ ExecutionEnvironment.canUseDOM = false;
+ return domElement.firstChild;
+ },
errorCount
);
}
@@ -61,15 +64,14 @@ function renderIntoDom(reactElement, domElement, errorCount = 0) {
// Renders text using SSR and then stuffs it into a DOM node; returns the DOM
// element that corresponds with the reactElement.
// Does not render on client or perform client-side revival.
-function serverRender(reactElement, errorCount = 0) {
- return expectErrors(
+async function serverRender(reactElement, errorCount = 0) {
+ const markup = await expectErrors(
() => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
- errorCount)
- .then((markup) => {
- var domElement = document.createElement('div');
- domElement.innerHTML = markup;
- return domElement.firstChild;
- });
+ errorCount
+ );
+ var domElement = document.createElement('div');
+ domElement.innerHTML = markup;
+ return domElement.firstChild;
}
const clientCleanRender = (element, errorCount = 0) => {
@@ -77,12 +79,11 @@ const clientCleanRender = (element, errorCount = 0) => {
return renderIntoDom(element, div, errorCount);
};
-const clientRenderOnServerString = (element, errorCount = 0) => {
- return serverRender(element, errorCount).then((markup) => {
- var domElement = document.createElement('div');
- domElement.innerHTML = markup;
- return renderIntoDom(element, domElement, errorCount);
- });
+const clientRenderOnServerString = async (element, errorCount = 0) => {
+ const markup = await serverRender(element, errorCount);
+ var domElement = document.createElement('div');
+ domElement.innerHTML = markup;
+ return renderIntoDom(element, domElement, errorCount);
};
const clientRenderOnBadMarkup = (element, errorCount = 0) => {
@@ -143,24 +144,26 @@ describe('ReactDOMServerIntegration', () => {
});
describe('basic rendering', function() {
- itRenders('a blank div', render =>
- render(
).then(e => expect(e.tagName).toBe('DIV')));
+ itRenders('a blank div', async render => {
+ const e = await render(
);
+ expect(e.tagName).toBe('DIV');
+ });
- itRenders('a div with inline styles', render =>
- render(
).then(e => {
- expect(e.style.color).toBe('red');
- expect(e.style.width).toBe('30px');
- })
- );
+ itRenders('a div with inline styles', async render => {
+ const e = await render(
);
+ expect(e.style.color).toBe('red');
+ expect(e.style.width).toBe('30px');
+ });
- itRenders('a self-closing tag', render =>
- render( ).then(e => expect(e.tagName).toBe('BR')));
+ itRenders('a self-closing tag', async render => {
+ const e = await render( );
+ expect(e.tagName).toBe('BR');
+ });
- itRenders('a self-closing tag as a child', render =>
- render(
).then(e => {
- expect(e.childNodes.length).toBe(1);
- expect(e.firstChild.tagName).toBe('BR');
- })
- );
+ itRenders('a self-closing tag as a child', async render => {
+ const e = await render(
);
+ expect(e.childNodes.length).toBe(1);
+ expect(e.firstChild.tagName).toBe('BR');
+ });
});
});
diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js
index 5af2249d3b..ae35f8db6d 100644
--- a/src/renderers/shared/fiber/ReactFiberClassComponent.js
+++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js
@@ -227,22 +227,6 @@ module.exports = function(
}
}
-
- function markUpdate(workInProgress) {
- workInProgress.effectTag |= Update;
- }
-
- function markUpdateIfAlreadyInProgress(current: Fiber | null, workInProgress : Fiber) {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (current !== null) {
- if (workInProgress.memoizedProps !== current.memoizedProps ||
- workInProgress.memoizedState !== current.memoizedState) {
- markUpdate(workInProgress);
- }
- }
- }
-
function resetInputPointers(workInProgress : Fiber, instance : any) {
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
@@ -276,7 +260,6 @@ module.exports = function(
// Invokes the mount life-cycles on a previously never rendered instance.
function mountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : void {
- markUpdate(workInProgress);
const instance = workInProgress.stateNode;
const state = instance.state || null;
@@ -310,12 +293,14 @@ module.exports = function(
);
}
}
+ if (typeof instance.componentDidMount === 'function') {
+ workInProgress.effectTag |= Update;
+ }
}
// Called on a preexisting class instance. Returns false if a resumed render
// could be reused.
function resumeMountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean {
- markUpdate(workInProgress);
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
@@ -378,6 +363,9 @@ module.exports = function(
priorityLevel
);
}
+ if (typeof instance.componentDidMount === 'function') {
+ workInProgress.effectTag |= Update;
+ }
return true;
}
@@ -447,7 +435,14 @@ module.exports = function(
oldState === newState &&
!hasContextChanged() &&
!(updateQueue !== null && updateQueue.hasForceUpdate)) {
- markUpdateIfAlreadyInProgress(current, workInProgress);
+ // If an update was already in progress, we should schedule an Update
+ // effect even though we're bailing out, so that cWU/cDU are called.
+ if (typeof instance.componentDidUpdate === 'function') {
+ if (oldProps !== current.memoizedProps ||
+ oldState !== current.memoizedState) {
+ workInProgress.effectTag |= Update;
+ }
+ }
return false;
}
@@ -461,12 +456,21 @@ module.exports = function(
);
if (shouldUpdate) {
- markUpdate(workInProgress);
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
}
+ if (typeof instance.componentDidUpdate === 'function') {
+ workInProgress.effectTag |= Update;
+ }
} else {
- markUpdateIfAlreadyInProgress(current, workInProgress);
+ // If an update was already in progress, we should schedule an Update
+ // effect even though we're bailing out, so that cWU/cDU are called.
+ if (typeof instance.componentDidUpdate === 'function') {
+ if (oldProps !== current.memoizedProps ||
+ oldState !== current.memoizedState) {
+ workInProgress.effectTag |= Update;
+ }
+ }
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js
index e9676b106a..32cd4fb52f 100644
--- a/src/renderers/shared/fiber/ReactFiberCommitWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js
@@ -87,16 +87,6 @@ module.exports = function(
}
}
- // Only called during update. It's ok to throw.
- function detachRefIfNeeded(current : Fiber | null, finishedWork : Fiber) {
- if (current) {
- const currentRef = current.ref;
- if (currentRef !== null && currentRef !== finishedWork.ref) {
- currentRef(null);
- }
- }
- }
-
function getHostParent(fiber : Fiber) : I | C {
let parent = fiber.return;
while (parent !== null) {
@@ -381,7 +371,6 @@ module.exports = function(
function commitWork(current : Fiber | null, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
case ClassComponent: {
- detachRefIfNeeded(current, finishedWork);
return;
}
case HostComponent: {
@@ -398,7 +387,6 @@ module.exports = function(
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
- detachRefIfNeeded(current, finishedWork);
return;
}
case HostText: {
@@ -435,15 +423,11 @@ module.exports = function(
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
- if (typeof instance.componentDidMount === 'function') {
- instance.componentDidMount();
- }
+ instance.componentDidMount();
} else {
- if (typeof instance.componentDidUpdate === 'function') {
- const prevProps = current.memoizedProps;
- const prevState = current.memoizedState;
- instance.componentDidUpdate(prevProps, prevState);
- }
+ const prevProps = current.memoizedProps;
+ const prevState = current.memoizedState;
+ instance.componentDidUpdate(prevProps, prevState);
}
}
if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) {
@@ -495,10 +479,7 @@ module.exports = function(
}
}
- function commitRef(finishedWork : Fiber) {
- if (finishedWork.tag !== ClassComponent && finishedWork.tag !== HostComponent) {
- return;
- }
+ function commitAttachRef(finishedWork : Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = getPublicInstance(finishedWork.stateNode);
@@ -506,12 +487,20 @@ module.exports = function(
}
}
+ function commitDetachRef(current : Fiber) {
+ const currentRef = current.ref;
+ if (currentRef !== null) {
+ currentRef(null);
+ }
+ }
+
return {
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
- commitRef,
+ commitAttachRef,
+ commitDetachRef,
};
};
diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
index 8a04936183..aef565e1d2 100644
--- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
@@ -85,6 +85,10 @@ module.exports = function(
workInProgress.effectTag |= Update;
}
+ function markRef(workInProgress : Fiber) {
+ workInProgress.effectTag |= Ref;
+ }
+
function appendAllYields(yields : Array, workInProgress : Fiber) {
let node = workInProgress.stateNode;
if (node) {
@@ -231,9 +235,12 @@ module.exports = function(
workInProgress.updateQueue = (updatePayload : any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
- if (updatePayload || current.ref !== workInProgress.ref) {
+ if (updatePayload) {
markUpdate(workInProgress);
}
+ if (current.ref !== workInProgress.ref) {
+ markRef(workInProgress);
+ }
} else {
if (!newProps) {
invariant(
@@ -270,7 +277,7 @@ module.exports = function(
workInProgress.stateNode = instance;
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
- workInProgress.effectTag |= Ref;
+ markRef(workInProgress);
}
}
return null;
diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js
index 256f882045..ecd907835d 100644
--- a/src/renderers/shared/fiber/ReactFiberScheduler.js
+++ b/src/renderers/shared/fiber/ReactFiberScheduler.js
@@ -146,7 +146,8 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig {
- var dispatchConfig = event.dispatchConfig;
- if (dispatchConfig.registrationName) {
- return EventPluginRegistry.registrationNameModules[
- dispatchConfig.registrationName
- ] || null;
- }
- if (dispatchConfig.phasedRegistrationNames !== undefined) {
- // pulling phasedRegistrationNames out of dispatchConfig helps Flow see
- // that it is not undefined.
- var {phasedRegistrationNames} = dispatchConfig;
- for (var phase in phasedRegistrationNames) {
- if (!phasedRegistrationNames.hasOwnProperty(phase)) {
- continue;
- }
- var pluginModule = EventPluginRegistry.registrationNameModules[
- phasedRegistrationNames[phase]
- ];
- if (pluginModule) {
- return pluginModule;
- }
- }
- }
- return null;
- },
-
/**
* Exposed for unit testing.
* @private
diff --git a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
index 245e7bcc49..ed22b5c7de 100644
--- a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
+++ b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
@@ -225,40 +225,4 @@ describe('EventPluginRegistry', () => {
'`one`.'
);
});
-
- it('should be able to get the plugin from synthetic events', () => {
- var clickDispatchConfig = {
- registrationName: 'onClick',
- };
- var magicDispatchConfig = {
- phasedRegistrationNames: {
- bubbled: 'onMagicBubble',
- captured: 'onMagicCapture',
- },
- };
-
- var OnePlugin = createPlugin({
- eventTypes: {
- click: clickDispatchConfig,
- magic: magicDispatchConfig,
- },
- });
-
- var clickEvent = {dispatchConfig: clickDispatchConfig};
- var magicEvent = {dispatchConfig: magicDispatchConfig};
-
- expect(EventPluginRegistry.getPluginModuleForEvent(clickEvent)).toBe(null);
- expect(EventPluginRegistry.getPluginModuleForEvent(magicEvent)).toBe(null);
-
- EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});
- EventPluginRegistry.injectEventPluginOrder(['one']);
-
- expect(
- EventPluginRegistry.getPluginModuleForEvent(clickEvent)
- ).toBe(OnePlugin);
- expect(
- EventPluginRegistry.getPluginModuleForEvent(magicEvent)
- ).toBe(OnePlugin);
- });
-
});
diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js
index c2cd671767..f53bdddc5a 100644
--- a/src/renderers/shared/utils/ReactErrorUtils.js
+++ b/src/renderers/shared/utils/ReactErrorUtils.js
@@ -26,7 +26,7 @@ let caughtError = null;
const ReactErrorUtils = {
invokeGuardedCallback: function(
name: string | null,
- func: (A, B, C, D, E, F) => void,
+ func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
context: Context,
a: A,
b: B,
@@ -55,7 +55,7 @@ const ReactErrorUtils = {
*/
invokeGuardedCallbackAndCatchFirstError: function (
name: string | null,
- func: (A, B, C, D, E, F) => void,
+ func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
context: Context,
a: A,
b: B,
diff --git a/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js b/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
index 74069e5900..c540a2bb1e 100644
--- a/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
+++ b/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
@@ -106,5 +106,30 @@ describe('ReactErrorUtils', () => {
expect(err3).toBe(null); // Returns null because inner error was already captured
expect(err2).toBe(err1);
});
+
+ it(`can be shimmed (${environment})`, () => {
+ const ops = [];
+ // Override the original invokeGuardedCallback
+ ReactErrorUtils.invokeGuardedCallback = function(name, func, context, a) {
+ ops.push(a);
+ try {
+ func.call(context, a);
+ } catch (error) {
+ return error;
+ }
+ return null;
+ };
+
+ var err = new Error('foo');
+ var callback = function() {
+ throw err;
+ };
+ ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError('foo', callback, null, 'somearg');
+ expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
+ // invokeGuardedCallbackAndCatchFirstError and rethrowCaughtError close
+ // over ReactErrorUtils.invokeGuardedCallback so should use the
+ // shimmed version.
+ expect(ops).toEqual(['somearg']);
+ });
}
});
diff --git a/yarn.lock b/yarn.lock
index bb5a0d6b47..ca3626dbbe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1700,6 +1700,15 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
dependencies:
readable-stream "^2.0.2"
+duplexify@^3.1.2:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
+ dependencies:
+ end-of-stream "1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -1721,6 +1730,18 @@ encoding@^0.1.11:
dependencies:
iconv-lite "~0.4.13"
+end-of-stream@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
+ dependencies:
+ once "~1.3.0"
+
+end-of-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
+ dependencies:
+ once "~1.3.0"
+
end-of-stream@~0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf"
@@ -1849,9 +1870,6 @@ eslint-plugin-flowtype@^2.25.0:
"eslint-plugin-react-internal@file:./eslint-rules":
version "0.0.0"
-"eslint-plugin-react-internal@file:eslint-rules":
- version "0.0.0"
-
eslint-plugin-react@^6.7.1:
version "6.9.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.9.0.tgz#54c2e9906b76f9d10142030bdc34e9d6840a0bb2"
@@ -1998,6 +2016,12 @@ expand-tilde@^1.2.1, expand-tilde@^1.2.2:
dependencies:
os-homedir "^1.0.1"
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ dependencies:
+ is-extendable "^0.1.0"
+
extend@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8"
@@ -2312,6 +2336,13 @@ glob-parent@^2.0.0:
dependencies:
is-glob "^2.0.0"
+glob-parent@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+ dependencies:
+ is-glob "^3.1.0"
+ path-dirname "^1.0.0"
+
glob-stream@^3.1.5:
version "3.1.18"
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b"
@@ -2323,6 +2354,21 @@ glob-stream@^3.1.5:
through2 "^0.6.1"
unique-stream "^1.0.0"
+glob-stream@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
+ dependencies:
+ extend "^3.0.0"
+ glob "^7.1.1"
+ glob-parent "^3.1.0"
+ is-negated-glob "^1.0.0"
+ ordered-read-streams "^1.0.0"
+ pumpify "^1.3.5"
+ readable-stream "^2.1.5"
+ remove-trailing-separator "^1.0.1"
+ to-absolute-glob "^2.0.0"
+ unique-stream "^2.0.2"
+
glob-watcher@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@@ -2364,7 +2410,7 @@ glob@^6.0.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0:
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
@@ -2842,7 +2888,7 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-is-absolute@^0.2.3:
+is-absolute@^0.2.3, is-absolute@^0.2.5:
version "0.2.6"
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb"
dependencies:
@@ -2893,7 +2939,7 @@ is-equal-shallow@^0.1.3:
dependencies:
is-primitive "^2.0.0"
-is-extendable@^0.1.1:
+is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -2901,6 +2947,10 @@ is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+is-extglob@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
is-finite@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
@@ -2923,6 +2973,12 @@ is-glob@^2.0.0, is-glob@^2.0.1:
dependencies:
is-extglob "^1.0.0"
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
is-integer@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.6.tgz#5273819fada880d123e1ac00a938e7172dd8d95e"
@@ -2938,6 +2994,10 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
jsonpointer "^4.0.0"
xtend "^4.0.0"
+is-negated-glob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
+
is-number@^2.0.2, is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -3922,7 +3982,7 @@ object.omit@^2.0.0:
for-own "^0.1.4"
is-extendable "^0.1.1"
-once@^1.3.0, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -3968,6 +4028,13 @@ ordered-read-streams@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126"
+ordered-read-streams@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.0.tgz#d674a86ffcedf83d0ae06afa2918855e96d4033a"
+ dependencies:
+ is-stream "^1.0.1"
+ readable-stream "^2.0.1"
+
os-browserify@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54"
@@ -4063,6 +4130,10 @@ path-browserify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+path-dirname@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+
path-exists@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081"
@@ -4197,6 +4268,21 @@ public-encrypt@^4.0.0:
parse-asn1 "^5.0.0"
randombytes "^2.0.1"
+pump@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@^1.3.5:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b"
+ dependencies:
+ duplexify "^3.1.2"
+ inherits "^2.0.1"
+ pump "^1.0.0"
+
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -4280,7 +4366,7 @@ read-pkg@^1.0.0:
isarray "0.0.1"
string_decoder "~0.10.x"
-"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.1.5:
+readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.1.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
dependencies:
@@ -4402,6 +4488,10 @@ regjsparser@^0.1.4:
dependencies:
jsesc "~0.5.0"
+remove-trailing-separator@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz#615ebb96af559552d4bf4057c8436d486ab63cc4"
+
repeat-element@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
@@ -4751,6 +4841,10 @@ stream-http@^2.0.0:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
stream-splicer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83"
@@ -4902,6 +4996,13 @@ throat@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6"
+through2-filter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec"
+ dependencies:
+ through2 "~2.0.0"
+ xtend "~4.0.0"
+
through2@^0.6.1:
version "0.6.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
@@ -4916,7 +5017,7 @@ through2@^1.0.0:
readable-stream ">=1.1.13-1 <1.2.0-0"
xtend ">=4.0.0 <4.1.0-0"
-through2@^2.0.0:
+through2@^2.0.0, through2@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
dependencies:
@@ -4953,6 +5054,14 @@ tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
+to-absolute-glob@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.1.tgz#70c375805b9e3105e899ee8dbdd6a9aa108f407b"
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-absolute "^0.2.5"
+ is-negated-glob "^1.0.0"
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@@ -5072,6 +5181,13 @@ unique-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b"
+unique-stream@^2.0.2:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"
+ dependencies:
+ json-stable-stringify "^1.0.0"
+ through2-filter "^2.0.0"
+
url@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -5261,7 +5377,7 @@ xml-name-validator@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
-"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1:
+"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"