Skip to content
Permalink
Browse files

[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
  • Loading branch information
sebmarkbage committed Nov 30, 2018
1 parent f1bf281 commit 1d25aa5787d4e19704c049c3cfa985d3b5190e0d
Showing with 1,214 additions and 87 deletions.
  1. +40 −0 fixtures/fizz-ssr-browser/index.html
  2. +2 −1 package.json
  3. +7 −0 packages/react-dom/npm/unstable-fizz.browser.js
  4. +3 −0 packages/react-dom/npm/unstable-fizz.js
  5. +7 −0 packages/react-dom/npm/unstable-fizz.node.js
  6. +5 −1 packages/react-dom/package.json
  7. +45 −0 packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
  8. +39 −0 packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
  9. +34 −0 packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
  10. +19 −0 packages/react-dom/src/server/ReactDOMFizzServerFormatConfig.js
  11. +30 −0 packages/react-dom/src/server/ReactDOMFizzServerNode.js
  12. +16 −0 packages/react-dom/unstable-fizz.browser.js
  13. +12 −0 packages/react-dom/unstable-fizz.js
  14. +16 −0 packages/react-dom/unstable-fizz.node.js
  15. +7 −0 packages/react-noop-renderer/npm/server.js
  16. +3 −1 packages/react-noop-renderer/package.json
  17. +16 −0 packages/react-noop-renderer/server.js
  18. +49 −0 packages/react-noop-renderer/src/ReactNoopServer.js
  19. +11 −0 packages/react-reconciler/inline.dom-browser.js
  20. +10 −0 packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-browser.js
  21. +25 −0 packages/react-stream/README.md
  22. +26 −0 packages/react-stream/index.js
  23. +24 −0 packages/react-stream/inline-typed.js
  24. +11 −0 packages/react-stream/inline.dom-browser.js
  25. +11 −0 packages/react-stream/inline.dom.js
  26. +7 −0 packages/react-stream/npm/index.js
  27. +36 −0 packages/react-stream/package.json
  28. +20 −0 packages/react-stream/src/ReactFizzFormatConfig.js
  29. +20 −0 packages/react-stream/src/ReactFizzHostConfig.js
  30. +37 −0 packages/react-stream/src/ReactFizzHostConfigBrowser.js
  31. +48 −0 packages/react-stream/src/ReactFizzHostConfigNode.js
  32. +85 −0 packages/react-stream/src/ReactFizzStreamer.js
  33. +28 −0 packages/react-stream/src/__tests__/ReactServer-test.js
  34. +29 −0 packages/react-stream/src/forks/ReactFizzFormatConfig.custom.js
  35. +10 −0 packages/react-stream/src/forks/ReactFizzFormatConfig.dom-browser.js
  36. +10 −0 packages/react-stream/src/forks/ReactFizzFormatConfig.dom.js
  37. +35 −0 packages/react-stream/src/forks/ReactFizzHostConfig.custom.js
  38. +10 −0 packages/react-stream/src/forks/ReactFizzHostConfig.dom-browser.js
  39. +10 −0 packages/react-stream/src/forks/ReactFizzHostConfig.dom.js
  40. +6 −2 scripts/flow/createFlowConfigs.js
  41. +49 −0 scripts/jest/setupHostConfigs.js
  42. +47 −0 scripts/rollup/bundles.js
  43. +60 −0 scripts/rollup/forks.js
  44. +164 −80 scripts/rollup/results.json
  45. +2 −0 scripts/rollup/validate/eslintrc.cjs.js
  46. +3 −0 scripts/rollup/validate/eslintrc.fb.js
  47. +18 −2 scripts/shared/inlinedHostConfigs.js
  48. +12 −0 yarn.lock
@@ -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>
@@ -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"
@@ -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');
}
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('./unstable-fizz.node');
@@ -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');
}
@@ -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": [
@@ -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>');
});
});
@@ -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>');
});
});
@@ -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,
};
@@ -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);
}
@@ -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,
};
@@ -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;
@@ -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');
@@ -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;
@@ -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');
}
@@ -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/"
]
}
@@ -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;

0 comments on commit 1d25aa5

Please sign in to comment.
You can’t perform that action at this time.