[Fizz] New Server Rendering Infra (#14144)

* [Fizz] Add Flow/Jest/Rollup build infra

Add a new package for react-stream which allows for custom server renderer
outputs. I picked the name because it's a reasonable name but also
because the npm name is currently owned by a friend of the project.

The react-dom build has its own inlined server renderer under the
name `react-dom/fizz`.

There is also a noop renderer to be used for testing. At some point
we might add a public one to test-renderer but for now I don't want to have
to think about public API design for the tests.

* Add FormatConfig too

We need to separate the format (DOM, React Native, etc) from the host
running the server (Node, Browser, etc).

* Basic wiring between Node, Noop and DOM configs

The Node DOM API is pipeToNodeStream which accepts a writable stream.

* Merge host and format config in dynamic react-stream entry point

Simpler API this way but also avoids having to fork the wrapper config.

Fixes noop builds.

* Add setImmediate/Buffer globals to lint config

Used by the server renderer

* Properly include fizz.node.js

Also use forwarding to it from fizz.js in builds so that tests covers
this.

* Make react-stream private since we're not ready to publish

or even name it yet

* Rename Renderer -> Streamer

* Prefix react-dom/fizz with react-dom/unstable-fizz

* 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.

* Add Fizz Browser Fixture

This is for testing server rendering - on the client.

* Lower version number to detach it from react-reconciler version
This commit is contained in:
Sebastian Markbåge 2018-11-30 11:38:22 -08:00 committed by GitHub
parent f1bf281605
commit 1d25aa5787
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1214 additions and 87 deletions

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8">
<title>Fizz Example</title>
</head>
<body>
<h1>Fizz Example</h1>
<div id="container">
<p>
To install React, follow the instructions on
<a href="https://github.com/facebook/react/">GitHub</a>.
</p>
<p>
If you can see this, React is <strong>not</strong> working right.
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
</p>
</div>
<script src="../../build/dist/react.development.js"></script>
<script src="../../build/dist/react-dom-unstable-fizz.browser.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
let stream = ReactDOMFizzServer.renderToReadableStream(<body>Success</body>);
let response = new Response(stream, {
headers: {'Content-Type': 'text/html'},
});
display(response);
async function display(responseToDisplay) {
let blob = await responseToDisplay.blob();
let url = URL.createObjectURL(blob);
let iframe = document.createElement('iframe');
iframe.src = url;
let container = document.getElementById('container');
container.innerHTML = '';
container.appendChild(iframe);
}
</script>
</body>
</html>

View File

@ -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"

View File

@ -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');
}

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./unstable-fizz.node');

View File

@ -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');
}

View File

@ -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": [

View File

@ -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(
<div>hello world</div>,
);
let result = await readResult(stream);
expect(result).toBe('<div>hello world</div>');
});
});

View File

@ -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(<div>hello world</div>, writable);
jest.runAllTimers();
expect(writable.result).toBe('<div>hello world</div>');
});
});

View File

@ -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,
};

View File

@ -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 += '</' + type + '>';
return convertStringToBuffer(str);
}

View File

@ -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,
};

View File

@ -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;

12
packages/react-dom/unstable-fizz.js vendored Normal file
View File

@ -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');

View File

@ -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;

View File

@ -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');
}

View File

@ -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/"
]
}

16
packages/react-noop-renderer/server.js vendored Normal file
View File

@ -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;

View File

@ -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<string>;
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<any>): Destination {
let destination: Destination = [];
let request = ReactNoopServer.createRequest(children, destination);
ReactNoopServer.startWork(request);
return destination;
}
export default {
render,
};

View File

@ -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';

View File

@ -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';

View File

@ -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;
```

26
packages/react-stream/index.js vendored Normal file
View File

@ -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 <renderer>`) is fully
// type-checked at any given time. The Flow config maps the
// `react-stream/inline.<renderer>` 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;

24
packages/react-stream/inline-typed.js vendored Normal file
View File

@ -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';

View File

@ -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';

View File

@ -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';

7
packages/react-stream/npm/index.js vendored Normal file
View File

@ -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');
}

View File

@ -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"
]
}
}

View File

@ -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.');

View File

@ -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.');

View File

@ -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);
}

View File

@ -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');
}

View File

@ -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<Uint8Array>,
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);
}

View File

@ -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(<div>hello world</div>);
expect(result).toEqual([{type: 'div', props: {children: 'hello world'}}]);
});
});

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -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);
}
});

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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
}
]
}

View File

@ -18,6 +18,8 @@ module.exports = {
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
// CommonJS / Node
process: true,
setImmediate: true,
Buffer: true,
},
parserOptions: {
ecmaVersion: 5,

View File

@ -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,

View File

@ -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,
},
];

View File

@ -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"