Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight] Integrate Blocks into Flight #18371

Merged
merged 6 commits into from Mar 24, 2020
Merged

Conversation

sebmarkbage
Copy link
Collaborator

The previous model was centered around JSX rendering random models. This moves to a Block centric approach. To do this, we need a way to pass modules to the client so this wires up the previous infra to resolve modules into meta data.

The server is expected to replace a Block with a compiler to instead return a "Server Block" which contains a ModuleReference and Query. We don't have the compiler parts yet so the tests mimics what the compiler would do for server code.

Now the expected Blocks-first programming model actually works.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Mar 23, 2020
@codesandbox-ci
Copy link

codesandbox-ci bot commented Mar 23, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 19435d0:

Sandbox Source
frosty-dew-wuuze Configuration

@sizebot
Copy link

sizebot commented Mar 23, 2020

Details of bundled changes.

Comparing: fc96a52...19435d0

react-flight-dom-webpack

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-flight-dom-webpack-server.browser.development.js +25.7% +23.6% 8.39 KB 10.54 KB 2.47 KB 3.05 KB UMD_DEV
react-flight-dom-webpack-server.browser.production.min.js 🔺+24.5% 🔺+22.7% 2.8 KB 3.48 KB 1.33 KB 1.63 KB UMD_PROD
react-flight-dom-webpack.development.js +26.9% +28.0% 10.97 KB 13.92 KB 3.1 KB 3.97 KB UMD_DEV
react-flight-dom-webpack.production.min.js 🔺+20.9% 🔺+16.8% 3.09 KB 3.73 KB 1.44 KB 1.68 KB UMD_PROD
react-flight-dom-webpack.development.js +27.7% +28.4% 10.1 KB 12.89 KB 3.01 KB 3.86 KB NODE_DEV
react-flight-dom-webpack.production.min.js 🔺+22.0% 🔺+17.5% 2.91 KB 3.55 KB 1.36 KB 1.59 KB NODE_PROD
react-flight-dom-webpack-server.node.development.js +23.7% +22.2% 8.61 KB 10.65 KB 2.63 KB 3.21 KB NODE_DEV
react-flight-dom-webpack-server.node.production.min.js 🔺+24.7% 🔺+25.2% 2.77 KB 3.46 KB 1.25 KB 1.57 KB NODE_PROD
react-flight-dom-webpack-server.browser.development.js +26.6% +24.4% 7.67 KB 9.71 KB 2.37 KB 2.95 KB NODE_DEV
react-flight-dom-webpack-server.browser.production.min.js 🔺+26.2% 🔺+25.5% 2.61 KB 3.3 KB 1.24 KB 1.56 KB NODE_PROD

react-flight-dom-relay

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactFlightDOMRelayServer-dev.js +28.4% +27.2% 7.7 KB 9.89 KB 2.29 KB 2.91 KB FB_WWW_DEV
ReactFlightDOMRelayServer-prod.js 🔺+29.7% 🔺+31.0% 5.77 KB 7.48 KB 1.67 KB 2.19 KB FB_WWW_PROD
ReactFlightDOMRelayClient-dev.js +20.2% +16.4% 6.96 KB 8.37 KB 2.27 KB 2.65 KB FB_WWW_DEV
ReactFlightDOMRelayClient-prod.js 🔺+36.0% 🔺+23.9% 3.45 KB 4.69 KB 1.22 KB 1.51 KB FB_WWW_PROD

react-client

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-client-flight.development.js +12.7% +10.8% 9.67 KB 10.89 KB 3.02 KB 3.34 KB NODE_DEV
react-client-flight.production.min.js 🔺+18.0% 🔺+11.2% 2.7 KB 3.18 KB 1.27 KB 1.42 KB NODE_PROD

react-server

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-server.development.js 0.0% +0.1% 3.62 KB 3.62 KB 1.22 KB 1.22 KB NODE_DEV
react-server-flight.development.js +23.5% +20.9% 8.33 KB 10.28 KB 2.58 KB 3.12 KB NODE_DEV
react-server-flight.production.min.js 🔺+25.5% 🔺+26.2% 2.84 KB 3.57 KB 1.29 KB 1.63 KB NODE_PROD

Size changes (experimental)

Generated by 🚫 dangerJS against 19435d0

@sizebot
Copy link

sizebot commented Mar 23, 2020

Details of bundled changes.

Comparing: fc96a52...19435d0

react-flight-dom-webpack

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-flight-dom-webpack-server.node.production.min.js 🔺+24.8% 🔺+25.4% 2.76 KB 3.45 KB 1.24 KB 1.56 KB NODE_PROD
react-flight-dom-webpack.development.js +27.7% +28.5% 10.08 KB 12.88 KB 3 KB 3.85 KB NODE_DEV
react-flight-dom-webpack.production.min.js 🔺+22.1% 🔺+17.5% 2.9 KB 3.54 KB 1.35 KB 1.59 KB NODE_PROD
react-flight-dom-webpack-server.node.development.js +23.7% +22.2% 8.6 KB 10.64 KB 2.62 KB 3.21 KB NODE_DEV
react-flight-dom-webpack-server.browser.development.js +25.7% +23.6% 8.37 KB 10.53 KB 2.46 KB 3.04 KB UMD_DEV
react-flight-dom-webpack-server.browser.production.min.js 🔺+24.6% 🔺+22.9% 2.79 KB 3.47 KB 1.32 KB 1.63 KB UMD_PROD
react-flight-dom-webpack-server.browser.development.js +26.6% +24.5% 7.66 KB 9.7 KB 2.36 KB 2.94 KB NODE_DEV
react-flight-dom-webpack-server.browser.production.min.js 🔺+26.4% 🔺+25.7% 2.6 KB 3.29 KB 1.23 KB 1.55 KB NODE_PROD
react-flight-dom-webpack.development.js +26.9% +28.0% 10.96 KB 13.91 KB 3.09 KB 3.96 KB UMD_DEV
react-flight-dom-webpack.production.min.js 🔺+21.0% 🔺+17.0% 3.08 KB 3.72 KB 1.43 KB 1.68 KB UMD_PROD

react-server

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-server-flight.development.js +23.5% +21.0% 8.31 KB 10.27 KB 2.58 KB 3.12 KB NODE_DEV
react-server-flight.production.min.js 🔺+25.6% 🔺+26.3% 2.83 KB 3.56 KB 1.28 KB 1.62 KB NODE_PROD
react-server.development.js 0.0% +0.1% 3.6 KB 3.6 KB 1.22 KB 1.22 KB NODE_DEV
react-server.production.min.js 0.0% 🔺+0.2% 1.14 KB 1.14 KB 634 B 635 B NODE_PROD

react-flight-dom-relay

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactFlightDOMRelayClient-dev.js +20.2% +16.4% 6.96 KB 8.37 KB 2.27 KB 2.65 KB FB_WWW_DEV
ReactFlightDOMRelayClient-prod.js 🔺+36.0% 🔺+23.9% 3.45 KB 4.69 KB 1.22 KB 1.51 KB FB_WWW_PROD
ReactFlightDOMRelayServer-dev.js +28.4% +27.2% 7.7 KB 9.89 KB 2.29 KB 2.91 KB FB_WWW_DEV
ReactFlightDOMRelayServer-prod.js 🔺+29.7% 🔺+31.0% 5.77 KB 7.48 KB 1.67 KB 2.19 KB FB_WWW_PROD

react-client

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-client-flight.development.js +12.7% +10.9% 9.66 KB 10.88 KB 3.01 KB 3.34 KB NODE_DEV
react-client-flight.production.min.js 🔺+18.1% 🔺+11.2% 2.68 KB 3.17 KB 1.27 KB 1.41 KB NODE_PROD

Size changes (stable)

Generated by 🚫 dangerJS against 19435d0

@sebmarkbage sebmarkbage force-pushed the blocks branch 3 times, most recently from b5c62c8 to 7072ce6 Compare March 23, 2020 23:05
return type(props);
} else if (typeof type === 'string') {
// This is a host element. E.g. HTML.
return [REACT_ELEMENT_TYPE, type, element.key, element.props];
} else if (type[0] === REACT_SERVER_BLOCK_TYPE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this? block return value on server only? How will this be wired up? I see you copy pasted it into tests but it's still a bit confusing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see you're saying the compiler would do that. Okay.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea. I mean I could keep it as $$typeof style objects and then convert it to arrays as part of JSON stringify but I figured this could be a good place to start experimenting with first class tuple style objects instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the compiler output should probably depend on a helper that generates this.

I'm thinking react/flight-runtime with a block(...) helper.

@@ -10,7 +10,11 @@

'use strict';

const ReactFeatureFlags = require('shared/ReactFeatureFlags');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acdlite Did you say we need to put these inline now? I don't remember why.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copy-pasted this from elsewhere so if we do, we should codemod the rest of them too.

@@ -138,10 +151,23 @@ export function reportGlobalError(response: Response, error: Error): void {
});
}

function definePendingProperty(
function readMaybeChunk<T>(maybeChunk: Chunk<T> | T): T {
if ((maybeChunk: any).$$typeof !== CHUNK_TYPE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed maybeChunk is literally the User function in the Relay test. Is this intentional? We're missing some mocking that represents the module transport there?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't intentional at first but I did notice it while writing the tests and decided to keep it. I think we should do some mocking here but we don't have it wired up yet in www so we're not sure what we're simulating yet. Once we have it set up we can simulate that here in the mocks.

Also, the tests are far from sufficient yet. Needs more async stuff. I need to write more but don't want to block on it.

BlockRenderFunction<Props, Data>,
> = resolveModuleReference(moduleMetaData);
// TODO: Do this earlier, as the chunk is resolved.
preloadModule(moduleReference);
Copy link
Collaborator

@gaearon gaearon Mar 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment before implementation of preloadModule says

 Returning null means [...]

but retvalue is never used. Is it outdated?

Also, is it completely unobservable? I deleted this and nothing failed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is outdated.

It's not unobservable because of this: https://github.com/facebook/react/blob/master/packages/react-flight-dom-webpack/src/ReactFlightClientWebpackBundlerConfig.js#L60-L61

Lots more tests needs to be written at some point. None of the tests cover that chunks are being loaded.

function initializeBlock<Props, Data>(
tuple: UninitializedBlockPayload<Data>,
): BlockComponent<Props, Data> {
// Require module first and then data. The ordering matters.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we test correct ordering?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Yea. We could test a side-effect in the require somehow while data is still blocked. There's no module function that runs in the tests atm though so needs some infra or hook into the mocks somehow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also don't have any async tests yet for noop which is where we should be adding tests like this.

React elements should no longer be used to extract arbitrary data but only
for prerendering trees.

Blocks are used to create asynchronous behavior.
It's supposed to pass the original object and not the new one.
This module has shared state. It needs to be external from builds.

This lets us test the built versions of the Noop renderer.

it('can render a server component', () => {
function Bar({text}) {
return text.toUpperCase();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is returning arrays from these supposed to work? I tried wrapping it in [] and got Cannot assign to read only property 'children' of object '#<Object>'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm actually this only happens when my toEqual fails. Maybe something's wrong with some test helper?

Copy link
Collaborator

@acdlite acdlite Mar 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Jest bug introduced in 25.0.0 jestjs/jest#9531

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in master but not yet released jestjs/jest#9575

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's more work to do on server components. If anything it's too permissive and allows anything to serialize here. It should take a separate path that only allows certain renderable things, same as the client.

Somewhat surprising that it doesn't allow an array though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think that particular error actually just a problem with jest logging/matching. When things are not toEqual which includes elements, that are frozen, for some reason there's a write somewhere. So if it doesn't match you get a completely confusing error.

You probably just didn't update the toEqual case to match and then you got this confusing error message instead of "it didn't match".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're right.

@@ -136,7 +163,11 @@ describe('ReactFlightDOM', () => {
}

let {writable, readable} = getTestStream();
ReactFlightDOMServer.pipeToNodeWritable(<RootModel />, writable);
ReactFlightDOMServer.pipeToNodeWritable(
<RootModel />,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we delete these non-Block tests now? What is their use?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're the only ones testing async right now but ideally they should be rewritten to a more idiomatic style.

while (
typeof value === 'object' &&
value !== null &&
value.$$typeof === REACT_ELEMENT_TYPE
) {
// TODO: Concatenate keys of parents onto children.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What case is this about?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Server components render away but their keys should still count.

return c ? <Foo key="a" /> : <Foo key="b" />;
function Foo() {
  return <div key="meh" />;
}

This renders ['$', 'div', 'meh', null] regardless. But if this is an update request, it should clear the state because the key has changed.

It's not going to be perfectly compatible semantics since the component type should be part of the equation too but that's a bit too much and not necessary since it doesn't have state.

I think a good compromise would be to concatenate the key onto the child. Possibly with some escaping rules. Similar to what React.Children does.

@sebmarkbage sebmarkbage merged commit a56309f into facebook:master Mar 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants