diff --git a/package.json b/package.json index 03f5aa57c6f50..258a4a093944d 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@babel/preset-flow": "^7.10.4", "@babel/preset-react": "^7.10.4", "@babel/traverse": "^7.11.0", - "@mattiasbuelens/web-streams-polyfill": "^0.3.2", + "web-streams-polyfill": "^3.1.1", "abort-controller": "^3.0.0", "art": "0.10.1", "babel-eslint": "^10.0.3", diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js index 546e1f622baaa..224dac20d1af4 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -10,7 +10,7 @@ 'use strict'; // Polyfills for test environment -global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; +global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; global.AbortController = require('abort-controller'); diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index 51a5604bd5554..eed5f2219fbfd 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -68,6 +68,7 @@ function render(model: ReactModel, options?: Options): Destination { options ? options.onError : undefined, ); ReactNoopFlightServer.startWork(request); + ReactNoopFlightServer.startFlowing(request); return destination; } diff --git a/packages/react-server-dom-relay/src/ReactFlightDOMRelayServer.js b/packages/react-server-dom-relay/src/ReactFlightDOMRelayServer.js index 56769e4255394..fe2f9c8008c85 100644 --- a/packages/react-server-dom-relay/src/ReactFlightDOMRelayServer.js +++ b/packages/react-server-dom-relay/src/ReactFlightDOMRelayServer.js @@ -13,7 +13,11 @@ import type { Destination, } from './ReactFlightDOMRelayServerHostConfig'; -import {createRequest, startWork} from 'react-server/src/ReactFlightServer'; +import { + createRequest, + startWork, + startFlowing, +} from 'react-server/src/ReactFlightServer'; type Options = { onError?: (error: mixed) => void, @@ -32,6 +36,7 @@ function render( options ? options.onError : undefined, ); startWork(request); + startFlowing(request); } export {render}; diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js index accd749a56d54..261cf85c9bdf4 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js @@ -26,7 +26,7 @@ function renderToReadableStream( options?: Options, ): ReadableStream { let request; - return new ReadableStream({ + const stream = new ReadableStream({ start(controller) { request = createRequest( model, @@ -37,10 +37,17 @@ function renderToReadableStream( startWork(request); }, pull(controller) { - startFlowing(request); + // Pull is called immediately even if the stream is not passed to anything. + // That's buffering too early. We want to start buffering once the stream + // is actually used by something so we can give it the best result possible + // at that point. + if (stream.locked) { + startFlowing(request); + } }, cancel(reason) {}, }); + return stream; } export {renderToReadableStream}; diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js index dc78c503aaf39..4529abd2b6718 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js @@ -37,8 +37,9 @@ function pipeToNodeWritable( webpackMap, options ? options.onError : undefined, ); - destination.on('drain', createDrainHandler(destination, request)); startWork(request); + startFlowing(request); + destination.on('drain', createDrainHandler(destination, request)); } export {pipeToNodeWritable}; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 12a50fce2187e..60fbac215f73d 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -10,7 +10,7 @@ 'use strict'; // Polyfills for test environment -global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; +global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextDecoder = require('util').TextDecoder; // Don't wait before processing work on the server. diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 04710cbdc5cf9..8b82caac6bc74 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -5,28 +5,56 @@ * LICENSE file in the root directory of this source tree. * * @emails react-core - * @jest-environment node */ 'use strict'; // Polyfills for test environment -global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; +global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; +let webpackModuleIdx = 0; +let webpackModules = {}; +let webpackMap = {}; +global.__webpack_require__ = function(id) { + return webpackModules[id]; +}; + +let act; let React; +let ReactDOM; let ReactServerDOMWriter; let ReactServerDOMReader; describe('ReactFlightDOMBrowser', () => { beforeEach(() => { jest.resetModules(); + webpackModules = {}; + webpackMap = {}; + act = require('jest-react').act; React = require('react'); + ReactDOM = require('react-dom'); ReactServerDOMWriter = require('react-server-dom-webpack/writer.browser.server'); ReactServerDOMReader = require('react-server-dom-webpack'); }); + function moduleReference(moduleExport) { + const idx = webpackModuleIdx++; + webpackModules[idx] = { + d: moduleExport, + }; + webpackMap['path/' + idx] = { + default: { + id: '' + idx, + chunks: [], + name: 'd', + }, + }; + const MODULE_TAG = Symbol.for('react.module.reference'); + return {$$typeof: MODULE_TAG, filepath: 'path/' + idx, name: 'default'}; + } + async function waitForSuspense(fn) { while (true) { try { @@ -75,4 +103,241 @@ describe('ReactFlightDOMBrowser', () => { }); }); }); + + it('should resolve HTML using W3C streams', async () => { + function Text({children}) { + return {children}; + } + function HTML() { + return ( +