@samitouri / QOS-React / commits / d5f1b067c8

[ServerContext] Flight support for ServerContext (#23244)

* Flight side of server context * 1 more test * rm unused function * flow+prettier * flow again =) * duplicate ReactServerContext across packages * store default value when lazily initializing server context * . * better comment * derp... missing import * rm optional chaining * missed feature flag * React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ?? * add warning if non ServerContext passed into useServerContext * pass context in as array of arrays * make importServerContext nott pollute the global context state * merge main * remove useServerContext * dont rely on object getters in ReactServerContext and disallow JSX * add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry * gate test case as experimental * feedback * remove unions * Lint * fix oopsies (tests/lint/mismatching arguments/signatures * lint again * replace-fork * remove extraneous change * rebase * 1 more test * rm unused function * flow+prettier * flow again =) * duplicate ReactServerContext across packages * store default value when lazily initializing server context * . * better comment * derp... missing import * rm optional chaining * missed feature flag * React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ?? * add warning if non ServerContext passed into useServerContext * pass context in as array of arrays * make importServerContext nott pollute the global context state * merge main * remove useServerContext * dont rely on object getters in ReactServerContext and disallow JSX * add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry * gate test case as experimental * feedback * remove unions * Lint * fix oopsies (tests/lint/mismatching arguments/signatures * lint again * replace-fork * remove extraneous change * rebase * reinline * rebase * add back changes lost due to rebase being hard * emit chunk for provider * remove case for React provider type * update type for SomeChunk * enable flag with experimental * add missing types * fix flow type * missing type * t: any * revert extraneous type change * better type * better type * feedback * change import to type import * test? * test? * remove react-dom * remove react-native-renderer from react-server-native-relay/package.json * gate change in FiberNewContext, getComponentNameFromType, use switch statement in FlightServer * getComponentNameFromTpe: server context type gated and use displayName if available * fallthrough * lint.... * POP * lint

salazarm committed Mar 8, 2022 at 07:55 UTC d5f1b067c8bbb826b823d0354a28ba31078b70c0
53 files changed +1242 -90
packages/react-client/src/ReactFlightClient.js
+18
@@ -26,6 +26,8 @@ import {
26
27 import {REACT_LAZY_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
28
29 +import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
30 +
31 export type JSONValue =
32 | number
33 | null
@@ -327,6 +329,7 @@ export function parseModelTuple(
329 value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
330 ): any {
331 const tuple: [mixed, mixed, mixed, mixed] = (value: any);
332 +
333 if (tuple[0] === REACT_ELEMENT_TYPE) {
334 // TODO: Consider having React just directly accept these arrays as elements.
335 // Or even change the ReactElement type to be an array.
@@ -358,6 +361,21 @@ export function resolveModel(
361 }
362 }
363
364 +export function resolveProvider(
365 + response: Response,
366 + id: number,
367 + contextName: string,
368 +): void {
369 + const chunks = response._chunks;
370 + chunks.set(
371 + id,
372 + createInitializedChunk(
373 + response,
374 + getOrCreateServerContext(contextName).Provider,
375 + ),
376 + );
377 +}
378 +
379 export function resolveModule(
380 response: Response,
381 id: number,
packages/react-client/src/ReactFlightClientStream.js
+5
@@ -12,6 +12,7 @@ import type {Response} from './ReactFlightClientHostConfigStream';
12 import {
13 resolveModule,
14 resolveModel,
15 + resolveProvider,
16 resolveSymbol,
17 resolveError,
18 createResponse as createResponseBase,
@@ -49,6 +50,10 @@ function processFullRow(response: Response, row: string): void {
50 resolveModule(response, id, text);
51 return;
52 }
53 + case 'P': {
54 + resolveProvider(response, id, text);
55 + return;
56 + }
57 case 'S': {
58 resolveSymbol(response, id, JSON.parse(text));
59 return;
packages/react-client/src/__tests__/ReactFlight-test.js
+352
@@ -17,6 +17,8 @@ let ReactNoopFlightServer;
17 let ReactNoopFlightClient;
18 let ErrorBoundary;
19 let NoErrorExpected;
20 +let Scheduler;
21 +let ContextRegistry;
22
23 describe('ReactFlight', () => {
24 beforeEach(() => {
@@ -27,6 +29,10 @@ describe('ReactFlight', () => {
29 ReactNoopFlightServer = require('react-noop-renderer/flight-server');
30 ReactNoopFlightClient = require('react-noop-renderer/flight-client');
31 act = require('jest-react').act;
32 + Scheduler = require('scheduler');
33 + const ReactSharedInternals =
34 + React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
35 + ContextRegistry = ReactSharedInternals.ContextRegistry;
36
37 ErrorBoundary = class extends React.Component {
38 state = {hasError: false, error: null};
@@ -302,4 +308,350 @@ describe('ReactFlight', () => {
308 {withoutStack: true},
309 );
310 });
311 +
312 + describe('ServerContext', () => {
313 + // @gate enableServerContext
314 + it('supports basic createServerContext usage', () => {
315 + const ServerContext = React.createServerContext(
316 + 'ServerContext',
317 + 'hello from server',
318 + );
319 + function Foo() {
320 + const context = React.useContext(ServerContext);
321 + return <div>{context}</div>;
322 + }
323 +
324 + const transport = ReactNoopFlightServer.render(<Foo />);
325 + act(() => {
326 + ServerContext._currentRenderer = null;
327 + ServerContext._currentRenderer2 = null;
328 + ReactNoop.render(ReactNoopFlightClient.read(transport));
329 + });
330 +
331 + expect(ReactNoop).toMatchRenderedOutput(<div>hello from server</div>);
332 + });
333 +
334 + // @gate enableServerContext
335 + it('propagates ServerContext providers in flight', () => {
336 + const ServerContext = React.createServerContext(
337 + 'ServerContext',
338 + 'default',
339 + );
340 +
341 + function Foo() {
342 + return (
343 + <div>
344 + <ServerContext.Provider value="hi this is server">
345 + <Bar />
346 + </ServerContext.Provider>
347 + </div>
348 + );
349 + }
350 + function Bar() {
351 + const context = React.useContext(ServerContext);
352 + return context;
353 + }
354 +
355 + const transport = ReactNoopFlightServer.render(<Foo />);
356 + act(() => {
357 + ServerContext._currentRenderer = null;
358 + ServerContext._currentRenderer2 = null;
359 + ReactNoop.render(ReactNoopFlightClient.read(transport));
360 + });
361 +
362 + expect(ReactNoop).toMatchRenderedOutput(<div>hi this is server</div>);
363 + });
364 +
365 + // @gate enableServerContext
366 + it('errors if you try passing JSX through ServerContext value', () => {
367 + const ServerContext = React.createServerContext('ServerContext', {
368 + foo: {
369 + bar: <span>hi this is default</span>,
370 + },
371 + });
372 +
373 + function Foo() {
374 + return (
375 + <div>
376 + <ServerContext.Provider
377 + value={{
378 + foo: {
379 + bar: <span>hi this is server</span>,
380 + },
381 + }}>
382 + <Bar />
383 + </ServerContext.Provider>
384 + </div>
385 + );
386 + }
387 + function Bar() {
388 + const context = React.useContext(ServerContext);
389 + return context.foo.bar;
390 + }
391 +
392 + expect(() => {
393 + ReactNoopFlightServer.render(<Foo />);
394 + }).toErrorDev('React elements are not allowed in ServerContext', {
395 + withoutStack: true,
396 + });
397 + });
398 +
399 + // @gate enableServerContext
400 + it('propagates ServerContext and cleansup providers in flight', () => {
401 + const ServerContext = React.createServerContext(
402 + 'ServerContext',
403 + 'default',
404 + );
405 +
406 + function Foo() {
407 + return (
408 + <>
409 + <ServerContext.Provider value="hi this is server outer">
410 + <ServerContext.Provider value="hi this is server">
411 + <Bar />
412 + </ServerContext.Provider>
413 + <ServerContext.Provider value="hi this is server2">
414 + <Bar />
415 + </ServerContext.Provider>
416 + <Bar />
417 + </ServerContext.Provider>
418 + <ServerContext.Provider value="hi this is server outer2">
419 + <Bar />
420 + </ServerContext.Provider>
421 + <Bar />
422 + </>
423 + );
424 + }
425 + function Bar() {
426 + const context = React.useContext(ServerContext);
427 + return <span>{context}</span>;
428 + }
429 +
430 + const transport = ReactNoopFlightServer.render(<Foo />);
431 + act(() => {
432 + ServerContext._currentRenderer = null;
433 + ServerContext._currentRenderer2 = null;
434 + ReactNoop.render(ReactNoopFlightClient.read(transport));
435 + });
436 +
437 + expect(ReactNoop).toMatchRenderedOutput(
438 + <>
439 + <span>hi this is server</span>
440 + <span>hi this is server2</span>
441 + <span>hi this is server outer</span>
442 + <span>hi this is server outer2</span>
443 + <span>default</span>
444 + </>,
445 + );
446 + });
447 +
448 + // @gate enableServerContext
449 + it('propagates ServerContext providers in flight after suspending', async () => {
450 + const ServerContext = React.createServerContext(
451 + 'ServerContext',
452 + 'default',
453 + );
454 +
455 + function Foo() {
456 + return (
457 + <div>
458 + <ServerContext.Provider value="hi this is server">
459 + <React.Suspense fallback={'Loading'}>
460 + <Bar />
461 + </React.Suspense>
462 + </ServerContext.Provider>
463 + </div>
464 + );
465 + }
466 +
467 + let resolve;
468 + const promise = new Promise(res => {
469 + resolve = () => {
470 + promise.unsuspend = true;
471 + res();
472 + };
473 + });
474 +
475 + function Bar() {
476 + if (!promise.unsuspend) {
477 + Scheduler.unstable_yieldValue('suspended');
478 + throw promise;
479 + }
480 + Scheduler.unstable_yieldValue('rendered');
481 + const context = React.useContext(ServerContext);
482 + return context;
483 + }
484 +
485 + const transport = ReactNoopFlightServer.render(<Foo />);
486 +
487 + expect(Scheduler).toHaveYielded(['suspended']);
488 +
489 + await act(async () => {
490 + resolve();
491 + await promise;
492 + jest.runAllImmediates();
493 + });
494 +
495 + expect(Scheduler).toHaveYielded(['rendered']);
496 +
497 + act(() => {
498 + ServerContext._currentRenderer = null;
499 + ServerContext._currentRenderer2 = null;
500 + ReactNoop.render(ReactNoopFlightClient.read(transport));
501 + });
502 +
503 + expect(ReactNoop).toMatchRenderedOutput(<div>hi this is server</div>);
504 + });
505 +
506 + // @gate enableServerContext
507 + it('serializes ServerContext to client', async () => {
508 + const ServerContext = React.createServerContext(
509 + 'ServerContext',
510 + 'default',
511 + );
512 +
513 + function ClientBar() {
514 + Scheduler.unstable_yieldValue('ClientBar');
515 + const context = React.useContext(ServerContext);
516 + return <span>{context}</span>;
517 + }
518 +
519 + const Bar = moduleReference(ClientBar);
520 +
521 + function Foo() {
522 + return (
523 + <ServerContext.Provider value="hi this is server">
524 + <Bar />
525 + </ServerContext.Provider>
526 + );
527 + }
528 +
529 + const model = {
530 + foo: <Foo />,
531 + };
532 +
533 + const transport = ReactNoopFlightServer.render(model);
534 +
535 + expect(Scheduler).toHaveYielded([]);
536 +
537 + act(() => {
538 + ServerContext._currentRenderer = null;
539 + ServerContext._currentRenderer2 = null;
540 + const flightModel = ReactNoopFlightClient.read(transport);
541 + ReactNoop.render(flightModel.foo);
542 + });
543 +
544 + expect(Scheduler).toHaveYielded(['ClientBar']);
545 + expect(ReactNoop).toMatchRenderedOutput(<span>hi this is server</span>);
546 +
547 + expect(() => {
548 + React.createServerContext('ServerContext', 'default');
549 + }).toThrow('ServerContext: ServerContext already defined');
550 + });
551 +
552 + // @gate enableServerContext
553 + it('takes ServerContext from client for refetching usecases', async () => {
554 + const ServerContext = React.createServerContext(
555 + 'ServerContext',
556 + 'default',
557 + );
558 + function Bar() {
559 + return <span>{React.useContext(ServerContext)}</span>;
560 + }
561 + const transport = ReactNoopFlightServer.render(<Bar />, {}, [
562 + ['ServerContext', 'Override'],
563 + ]);
564 +
565 + act(() => {
566 + const flightModel = ReactNoopFlightClient.read(transport);
567 + ReactNoop.render(flightModel);
568 + });
569 +
570 + expect(ReactNoop).toMatchRenderedOutput(<span>Override</span>);
571 + });
572 +
573 + // @gate enableServerContext
574 + it('sets default initial value when defined lazily on server or client', async () => {
575 + let ServerContext;
576 + function inlineLazyServerContextInitialization() {
577 + if (!ServerContext) {
578 + ServerContext = React.createServerContext('ServerContext', 'default');
579 + }
580 + return ServerContext;
581 + }
582 +
583 + let ClientContext;
584 + function inlineContextInitialization() {
585 + if (!ClientContext) {
586 + ClientContext = React.createServerContext('ServerContext', 'default');
587 + }
588 + return ClientContext;
589 + }
590 +
591 + function ClientBaz() {
592 + const context = inlineContextInitialization();
593 + const value = React.useContext(context);
594 + return <div>{value}</div>;
595 + }
596 +
597 + const Baz = moduleReference(ClientBaz);
598 +
599 + function Bar() {
600 + return (
601 + <article>
602 + <div>
603 + {React.useContext(inlineLazyServerContextInitialization())}
604 + </div>
605 + <Baz />
606 + </article>
607 + );
608 + }
609 +
610 + function ServerApp() {
611 + const Context = inlineLazyServerContextInitialization();
612 + return (
613 + <>
614 + <Context.Provider value="test">
615 + <Bar />
616 + </Context.Provider>
617 + <Bar />
618 + </>
619 + );
620 + }
621 +
622 + function ClientApp({serverModel}) {
623 + return (
624 + <>
625 + {serverModel}
626 + <ClientBaz />
627 + </>
628 + );
629 + }
630 +
631 + const transport = ReactNoopFlightServer.render(<ServerApp />);
632 +
633 + expect(ClientContext).toBe(undefined);
634 + act(() => {
635 + delete ContextRegistry.ServerContext;
636 + ServerContext._currentRenderer = null;
637 + ServerContext._currentRenderer2 = null;
638 + const serverModel = ReactNoopFlightClient.read(transport);
639 + ReactNoop.render(<ClientApp serverModel={serverModel} />);
640 + });
641 +
642 + expect(ReactNoop).toMatchRenderedOutput(
643 + <>
644 + <article>
645 + <div>test</div>
646 + <div>test</div>
647 + </article>
648 + <article>
649 + <div>default</div>
650 + <div>default</div>
651 + </article>
652 + <div>default</div>
653 + </>,
654 + );
655 + });
656 + });
657 });
packages/react-devtools-shared/src/backend/ReactSymbols.js
+5
@@ -19,6 +19,8 @@ export const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)';
19 export const CONTEXT_NUMBER = 0xeace;
20 export const CONTEXT_SYMBOL_STRING = 'Symbol(react.context)';
21
22 +export const SERVER_CONTEXT_SYMBOL_STRING = 'Symbol(react.server_context)';
23 +
24 export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)';
25
26 export const ELEMENT_NUMBER = 0xeac7;
@@ -60,3 +62,6 @@ export const SUSPENSE_SYMBOL_STRING = 'Symbol(react.suspense)';
62
63 export const SUSPENSE_LIST_NUMBER = 0xead8;
64 export const SUSPENSE_LIST_SYMBOL_STRING = 'Symbol(react.suspense_list)';
65 +
66 +export const SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED_SYMBOL_STRING =
67 + 'Symbol(react.server_context.defaultValue)';
packages/react-devtools-shared/src/backend/renderer.js
+2
@@ -85,6 +85,7 @@ import {
85 FORWARD_REF_SYMBOL_STRING,
86 MEMO_NUMBER,
87 MEMO_SYMBOL_STRING,
88 + SERVER_CONTEXT_SYMBOL_STRING,
89 } from './ReactSymbols';
90 import {format} from './utils';
91 import {
@@ -511,6 +512,7 @@ export function getInternalReactConstants(
512 return `${resolvedContext.displayName || 'Context'}.Provider`;
513 case CONTEXT_NUMBER:
514 case CONTEXT_SYMBOL_STRING:
515 + case SERVER_CONTEXT_SYMBOL_STRING:
516 // 16.3-16.5 read from "type" because the Consumer is the actual context object.
517 // 16.6+ should read from "type._context" because Consumer can be different (in DEV).
518 // NOTE Keep in sync with inspectElementRaw()
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+49
@@ -2455,4 +2455,53 @@ describe('ReactDOMFizzServer', () => {
2455 'Suspense boundary. Switched to client rendering.',
2456 ]);
2457 });
2458 +
2459 + // @gate enableServerContext && experimental
2460 + it('supports ServerContext', async () => {
2461 + let ServerContext;
2462 + function inlineLazyServerContextInitialization() {
2463 + if (!ServerContext) {
2464 + ServerContext = React.createServerContext('ServerContext', 'default');
2465 + }
2466 + return ServerContext;
2467 + }
2468 +
2469 + function Foo() {
2470 + inlineLazyServerContextInitialization();
2471 + return (
2472 + <>
2473 + <ServerContext.Provider value="hi this is server outer">
2474 + <ServerContext.Provider value="hi this is server">
2475 + <Bar />
2476 + </ServerContext.Provider>
2477 + <ServerContext.Provider value="hi this is server2">
2478 + <Bar />
2479 + </ServerContext.Provider>
2480 + <Bar />
2481 + </ServerContext.Provider>
2482 + <ServerContext.Provider value="hi this is server outer2">
2483 + <Bar />
2484 + </ServerContext.Provider>
2485 + <Bar />
2486 + </>
2487 + );
2488 + }
2489 + function Bar() {
2490 + const context = React.useContext(inlineLazyServerContextInitialization());
2491 + return <span>{context}</span>;
2492 + }
2493 +
2494 + await act(async () => {
2495 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
2496 + pipe(writable);
2497 + });
2498 +
2499 + expect(getVisibleChildren(container)).toEqual([
2500 + <span>hi this is server</span>,
2501 + <span>hi this is server2</span>,
2502 + <span>hi this is server outer</span>,
2503 + <span>hi this is server outer2</span>,
2504 + <span>default</span>,
2505 + ]);
2506 + });
2507 });
packages/react-is/src/ReactIs.js
+2
@@ -11,6 +11,7 @@
11
12 import {
13 REACT_CONTEXT_TYPE,
14 + REACT_SERVER_CONTEXT_TYPE,
15 REACT_ELEMENT_TYPE,
16 REACT_FORWARD_REF_TYPE,
17 REACT_FRAGMENT_TYPE,
@@ -43,6 +44,7 @@ export function typeOf(object: any) {
44 const $$typeofType = type && type.$$typeof;
45
46 switch ($$typeofType) {
47 + case REACT_SERVER_CONTEXT_TYPE:
48 case REACT_CONTEXT_TYPE:
49 case REACT_FORWARD_REF_TYPE:
50 case REACT_LAZY_TYPE:
packages/react-noop-renderer/src/ReactNoopFlightServer.js
+7 -1
@@ -15,6 +15,7 @@
15 */
16
17 import type {ReactModel} from 'react-server/src/ReactFlightServer';
18 +import type {ServerContextJSONValue} from 'shared/ReactTypes';
19
20 import {saveModule} from 'react-noop-renderer/flight-modules';
21
@@ -62,13 +63,18 @@ type Options = {
63 onError?: (error: mixed) => void,
64 };
65
65 -function render(model: ReactModel, options?: Options): Destination {
66 +function render(
67 + model: ReactModel,
68 + options?: Options,
69 + context?: Array<[string, ServerContextJSONValue]>,
70 +): Destination {
71 const destination: Destination = [];
72 const bundlerConfig = undefined;
73 const request = ReactNoopFlightServer.createRequest(
74 model,
75 bundlerConfig,
76 options ? options.onError : undefined,
77 + context,
78 );
79 ReactNoopFlightServer.startWork(request);
80 ReactNoopFlightServer.startFlowing(request, destination);
packages/react-reconciler/src/ReactFiberCacheComponent.new.js
+2
@@ -48,6 +48,8 @@ export const CacheContext: ReactContext<Cache> = enableCache
48 _currentValue: (null: any),
49 _currentValue2: (null: any),
50 _threadCount: 0,
51 + _defaultValue: (null: any),
52 + _globalName: (null: any),
53 }
54 : (null: any);
55
packages/react-reconciler/src/ReactFiberCacheComponent.old.js
+2
@@ -48,6 +48,8 @@ export const CacheContext: ReactContext<Cache> = enableCache
48 _currentValue: (null: any),
49 _currentValue2: (null: any),
50 _threadCount: 0,
51 + _defaultValue: (null: any),
52 + _globalName: (null: any),
53 }
54 : (null: any);
55
packages/react-reconciler/src/ReactFiberHooks.new.js
+1 -2
@@ -2451,7 +2451,6 @@ if (enableCache) {
2451 (HooksDispatcherOnMount: Dispatcher).getCacheForType = getCacheForType;
2452 (HooksDispatcherOnMount: Dispatcher).useCacheRefresh = mountRefresh;
2453 }
2454 -
2454 const HooksDispatcherOnUpdate: Dispatcher = {
2455 readContext,
2456
@@ -3264,7 +3263,7 @@ if (__DEV__) {
3263 (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
3264 (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
3265 currentHookNameInDev = 'useCacheRefresh';
3267 - updateHookTypesDev();
3266 + mountHookTypesDev();
3267 return mountRefresh();
3268 };
3269 }
packages/react-reconciler/src/ReactFiberHooks.old.js
+1 -2
@@ -2451,7 +2451,6 @@ if (enableCache) {
2451 (HooksDispatcherOnMount: Dispatcher).getCacheForType = getCacheForType;
2452 (HooksDispatcherOnMount: Dispatcher).useCacheRefresh = mountRefresh;
2453 }
2454 -
2454 const HooksDispatcherOnUpdate: Dispatcher = {
2455 readContext,
2456
@@ -3264,7 +3263,7 @@ if (__DEV__) {
3263 (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
3264 (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
3265 currentHookNameInDev = 'useCacheRefresh';
3267 - updateHookTypesDev();
3266 + mountHookTypesDev();
3267 return mountRefresh();
3268 };
3269 }
packages/react-reconciler/src/ReactFiberNewContext.new.js
+18 -2
@@ -44,7 +44,9 @@ import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
44 import {
45 enableSuspenseServerRenderer,
46 enableLazyContextPropagation,
47 + enableServerContext,
48 } from 'shared/ReactFeatureFlags';
49 +import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
50
51 const valueCursor: StackCursor<mixed> = createCursor(null);
52
@@ -132,9 +134,23 @@ export function popProvider(
134 const currentValue = valueCursor.current;
135 pop(valueCursor, providerFiber);
136 if (isPrimaryRenderer) {
135 - context._currentValue = currentValue;
137 + if (
138 + enableServerContext &&
139 + currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
140 + ) {
141 + context._currentValue = context._defaultValue;
142 + } else {
143 + context._currentValue = currentValue;
144 + }
145 } else {
137 - context._currentValue2 = currentValue;
146 + if (
147 + enableServerContext &&
148 + currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
149 + ) {
150 + context._currentValue2 = context._defaultValue;
151 + } else {
152 + context._currentValue2 = currentValue;
153 + }
154 }
155 }
156
packages/react-reconciler/src/ReactFiberNewContext.old.js
+18 -2
@@ -44,7 +44,9 @@ import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';
44 import {
45 enableSuspenseServerRenderer,
46 enableLazyContextPropagation,
47 + enableServerContext,
48 } from 'shared/ReactFeatureFlags';
49 +import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
50
51 const valueCursor: StackCursor<mixed> = createCursor(null);
52
@@ -132,9 +134,23 @@ export function popProvider(
134 const currentValue = valueCursor.current;
135 pop(valueCursor, providerFiber);
136 if (isPrimaryRenderer) {
135 - context._currentValue = currentValue;
137 + if (
138 + enableServerContext &&
139 + currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
140 + ) {
141 + context._currentValue = context._defaultValue;
142 + } else {
143 + context._currentValue = currentValue;
144 + }
145 } else {
137 - context._currentValue2 = currentValue;
146 + if (
147 + enableServerContext &&
148 + currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
149 + ) {
150 + context._currentValue2 = context._defaultValue;
151 + } else {
152 + context._currentValue2 = currentValue;
153 + }
154 }
155 }
156
packages/react-reconciler/src/ReactInternalTypes.js
+1 -1
@@ -16,6 +16,7 @@ import type {
16 MutableSourceVersion,
17 MutableSource,
18 StartTransitionOptions,
19 + Wakeable,
20 } from 'shared/ReactTypes';
21 import type {SuspenseInstance} from './ReactFiberHostConfig';
22 import type {WorkTag} from './ReactWorkTags';
@@ -24,7 +25,6 @@ import type {Flags} from './ReactFiberFlags';
25 import type {Lane, Lanes, LaneMap} from './ReactFiberLane.old';
26 import type {RootTag} from './ReactRootTags';
27 import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
27 -import type {Wakeable} from 'shared/ReactTypes';
28 import type {Cache} from './ReactFiberCacheComponent.old';
29 import type {Transitions} from './ReactFiberTracingMarkerComponent.new';
30
packages/react-server-dom-relay/src/ReactFlightDOMRelayProtocol.js
+1
@@ -20,6 +20,7 @@ export type JSONValue =
20 export type RowEncoding =
21 | ['J', number, JSONValue]
22 | ['M', number, ModuleMetaData]
23 + | ['P', number, string]
24 | ['S', number, string]
25 | [
26 'E',
packages/react-server-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js
+8
@@ -125,6 +125,14 @@ export function processModuleChunk(
125 return ['M', id, moduleMetaData];
126 }
127
128 +export function processProviderChunk(
129 + request: Request,
130 + id: number,
131 + contextName: string,
132 +): Chunk {
133 + return ['P', id, contextName];
134 +}
135 +
136 export function processSymbolChunk(
137 request: Request,
138 id: number,
packages/react-server-dom-webpack/package.json
-1
@@ -48,7 +48,6 @@
48 },
49 "peerDependencies": {
50 "react": "^17.0.0",
51 - "react-dom": "^17.0.0",
51 "webpack": "^5.59.0"
52 },
53 "dependencies": {
packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js
+3
@@ -8,6 +8,7 @@
8 */
9
10 import type {ReactModel} from 'react-server/src/ReactFlightServer';
11 +import type {ServerContextJSONValue} from 'shared/ReactTypes';
12 import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig';
13
14 import {
@@ -24,11 +25,13 @@ function renderToReadableStream(
25 model: ReactModel,
26 webpackMap: BundlerConfig,
27 options?: Options,
28 + context?: Array<[string, ServerContextJSONValue]>,
29 ): ReadableStream {
30 const request = createRequest(
31 model,
32 webpackMap,
33 options ? options.onError : undefined,
34 + context,
35 );
36 const stream = new ReadableStream({
37 type: 'bytes',
packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
+3
@@ -10,6 +10,7 @@
10 import type {ReactModel} from 'react-server/src/ReactFlightServer';
11 import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig';
12 import type {Writable} from 'stream';
13 +import type {ServerContextJSONValue} from 'shared/ReactTypes';
14
15 import {
16 createRequest,
@@ -33,11 +34,13 @@ function renderToPipeableStream(
34 model: ReactModel,
35 webpackMap: BundlerConfig,
36 options?: Options,
37 + context?: Array<[string, ServerContextJSONValue]>,
38 ): Controls {
39 const request = createRequest(
40 model,
41 webpackMap,
42 options ? options.onError : undefined,
43 + context,
44 );
45 let hasStartedFlowing = false;
46 startWork(request);
packages/react-server-native-relay/package.json
+1 -2
@@ -11,7 +11,6 @@
11 "scheduler": "^0.11.0"
12 },
13 "peerDependencies": {
14 - "react": "^17.0.0",
15 - "react-native-renderer": "^17.0.0"
14 + "react": "^17.0.0"
15 }
16 }
packages/react-server-native-relay/src/ReactFlightNativeRelayProtocol.js
+1
@@ -20,6 +20,7 @@ export type JSONValue =
20 export type RowEncoding =
21 | ['J', number, JSONValue]
22 | ['M', number, ModuleMetaData]
23 + | ['P', number, string]
24 | ['S', number, string]
25 | [
26 'E',
packages/react-server-native-relay/src/ReactFlightNativeRelayServerHostConfig.js
+8
@@ -122,6 +122,14 @@ export function processModuleChunk(
122 return ['M', id, moduleMetaData];
123 }
124
125 +export function processProviderChunk(
126 + request: Request,
127 + id: number,
128 + contextName: string,
129 +): Chunk {
130 + return ['P', id, contextName];
131 +}
132 +
133 export function processSymbolChunk(
134 request: Request,
135 id: number,
packages/react-server/src/ReactFizzNewContext.js
+13 -2
@@ -9,6 +9,7 @@
9
10 import type {ReactContext} from 'shared/ReactTypes';
11
12 +import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
13 import {isPrimaryRenderer} from './ReactServerFormatConfig';
14
15 let rendererSigil;
@@ -244,7 +245,12 @@ export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
245 }
246 }
247 if (isPrimaryRenderer) {
247 - prevSnapshot.context._currentValue = prevSnapshot.parentValue;
248 + const value = prevSnapshot.parentValue;
249 + if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
250 + prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
251 + } else {
252 + prevSnapshot.context._currentValue = value;
253 + }
254 if (__DEV__) {
255 if (
256 context._currentRenderer !== undefined &&
@@ -259,7 +265,12 @@ export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
265 context._currentRenderer = rendererSigil;
266 }
267 } else {
262 - prevSnapshot.context._currentValue2 = prevSnapshot.parentValue;
268 + const value = prevSnapshot.parentValue;
269 + if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
270 + prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
271 + } else {
272 + prevSnapshot.context._currentValue2 = value;
273 + }
274 if (__DEV__) {
275 if (
276 context._currentRenderer2 !== undefined &&
packages/react-server/src/ReactFlightHooks.js new
+93
@@ -0,0 +1,93 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11 +import type {ReactServerContext} from 'shared/ReactTypes';
12 +import {REACT_SERVER_CONTEXT_TYPE} from 'shared/ReactSymbols';
13 +import {readContext as readContextImpl} from './ReactFlightNewContext';
14 +
15 +function readContext<T>(context: ReactServerContext<T>): T {
16 + if (__DEV__) {
17 + if (context.$$typeof !== REACT_SERVER_CONTEXT_TYPE) {
18 + console.error('Only ServerContext is supported in Flight');
19 + }
20 + if (currentCache === null) {
21 + console.error(
22 + 'Context can only be read while React is rendering. ' +
23 + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
24 + 'In function components, you can read it directly in the function body, but not ' +
25 + 'inside Hooks like useReducer() or useMemo().',
26 + );
27 + }
28 + }
29 + return readContextImpl(context);
30 +}
31 +
32 +export const Dispatcher: DispatcherType = {
33 + useMemo<T>(nextCreate: () => T): T {
34 + return nextCreate();
35 + },
36 + useCallback<T>(callback: T): T {
37 + return callback;
38 + },
39 + useDebugValue(): void {},
40 + useDeferredValue: (unsupportedHook: any),
41 + useTransition: (unsupportedHook: any),
42 + getCacheForType<T>(resourceType: () => T): T {
43 + if (!currentCache) {
44 + throw new Error('Reading the cache is only supported while rendering.');
45 + }
46 +
47 + let entry: T | void = (currentCache.get(resourceType): any);
48 + if (entry === undefined) {
49 + entry = resourceType();
50 + // TODO: Warn if undefined?
51 + currentCache.set(resourceType, entry);
52 + }
53 + return entry;
54 + },
55 + readContext,
56 + useContext: readContext,
57 + useReducer: (unsupportedHook: any),
58 + useRef: (unsupportedHook: any),
59 + useState: (unsupportedHook: any),
60 + useInsertionEffect: (unsupportedHook: any),
61 + useLayoutEffect: (unsupportedHook: any),
62 + useImperativeHandle: (unsupportedHook: any),
63 + useEffect: (unsupportedHook: any),
64 + useId: (unsupportedHook: any),
65 + useMutableSource: (unsupportedHook: any),
66 + useSyncExternalStore: (unsupportedHook: any),
67 + useCacheRefresh(): <T>(?() => T, ?T) => void {
68 + return unsupportedRefresh;
69 + },
70 +};
71 +
72 +function unsupportedHook(): void {
73 + throw new Error('This Hook is not supported in Server Components.');
74 +}
75 +
76 +function unsupportedRefresh(): void {
77 + if (!currentCache) {
78 + throw new Error(
79 + 'Refreshing the cache is not supported in Server Components.',
80 + );
81 + }
82 +}
83 +
84 +let currentCache: Map<Function, mixed> | null = null;
85 +
86 +export function setCurrentCache(cache: Map<Function, mixed> | null) {
87 + currentCache = cache;
88 + return currentCache;
89 +}
90 +
91 +export function getCurrentCache() {
92 + return currentCache;
93 +}
packages/react-server/src/ReactFlightNewContext.js new
+269
@@ -0,0 +1,269 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {
11 + ReactServerContext,
12 + ServerContextJSONValue,
13 +} from 'shared/ReactTypes';
14 +
15 +import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
16 +import {isPrimaryRenderer} from './ReactServerFormatConfig';
17 +
18 +let rendererSigil;
19 +if (__DEV__) {
20 + // Use this to detect multiple renderers using the same context
21 + rendererSigil = {};
22 +}
23 +
24 +// Used to store the parent path of all context overrides in a shared linked list.
25 +// Forming a reverse tree.
26 +type ContextNode<T: ServerContextJSONValue> = {
27 + parent: null | ContextNode<any>,
28 + depth: number, // Short hand to compute the depth of the tree at this node.
29 + context: ReactServerContext<T>,
30 + parentValue: T,
31 + value: T,
32 +};
33 +
34 +// The structure of a context snapshot is an implementation of this file.
35 +// Currently, it's implemented as tracking the current active node.
36 +export opaque type ContextSnapshot = null | ContextNode<any>;
37 +
38 +export const rootContextSnapshot: ContextSnapshot = null;
39 +
40 +// We assume that this runtime owns the "current" field on all ReactContext instances.
41 +// This global (actually thread local) state represents what state all those "current",
42 +// fields are currently in.
43 +let currentActiveSnapshot: ContextSnapshot = null;
44 +
45 +function popNode(prev: ContextNode<any>): void {
46 + if (isPrimaryRenderer) {
47 + prev.context._currentValue = prev.parentValue;
48 + } else {
49 + prev.context._currentValue2 = prev.parentValue;
50 + }
51 +}
52 +
53 +function pushNode(next: ContextNode<any>): void {
54 + if (isPrimaryRenderer) {
55 + next.context._currentValue = next.value;
56 + } else {
57 + next.context._currentValue2 = next.value;
58 + }
59 +}
60 +
61 +function popToNearestCommonAncestor(
62 + prev: ContextNode<any>,
63 + next: ContextNode<any>,
64 +): void {
65 + if (prev === next) {
66 + // We've found a shared ancestor. We don't need to pop nor reapply this one or anything above.
67 + } else {
68 + popNode(prev);
69 + const parentPrev = prev.parent;
70 + const parentNext = next.parent;
71 + if (parentPrev === null) {
72 + if (parentNext !== null) {
73 + throw new Error(
74 + 'The stacks must reach the root at the same time. This is a bug in React.',
75 + );
76 + }
77 + } else {
78 + if (parentNext === null) {
79 + throw new Error(
80 + 'The stacks must reach the root at the same time. This is a bug in React.',
81 + );
82 + }
83 +
84 + popToNearestCommonAncestor(parentPrev, parentNext);
85 + // On the way back, we push the new ones that weren't common.
86 + pushNode(next);
87 + }
88 + }
89 +}
90 +
91 +function popAllPrevious(prev: ContextNode<any>): void {
92 + popNode(prev);
93 + const parentPrev = prev.parent;
94 + if (parentPrev !== null) {
95 + popAllPrevious(parentPrev);
96 + }
97 +}
98 +
99 +function pushAllNext(next: ContextNode<any>): void {
100 + const parentNext = next.parent;
101 + if (parentNext !== null) {
102 + pushAllNext(parentNext);
103 + }
104 + pushNode(next);
105 +}
106 +
107 +function popPreviousToCommonLevel(
108 + prev: ContextNode<any>,
109 + next: ContextNode<any>,
110 +): void {
111 + popNode(prev);
112 + const parentPrev = prev.parent;
113 +
114 + if (parentPrev === null) {
115 + throw new Error(
116 + 'The depth must equal at least at zero before reaching the root. This is a bug in React.',
117 + );
118 + }
119 +
120 + if (parentPrev.depth === next.depth) {
121 + // We found the same level. Now we just need to find a shared ancestor.
122 + popToNearestCommonAncestor(parentPrev, next);
123 + } else {
124 + // We must still be deeper.
125 + popPreviousToCommonLevel(parentPrev, next);
126 + }
127 +}
128 +
129 +function popNextToCommonLevel(
130 + prev: ContextNode<any>,
131 + next: ContextNode<any>,
132 +): void {
133 + const parentNext = next.parent;
134 +
135 + if (parentNext === null) {
136 + throw new Error(
137 + 'The depth must equal at least at zero before reaching the root. This is a bug in React.',
138 + );
139 + }
140 +
141 + if (prev.depth === parentNext.depth) {
142 + // We found the same level. Now we just need to find a shared ancestor.
143 + popToNearestCommonAncestor(prev, parentNext);
144 + } else {
145 + // We must still be deeper.
146 + popNextToCommonLevel(prev, parentNext);
147 + }
148 + pushNode(next);
149 +}
150 +
151 +// Perform context switching to the new snapshot.
152 +// To make it cheap to read many contexts, while not suspending, we make the switch eagerly by
153 +// updating all the context's current values. That way reads, always just read the current value.
154 +// At the cost of updating contexts even if they're never read by this subtree.
155 +export function switchContext(newSnapshot: ContextSnapshot): void {
156 + // The basic algorithm we need to do is to pop back any contexts that are no longer on the stack.
157 + // We also need to update any new contexts that are now on the stack with the deepest value.
158 + // The easiest way to update new contexts is to just reapply them in reverse order from the
159 + // perspective of the backpointers. To avoid allocating a lot when switching, we use the stack
160 + // for that. Therefore this algorithm is recursive.
161 + // 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go.
162 + // 2) Then we find the nearest common ancestor from there. Popping old contexts as we go.
163 + // 3) Then we reapply new contexts on the way back up the stack.
164 + const prev = currentActiveSnapshot;
165 + const next = newSnapshot;
166 + if (prev !== next) {
167 + if (prev === null) {
168 + // $FlowFixMe: This has to be non-null since it's not equal to prev.
169 + pushAllNext(next);
170 + } else if (next === null) {
171 + popAllPrevious(prev);
172 + } else if (prev.depth === next.depth) {
173 + popToNearestCommonAncestor(prev, next);
174 + } else if (prev.depth > next.depth) {
175 + popPreviousToCommonLevel(prev, next);
176 + } else {
177 + popNextToCommonLevel(prev, next);
178 + }
179 + currentActiveSnapshot = next;
180 + }
181 +}
182 +
183 +export function pushProvider<T: ServerContextJSONValue>(
184 + context: ReactServerContext<T>,
185 + nextValue: T,
186 +): ContextSnapshot {
187 + let prevValue;
188 + if (isPrimaryRenderer) {
189 + prevValue = context._currentValue;
190 + context._currentValue = nextValue;
191 + if (__DEV__) {
192 + if (
193 + context._currentRenderer !== undefined &&
194 + context._currentRenderer !== null &&
195 + context._currentRenderer !== rendererSigil
196 + ) {
197 + console.error(
198 + 'Detected multiple renderers concurrently rendering the ' +
199 + 'same context provider. This is currently unsupported.',
200 + );
201 + }
202 + context._currentRenderer = rendererSigil;
203 + }
204 + } else {
205 + prevValue = context._currentValue2;
206 + context._currentValue2 = nextValue;
207 + if (__DEV__) {
208 + if (
209 + context._currentRenderer2 !== undefined &&
210 + context._currentRenderer2 !== null &&
211 + context._currentRenderer2 !== rendererSigil
212 + ) {
213 + console.error(
214 + 'Detected multiple renderers concurrently rendering the ' +
215 + 'same context provider. This is currently unsupported.',
216 + );
217 + }
218 + context._currentRenderer2 = rendererSigil;
219 + }
220 + }
221 + const prevNode = currentActiveSnapshot;
222 + const newNode: ContextNode<T> = {
223 + parent: prevNode,
224 + depth: prevNode === null ? 0 : prevNode.depth + 1,
225 + context: context,
226 + parentValue: prevValue,
227 + value: nextValue,
228 + };
229 + currentActiveSnapshot = newNode;
230 + return newNode;
231 +}
232 +
233 +export function popProvider(): ContextSnapshot {
234 + const prevSnapshot = currentActiveSnapshot;
235 +
236 + if (prevSnapshot === null) {
237 + throw new Error(
238 + 'Tried to pop a Context at the root of the app. This is a bug in React.',
239 + );
240 + }
241 +
242 + if (isPrimaryRenderer) {
243 + const value = prevSnapshot.parentValue;
244 + if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
245 + prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
246 + } else {
247 + prevSnapshot.context._currentValue = value;
248 + }
249 + } else {
250 + const value = prevSnapshot.parentValue;
251 + if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
252 + prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
253 + } else {
254 + prevSnapshot.context._currentValue2 = value;
255 + }
256 + }
257 + return (currentActiveSnapshot = prevSnapshot.parent);
258 +}
259 +
260 +export function getActiveContext(): ContextSnapshot {
261 + return currentActiveSnapshot;
262 +}
263 +
264 +export function readContext<T>(context: ReactServerContext<T>): T {
265 + const value = isPrimaryRenderer
266 + ? context._currentValue
267 + : context._currentValue2;
268 + return value;
269 +}
packages/react-server/src/ReactFlightServer.js
+139 -63
@@ -7,7 +7,6 @@
7 * @flow
8 */
9
10 -import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
10 import type {
11 Destination,
12 Chunk,
@@ -16,6 +15,11 @@ import type {
15 ModuleReference,
16 ModuleKey,
17 } from './ReactFlightServerConfig';
18 +import type {ContextSnapshot} from './ReactFlightNewContext';
19 +import type {
20 + ReactProviderType,
21 + ServerContextJSONValue,
22 +} from 'shared/ReactTypes';
23
24 import {
25 scheduleWork,
@@ -27,6 +31,7 @@ import {
31 closeWithError,
32 processModelChunk,
33 processModuleChunk,
34 + processProviderChunk,
35 processSymbolChunk,
36 processErrorChunk,
37 resolveModuleMetaData,
@@ -34,14 +39,25 @@ import {
39 isModuleReference,
40 } from './ReactFlightServerConfig';
41
42 +import {Dispatcher, getCurrentCache, setCurrentCache} from './ReactFlightHooks';
43 +import {
44 + pushProvider,
45 + popProvider,
46 + switchContext,
47 + getActiveContext,
48 + rootContextSnapshot,
49 +} from './ReactFlightNewContext';
50 +
51 import {
52 REACT_ELEMENT_TYPE,
53 REACT_FORWARD_REF_TYPE,
54 REACT_FRAGMENT_TYPE,
55 REACT_LAZY_TYPE,
56 REACT_MEMO_TYPE,
57 + REACT_PROVIDER_TYPE,
58 } from 'shared/ReactSymbols';
59
60 +import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
61 import ReactSharedInternals from 'shared/ReactSharedInternals';
62 import isArray from 'shared/isArray';
63
@@ -68,6 +84,7 @@ type Segment = {
84 id: number,
85 model: ReactModel,
86 ping: () => void,
87 + context: ContextSnapshot,
88 };
89
90 export type Request = {
@@ -84,10 +101,15 @@ export type Request = {
101 completedErrorChunks: Array<Chunk>,
102 writtenSymbols: Map<Symbol, number>,
103 writtenModules: Map<ModuleKey, number>,
104 + writtenProviders: Map<string, number>,
105 onError: (error: mixed) => void,
106 toJSON: (key: string, value: ReactModel) => ReactJSONValue,
107 };
108
109 +export type Options = {
110 + onError?: (error: mixed) => void,
111 +};
112 +
113 const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
114
115 function defaultErrorHandler(error: mixed) {
@@ -103,6 +125,7 @@ export function createRequest(
125 model: ReactModel,
126 bundlerConfig: BundlerConfig,
127 onError: void | ((error: mixed) => void),
128 + context?: Array<[string, ServerContextJSONValue]>,
129 ): Request {
130 const pingedSegments = [];
131 const request = {
@@ -119,17 +142,27 @@ export function createRequest(
142 completedErrorChunks: [],
143 writtenSymbols: new Map(),
144 writtenModules: new Map(),
145 + writtenProviders: new Map(),
146 onError: onError === undefined ? defaultErrorHandler : onError,
147 toJSON: function(key: string, value: ReactModel): ReactJSONValue {
148 return resolveModelToJSON(request, this, key, value);
149 },
150 };
151 request.pendingChunks++;
128 - const rootSegment = createSegment(request, model);
152 + const rootContext = createRootContext(context);
153 + const rootSegment = createSegment(request, model, rootContext);
154 pingedSegments.push(rootSegment);
155 return request;
156 }
157
158 +function createRootContext(
159 + reqContext?: Array<[string, ServerContextJSONValue]>,
160 +) {
161 + return importServerContexts(reqContext);
162 +}
163 +
164 +const POP = {};
165 +
166 function attemptResolveElement(
167 type: any,
168 key: null | React$Key,
@@ -174,6 +207,30 @@ function attemptResolveElement(
207 case REACT_MEMO_TYPE: {
208 return attemptResolveElement(type.type, key, ref, props);
209 }
210 + case REACT_PROVIDER_TYPE: {
211 + pushProvider(type._context, props.value);
212 + if (__DEV__) {
213 + const extraKeys = Object.keys(props).filter(value => {
214 + if (value === 'children' || value === 'value') {
215 + return false;
216 + }
217 + return true;
218 + });
219 + if (extraKeys.length !== 0) {
220 + console.error(
221 + 'ServerContext can only have a value prop and children. Found: %s',
222 + JSON.stringify(extraKeys),
223 + );
224 + }
225 + }
226 + return [
227 + REACT_ELEMENT_TYPE,
228 + type,
229 + key,
230 + // Rely on __popProvider being serialized last to pop the provider.
231 + {value: props.value, children: props.children, __pop: POP},
232 + ];
233 + }
234 }
235 }
236 throw new Error(
@@ -189,11 +246,16 @@ function pingSegment(request: Request, segment: Segment): void {
246 }
247 }
248
192 -function createSegment(request: Request, model: ReactModel): Segment {
249 +function createSegment(
250 + request: Request,
251 + model: ReactModel,
252 + context: ContextSnapshot,
253 +): Segment {
254 const id = request.nextChunkId++;
255 const segment = {
256 id,
257 model,
258 + context,
259 ping: () => pingSegment(request, segment),
260 };
261 return segment;
@@ -221,7 +283,6 @@ function isObjectPrototype(object): boolean {
283 if (!object) {
284 return false;
285 }
224 - // $FlowFixMe
286 const ObjectPrototype = Object.prototype;
287 if (object === ObjectPrototype) {
288 return true;
@@ -311,7 +372,6 @@ function describeObjectForErrorMessage(
372 ): string {
373 if (isArray(objectOrArray)) {
374 let str = '[';
314 - // $FlowFixMe: Should be refined by now.
375 const array: $ReadOnlyArray<ReactModel> = objectOrArray;
376 for (let i = 0; i < array.length; i++) {
377 if (i > 0) {
@@ -336,7 +396,6 @@ function describeObjectForErrorMessage(
396 return str;
397 } else {
398 let str = '{';
339 - // $FlowFixMe: Should be refined by now.
399 const object: {+[key: string | number]: ReactModel} = objectOrArray;
400 const names = Object.keys(object);
401 for (let i = 0; i < names.length; i++) {
@@ -365,6 +424,9 @@ function describeObjectForErrorMessage(
424 }
425 }
426
427 +let insideContextProps = null;
428 +let isInsideContextValue = false;
429 +
430 export function resolveModelToJSON(
431 request: Request,
432 parent: {+[key: string | number]: ReactModel} | $ReadOnlyArray<ReactModel>,
@@ -396,12 +458,32 @@ export function resolveModelToJSON(
458 );
459 }
460
461 + if (__DEV__) {
462 + if (
463 + parent[0] === REACT_ELEMENT_TYPE &&
464 + parent[1] &&
465 + parent[1].$$typeof === REACT_PROVIDER_TYPE &&
466 + key === '3'
467 + ) {
468 + insideContextProps = value;
469 + } else if (insideContextProps === parent && key === 'value') {
470 + isInsideContextValue = true;
471 + } else if (insideContextProps === parent && key === 'children') {
472 + isInsideContextValue = false;
473 + }
474 + }
475 +
476 // Resolve server components.
477 while (
478 typeof value === 'object' &&
479 value !== null &&
403 - value.$$typeof === REACT_ELEMENT_TYPE
480 + (value: any).$$typeof === REACT_ELEMENT_TYPE
481 ) {
482 + if (__DEV__) {
483 + if (isInsideContextValue) {
484 + console.error('React elements are not allowed in ServerContext');
485 + }
486 + }
487 // TODO: Concatenate keys of parents onto children.
488 const element: React$Element<any> = (value: any);
489 try {
@@ -416,7 +498,7 @@ export function resolveModelToJSON(
498 if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
499 // Something suspended, we'll need to create a new segment and resolve it later.
500 request.pendingChunks++;
419 - const newSegment = createSegment(request, value);
501 + const newSegment = createSegment(request, value, getActiveContext());
502 const ping = newSegment.ping;
503 x.then(ping, ping);
504 return serializeByRefID(newSegment.id);
@@ -478,6 +560,25 @@ export function resolveModelToJSON(
560 emitErrorChunk(request, errorId, x);
561 return serializeByValueID(errorId);
562 }
563 + } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
564 + const providerKey = ((value: any): ReactProviderType<any>)._context
565 + ._globalName;
566 + const writtenProviders = request.writtenProviders;
567 + let providerId = writtenProviders.get(key);
568 + if (providerId === undefined) {
569 + request.pendingChunks++;
570 + providerId = request.nextChunkId++;
571 + writtenProviders.set(providerKey, providerId);
572 + emitProviderChunk(request, providerId, providerKey);
573 + }
574 + return serializeByValueID(providerId);
575 + } else if (value === POP) {
576 + popProvider();
577 + if (__DEV__) {
578 + insideContextProps = null;
579 + isInsideContextValue = false;
580 + }
581 + return (undefined: any);
582 }
583
584 if (__DEV__) {
@@ -515,6 +616,7 @@ export function resolveModelToJSON(
616 }
617 }
618 }
619 +
620 return value;
621 }
622
@@ -657,13 +759,23 @@ function emitSymbolChunk(request: Request, id: number, name: string): void {
759 request.completedModuleChunks.push(processedChunk);
760 }
761
762 +function emitProviderChunk(
763 + request: Request,
764 + id: number,
765 + contextName: string,
766 +): void {
767 + const processedChunk = processProviderChunk(request, id, contextName);
768 + request.completedJSONChunks.push(processedChunk);
769 +}
770 +
771 function retrySegment(request: Request, segment: Segment): void {
772 + switchContext(segment.context);
773 try {
774 let value = segment.model;
775 while (
776 typeof value === 'object' &&
777 value !== null &&
666 - value.$$typeof === REACT_ELEMENT_TYPE
778 + (value: any).$$typeof === REACT_ELEMENT_TYPE
779 ) {
780 // TODO: Concatenate keys of parents onto children.
781 const element: React$Element<any> = (value: any);
@@ -696,9 +808,9 @@ function retrySegment(request: Request, segment: Segment): void {
808
809 function performWork(request: Request): void {
810 const prevDispatcher = ReactCurrentDispatcher.current;
699 - const prevCache = currentCache;
811 + const prevCache = getCurrentCache();
812 ReactCurrentDispatcher.current = Dispatcher;
701 - currentCache = request.cache;
813 + setCurrentCache(request.cache);
814
815 try {
816 const pingedSegments = request.pingedSegments;
@@ -715,7 +827,7 @@ function performWork(request: Request): void {
827 fatalError(request, error);
828 } finally {
829 ReactCurrentDispatcher.current = prevDispatcher;
718 - currentCache = prevCache;
830 + setCurrentCache(prevCache);
831 }
832 }
833
@@ -806,56 +918,20 @@ export function startFlowing(request: Request, destination: Destination): void {
918 }
919 }
920
809 -function unsupportedHook(): void {
810 - throw new Error('This Hook is not supported in Server Components.');
811 -}
812 -
813 -function unsupportedRefresh(): void {
814 - if (!currentCache) {
815 - throw new Error(
816 - 'Refreshing the cache is not supported in Server Components.',
817 - );
921 +function importServerContexts(
922 + contexts?: Array<[string, ServerContextJSONValue]>,
923 +) {
924 + if (contexts) {
925 + const prevContext = getActiveContext();
926 + switchContext(rootContextSnapshot);
927 + for (let i = 0; i < contexts.length; i++) {
928 + const [name, value] = contexts[i];
929 + const context = getOrCreateServerContext(name);
930 + pushProvider(context, value);
931 + }
932 + const importedContext = getActiveContext();
933 + switchContext(prevContext);
934 + return importedContext;
935 }
936 + return rootContextSnapshot;
937 }
820 -
821 -let currentCache: Map<Function, mixed> | null = null;
822 -
823 -const Dispatcher: DispatcherType = {
824 - useMemo<T>(nextCreate: () => T): T {
825 - return nextCreate();
826 - },
827 - useCallback<T>(callback: T): T {
828 - return callback;
829 - },
830 - useDebugValue(): void {},
831 - useDeferredValue: (unsupportedHook: any),
832 - useTransition: (unsupportedHook: any),
833 - getCacheForType<T>(resourceType: () => T): T {
834 - if (!currentCache) {
835 - throw new Error('Reading the cache is only supported while rendering.');
836 - }
837 -
838 - let entry: T | void = (currentCache.get(resourceType): any);
839 - if (entry === undefined) {
840 - entry = resourceType();
841 - // TODO: Warn if undefined?
842 - currentCache.set(resourceType, entry);
843 - }
844 - return entry;
845 - },
846 - readContext: (unsupportedHook: any),
847 - useContext: (unsupportedHook: any),
848 - useReducer: (unsupportedHook: any),
849 - useRef: (unsupportedHook: any),
850 - useState: (unsupportedHook: any),
851 - useInsertionEffect: (unsupportedHook: any),
852 - useLayoutEffect: (unsupportedHook: any),
853 - useImperativeHandle: (unsupportedHook: any),
854 - useEffect: (unsupportedHook: any),
855 - useId: (unsupportedHook: any),
856 - useMutableSource: (unsupportedHook: any),
857 - useSyncExternalStore: (unsupportedHook: any),
858 - useCacheRefresh(): <T>(?() => T, ?T) => void {
859 - return unsupportedRefresh;
860 - },
861 -};
packages/react-server/src/ReactFlightServerConfigStream.js
+9
@@ -109,6 +109,15 @@ export function processModuleChunk(
109 return stringToChunk(row);
110 }
111
112 +export function processProviderChunk(
113 + request: Request,
114 + id: number,
115 + contextName: string,
116 +): Chunk {
117 + const row = serializeRowHeader('P', id) + contextName + '\n';
118 + return stringToChunk(row);
119 +}
120 +
121 export function processSymbolChunk(
122 request: Request,
123 id: number,
packages/react/index.classic.fb.js
+2 -1
@@ -26,6 +26,7 @@ export {
26 createMutableSource,
27 createMutableSource as unstable_createMutableSource,
28 createRef,
29 + createServerContext,
30 forwardRef,
31 isValidElement,
32 lazy,
@@ -53,10 +54,10 @@ export {
54 useMemo,
55 useMutableSource,
56 useMutableSource as unstable_useMutableSource,
56 - useSyncExternalStore,
57 useReducer,
58 useRef,
59 useState,
60 + useSyncExternalStore,
61 useTransition,
62 useTransition as unstable_useTransition, // TODO: Remove once call sights updated to useTransition
63 version,
packages/react/index.experimental.js
+2 -1
@@ -24,6 +24,7 @@ export {
24 createFactory,
25 createMutableSource as unstable_createMutableSource,
26 createRef,
27 + createServerContext,
28 forwardRef,
29 isValidElement,
30 lazy,
@@ -46,10 +47,10 @@ export {
47 useInsertionEffect,
48 useLayoutEffect,
49 useMemo,
49 - useSyncExternalStore,
50 useReducer,
51 useRef,
52 useState,
53 + useSyncExternalStore,
54 useTransition,
55 version,
56 } from './src/React';
packages/react/index.js
+1
@@ -48,6 +48,7 @@ export {
48 createFactory,
49 createMutableSource,
50 createRef,
51 + createServerContext,
52 forwardRef,
53 isValidElement,
54 lazy,
packages/react/index.modern.fb.js
+2 -1
@@ -25,6 +25,7 @@ export {
25 createMutableSource,
26 createMutableSource as unstable_createMutableSource,
27 createRef,
28 + createServerContext,
29 forwardRef,
30 isValidElement,
31 lazy,
@@ -52,10 +53,10 @@ export {
53 useMemo,
54 useMutableSource,
55 useMutableSource as unstable_useMutableSource,
55 - useSyncExternalStore,
56 useReducer,
57 useRef,
58 useState,
59 + useSyncExternalStore,
60 useTransition,
61 useTransition as unstable_useTransition, // TODO: Remove once call sights updated to useTransition
62 version,
packages/react/index.stable.js
+1 -1
@@ -37,10 +37,10 @@ export {
37 useInsertionEffect,
38 useLayoutEffect,
39 useMemo,
40 - useSyncExternalStore,
40 useReducer,
41 useRef,
42 useState,
43 + useSyncExternalStore,
44 useTransition,
45 version,
46 } from './src/React';
packages/react/src/React.js
+2
@@ -61,6 +61,7 @@ import {
61 createFactoryWithValidation,
62 cloneElementWithValidation,
63 } from './ReactElementValidator';
64 +import {createServerContext} from './ReactServerContext';
65 import {createMutableSource} from './ReactMutableSource';
66 import ReactSharedInternals from './ReactSharedInternals';
67 import {startTransition} from './ReactStartTransition';
@@ -86,6 +87,7 @@ export {
87 Component,
88 PureComponent,
89 createContext,
90 + createServerContext,
91 forwardRef,
92 lazy,
93 memo,
packages/react/src/ReactContext.js
+4
@@ -30,6 +30,10 @@ export function createContext<T>(defaultValue: T): ReactContext<T> {
30 // These are circular
31 Provider: (null: any),
32 Consumer: (null: any),
33 +
34 + // Add these to use same hidden class in VM as ServerContext
35 + _defaultValue: (null: any),
36 + _globalName: (null: any),
37 };
38
39 context.Provider = {
packages/react/src/ReactServerContext.js new
+104
@@ -0,0 +1,104 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {
11 + REACT_PROVIDER_TYPE,
12 + REACT_SERVER_CONTEXT_TYPE,
13 + REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
14 +} from 'shared/ReactSymbols';
15 +
16 +import type {
17 + ReactServerContext,
18 + ServerContextJSONValue,
19 +} from 'shared/ReactTypes';
20 +
21 +import {enableServerContext} from 'shared/ReactFeatureFlags';
22 +import ReactSharedInternals from 'shared/ReactSharedInternals';
23 +
24 +const ContextRegistry = ReactSharedInternals.ContextRegistry;
25 +
26 +export function createServerContext<T: ServerContextJSONValue>(
27 + globalName: string,
28 + defaultValue: T,
29 +): ReactServerContext<T> {
30 + if (!enableServerContext) {
31 + throw new Error('Not implemented.');
32 + }
33 + let wasDefined = true;
34 + if (!ContextRegistry[globalName]) {
35 + wasDefined = false;
36 + const context: ReactServerContext<T> = {
37 + $$typeof: REACT_SERVER_CONTEXT_TYPE,
38 +
39 + // As a workaround to support multiple concurrent renderers, we categorize
40 + // some renderers as primary and others as secondary. We only expect
41 + // there to be two concurrent renderers at most: React Native (primary) and
42 + // Fabric (secondary); React DOM (primary) and React ART (secondary).
43 + // Secondary renderers store their context values on separate fields.
44 + _currentValue: defaultValue,
45 + _currentValue2: defaultValue,
46 +
47 + _defaultValue: defaultValue,
48 +
49 + // Used to track how many concurrent renderers this context currently
50 + // supports within in a single renderer. Such as parallel server rendering.
51 + _threadCount: 0,
52 + // These are circular
53 + Provider: (null: any),
54 + Consumer: (null: any),
55 + _globalName: globalName,
56 + };
57 +
58 + context.Provider = {
59 + $$typeof: REACT_PROVIDER_TYPE,
60 + _context: context,
61 + };
62 +
63 + if (__DEV__) {
64 + let hasWarnedAboutUsingConsumer;
65 + context._currentRenderer = null;
66 + context._currentRenderer2 = null;
67 + Object.defineProperties(
68 + context,
69 + ({
70 + Consumer: {
71 + get() {
72 + if (!hasWarnedAboutUsingConsumer) {
73 + console.error(
74 + 'Consumer pattern is not supported by ReactServerContext',
75 + );
76 + hasWarnedAboutUsingConsumer = true;
77 + }
78 + return null;
79 + },
80 + },
81 + }: any),
82 + );
83 + }
84 + ContextRegistry[globalName] = context;
85 + }
86 +
87 + const context = ContextRegistry[globalName];
88 + if (context._defaultValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
89 + context._defaultValue = defaultValue;
90 + if (
91 + context._currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
92 + ) {
93 + context._currentValue = defaultValue;
94 + }
95 + if (
96 + context._currentValue2 === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
97 + ) {
98 + context._currentValue2 = defaultValue;
99 + }
100 + } else if (wasDefined) {
101 + throw new Error(`ServerContext: ${globalName} already defined`);
102 + }
103 + return context;
104 +}
packages/react/src/ReactServerContextRegistry.js new
+5
@@ -0,0 +1,5 @@
1 +import type {ReactServerContext} from 'shared/ReactTypes';
2 +
3 +export const ContextRegistry: {
4 + [globalName: string]: ReactServerContext<any>,
5 +} = {};
packages/react/src/ReactSharedInternals.js
+6
@@ -10,6 +10,8 @@ import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
10 import ReactCurrentActQueue from './ReactCurrentActQueue';
11 import ReactCurrentOwner from './ReactCurrentOwner';
12 import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
13 +import {enableServerContext} from 'shared/ReactFeatureFlags';
14 +import {ContextRegistry} from './ReactServerContextRegistry';
15
16 const ReactSharedInternals = {
17 ReactCurrentDispatcher,
@@ -22,4 +24,8 @@ if (__DEV__) {
24 ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue;
25 }
26
27 +if (enableServerContext) {
28 + ReactSharedInternals.ContextRegistry = ContextRegistry;
29 +}
30 +
31 export default ReactSharedInternals;
packages/react/src/forks/ReactSharedInternals.umd.js
+6
@@ -11,6 +11,8 @@ import ReactCurrentActQueue from '../ReactCurrentActQueue';
11 import ReactCurrentOwner from '../ReactCurrentOwner';
12 import ReactDebugCurrentFrame from '../ReactDebugCurrentFrame';
13 import ReactCurrentBatchConfig from '../ReactCurrentBatchConfig';
14 +import {enableServerContext} from 'shared/ReactFeatureFlags';
15 +import {ContextRegistry} from '../ReactServerContextRegistry';
16
17 const ReactSharedInternals = {
18 ReactCurrentDispatcher,
@@ -30,4 +32,8 @@ if (__DEV__) {
32 ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
33 }
34
35 +if (enableServerContext) {
36 + ReactSharedInternals.ContextRegistry = ContextRegistry;
37 +}
38 +
39 export default ReactSharedInternals;
packages/shared/ReactFeatureFlags.js
+1
@@ -249,6 +249,7 @@ export const enableUpdaterTracking = __PROFILE__;
249
250 // Only enabled in RN, related to enableComponentStackLocations
251 export const disableNativeComponentFrames = false;
252 +export const enableServerContext = __EXPERIMENTAL__;
253
254 // Internal only.
255 export const enableGetInspectorDataForInstanceInProduction = false;
packages/shared/ReactServerContextRegistry.js new
+24
@@ -0,0 +1,24 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
11 +import ReactSharedInternals from 'shared/ReactSharedInternals';
12 +import {createServerContext} from 'react';
13 +
14 +const ContextRegistry = ReactSharedInternals.ContextRegistry;
15 +
16 +export function getOrCreateServerContext(globalName: string) {
17 + if (!ContextRegistry[globalName]) {
18 + ContextRegistry[globalName] = createServerContext(
19 + globalName,
20 + REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
21 + );
22 + }
23 + return ContextRegistry[globalName];
24 +}
packages/shared/ReactSymbols.js
+4
@@ -19,6 +19,7 @@ export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
19 export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');
20 export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
21 export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
22 +export const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context');
23 export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
24 export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
25 export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
@@ -32,6 +33,9 @@ export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
33 export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden');
34 export const REACT_CACHE_TYPE = Symbol.for('react.cache');
35 export const REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker');
36 +export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for(
37 + 'react.default_value',
38 +);
39
40 const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
41 const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactTypes.js
+14
@@ -67,9 +67,23 @@ export type ReactContext<T> = {
67 // This value may be added by application code
68 // to improve DEV tooling display names
69 displayName?: string,
70 +
71 + // only used by ServerContext
72 + _defaultValue: T,
73 + _globalName: string,
74 ...
75 };
76
77 +export type ServerContextJSONValue =
78 + | string
79 + | boolean
80 + | number
81 + | null
82 + | $ReadOnlyArray<ServerContextJSONValue>
83 + | {+[key: string]: ServerContextJSONValue};
84 +
85 +export type ReactServerContext<T: any> = ReactContext<T>;
86 +
87 export type ReactPortal = {
88 $$typeof: Symbol | number,
89 key: null | string,
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -75,6 +75,8 @@ export const allowConcurrentByDefault = true;
75 export const enableCustomElementPropertySupport = false;
76
77 export const consoleManagedByDevToolsDuringStrictMode = false;
78 +export const enableServerContext = false;
79 +
80 export const enableUseMutableSource = true;
81
82 export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -67,6 +67,7 @@ export const enablePersistentOffscreenHostContainer = false;
67 export const enableCustomElementPropertySupport = false;
68
69 export const consoleManagedByDevToolsDuringStrictMode = false;
70 +export const enableServerContext = false;
71 export const enableUseMutableSource = false;
72
73 export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -67,6 +67,7 @@ export const enablePersistentOffscreenHostContainer = false;
67 export const enableCustomElementPropertySupport = false;
68
69 export const consoleManagedByDevToolsDuringStrictMode = false;
70 +export const enableServerContext = false;
71 export const enableUseMutableSource = false;
72
73 export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -65,6 +65,7 @@ export const allowConcurrentByDefault = true;
65 export const enablePersistentOffscreenHostContainer = false;
66
67 export const consoleManagedByDevToolsDuringStrictMode = false;
68 +export const enableServerContext = false;
69 export const enableUseMutableSource = false;
70
71 export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -67,6 +67,7 @@ export const enablePersistentOffscreenHostContainer = false;
67 export const enableCustomElementPropertySupport = false;
68
69 export const consoleManagedByDevToolsDuringStrictMode = false;
70 +export const enableServerContext = false;
71
72 // Some www surfaces are still using this. Remove once they have been migrated.
73 export const enableUseMutableSource = true;
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -67,6 +67,7 @@ export const enablePersistentOffscreenHostContainer = false;
67 export const enableCustomElementPropertySupport = false;
68
69 export const consoleManagedByDevToolsDuringStrictMode = false;
70 +export const enableServerContext = false;
71 export const enableUseMutableSource = false;
72
73 export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1 -1
@@ -67,7 +67,7 @@ export const enablePersistentOffscreenHostContainer = false;
67 export const enableCustomElementPropertySupport = false;
68
69 export const consoleManagedByDevToolsDuringStrictMode = false;
70 -
70 +export const enableServerContext = false;
71 // Some www surfaces are still using this. Remove once they have been migrated.
72 export const enableUseMutableSource = true;
73
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -102,6 +102,7 @@ export const deletedTreeCleanUpLevel = 3;
102 export const enablePersistentOffscreenHostContainer = false;
103
104 export const consoleManagedByDevToolsDuringStrictMode = true;
105 +export const enableServerContext = true;
106
107 // Some www surfaces are still using this. Remove once they have been migrated.
108 export const enableUseMutableSource = true;
packages/shared/getComponentNameFromType.js
+12 -1
@@ -24,9 +24,14 @@ import {
24 REACT_LAZY_TYPE,
25 REACT_CACHE_TYPE,
26 REACT_TRACING_MARKER_TYPE,
27 + REACT_SERVER_CONTEXT_TYPE,
28 } from 'shared/ReactSymbols';
29
29 -import {enableTransitionTracing, enableCache} from './ReactFeatureFlags';
30 +import {
31 + enableServerContext,
32 + enableTransitionTracing,
33 + enableCache,
34 +} from './ReactFeatureFlags';
35
36 // Keep in sync with react-reconciler/getComponentNameFromFiber
37 function getWrappedName(
@@ -116,6 +121,12 @@ export default function getComponentNameFromType(type: mixed): string | null {
121 return null;
122 }
123 }
124 + case REACT_SERVER_CONTEXT_TYPE:
125 + if (enableServerContext) {
126 + const context2 = ((type: any): ReactContext<any>);
127 + return (context2.displayName || context2._globalName) + '.Provider';
128 + }
129 + // eslint-disable-next-line no-fallthrough
130 }
131 }
132 return null;
scripts/error-codes/codes.json
+12 -6
@@ -405,10 +405,16 @@
405 "417": "React currently only supports piping to one writable stream.",
406 "418": "Hydration failed because the initial UI does not match what was rendered on the server.",
407 "419": "The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.",
408 - "420": "This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.",
409 - "421": "There was an error while hydrating this Suspense boundary. Switched to client rendering.",
410 - "422": "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.",
411 - "423": "This root received an early update, before anything was able hydrate. Switched the entire root to client rendering.",
412 - "424": "Text content does not match server-rendered HTML.",
413 - "425": "A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition."
408 + "420": "ServerContext: %s already defined",
409 + "421": "This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.",
410 + "422": "There was an error while hydrating this Suspense boundary. Switched to client rendering.",
411 + "423": "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.",
412 + "424": "This root received an early update, before anything was able hydrate. Switched the entire root to client rendering.",
413 + "425": "Text content does not match server-rendered HTML.",
414 + "426": "A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.",
415 + "427": "useServerContext expects a context created with React.createServerContext",
416 + "428": "useServerContext is only supported while rendering.",
417 + "429": "ServerContext: %s already defined",
418 + "430": "ServerContext can only have a value prop and children. Found: %s",
419 + "431": "React elements are not allowed in ServerContext"
420 }