Skip to content

Commit 1e241f9

Browse files
authored
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.
1 parent b3aface commit 1e241f9

22 files changed

+826
-181
lines changed

packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js

Lines changed: 14 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,82 +7,20 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes';
10+
export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
11+
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
1112

12-
export * from 'react-html/src/ReactHTMLLegacyClientStreamConfig.js';
13-
export * from 'react-client/src/ReactClientConsoleConfigPlain';
14-
15-
export type ModuleLoading = null;
16-
export type SSRModuleMap = null;
17-
export opaque type ServerManifest = null;
13+
export type Response = any;
14+
export opaque type ModuleLoading = mixed;
15+
export opaque type SSRModuleMap = mixed;
16+
export opaque type ServerManifest = mixed;
1817
export opaque type ServerReferenceId = string;
19-
export opaque type ClientReferenceMetadata = null;
20-
export opaque type ClientReference<T> = null; // eslint-disable-line no-unused-vars
21-
22-
export function prepareDestinationForModule(
23-
moduleLoading: ModuleLoading,
24-
nonce: ?string,
25-
metadata: ClientReferenceMetadata,
26-
) {
27-
throw new Error(
28-
'renderToMarkup should not have emitted Client References. This is a bug in React.',
29-
);
30-
}
31-
32-
export function resolveClientReference<T>(
33-
bundlerConfig: SSRModuleMap,
34-
metadata: ClientReferenceMetadata,
35-
): ClientReference<T> {
36-
throw new Error(
37-
'renderToMarkup should not have emitted Client References. This is a bug in React.',
38-
);
39-
}
40-
41-
export function resolveServerReference<T>(
42-
config: ServerManifest,
43-
id: ServerReferenceId,
44-
): ClientReference<T> {
45-
throw new Error(
46-
'renderToMarkup should not have emitted Server References. This is a bug in React.',
47-
);
48-
}
49-
50-
export function preloadModule<T>(
51-
metadata: ClientReference<T>,
52-
): null | Thenable<T> {
53-
return null;
54-
}
55-
56-
export function requireModule<T>(metadata: ClientReference<T>): T {
57-
throw new Error(
58-
'renderToMarkup should not have emitted Client References. This is a bug in React.',
59-
);
60-
}
61-
18+
export opaque type ClientReferenceMetadata = mixed;
19+
export opaque type ClientReference<T> = mixed; // eslint-disable-line no-unused-vars
20+
export const resolveClientReference: any = null;
21+
export const resolveServerReference: any = null;
22+
export const preloadModule: any = null;
23+
export const requireModule: any = null;
24+
export const dispatchHint: any = null;
25+
export const prepareDestinationForModule: any = null;
6226
export const usedWithSSR = true;
63-
64-
type HintCode = string;
65-
type HintModel<T: HintCode> = null; // eslint-disable-line no-unused-vars
66-
67-
export function dispatchHint<Code: HintCode>(
68-
code: Code,
69-
model: HintModel<Code>,
70-
): void {
71-
// Should never happen.
72-
}
73-
74-
export function preinitModuleForSSR(
75-
href: string,
76-
nonce: ?string,
77-
crossOrigin: ?string,
78-
) {
79-
// Should never happen.
80-
}
81-
82-
export function preinitScriptForSSR(
83-
href: string,
84-
nonce: ?string,
85-
crossOrigin: ?string,
86-
) {
87-
// Should never happen.
88-
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {Thenable} from 'shared/ReactTypes';
11+
12+
export * from 'react-html/src/ReactHTMLLegacyClientStreamConfig.js';
13+
export * from 'react-client/src/ReactClientConsoleConfigPlain';
14+
15+
export type ModuleLoading = null;
16+
export type SSRModuleMap = null;
17+
export opaque type ServerManifest = null;
18+
export opaque type ServerReferenceId = string;
19+
export opaque type ClientReferenceMetadata = null;
20+
export opaque type ClientReference<T> = null; // eslint-disable-line no-unused-vars
21+
22+
export function prepareDestinationForModule(
23+
moduleLoading: ModuleLoading,
24+
nonce: ?string,
25+
metadata: ClientReferenceMetadata,
26+
) {
27+
throw new Error(
28+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
29+
);
30+
}
31+
32+
export function resolveClientReference<T>(
33+
bundlerConfig: SSRModuleMap,
34+
metadata: ClientReferenceMetadata,
35+
): ClientReference<T> {
36+
throw new Error(
37+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
38+
);
39+
}
40+
41+
export function resolveServerReference<T>(
42+
config: ServerManifest,
43+
id: ServerReferenceId,
44+
): ClientReference<T> {
45+
throw new Error(
46+
'renderToMarkup should not have emitted Server References. This is a bug in React.',
47+
);
48+
}
49+
50+
export function preloadModule<T>(
51+
metadata: ClientReference<T>,
52+
): null | Thenable<T> {
53+
return null;
54+
}
55+
56+
export function requireModule<T>(metadata: ClientReference<T>): T {
57+
throw new Error(
58+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
59+
);
60+
}
61+
62+
export const usedWithSSR = true;
63+
64+
type HintCode = string;
65+
type HintModel<T: HintCode> = null; // eslint-disable-line no-unused-vars
66+
67+
export function dispatchHint<Code: HintCode>(
68+
code: Code,
69+
model: HintModel<Code>,
70+
): void {
71+
// Should never happen.
72+
}
73+
74+
export function preinitModuleForSSR(
75+
href: string,
76+
nonce: ?string,
77+
crossOrigin: ?string,
78+
) {
79+
// Should never happen.
80+
}
81+
82+
export function preinitScriptForSSR(
83+
href: string,
84+
nonce: ?string,
85+
crossOrigin: ?string,
86+
) {
87+
// Should never happen.
88+
}

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export type HeadersDescriptor = {
108108
// E.g. this can be used to distinguish legacy renderers from this modern one.
109109
export const isPrimaryRenderer = true;
110110

111+
export const supportsClientAPIs = true;
112+
111113
export type StreamingFormat = 0 | 1;
112114
const ScriptStreamingFormat: StreamingFormat = 0;
113115
const DataStreamingFormat: StreamingFormat = 1;

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export {
166166
resetResumableState,
167167
completeResumableState,
168168
emitEarlyPreloads,
169+
supportsClientAPIs,
169170
} from './ReactFizzConfigDOM';
170171

171172
import escapeTextForBrowser from './escapeTextForBrowser';

packages/react-html/index.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
'use strict';
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
29

3-
throw new Error(
4-
'react-html is not supported outside a React Server Components environment.',
5-
);
10+
export * from './src/ReactHTMLClient';

packages/react-html/npm/index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3-
throw new Error(
4-
'react-html is not supported outside a React Server Components environment.'
5-
);
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/react-html.production.js');
5+
} else {
6+
module.exports = require('./cjs/react-html.development.js');
7+
}

0 commit comments

Comments
 (0)