From e65b22f304e3a3d8e86650f195a0222e2034a1fb Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 7 Nov 2018 23:20:37 -0800 Subject: [PATCH] Add Fizz Browser host config This lets Fizz render to WHATWG streams. E.g. for rendering in a Service Worker. I added react-dom/unstable-fizz.browser as the entry point for this. Since we now have two configurations of DOM. I had to add another inlinedHostConfigs configuration called `dom-browser`. The reconciler treats this configuration the same as `dom`. For stream it checks against the ReactFizzHostConfigBrowser instead of the Node one. --- package.json | 3 +- .../react-dom/npm/unstable-fizz.browser.js | 7 ++ packages/react-dom/npm/unstable-fizz.node.js | 4 +- packages/react-dom/package.json | 4 +- .../ReactDOMFizzServerBrowser-test.js | 45 +++++++++++++ ...test.js => ReactDOMFizzServerNode-test.js} | 0 .../src/server/ReactDOMFizzServerBrowser.js | 34 ++++++++++ .../src/server/ReactDOMFizzServerNode.js | 5 +- packages/react-dom/unstable-fizz.browser.js | 16 +++++ .../src/ReactNoopServer.js | 1 + .../react-reconciler/inline.dom-browser.js | 11 ++++ .../forks/ReactFiberHostConfig.dom-browser.js | 10 +++ packages/react-stream/inline.dom-browser.js | 11 ++++ .../src/ReactFizzHostConfigBrowser.js | 37 +++++++++++ .../src/ReactFizzHostConfigNode.js | 4 ++ .../react-stream/src/ReactFizzStreamer.js | 6 +- .../ReactFizzFormatConfig.dom-browser.js | 10 +++ .../src/forks/ReactFizzHostConfig.custom.js | 1 + .../forks/ReactFizzHostConfig.dom-browser.js | 10 +++ scripts/rollup/bundles.js | 9 ++- scripts/rollup/results.json | 64 +++++++++++++------ scripts/shared/inlinedHostConfigs.js | 8 ++- yarn.lock | 12 ++++ 23 files changed, 286 insertions(+), 26 deletions(-) create mode 100644 packages/react-dom/npm/unstable-fizz.browser.js create mode 100644 packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js rename packages/react-dom/src/__tests__/{ReactDOMFizzServer-test.js => ReactDOMFizzServerNode-test.js} (100%) create mode 100644 packages/react-dom/src/server/ReactDOMFizzServerBrowser.js create mode 100644 packages/react-dom/unstable-fizz.browser.js create mode 100644 packages/react-reconciler/inline.dom-browser.js create mode 100644 packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-browser.js create mode 100644 packages/react-stream/inline.dom-browser.js create mode 100644 packages/react-stream/src/ReactFizzHostConfigBrowser.js create mode 100644 packages/react-stream/src/forks/ReactFizzFormatConfig.dom-browser.js create mode 100644 packages/react-stream/src/forks/ReactFizzHostConfig.dom-browser.js diff --git a/package.json b/package.json index 9f3d3d0970d80..e3e37c428d4a1 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,8 @@ "targz": "^1.0.1", "through2": "^2.0.0", "tmp": "~0.0.28", - "typescript": "~1.8.10" + "typescript": "~1.8.10", + "@mattiasbuelens/web-streams-polyfill": "0.1.0" }, "devEngines": { "node": "8.x || 9.x || 10.x" diff --git a/packages/react-dom/npm/unstable-fizz.browser.js b/packages/react-dom/npm/unstable-fizz.browser.js new file mode 100644 index 0000000000000..976fac7eca51c --- /dev/null +++ b/packages/react-dom/npm/unstable-fizz.browser.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-dom-unstable-fizz.browser.production.min.js'); +} else { + module.exports = require('./cjs/react-dom-unstable-fizz.browser.development.js'); +} diff --git a/packages/react-dom/npm/unstable-fizz.node.js b/packages/react-dom/npm/unstable-fizz.node.js index 45babe4e00588..038a3fd0fb495 100644 --- a/packages/react-dom/npm/unstable-fizz.node.js +++ b/packages/react-dom/npm/unstable-fizz.node.js @@ -1,7 +1,7 @@ 'use strict'; if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-dom-unstable-fizz.production.min.js'); + module.exports = require('./cjs/react-dom-unstable-fizz.node.production.min.js'); } else { - module.exports = require('./cjs/react-dom-unstable-fizz.development.js'); + module.exports = require('./cjs/react-dom-unstable-fizz.node.development.js'); } diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index c4d017e084a48..71192714262bc 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -31,13 +31,15 @@ "server.node.js", "test-utils.js", "unstable-fizz.js", + "unstable-fizz.browser.js", "unstable-fizz.node.js", "unstable-native-dependencies.js", "cjs/", "umd/" ], "browser": { - "./server.js": "./server.browser.js" + "./server.js": "./server.browser.js", + "./unstable-fizz.js": "./unstable-fizz.browser.js" }, "browserify": { "transform": [ diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js new file mode 100644 index 0000000000000..3d34b661104bd --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -0,0 +1,45 @@ +/** + * 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. + * + * @emails react-core + */ + +'use strict'; + +// Polyfills for test environment +global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; +global.TextEncoder = require('util').TextEncoder; + +let React; +let ReactDOMFizzServer; + +describe('ReactDOMFizzServer', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMFizzServer = require('react-dom/unstable-fizz.browser'); + }); + + async function readResult(stream) { + let reader = stream.getReader(); + let result = ''; + while (true) { + let {done, value} = await reader.read(); + if (done) { + return result; + } + result += Buffer.from(value).toString('utf8'); + } + } + + it('should call renderToReadableStream', async () => { + let stream = ReactDOMFizzServer.renderToReadableStream( +
hello world
, + ); + let result = await readResult(stream); + expect(result).toBe('
hello world
'); + }); +}); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js similarity index 100% rename from packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js rename to packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js diff --git a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js new file mode 100644 index 0000000000000..17da2d7e48e87 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js @@ -0,0 +1,34 @@ +/** + * 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 {ReactNodeList} from 'shared/ReactTypes'; + +import { + createRequest, + startWork, + startFlowing, +} from 'react-stream/inline.dom-browser'; + +function renderToReadableStream(children: ReactNodeList): ReadableStream { + let request; + return new ReadableStream({ + start(controller) { + request = createRequest(children, controller); + startWork(request); + }, + pull(controller) { + startFlowing(request, controller.desiredSize); + }, + cancel(reason) {}, + }); +} + +export default { + renderToReadableStream, +}; diff --git a/packages/react-dom/src/server/ReactDOMFizzServerNode.js b/packages/react-dom/src/server/ReactDOMFizzServerNode.js index 39947c4defc75..72b1bcb74272c 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerNode.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerNode.js @@ -16,7 +16,10 @@ function createDrainHandler(destination, request) { return () => startFlowing(request, 0); } -function pipeToNodeWritable(children: ReactNodeList, destination: Writable) { +function pipeToNodeWritable( + children: ReactNodeList, + destination: Writable, +): void { let request = createRequest(children, destination); destination.on('drain', createDrainHandler(destination, request)); startWork(request); diff --git a/packages/react-dom/unstable-fizz.browser.js b/packages/react-dom/unstable-fizz.browser.js new file mode 100644 index 0000000000000..4fb175ec6825e --- /dev/null +++ b/packages/react-dom/unstable-fizz.browser.js @@ -0,0 +1,16 @@ +/** + * 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 + */ + +'use strict'; + +const ReactDOMFizzServerBrowser = require('./src/server/ReactDOMFizzServerBrowser'); + +// TODO: decide on the top-level export form. +// This is hacky but makes it work with both Rollup and Jest +module.exports = ReactDOMFizzServerBrowser.default || ReactDOMFizzServerBrowser; diff --git a/packages/react-noop-renderer/src/ReactNoopServer.js b/packages/react-noop-renderer/src/ReactNoopServer.js index 491675dc73d1c..2077687b9f9b1 100644 --- a/packages/react-noop-renderer/src/ReactNoopServer.js +++ b/packages/react-noop-renderer/src/ReactNoopServer.js @@ -27,6 +27,7 @@ const ReactNoopServer = ReactFizzStreamer({ destination.push(JSON.parse(Buffer.from((buffer: any)).toString('utf8'))); }, completeWriting(destination: Destination): void {}, + close(destination: Destination): void {}, flushBuffered(destination: Destination): void {}, convertStringToBuffer(content: string): Uint8Array { return Buffer.from(content, 'utf8'); diff --git a/packages/react-reconciler/inline.dom-browser.js b/packages/react-reconciler/inline.dom-browser.js new file mode 100644 index 0000000000000..0d0c1546cc9a2 --- /dev/null +++ b/packages/react-reconciler/inline.dom-browser.js @@ -0,0 +1,11 @@ +/** + * 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. + */ + +// This file intentionally does *not* have the Flow annotation. +// Don't add it. See `./inline-typed.js` for an explanation. + +export * from './src/ReactFiberReconciler'; diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-browser.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-browser.js new file mode 100644 index 0000000000000..d830c8501be27 --- /dev/null +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-browser.js @@ -0,0 +1,10 @@ +/** + * 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 + */ + +export * from 'react-dom/src/client/ReactDOMHostConfig'; diff --git a/packages/react-stream/inline.dom-browser.js b/packages/react-stream/inline.dom-browser.js new file mode 100644 index 0000000000000..9bae815dca122 --- /dev/null +++ b/packages/react-stream/inline.dom-browser.js @@ -0,0 +1,11 @@ +/** + * 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. + */ + +// This file intentionally does *not* have the Flow annotation. +// Don't add it. See `./inline-typed.js` for an explanation. + +export * from './src/ReactFizzStreamer'; diff --git a/packages/react-stream/src/ReactFizzHostConfigBrowser.js b/packages/react-stream/src/ReactFizzHostConfigBrowser.js new file mode 100644 index 0000000000000..390a6efc82749 --- /dev/null +++ b/packages/react-stream/src/ReactFizzHostConfigBrowser.js @@ -0,0 +1,37 @@ +/** + * 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 + */ + +export type Destination = ReadableStreamController; + +export function scheduleWork(callback: () => void) { + callback(); +} + +export function flushBuffered(destination: Destination) { + // WHATWG Streams do not yet have a way to flush the underlying + // transform streams. https://github.com/whatwg/streams/issues/960 +} + +export function beginWriting(destination: Destination) {} + +export function writeChunk(destination: Destination, buffer: Uint8Array) { + destination.enqueue(buffer); +} + +export function completeWriting(destination: Destination) {} + +export function close(destination: Destination) { + destination.close(); +} + +const textEncoder = new TextEncoder(); + +export function convertStringToBuffer(content: string): Uint8Array { + return textEncoder.encode(content); +} diff --git a/packages/react-stream/src/ReactFizzHostConfigNode.js b/packages/react-stream/src/ReactFizzHostConfigNode.js index d6622376e3372..2ee9ddec354d9 100644 --- a/packages/react-stream/src/ReactFizzHostConfigNode.js +++ b/packages/react-stream/src/ReactFizzHostConfigNode.js @@ -39,6 +39,10 @@ export function completeWriting(destination: Destination) { destination.uncork(); } +export function close(destination: Destination) { + destination.end(); +} + export function convertStringToBuffer(content: string): Uint8Array { return Buffer.from(content, 'utf8'); } diff --git a/packages/react-stream/src/ReactFizzStreamer.js b/packages/react-stream/src/ReactFizzStreamer.js index 6048073535623..80239287c2160 100644 --- a/packages/react-stream/src/ReactFizzStreamer.js +++ b/packages/react-stream/src/ReactFizzStreamer.js @@ -16,6 +16,7 @@ import { writeChunk, completeWriting, flushBuffered, + close, } from './ReactFizzHostConfig'; import {formatChunk} from './ReactFizzFormatConfig'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; @@ -36,7 +37,8 @@ export function createRequest( function performWork(request: OpaqueRequest): void { let element = (request.children: any); - if (element.$$typeof !== REACT_ELEMENT_TYPE) { + request.children = null; + if (element && element.$$typeof !== REACT_ELEMENT_TYPE) { return; } let type = element.type; @@ -55,6 +57,7 @@ function performWork(request: OpaqueRequest): void { function flushCompletedChunks(request: OpaqueRequest) { let destination = request.destination; let chunks = request.completedChunks; + request.completedChunks = []; beginWriting(destination); try { @@ -65,6 +68,7 @@ function flushCompletedChunks(request: OpaqueRequest) { } finally { completeWriting(destination); } + close(destination); } export function startWork(request: OpaqueRequest): void { diff --git a/packages/react-stream/src/forks/ReactFizzFormatConfig.dom-browser.js b/packages/react-stream/src/forks/ReactFizzFormatConfig.dom-browser.js new file mode 100644 index 0000000000000..e3b8782adee7c --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzFormatConfig.dom-browser.js @@ -0,0 +1,10 @@ +/** + * 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 + */ + +export * from 'react-dom/src/server/ReactDOMFizzServerFormatConfig'; diff --git a/packages/react-stream/src/forks/ReactFizzHostConfig.custom.js b/packages/react-stream/src/forks/ReactFizzHostConfig.custom.js index aca4559cd2334..71d876a10e5a1 100644 --- a/packages/react-stream/src/forks/ReactFizzHostConfig.custom.js +++ b/packages/react-stream/src/forks/ReactFizzHostConfig.custom.js @@ -31,4 +31,5 @@ export const beginWriting = $$$hostConfig.beginWriting; export const writeChunk = $$$hostConfig.writeChunk; export const completeWriting = $$$hostConfig.completeWriting; export const flushBuffered = $$$hostConfig.flushBuffered; +export const close = $$$hostConfig.close; export const convertStringToBuffer = $$$hostConfig.convertStringToBuffer; diff --git a/packages/react-stream/src/forks/ReactFizzHostConfig.dom-browser.js b/packages/react-stream/src/forks/ReactFizzHostConfig.dom-browser.js new file mode 100644 index 0000000000000..ff949e1c746a1 --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzHostConfig.dom-browser.js @@ -0,0 +1,10 @@ +/** + * 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 + */ + +export * from '../ReactFizzHostConfigBrowser'; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 4932df206634f..f049b76f6b051 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -140,10 +140,17 @@ const bundles = [ }, /******* React DOM Fizz Server *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], + moduleType: RENDERER, + entry: 'react-dom/unstable-fizz.browser', + global: 'ReactDOMFizzServer', + externals: ['react'], + }, { bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], moduleType: RENDERER, - entry: 'react-dom/unstable-fizz', + entry: 'react-dom/unstable-fizz.node', global: 'ReactDOMFizzServer', externals: ['react'], }, diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index b263a7dd2760e..91d4f827bac0d 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -942,57 +942,85 @@ "filename": "ReactDOMFizzServer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 3660, - "gzip": 1408 + "size": 3772, + "gzip": 1432 }, { "filename": "ReactDOMFizzServer-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 2092, - "gzip": 844 + "size": 2211, + "gzip": 874 }, { "filename": "react-noop-renderer-server.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 1825, - "gzip": 864 + "size": 1861, + "gzip": 869 }, { "filename": "react-noop-renderer-server.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 785, - "gzip": 477 + "size": 804, + "gzip": 482 }, { "filename": "react-stream.development.js", "bundleType": "NODE_DEV", "packageName": "react-stream", - "size": 4508, - "gzip": 1650 + "size": 4633, + "gzip": 1683 }, { "filename": "react-stream.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-stream", - "size": 1161, - "gzip": 631 + "size": 1224, + "gzip": 655 }, { - "filename": "react-dom-unstable-fizz.development.js", + "filename": "react-dom-unstable-fizz.browser.development.js", + "bundleType": "UMD_DEV", + "packageName": "react-dom", + "size": 3704, + "gzip": 1463 + }, + { + "filename": "react-dom-unstable-fizz.browser.production.min.js", + "bundleType": "UMD_PROD", + "packageName": "react-dom", + "size": 1227, + "gzip": 697 + }, + { + "filename": "react-dom-unstable-fizz.browser.development.js", + "bundleType": "NODE_DEV", + "packageName": "react-dom", + "size": 3528, + "gzip": 1416 + }, + { + "filename": "react-dom-unstable-fizz.browser.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-dom", + "size": 1063, + "gzip": 628 + }, + { + "filename": "react-dom-unstable-fizz.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 3663, - "gzip": 1417 + "size": 3780, + "gzip": 1443 }, { - "filename": "react-dom-unstable-fizz.production.min.js", + "filename": "react-dom-unstable-fizz.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 1073, - "gzip": 636 + "size": 1122, + "gzip": 659 } ] } \ No newline at end of file diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 137332f990c6f..1f66699387638 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -9,7 +9,13 @@ module.exports = [ { shortName: 'dom', - entryPoints: ['react-dom', 'react-dom/unstable-fizz'], + entryPoints: ['react-dom', 'react-dom/unstable-fizz.node'], + isFlowTyped: true, + isFizzSupported: true, + }, + { + shortName: 'dom-browser', + entryPoints: ['react-dom/unstable-fizz.browser'], isFlowTyped: true, isFizzSupported: true, }, diff --git a/yarn.lock b/yarn.lock index c9ffffa9fd9c8..5bb72e40fd3aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -94,6 +94,18 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@mattiasbuelens/web-streams-polyfill@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mattiasbuelens/web-streams-polyfill/-/web-streams-polyfill-0.1.0.tgz#c06ebfa7e00cc512a878c3aaae4cf113ac89ac24" + integrity sha512-oMsvblvOezdM/j1ph0uU8s6Wm0EfCyMZtZcxQ232CqSpjm08lrKPizeMltN0eVv4dQf0DDFaxUFyiz8x51lgAA== + dependencies: + "@types/whatwg-streams" "^0.0.6" + +"@types/whatwg-streams@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/whatwg-streams/-/whatwg-streams-0.0.6.tgz#5062c67efb695c886fe3dbb9618df35aac418503" + integrity sha512-O4Hat94N1RUCObqAbVUtd6EcucseqBcpfbFXzy12CYF6BQVHWR+ztDA3YPjewCmdKHYZ5VA7TZ5hq2bMyqxiBw== + abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"