Add renderToMarkup for Client Components (#30121)

Follow up to #30105.

This supports `renderToMarkup` in a non-RSC environment (not the
`react-server` condition).

This is just a Fizz renderer but it errors at runtime when you use
state, effects or event handlers that would require hydration - like the
RSC version would. (Except RSC can give early errors too.)

To do this I have to move the `react-html` builds to a new `markup`
dimension out of the `dom-legacy` dimension so that we can configure
this differently from `renderToString`/`renderToStaticMarkup`.
Eventually that dimension can go away though if deprecated. That also
helps us avoid dynamic configuration and we can just compile in the
right configuration so the split helps anyway.

One consideration is that if a compiler strips out useEffects or inlines
initial state from useState, then it would not get called an the error
wouldn't happen. Therefore to preserve semantics, a compiler would need
to inject some call that can check the current renderer and whether it
should throw.

There is an argument that it could be useful to not error for these
because it's possible to write components that works with SSR but are
just optionally hydrated. However, there's also an argument that doing
that silently is too easy to lead to mistakes and it's better to error -
especially for the e-mail use case where you can't take it back but you
can replay a queue that had failures. There are other ways to
conditionally branch components intentionally. Besides if you want it to
be silent you can still use renderToString (or better yet
renderToReadableStream).

The primary mechanism is the RSC environment and the client-environment
is really the secondary one that's only there to support legacy
environments. So this also ensures parity with the primary environment.
This commit is contained in:
Sebastian Markbåge
2024-06-28 15:25:10 +02:00
committed by GitHub
parent b3aface19a
commit 1e241f9d6c
22 changed files with 826 additions and 181 deletions

View File

@@ -520,5 +520,8 @@
"532": "Attempted to render a Client Component from renderToMarkup. This is not supported since it will never hydrate. Only render Server Components with renderToMarkup.",
"533": "Attempted to render a Server Action from renderToMarkup. This is not supported since it varies by version of the app. Use a fixed URL for any forms instead.",
"534": "renderToMarkup should not have emitted Client References. This is a bug in React.",
"535": "renderToMarkup should not have emitted Server References. This is a bug in React."
"535": "renderToMarkup should not have emitted Server References. This is a bug in React.",
"536": "Cannot pass ref in renderToMarkup because they will never be hydrated.",
"537": "Cannot pass event handlers (%s) in renderToMarkup because the HTML will never be hydrated so they can never get called.",
"538": "Cannot use state or effect Hooks in renderToMarkup because this component will never be hydrated."
}

View File

@@ -363,7 +363,7 @@ const bundles = [
externals: [],
},
/******* React HTML *******/
/******* React HTML RSC *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
@@ -376,6 +376,18 @@ const bundles = [
externals: ['react'],
},
/******* React HTML Client *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-html/src/ReactHTMLClient.js',
name: 'react-html',
global: 'ReactHTML',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react'],
},
/******* React Server DOM Webpack Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],

View File

@@ -100,6 +100,7 @@ const forks = Object.freeze({
entry === 'react-dom/src/ReactDOMFB.js' ||
entry === 'react-dom/src/ReactDOMTestingFB.js' ||
entry === 'react-dom/src/ReactDOMServer.js' ||
entry === 'react-html/src/ReactHTMLClient.js' ||
entry === 'react-html/src/ReactHTMLServer.js'
) {
if (

View File

@@ -428,16 +428,29 @@ module.exports = [
entryPoints: [
'react-dom/src/server/ReactDOMLegacyServerBrowser.js', // react-dom/server.browser
'react-dom/src/server/ReactDOMLegacyServerNode.js', // react-dom/server.node
'react-html/src/ReactHTMLServer.js',
],
paths: [
'react-dom',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-server-dom-webpack',
'react-dom/src/server/ReactDOMLegacyServerImpl.js', // not an entrypoint, but only usable in *Browser and *Node files
'react-dom/src/server/ReactDOMLegacyServerBrowser.js', // react-dom/server.browser
'react-dom/src/server/ReactDOMLegacyServerNode.js', // react-dom/server.node
'shared/ReactDOMSharedInternals',
],
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'markup',
entryPoints: [
'react-html/src/ReactHTMLClient.js', // react-html
'react-html/src/ReactHTMLServer.js', // react-html/react-html.react-server
],
paths: [
'react-dom',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-html',
'shared/ReactDOMSharedInternals',
],