diff --git a/fixtures/fizz-ssr-browser/index.html b/fixtures/fizz-ssr-browser/index.html new file mode 100644 index 0000000000000..aa522dfe83653 --- /dev/null +++ b/fixtures/fizz-ssr-browser/index.html @@ -0,0 +1,40 @@ + + + + + Fizz Example + + +

Fizz Example

+
+

+ To install React, follow the instructions on + GitHub. +

+

+ If you can see this, React is not working right. + If you checked out the source from GitHub make sure to run npm run build. +

+
+ + + + + + diff --git a/package.json b/package.json index f6829548c0a66..eb5fe1324ba81 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.js b/packages/react-dom/npm/unstable-fizz.js new file mode 100644 index 0000000000000..ca4135d36b87c --- /dev/null +++ b/packages/react-dom/npm/unstable-fizz.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./unstable-fizz.node'); diff --git a/packages/react-dom/npm/unstable-fizz.node.js b/packages/react-dom/npm/unstable-fizz.node.js new file mode 100644 index 0000000000000..038a3fd0fb495 --- /dev/null +++ b/packages/react-dom/npm/unstable-fizz.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-dom-unstable-fizz.node.production.min.js'); +} else { + 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 6c4e6255acc75..c0c283ea04b49 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -32,12 +32,16 @@ "server.node.js", "test-utils.js", "unstable-fire.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__/ReactDOMFizzServerNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js new file mode 100644 index 0000000000000..79fa80b322b71 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js @@ -0,0 +1,39 @@ +/** + * 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 + * @jest-environment node + */ + +'use strict'; + +let Stream; +let React; +let ReactDOMFizzServer; + +describe('ReactDOMFizzServer', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMFizzServer = require('react-dom/unstable-fizz'); + Stream = require('stream'); + }); + + function getTestWritable() { + let writable = new Stream.PassThrough(); + writable.setEncoding('utf8'); + writable.result = ''; + writable.on('data', chunk => (writable.result += chunk)); + return writable; + } + + it('should call pipeToNodeWritable', () => { + let writable = getTestWritable(); + ReactDOMFizzServer.pipeToNodeWritable(
hello world
, writable); + jest.runAllTimers(); + expect(writable.result).toBe('
hello world
'); + }); +}); 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/ReactDOMFizzServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMFizzServerFormatConfig.js new file mode 100644 index 0000000000000..a9c6dc79df830 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMFizzServerFormatConfig.js @@ -0,0 +1,19 @@ +/** + * 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 {convertStringToBuffer} from 'react-stream/src/ReactFizzHostConfig'; + +export function formatChunk(type: string, props: Object): Uint8Array { + let str = '<' + type + '>'; + if (typeof props.children === 'string') { + str += props.children; + } + str += ''; + return convertStringToBuffer(str); +} diff --git a/packages/react-dom/src/server/ReactDOMFizzServerNode.js b/packages/react-dom/src/server/ReactDOMFizzServerNode.js new file mode 100644 index 0000000000000..72b1bcb74272c --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMFizzServerNode.js @@ -0,0 +1,30 @@ +/** + * 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 type {Writable} from 'stream'; + +import {createRequest, startWork, startFlowing} from 'react-stream/inline.dom'; + +function createDrainHandler(destination, request) { + return () => startFlowing(request, 0); +} + +function pipeToNodeWritable( + children: ReactNodeList, + destination: Writable, +): void { + let request = createRequest(children, destination); + destination.on('drain', createDrainHandler(destination, request)); + startWork(request); +} + +export default { + pipeToNodeWritable, +}; 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-dom/unstable-fizz.js b/packages/react-dom/unstable-fizz.js new file mode 100644 index 0000000000000..81d1ccee698d1 --- /dev/null +++ b/packages/react-dom/unstable-fizz.js @@ -0,0 +1,12 @@ +/** + * 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'; + +module.exports = require('./unstable-fizz.node'); diff --git a/packages/react-dom/unstable-fizz.node.js b/packages/react-dom/unstable-fizz.node.js new file mode 100644 index 0000000000000..ef943c74eccf5 --- /dev/null +++ b/packages/react-dom/unstable-fizz.node.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 ReactDOMFizzServerNode = require('./src/server/ReactDOMFizzServerNode'); + +// TODO: decide on the top-level export form. +// This is hacky but makes it work with both Rollup and Jest +module.exports = ReactDOMFizzServerNode.default || ReactDOMFizzServerNode; diff --git a/packages/react-noop-renderer/npm/server.js b/packages/react-noop-renderer/npm/server.js new file mode 100644 index 0000000000000..1abd91cef128e --- /dev/null +++ b/packages/react-noop-renderer/npm/server.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-noop-renderer-server.production.min.js'); +} else { + module.exports = require('./cjs/react-noop-renderer-server.development.js'); +} diff --git a/packages/react-noop-renderer/package.json b/packages/react-noop-renderer/package.json index 51b668a366b76..87f4ba431beb6 100644 --- a/packages/react-noop-renderer/package.json +++ b/packages/react-noop-renderer/package.json @@ -10,7 +10,8 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2", "regenerator-runtime": "^0.11.0", - "react-reconciler": "*" + "react-reconciler": "*", + "react-stream": "*" }, "peerDependencies": { "react": "^16.0.0" @@ -21,6 +22,7 @@ "build-info.json", "index.js", "persistent.js", + "server.js", "cjs/" ] } diff --git a/packages/react-noop-renderer/server.js b/packages/react-noop-renderer/server.js new file mode 100644 index 0000000000000..1665a0767a020 --- /dev/null +++ b/packages/react-noop-renderer/server.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 ReactNoopServer = require('./src/ReactNoopServer'); + +// TODO: decide on the top-level export form. +// This is hacky but makes it work with both Rollup and Jest. +module.exports = ReactNoopServer.default || ReactNoopServer; diff --git a/packages/react-noop-renderer/src/ReactNoopServer.js b/packages/react-noop-renderer/src/ReactNoopServer.js new file mode 100644 index 0000000000000..2077687b9f9b1 --- /dev/null +++ b/packages/react-noop-renderer/src/ReactNoopServer.js @@ -0,0 +1,49 @@ +/** + * 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 + */ + +/** + * This is a renderer of React that doesn't have a render target output. + * It is useful to demonstrate the internals of the reconciler in isolation + * and for testing semantics of reconciliation separate from the host + * environment. + */ + +import ReactFizzStreamer from 'react-stream'; + +type Destination = Array; + +const ReactNoopServer = ReactFizzStreamer({ + scheduleWork(callback: () => void) { + callback(); + }, + beginWriting(destination: Destination): void {}, + writeChunk(destination: Destination, buffer: Uint8Array): void { + 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'); + }, + formatChunk(type: string, props: Object): Uint8Array { + return Buffer.from(JSON.stringify({type, props}), 'utf8'); + }, +}); + +function render(children: React$Element): Destination { + let destination: Destination = []; + let request = ReactNoopServer.createRequest(children, destination); + ReactNoopServer.startWork(request); + return destination; +} + +export default { + render, +}; 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/README.md b/packages/react-stream/README.md new file mode 100644 index 0000000000000..087175bb7dc31 --- /dev/null +++ b/packages/react-stream/README.md @@ -0,0 +1,25 @@ +# react-stream + +This is an experimental package for creating custom React streaming server renderers. + +**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.** + +**Use it at your own risk.** + +## API + +```js +var Renderer = require('react-stream'); + +var HostConfig = { + // You'll need to implement some methods here. + // See below for more information and examples. +}; + +var MyRenderer = Renderer(HostConfig); + +var RendererPublicAPI = { +}; + +module.exports = RendererPublicAPI; +``` diff --git a/packages/react-stream/index.js b/packages/react-stream/index.js new file mode 100644 index 0000000000000..66445f67bf3eb --- /dev/null +++ b/packages/react-stream/index.js @@ -0,0 +1,26 @@ +/** + * 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 entry point is intentionally not typed. It exists only for third-party +// renderers. The renderers we ship (such as React DOM) instead import a named +// "inline" entry point (for example, `react-stream/inline.dom`). It uses +// the same code, but the Flow configuration redirects the host config to its +// real implementation so we can check it against exact intended host types. +// +// Only one renderer (the one you passed to `yarn flow `) is fully +// type-checked at any given time. The Flow config maps the +// `react-stream/inline.` import (which is *not* Flow typed) to +// `react-stream/inline-typed` (which *is*) for the current renderer. +// On CI, we run Flow checks for each renderer separately. + +'use strict'; + +const ReactFizzStreamer = require('./src/ReactFizzStreamer'); + +// TODO: decide on the top-level export form. +// This is hacky but makes it work with both Rollup and Jest. +module.exports = ReactFizzStreamer.default || ReactFizzStreamer; diff --git a/packages/react-stream/inline-typed.js b/packages/react-stream/inline-typed.js new file mode 100644 index 0000000000000..32358e21e457b --- /dev/null +++ b/packages/react-stream/inline-typed.js @@ -0,0 +1,24 @@ +/** + * 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 + */ + +// This file must have the Flow annotation. +// +// This is the Flow-typed entry point for the renderer. It should not be +// imported directly in code. Instead, our Flow configuration uses this entry +// point for the currently checked renderer (the one you passed to `yarn flow`). +// +// For example, if you run `yarn flow dom`, `react-stream/inline.dom` points +// to this module (and thus will be considered Flow-typed). But other renderers +// (e.g. `react-test-renderer`) will see stream as untyped during the check. +// +// We can't make all entry points typed at the same time because different +// renderers have different host config types. So we check them one by one. +// We run Flow on all renderers on CI. + +export * from './src/ReactFizzStreamer'; 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/inline.dom.js b/packages/react-stream/inline.dom.js new file mode 100644 index 0000000000000..9bae815dca122 --- /dev/null +++ b/packages/react-stream/inline.dom.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/npm/index.js b/packages/react-stream/npm/index.js new file mode 100644 index 0000000000000..fb7a252fd61a1 --- /dev/null +++ b/packages/react-stream/npm/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-stream.production.min.js'); +} else { + module.exports = require('./cjs/react-stream.development.js'); +} diff --git a/packages/react-stream/package.json b/packages/react-stream/package.json new file mode 100644 index 0000000000000..411dab92c019d --- /dev/null +++ b/packages/react-stream/package.json @@ -0,0 +1,36 @@ +{ + "name": "react-stream", + "description": "React package for creating custom streaming server renderers.", + "version": "0.1.0", + "private": true, + "keywords": [ + "react" + ], + "homepage": "https://reactjs.org/", + "bugs": "https://github.com/facebook/react/issues", + "license": "MIT", + "files": [ + "LICENSE", + "README.md", + "index.js", + "cjs/" + ], + "main": "index.js", + "repository": "facebook/react", + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^16.0.0" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + } +} diff --git a/packages/react-stream/src/ReactFizzFormatConfig.js b/packages/react-stream/src/ReactFizzFormatConfig.js new file mode 100644 index 0000000000000..ae70ca5cda9f6 --- /dev/null +++ b/packages/react-stream/src/ReactFizzFormatConfig.js @@ -0,0 +1,20 @@ +/** + * 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 invariant from 'shared/invariant'; + +// We expect that our Rollup, Jest, and Flow configurations +// always shim this module with the corresponding host config +// (either provided by a renderer, or a generic shim for npm). +// +// We should never resolve to this file, but it exists to make +// sure that if we *do* accidentally break the configuration, +// the failure isn't silent. + +invariant(false, 'This module must be shimmed by a specific renderer.'); diff --git a/packages/react-stream/src/ReactFizzHostConfig.js b/packages/react-stream/src/ReactFizzHostConfig.js new file mode 100644 index 0000000000000..ae70ca5cda9f6 --- /dev/null +++ b/packages/react-stream/src/ReactFizzHostConfig.js @@ -0,0 +1,20 @@ +/** + * 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 invariant from 'shared/invariant'; + +// We expect that our Rollup, Jest, and Flow configurations +// always shim this module with the corresponding host config +// (either provided by a renderer, or a generic shim for npm). +// +// We should never resolve to this file, but it exists to make +// sure that if we *do* accidentally break the configuration, +// the failure isn't silent. + +invariant(false, 'This module must be shimmed by a specific renderer.'); 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 new file mode 100644 index 0000000000000..2ee9ddec354d9 --- /dev/null +++ b/packages/react-stream/src/ReactFizzHostConfigNode.js @@ -0,0 +1,48 @@ +/** + * 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 {Writable} from 'stream'; + +type MightBeFlushable = {flush?: () => void}; + +export type Destination = Writable & MightBeFlushable; + +export function scheduleWork(callback: () => void) { + setImmediate(callback); +} + +export function flushBuffered(destination: Destination) { + // If we don't have any more data to send right now. + // Flush whatever is in the buffer to the wire. + if (typeof destination.flush === 'function') { + // By convention the Zlib streams provide a flush function for this purpose. + destination.flush(); + } +} + +export function beginWriting(destination: Destination) { + destination.cork(); +} + +export function writeChunk(destination: Destination, buffer: Uint8Array) { + let nodeBuffer = ((buffer: any): Buffer); // close enough + destination.write(nodeBuffer); +} + +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 new file mode 100644 index 0000000000000..80239287c2160 --- /dev/null +++ b/packages/react-stream/src/ReactFizzStreamer.js @@ -0,0 +1,85 @@ +/** + * 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 {Destination} from './ReactFizzHostConfig'; +import type {ReactNodeList} from 'shared/ReactTypes'; + +import { + scheduleWork, + beginWriting, + writeChunk, + completeWriting, + flushBuffered, + close, +} from './ReactFizzHostConfig'; +import {formatChunk} from './ReactFizzFormatConfig'; +import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; + +type OpaqueRequest = { + destination: Destination, + children: ReactNodeList, + completedChunks: Array, + flowing: boolean, +}; + +export function createRequest( + children: ReactNodeList, + destination: Destination, +): OpaqueRequest { + return {destination, children, completedChunks: [], flowing: false}; +} + +function performWork(request: OpaqueRequest): void { + let element = (request.children: any); + request.children = null; + if (element && element.$$typeof !== REACT_ELEMENT_TYPE) { + return; + } + let type = element.type; + let props = element.props; + if (typeof type !== 'string') { + return; + } + request.completedChunks.push(formatChunk(type, props)); + if (request.flowing) { + flushCompletedChunks(request); + } + + flushBuffered(request.destination); +} + +function flushCompletedChunks(request: OpaqueRequest) { + let destination = request.destination; + let chunks = request.completedChunks; + request.completedChunks = []; + + beginWriting(destination); + try { + for (let i = 0; i < chunks.length; i++) { + let chunk = chunks[i]; + writeChunk(destination, chunk); + } + } finally { + completeWriting(destination); + } + close(destination); +} + +export function startWork(request: OpaqueRequest): void { + request.flowing = true; + scheduleWork(() => performWork(request)); +} + +export function startFlowing( + request: OpaqueRequest, + desiredBytes: number, +): void { + request.flowing = false; + flushCompletedChunks(request); +} diff --git a/packages/react-stream/src/__tests__/ReactServer-test.js b/packages/react-stream/src/__tests__/ReactServer-test.js new file mode 100644 index 0000000000000..0c6c13c44f14e --- /dev/null +++ b/packages/react-stream/src/__tests__/ReactServer-test.js @@ -0,0 +1,28 @@ +/** + * 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 + * @jest-environment node + */ + +'use strict'; + +let React; +let ReactNoopServer; + +describe('ReactServer', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoopServer = require('react-noop-renderer/server'); + }); + + it('can call render', () => { + let result = ReactNoopServer.render(
hello world
); + expect(result).toEqual([{type: 'div', props: {children: 'hello world'}}]); + }); +}); diff --git a/packages/react-stream/src/forks/ReactFizzFormatConfig.custom.js b/packages/react-stream/src/forks/ReactFizzFormatConfig.custom.js new file mode 100644 index 0000000000000..9c4e181652265 --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzFormatConfig.custom.js @@ -0,0 +1,29 @@ +/** + * 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 + */ + +// This is a host config that's used for the `react-stream` package on npm. +// It is only used by third-party renderers. +// +// Its API lets you pass the host config as an argument. +// However, inside the `react-stream` we treat host config as a module. +// This file is a shim between two worlds. +// +// It works because the `react-stream` bundle is wrapped in something like: +// +// module.exports = function ($$$config) { +// /* renderer code */ +// } +// +// So `$$$config` looks like a global variable, but it's +// really an argument to a top-level wrapping function. + +declare var $$$hostConfig: any; +export opaque type Destination = mixed; // eslint-disable-line no-undef + +export const formatChunk = $$$hostConfig.formatChunk; 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/ReactFizzFormatConfig.dom.js b/packages/react-stream/src/forks/ReactFizzFormatConfig.dom.js new file mode 100644 index 0000000000000..e3b8782adee7c --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzFormatConfig.dom.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 new file mode 100644 index 0000000000000..71d876a10e5a1 --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzHostConfig.custom.js @@ -0,0 +1,35 @@ +/** + * 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 + */ + +// This is a host config that's used for the `react-stream` package on npm. +// It is only used by third-party renderers. +// +// Its API lets you pass the host config as an argument. +// However, inside the `react-stream` we treat host config as a module. +// This file is a shim between two worlds. +// +// It works because the `react-stream` bundle is wrapped in something like: +// +// module.exports = function ($$$config) { +// /* renderer code */ +// } +// +// So `$$$config` looks like a global variable, but it's +// really an argument to a top-level wrapping function. + +declare var $$$hostConfig: any; +export opaque type Destination = mixed; // eslint-disable-line no-undef + +export const scheduleWork = $$$hostConfig.scheduleWork; +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/packages/react-stream/src/forks/ReactFizzHostConfig.dom.js b/packages/react-stream/src/forks/ReactFizzHostConfig.dom.js new file mode 100644 index 0000000000000..88d44bc2e4519 --- /dev/null +++ b/packages/react-stream/src/forks/ReactFizzHostConfig.dom.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 '../ReactFizzHostConfigNode'; diff --git a/scripts/flow/createFlowConfigs.js b/scripts/flow/createFlowConfigs.js index d02ba58c9b511..f1ca3523c77d8 100644 --- a/scripts/flow/createFlowConfigs.js +++ b/scripts/flow/createFlowConfigs.js @@ -18,15 +18,19 @@ const configTemplate = fs .readFileSync(__dirname + '/config/flowconfig') .toString(); -function writeConfig(renderer) { +function writeConfig(renderer, isFizzSupported) { const folder = __dirname + '/' + renderer; mkdirp.sync(folder); + const fizzRenderer = isFizzSupported ? renderer : 'custom'; const config = configTemplate.replace( '%REACT_RENDERER_FLOW_OPTIONS%', ` module.name_mapper='react-reconciler/inline.${renderer}$$' -> 'react-reconciler/inline-typed' module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}' +module.name_mapper='react-stream/inline.${renderer}$$' -> 'react-stream/inline-typed' +module.name_mapper='ReactFizzHostConfig$$' -> 'forks/ReactFizzHostConfig.${fizzRenderer}' +module.name_mapper='ReactFizzFormatConfig$$' -> 'forks/ReactFizzFormatConfig.${fizzRenderer}' `.trim(), ); @@ -61,6 +65,6 @@ ${disclaimer} // so that we can run those checks in parallel if we want. inlinedHostConfigs.forEach(rendererInfo => { if (rendererInfo.isFlowTyped) { - writeConfig(rendererInfo.shortName); + writeConfig(rendererInfo.shortName, rendererInfo.isFizzSupported); } }); diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js index 888fade848196..d7f4aab36741e 100644 --- a/scripts/jest/setupHostConfigs.js +++ b/scripts/jest/setupHostConfigs.js @@ -17,6 +17,15 @@ jest.mock('react-reconciler/persistent', () => { return require.requireActual('react-reconciler/persistent'); }; }); +const shimFizzHostConfigPath = 'react-stream/src/ReactFizzHostConfig'; +const shimFizzFormatConfigPath = 'react-stream/src/ReactFizzFormatConfig'; +jest.mock('react-stream', () => { + return config => { + jest.mock(shimFizzHostConfigPath, () => config); + jest.mock(shimFizzFormatConfigPath, () => config); + return require.requireActual('react-stream'); + }; +}); // But for inlined host configs (such as React DOM, Native, etc), we // mock their named entry points to establish a host config mapping. @@ -55,6 +64,46 @@ inlinedHostConfigs.forEach(rendererInfo => { return renderer; }); + + if (rendererInfo.isFizzSupported) { + jest.mock(`react-stream/inline.${rendererInfo.shortName}`, () => { + let hasImportedShimmedConfig = false; + + // We want the renderer to pick up the host config for this renderer. + jest.mock(shimFizzHostConfigPath, () => { + hasImportedShimmedConfig = true; + return require.requireActual( + `react-stream/src/forks/ReactFizzHostConfig.${ + rendererInfo.shortName + }.js` + ); + }); + jest.mock(shimFizzFormatConfigPath, () => { + hasImportedShimmedConfig = true; + return require.requireActual( + `react-stream/src/forks/ReactFizzFormatConfig.${ + rendererInfo.shortName + }.js` + ); + }); + + const renderer = require.requireActual('react-stream'); + // If the shimmed config factory function above has not run, + // it means this test file loads more than one renderer + // but doesn't reset modules between them. This won't work. + if (!hasImportedShimmedConfig) { + throw new Error( + `Could not import the "${rendererInfo.shortName}" renderer ` + + `in this suite because another renderer has already been ` + + `loaded earlier. Call jest.resetModules() before importing any ` + + `of the following entry points:\n\n` + + rendererInfo.entryPoints.map(entry => ` * ${entry}`) + ); + } + + return renderer; + }); + } }); // Make it possible to import this module inside diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index c250600b99280..b599ea4055407 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -158,6 +158,22 @@ const bundles = [ externals: ['react', 'stream'], }, + /******* 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.node', + global: 'ReactDOMFizzServer', + externals: ['react'], + }, + /******* React ART *******/ { bundleTypes: [ @@ -320,6 +336,28 @@ const bundles = [ }), }, + /******* React Noop Server Renderer (used for tests) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-noop-renderer/server', + global: 'ReactNoopRendererServer', + externals: ['react', 'expect'], + // React Noop uses generators. However GCC currently + // breaks when we attempt to use them in the output. + // So we precompile them with regenerator, and include + // it as a runtime dependency of React Noop. In practice + // this isn't an issue because React Noop is only used + // in our tests. We wouldn't want to do this for any + // public package though. + babel: opts => + Object.assign({}, opts, { + plugins: opts.plugins.concat([ + require.resolve('babel-plugin-transform-regenerator'), + ]), + }), + }, + /******* React Reconciler *******/ { bundleTypes: [NODE_DEV, NODE_PROD], @@ -338,6 +376,15 @@ const bundles = [ externals: ['react'], }, + /******* React Stream *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RECONCILER, + entry: 'react-stream', + global: 'ReactStream', + externals: ['react'], + }, + /******* Reflection *******/ { moduleType: RENDERER_UTILS, diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index bbb22d7a8c950..a6fa873078eb4 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -272,6 +272,66 @@ const forks = Object.freeze({ ); }, + 'react-stream/src/ReactFizzHostConfig': ( + bundleType, + entry, + dependencies, + moduleType + ) => { + if (dependencies.indexOf('react-stream') !== -1) { + return null; + } + if (moduleType !== RENDERER && moduleType !== RECONCILER) { + return null; + } + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let rendererInfo of inlinedHostConfigs) { + if (rendererInfo.entryPoints.indexOf(entry) !== -1) { + if (!rendererInfo.isFizzSupported) { + return null; + } + return `react-stream/src/forks/ReactFizzHostConfig.${ + rendererInfo.shortName + }.js`; + } + } + throw new Error( + 'Expected ReactFizzHostConfig to always be replaced with a shim, but ' + + `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` + + 'Did you mean to add it there to associate it with a specific renderer?' + ); + }, + + 'react-stream/src/ReactFizzFormatConfig': ( + bundleType, + entry, + dependencies, + moduleType + ) => { + if (dependencies.indexOf('react-stream') !== -1) { + return null; + } + if (moduleType !== RENDERER && moduleType !== RECONCILER) { + return null; + } + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let rendererInfo of inlinedHostConfigs) { + if (rendererInfo.entryPoints.indexOf(entry) !== -1) { + if (!rendererInfo.isFizzSupported) { + return null; + } + return `react-stream/src/forks/ReactFizzFormatConfig.${ + rendererInfo.shortName + }.js`; + } + } + throw new Error( + 'Expected ReactFizzFormatConfig to always be replaced with a shim, but ' + + `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` + + 'Did you mean to add it there to associate it with a specific renderer?' + ); + }, + // We wrap top-level listeners into guards on www. 'react-dom/src/events/EventListener': (bundleType, entry) => { switch (bundleType) { diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index dabd5feedda2f..91d4f827bac0d 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -46,29 +46,29 @@ "filename": "react-dom.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 725515, - "gzip": 167737 + "size": 725813, + "gzip": 167799 }, { "filename": "react-dom.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 100307, - "gzip": 32617 + "size": 99920, + "gzip": 32521 }, { "filename": "react-dom.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 720713, - "gzip": 166331 + "size": 721011, + "gzip": 166399 }, { "filename": "react-dom.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 100301, - "gzip": 32146 + "size": 99914, + "gzip": 32052 }, { "filename": "ReactDOM-dev.js", @@ -165,8 +165,8 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 120524, - "gzip": 32011 + "size": 122056, + "gzip": 32469 }, { "filename": "react-dom-server.browser.production.min.js", @@ -179,8 +179,8 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 116562, - "gzip": 31037 + "size": 118094, + "gzip": 31495 }, { "filename": "react-dom-server.browser.production.min.js", @@ -207,8 +207,8 @@ "filename": "react-dom-server.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 118530, - "gzip": 31584 + "size": 120062, + "gzip": 32036 }, { "filename": "react-dom-server.node.production.min.js", @@ -221,29 +221,29 @@ "filename": "react-art.development.js", "bundleType": "UMD_DEV", "packageName": "react-art", - "size": 507548, - "gzip": 112066 + "size": 507846, + "gzip": 112120 }, { "filename": "react-art.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-art", - "size": 92284, - "gzip": 28330 + "size": 91897, + "gzip": 28219 }, { "filename": "react-art.development.js", "bundleType": "NODE_DEV", "packageName": "react-art", - "size": 437685, - "gzip": 94610 + "size": 437983, + "gzip": 94680 }, { "filename": "react-art.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-art", - "size": 56431, - "gzip": 17393 + "size": 56044, + "gzip": 17298 }, { "filename": "ReactART-dev.js", @@ -291,29 +291,29 @@ "filename": "react-test-renderer.development.js", "bundleType": "UMD_DEV", "packageName": "react-test-renderer", - "size": 450653, - "gzip": 97378 + "size": 450951, + "gzip": 97435 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-test-renderer", - "size": 57674, - "gzip": 17698 + "size": 57293, + "gzip": 17617 }, { "filename": "react-test-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 445754, - "gzip": 96206 + "size": 446052, + "gzip": 96263 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 57342, - "gzip": 17539 + "size": 56955, + "gzip": 17453 }, { "filename": "ReactTestRenderer-dev.js", @@ -375,29 +375,29 @@ "filename": "react-reconciler.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 435479, - "gzip": 93068 + "size": 435777, + "gzip": 93120 }, { "filename": "react-reconciler.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 57593, - "gzip": 17242 + "size": 57202, + "gzip": 17158 }, { "filename": "react-reconciler-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 433889, - "gzip": 92426 + "size": 434187, + "gzip": 92481 }, { "filename": "react-reconciler-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 57604, - "gzip": 17248 + "size": 57213, + "gzip": 17164 }, { "filename": "react-reconciler-reflection.development.js", @@ -515,15 +515,15 @@ "filename": "ReactDOM-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 742105, - "gzip": 167549 + "size": 742241, + "gzip": 167544 }, { "filename": "ReactDOM-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 318358, - "gzip": 58562 + "size": 317018, + "gzip": 58396 }, { "filename": "ReactTestUtils-dev.js", @@ -550,8 +550,8 @@ "filename": "ReactDOMServer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 117649, - "gzip": 30639 + "size": 119227, + "gzip": 31112 }, { "filename": "ReactDOMServer-prod.js", @@ -564,78 +564,78 @@ "filename": "ReactART-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-art", - "size": 445159, - "gzip": 93560 + "size": 445295, + "gzip": 93558 }, { "filename": "ReactART-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-art", - "size": 189135, - "gzip": 32266 + "size": 187912, + "gzip": 32090 }, { "filename": "ReactNativeRenderer-dev.js", "bundleType": "RN_FB_DEV", "packageName": "react-native-renderer", - "size": 573357, - "gzip": 124963 + "size": 573493, + "gzip": 124971 }, { "filename": "ReactNativeRenderer-prod.js", "bundleType": "RN_FB_PROD", "packageName": "react-native-renderer", - "size": 245987, - "gzip": 43227 + "size": 244566, + "gzip": 43021 }, { "filename": "ReactNativeRenderer-dev.js", "bundleType": "RN_OSS_DEV", "packageName": "react-native-renderer", - "size": 573046, - "gzip": 124866 + "size": 573182, + "gzip": 124874 }, { "filename": "ReactNativeRenderer-prod.js", "bundleType": "RN_OSS_PROD", "packageName": "react-native-renderer", - "size": 231080, - "gzip": 40077 + "size": 229659, + "gzip": 39880 }, { "filename": "ReactFabric-dev.js", "bundleType": "RN_FB_DEV", "packageName": "react-native-renderer", - "size": 563440, - "gzip": 122415 + "size": 563576, + "gzip": 122418 }, { "filename": "ReactFabric-prod.js", "bundleType": "RN_FB_PROD", "packageName": "react-native-renderer", - "size": 225188, - "gzip": 38730 + "size": 223730, + "gzip": 38540 }, { "filename": "ReactFabric-dev.js", "bundleType": "RN_OSS_DEV", "packageName": "react-native-renderer", - "size": 563475, - "gzip": 122429 + "size": 563611, + "gzip": 122433 }, { "filename": "ReactFabric-prod.js", "bundleType": "RN_OSS_PROD", "packageName": "react-native-renderer", - "size": 225224, - "gzip": 38745 + "size": 223766, + "gzip": 38555 }, { "filename": "ReactTestRenderer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-test-renderer", - "size": 453421, - "gzip": 95442 + "size": 453557, + "gzip": 95443 }, { "filename": "ReactShallowRenderer-dev.js", @@ -718,22 +718,22 @@ "filename": "react-dom.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 103137, - "gzip": 32687 + "size": 102985, + "gzip": 32673 }, { "filename": "ReactNativeRenderer-profiling.js", "bundleType": "RN_OSS_PROFILING", "packageName": "react-native-renderer", - "size": 236392, - "gzip": 41298 + "size": 235342, + "gzip": 41248 }, { "filename": "ReactFabric-profiling.js", "bundleType": "RN_OSS_PROFILING", "packageName": "react-native-renderer", - "size": 229595, - "gzip": 40005 + "size": 228530, + "gzip": 39962 }, { "filename": "Scheduler-dev.js", @@ -767,22 +767,22 @@ "filename": "ReactDOM-profiling.js", "bundleType": "FB_WWW_PROFILING", "packageName": "react-dom", - "size": 324711, - "gzip": 59847 + "size": 323954, + "gzip": 59824 }, { "filename": "ReactNativeRenderer-profiling.js", "bundleType": "RN_FB_PROFILING", "packageName": "react-native-renderer", - "size": 251555, - "gzip": 44455 + "size": 250505, + "gzip": 44397 }, { "filename": "ReactFabric-profiling.js", "bundleType": "RN_FB_PROFILING", "packageName": "react-native-renderer", - "size": 229554, - "gzip": 39988 + "size": 228489, + "gzip": 39945 }, { "filename": "react.profiling.min.js", @@ -795,8 +795,8 @@ "filename": "react-dom.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react-dom", - "size": 103046, - "gzip": 33256 + "size": 102894, + "gzip": 33243 }, { "filename": "scheduler-tracing.development.js", @@ -937,6 +937,90 @@ "packageName": "eslint-plugin-react-hooks", "size": 4943, "gzip": 1815 + }, + { + "filename": "ReactDOMFizzServer-dev.js", + "bundleType": "FB_WWW_DEV", + "packageName": "react-dom", + "size": 3772, + "gzip": 1432 + }, + { + "filename": "ReactDOMFizzServer-prod.js", + "bundleType": "FB_WWW_PROD", + "packageName": "react-dom", + "size": 2211, + "gzip": 874 + }, + { + "filename": "react-noop-renderer-server.development.js", + "bundleType": "NODE_DEV", + "packageName": "react-noop-renderer", + "size": 1861, + "gzip": 869 + }, + { + "filename": "react-noop-renderer-server.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-noop-renderer", + "size": 804, + "gzip": 482 + }, + { + "filename": "react-stream.development.js", + "bundleType": "NODE_DEV", + "packageName": "react-stream", + "size": 4633, + "gzip": 1683 + }, + { + "filename": "react-stream.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-stream", + "size": 1224, + "gzip": 655 + }, + { + "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": 3780, + "gzip": 1443 + }, + { + "filename": "react-dom-unstable-fizz.node.production.min.js", + "bundleType": "NODE_PROD", + "packageName": "react-dom", + "size": 1122, + "gzip": 659 } ] } \ No newline at end of file diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index a1b4a7fdfac44..e23b7c68fce15 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -18,6 +18,8 @@ module.exports = { __REACT_DEVTOOLS_GLOBAL_HOOK__: true, // CommonJS / Node process: true, + setImmediate: true, + Buffer: true, }, parserOptions: { ecmaVersion: 5, diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 2e2949444f5da..878a170a31b5e 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -18,6 +18,9 @@ module.exports = { __REACT_DEVTOOLS_GLOBAL_HOOK__: true, // FB __DEV__: true, + // Node.js Server Rendering + setImmediate: true, + Buffer: true, }, parserOptions: { ecmaVersion: 5, diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 2892a9abeb06b..66c54963c7f28 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -9,8 +9,15 @@ module.exports = [ { shortName: 'dom', - entryPoints: ['react-dom'], + 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, }, { shortName: 'fire', @@ -21,25 +28,34 @@ module.exports = [ shortName: 'art', entryPoints: ['react-art'], isFlowTyped: false, // TODO: type it. + isFizzSupported: false, }, { shortName: 'native', entryPoints: ['react-native-renderer'], isFlowTyped: true, + isFizzSupported: false, }, { shortName: 'fabric', entryPoints: ['react-native-renderer/fabric'], isFlowTyped: true, + isFizzSupported: false, }, { shortName: 'test', entryPoints: ['react-test-renderer'], isFlowTyped: true, + isFizzSupported: false, }, { shortName: 'custom', - entryPoints: ['react-reconciler', 'react-reconciler/persistent'], + entryPoints: [ + 'react-reconciler', + 'react-reconciler/persistent', + 'react-stream', + ], 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"