diff --git a/packages/react-client/flight-hooks.js b/packages/react-client/flight-hooks.js new file mode 100644 index 0000000000000..3247eda9546c8 --- /dev/null +++ b/packages/react-client/flight-hooks.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export {useServerContextsForRefetch} from './src/ReactFlightClient'; diff --git a/packages/react-client/npm/flight-hooks.js b/packages/react-client/npm/flight-hooks.js new file mode 100644 index 0000000000000..b67cdb4a47629 --- /dev/null +++ b/packages/react-client/npm/flight-hooks.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-client-flight-hooks.production.min.js'); +} else { + module.exports = require('./cjs/react-client-flight-hooks.development.js'); +} diff --git a/packages/react-client/package.json b/packages/react-client/package.json index 64670a870d8a8..59c20530857ba 100644 --- a/packages/react-client/package.json +++ b/packages/react-client/package.json @@ -13,8 +13,16 @@ "LICENSE", "README.md", "flight.js", + "flight-hooks.js", "cjs/" ], + "exports": { + ".": "./flight.js", + "./flight": "./flight.js", + "./flight-hooks": "./flight-hooks.js", + "./src/*": "./src/*", + "./package.json": "./package.json" + }, "repository": { "type" : "git", "url" : "https://github.com/facebook/react.git", diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 13fb0897e747b..2ae5d5c5bd5e8 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -9,6 +9,11 @@ import type {Wakeable} from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; +import type { + ReactServerContext, + ServerContextJSONValue, + ServerContextNode, +} from 'shared/ReactTypes'; import type { ModuleReference, @@ -17,7 +22,6 @@ import type { Response, BundlerConfig, } from './ReactFlightClientHostConfig'; - import { resolveModuleReference, preloadModule, @@ -25,9 +29,14 @@ import { parseModel, } from './ReactFlightClientHostConfig'; -import {REACT_LAZY_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; - import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry'; +import { + REACT_LAZY_TYPE, + REACT_ELEMENT_TYPE, + REACT_PROVIDER_TYPE, +} from 'shared/ReactSymbols'; + +import {useContext, useMemo, createContext} from 'react'; export type JSONValue = | number @@ -330,9 +339,16 @@ export function parseModelTuple( response: Response, value: {+[key: string]: JSONValue} | $ReadOnlyArray, ): any { - const tuple: [mixed, mixed, mixed, mixed] = (value: any); + const tuple: [mixed, any, any, any] = (value: any); if (tuple[0] === REACT_ELEMENT_TYPE) { + if (tuple[1].$$typeof === REACT_PROVIDER_TYPE) { + return createElement(ServerContextWrapper, null, { + ServerContext: tuple[1]._context, + value: tuple[3].value, + children: tuple[3].children, + }); + } // TODO: Consider having React just directly accept these arrays as elements. // Or even change the ReactElement type to be an array. return createElement(tuple[1], tuple[2], tuple[3]); @@ -440,3 +456,33 @@ export function close(response: Response): void { // ref count of pending chunks. reportGlobalError(response, new Error('Connection closed.')); } + +const ServerContextContext = createContext((null: null | ServerContextNode)); + +type Props = { + ServerContext: ReactServerContext, + value: ServerContextJSONValue, + children: any, +}; + +function ServerContextWrapper({ServerContext, value, children}: Props) { + const parent = useContext(ServerContextContext); + + const context = useMemo(() => { + const ctx = [ServerContext._globalName, value, parent]; + if (__DEV__) { + return Object.freeze(ctx); + } + return ctx; + }, [parent, value]); + + const ret = createElement(ServerContextContext.Provider, null, { + value: context, + children: createElement(ServerContext.Provider, null, {value, children}), + }); + return ret; +} + +export function useServerContextsForRefetch() { + return useContext(ServerContextContext); +} diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 505b9072ff7c2..0046747d72f71 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -15,6 +15,7 @@ let React; let ReactNoop; let ReactNoopFlightServer; let ReactNoopFlightClient; +let ReactNoopFlightHooks; let ErrorBoundary; let NoErrorExpected; let Scheduler; @@ -27,6 +28,8 @@ describe('ReactFlight', () => { ReactNoop = require('react-noop-renderer'); ReactNoopFlightServer = require('react-noop-renderer/flight-server'); ReactNoopFlightClient = require('react-noop-renderer/flight-client'); + ReactNoopFlightHooks = require('react-noop-renderer/flight-hooks'); + act = require('jest-react').act; Scheduler = require('scheduler'); @@ -79,6 +82,21 @@ describe('ReactFlight', () => { }; } + function resetModulesForClient() { + // Reset all modules, except flight-modules which keeps the registry of client components + const flightModules = require('react-noop-renderer/flight-modules'); + jest.resetModules(); + jest.mock('react-noop-renderer/flight-modules', () => flightModules); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + ReactNoopFlightServer = require('react-noop-renderer/flight-server'); + ReactNoopFlightClient = require('react-noop-renderer/flight-client'); + ReactNoopFlightHooks = require('react-noop-renderer/flight-hooks'); + act = require('jest-react').act; + Scheduler = require('scheduler'); + } + it('can render a server component', () => { function Bar({text}) { return text.toUpperCase(); @@ -618,9 +636,10 @@ describe('ReactFlight', () => { } const transport = ReactNoopFlightServer.render(); + + resetModulesForClient(); + act(() => { - ServerContext._currentRenderer = null; - ServerContext._currentRenderer2 = null; ReactNoop.render(ReactNoopFlightClient.read(transport)); }); @@ -649,9 +668,10 @@ describe('ReactFlight', () => { } const transport = ReactNoopFlightServer.render(); + + resetModulesForClient(); + act(() => { - ServerContext._currentRenderer = null; - ServerContext._currentRenderer2 = null; ReactNoop.render(ReactNoopFlightClient.read(transport)); }); @@ -788,9 +808,9 @@ describe('ReactFlight', () => { expect(Scheduler).toHaveYielded(['rendered']); + resetModulesForClient(); + act(() => { - ServerContext._currentRenderer = null; - ServerContext._currentRenderer2 = null; ReactNoop.render(ReactNoopFlightClient.read(transport)); }); @@ -829,8 +849,6 @@ describe('ReactFlight', () => { expect(Scheduler).toHaveYielded([]); act(() => { - ServerContext._currentRenderer = null; - ServerContext._currentRenderer2 = null; const flightModel = ReactNoopFlightClient.read(transport); ReactNoop.render(flightModel.foo); }); @@ -844,23 +862,80 @@ describe('ReactFlight', () => { }); // @gate enableServerContext - it('takes ServerContext from client for refetching usecases', async () => { + it('takes ServerContext from client as array for refetching usecases', async () => { const ServerContext = React.createServerContext( 'ServerContext', 'default', ); + const ServerContext2 = React.createServerContext( + 'ServerContext2', + 'default', + ); function Bar() { - return {React.useContext(ServerContext)}; + return ( + <> + {React.useContext(ServerContext)} + {React.useContext(ServerContext2)} + + ); } const transport = ReactNoopFlightServer.render(, { - context: [['ServerContext', 'Override']], + context: [ + ['ServerContext', 'Override'], + ['ServerContext2', 'Override2'], + ], }); act(() => { const flightModel = ReactNoopFlightClient.read(transport); ReactNoop.render(flightModel); }); - expect(ReactNoop).toMatchRenderedOutput(Override); + + expect(ReactNoop).toMatchRenderedOutput( + <> + Override + Override2 + , + ); + }); + + // @gate enableServerContext + it('takes ServerContext from client as context node for refetching usecases', async () => { + const ServerContext = React.createServerContext( + 'ServerContext', + 'default', + ); + const ServerContext2 = React.createServerContext( + 'ServerContext2', + 'default', + ); + function Bar() { + return ( + <> + {React.useContext(ServerContext)} + {React.useContext(ServerContext2)} + + ); + } + const transport = ReactNoopFlightServer.render(, { + context: [ + 'ServerContext', + 'Override', + ['ServerContext2', 'Override2', null], + ], + }); + + act(() => { + const flightModel = ReactNoopFlightClient.read(transport); + ReactNoop.render(flightModel); + }); + + expect(ReactNoop).toMatchRenderedOutput( + <> + Override + Override2 + , + ); }); // @gate enableServerContext @@ -925,17 +1000,7 @@ describe('ReactFlight', () => { expect(ClientContext).toBe(undefined); - // Reset all modules, except flight-modules which keeps the registry of client components - const flightModules = require('react-noop-renderer/flight-modules'); - jest.resetModules(); - jest.mock('react-noop-renderer/flight-modules', () => flightModules); - - React = require('react'); - ReactNoop = require('react-noop-renderer'); - ReactNoopFlightServer = require('react-noop-renderer/flight-server'); - ReactNoopFlightClient = require('react-noop-renderer/flight-client'); - act = require('jest-react').act; - Scheduler = require('scheduler'); + resetModulesForClient(); act(() => { const serverModel = ReactNoopFlightClient.read(transport); @@ -958,5 +1023,85 @@ describe('ReactFlight', () => { , ); }); + + // @gate enableServerContext + it('supports useServerContextsForRefetch', () => { + const ServerContext = React.createServerContext( + 'ServerContext', + 'default', + ); + + function Foo() { + return ( + <> + + + + + + + + + + + + + + + ); + } + const contextsForRefetch = []; + function ClientBaz() { + const context = React.useContext(ServerContext); + contextsForRefetch.push( + ReactNoopFlightHooks.useServerContextsForRefetch(), + ); + return {context}; + } + function ClientBar({value}) { + return ( + + + + ); + } + const Bar = moduleReference(ClientBar); + + const transport = ReactNoopFlightServer.render(); + + resetModulesForClient(); + + act(() => { + ReactNoop.render(ReactNoopFlightClient.read(transport)); + }); + expect(ReactNoop).toMatchRenderedOutput( + <> + hi this is client + hi this is client 2 + hi this is client outer + hi this is client outer2 + hi this is client default + , + ); + const outer = ['ServerContext', 'hi this is server outer', null]; + expect(contextsForRefetch[0]).toEqual([ + 'ServerContext', + 'hi this is server', + outer, + ]); + expect(contextsForRefetch[1]).toEqual([ + 'ServerContext', + 'hi this is server2', + outer, + ]); + expect(contextsForRefetch[2]).toEqual(outer); + expect(contextsForRefetch[3]).toEqual([ + 'ServerContext', + 'hi this is server outer2', + null, + ]); + expect(contextsForRefetch[4]).toEqual(null); + }); }); }); diff --git a/packages/react-noop-renderer/flight-hooks.js b/packages/react-noop-renderer/flight-hooks.js new file mode 100644 index 0000000000000..bed7791f582a4 --- /dev/null +++ b/packages/react-noop-renderer/flight-hooks.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export {useServerContextsForRefetch} from 'react-client/flight-hooks'; diff --git a/packages/react-noop-renderer/npm/flight-hooks.js b/packages/react-noop-renderer/npm/flight-hooks.js new file mode 100644 index 0000000000000..eedaafa414931 --- /dev/null +++ b/packages/react-noop-renderer/npm/flight-hooks.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-noop-renderer-flight-hooks.production.min.js'); +} else { + module.exports = require('./cjs/react-noop-renderer-flight-hooks.development.js'); +} diff --git a/packages/react-noop-renderer/package.json b/packages/react-noop-renderer/package.json index aae14070714c0..4a72444d389f3 100644 --- a/packages/react-noop-renderer/package.json +++ b/packages/react-noop-renderer/package.json @@ -27,6 +27,7 @@ "flight-client.js", "flight-modules.js", "flight-server.js", + "flight-hooks.js", "cjs/" ] } diff --git a/packages/react-noop-renderer/src/ReactNoopFlightHooks.js b/packages/react-noop-renderer/src/ReactNoopFlightHooks.js new file mode 100644 index 0000000000000..0837c620a623a --- /dev/null +++ b/packages/react-noop-renderer/src/ReactNoopFlightHooks.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/** + * This is a renderer of React that doesn't have a render target output. + * It is useful to demonstrate the internals of the reconciler in isolation + * and for testing semantics of reconciliation separate from the host + * environment. + */ + + import ReactFlightHooks from 'react-client/flight-hooks'; + + type Source = Array; + + const {createResponse, processStringChunk, close} = ReactFlightClient({ + supportsBinaryStreams: false, + resolveModuleReference(idx: string) { + return idx; + }, + preloadModule(idx: string) {}, + requireModule(idx: string) { + return readModule(idx); + }, + parseModel(response: Response, json) { + return JSON.parse(json, response._fromJSON); + }, + }); + + function read(source: Source): T { + const response = createResponse(source); + for (let i = 0; i < source.length; i++) { + processStringChunk(response, source[i], 0); + } + close(response); + return response.readRoot(); + } + + export {read}; + \ No newline at end of file diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index 3a84a06d0843c..5db9793446d95 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -15,7 +15,7 @@ */ import type {ReactModel} from 'react-server/src/ReactFlightServer'; -import type {ServerContextJSONValue} from 'shared/ReactTypes'; +import type {ServerContextType} from 'shared/ReactTypes'; import {saveModule} from 'react-noop-renderer/flight-modules'; @@ -61,7 +61,7 @@ const ReactNoopFlightServer = ReactFlightServer({ type Options = { onError?: (error: mixed) => void, - context?: Array<[string, ServerContextJSONValue]>, + context?: ServerContextType, identifierPrefix?: string, }; diff --git a/packages/react-server-dom-relay/flight-hooks.js b/packages/react-server-dom-relay/flight-hooks.js new file mode 100644 index 0000000000000..0ff5d43d93952 --- /dev/null +++ b/packages/react-server-dom-relay/flight-hooks.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export {useServerContextsForRefetch} from 'react-client/src/ReactFlightClient'; diff --git a/packages/react-server-dom-webpack/flight-hooks.js b/packages/react-server-dom-webpack/flight-hooks.js new file mode 100644 index 0000000000000..0ff5d43d93952 --- /dev/null +++ b/packages/react-server-dom-webpack/flight-hooks.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export {useServerContextsForRefetch} from 'react-client/src/ReactFlightClient'; diff --git a/packages/react-server-dom-webpack/package.json b/packages/react-server-dom-webpack/package.json index 0a8c3389de711..2fa85fee2ac70 100644 --- a/packages/react-server-dom-webpack/package.json +++ b/packages/react-server-dom-webpack/package.json @@ -17,6 +17,7 @@ "writer.browser.server.js", "writer.node.server.js", "node-register.js", + "flight-hooks.js", "cjs/", "umd/", "esm/" @@ -35,7 +36,8 @@ "./writer.browser.server": "./writer.browser.server.js", "./node-loader": "./esm/react-server-dom-webpack-node-loader.js", "./node-register": "./node-register.js", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./flight-hooks": "./flight-hooks.js" }, "main": "index.js", "repository": { @@ -60,4 +62,4 @@ "loose-envify" ] } -} +} \ No newline at end of file diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js index bfd47cc40a697..5394b4559198f 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js @@ -8,7 +8,7 @@ */ import type {ReactModel} from 'react-server/src/ReactFlightServer'; -import type {ServerContextJSONValue} from 'shared/ReactTypes'; +import type {ServerContextType} from 'shared/ReactTypes'; import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig'; import { @@ -19,7 +19,7 @@ import { type Options = { onError?: (error: mixed) => void, - context?: Array<[string, ServerContextJSONValue]>, + context?: ServerContextType, identifierPrefix?: string, }; diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js index 6bb32203baff9..4cc2e8ab6010f 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js @@ -10,7 +10,7 @@ import type {ReactModel} from 'react-server/src/ReactFlightServer'; import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig'; import type {Writable} from 'stream'; -import type {ServerContextJSONValue} from 'shared/ReactTypes'; +import type {ServerContextType} from 'shared/ReactTypes'; import { createRequest, @@ -24,7 +24,7 @@ function createDrainHandler(destination, request) { type Options = { onError?: (error: mixed) => void, - context?: Array<[string, ServerContextJSONValue]>, + context?: ServerContextType, identifierPrefix?: string, }; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 4ca3f903e779c..ecec59ac75fb1 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -30,6 +30,7 @@ let React; let ReactDOMClient; let ReactServerDOMWriter; let ReactServerDOMReader; +let ReactServerDOMHooks; describe('ReactFlightDOM', () => { beforeEach(() => { @@ -42,6 +43,7 @@ describe('ReactFlightDOM', () => { ReactDOMClient = require('react-dom/client'); ReactServerDOMWriter = require('react-server-dom-webpack/writer.node.server'); ReactServerDOMReader = require('react-server-dom-webpack'); + ReactServerDOMHooks = require('react-server-dom-webpack/flight-hooks'); }); function getTestStream() { @@ -545,4 +547,105 @@ describe('ReactFlightDOM', () => { expect(inputB.tagName).toBe('INPUT'); expect(inputB.value).toBe('goodbye'); }); + + // @gate enableServerContext + it('supports useServerContextsForRefetch', async () => { + let ServerContext = React.createServerContext('ServerContext', 'default'); + + function Foo() { + return ( + <> + + + + + + + + + + + + + + + ); + } + const contextsForRefetch = []; + function ClientBaz() { + const context = React.useContext(ServerContext); + contextsForRefetch.push( + ReactServerDOMHooks.useServerContextsForRefetch(), + ); + return {context}; + } + function ClientBar({value}) { + return ( + + + + ); + } + const Bar = moduleReference(ClientBar); + + const {writable, readable} = getTestStream(); + const {pipe} = ReactServerDOMWriter.renderToPipeableStream( + , + webpackMap, + ); + pipe(writable); + + // Reset modules + jest.resetModules(); + act = require('jest-react').act; + Stream = require('stream'); + React = require('react'); + ReactDOMClient = require('react-dom/client'); + ReactServerDOMWriter = require('react-server-dom-webpack/writer.node.server'); + ReactServerDOMReader = require('react-server-dom-webpack'); + ReactServerDOMHooks = require('react-server-dom-webpack/flight-hooks'); + + ServerContext = React.createServerContext('ServerContext', 'default'); + + const response = ReactServerDOMReader.createFromReadableStream(readable); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + function App({response: res}) { + return res.readRoot(); + } + + await act(async () => { + root.render(); + }); + + expect(container.innerHTML).toBe( + 'hi this is client' + + 'hi this is client 2' + + 'hi this is client outer' + + 'hi this is client outer2' + + 'hi this is client default', + ); + + const outer = ['ServerContext', 'hi this is server outer', null]; + expect(contextsForRefetch[0]).toEqual([ + 'ServerContext', + 'hi this is server', + outer, + ]); + expect(contextsForRefetch[1]).toEqual([ + 'ServerContext', + 'hi this is server2', + outer, + ]); + expect(contextsForRefetch[2]).toEqual(outer); + expect(contextsForRefetch[3]).toEqual([ + 'ServerContext', + 'hi this is server outer2', + null, + ]); + expect(contextsForRefetch[4]).toEqual(null); + }); }); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index d1399ce63ae85..cc5f8ea1d7173 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -18,7 +18,9 @@ import type { import type {ContextSnapshot} from './ReactFlightNewContext'; import type { ReactProviderType, + ServerContextType, ServerContextJSONValue, + ServerContextNode, } from 'shared/ReactTypes'; import { @@ -129,7 +131,7 @@ export function createRequest( model: ReactModel, bundlerConfig: BundlerConfig, onError: void | ((error: mixed) => void), - context?: Array<[string, ServerContextJSONValue]>, + context?: ServerContextType, identifierPrefix?: string, ): Request { const pingedSegments = []; @@ -162,9 +164,7 @@ export function createRequest( return request; } -function createRootContext( - reqContext?: Array<[string, ServerContextJSONValue]>, -) { +function createRootContext(reqContext?: ServerContextType) { return importServerContexts(reqContext); } @@ -942,16 +942,27 @@ export function startFlowing(request: Request, destination: Destination): void { } } -function importServerContexts( - contexts?: Array<[string, ServerContextJSONValue]>, -) { - if (contexts) { +function importServerContexts(contexts?: ServerContextType) { + if (contexts && contexts.length) { const prevContext = getActiveContext(); switchContext(rootContextSnapshot); - for (let i = 0; i < contexts.length; i++) { - const [name, value] = contexts[i]; - const context = getOrCreateServerContext(name); - pushProvider(context, value); + if (contexts[0].length === 2) { + // $FlowExpectedError - We made sure its an array above + const contextsArray = (contexts: Array<[string, ServerContextJSONValue]>); + for (let i = 0; i < contextsArray.length; i++) { + const [name, value] = contextsArray[i]; + const context = getOrCreateServerContext(name); + pushProvider(context, value); + } + } else { + // $FlowExpectedError - The above `if` makes sure this is a ServerContextNode + let node = (contexts: ServerContextNode); + while (node) { + const [name, value, parent] = node; + const context = getOrCreateServerContext(name); + pushProvider(context, value); + node = parent; + } } const importedContext = getActiveContext(); switchContext(prevContext); diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 17aa509e89eb9..059277886eb5b 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -42,6 +42,18 @@ export type ReactProviderType = { ... }; +export type ServerContextNode = + | [ + string, // name + ServerContextJSONValue, // value + ServerContextNode, // parent + ] + | null; + +export type ServerContextType = + | Array<[string, ServerContextJSONValue]> + | ServerContextNode; + export type ReactConsumer = { $$typeof: Symbol | number, type: ReactContext, diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 0c8a3b64030c3..805140291088c 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -424,6 +424,21 @@ const bundles = [ ], }, + /******* React Server DOM Relay Hooks *******/ + { + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-relay/flight-hooks', + global: 'ReactFlightDOMRelayClientHooks', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: [ + 'react', + 'ReactFlightDOMRelayClientIntegration', + 'JSResourceReference', + ], + }, + /******* React Server Native Relay Writer *******/ { bundleTypes: [RN_FB_DEV, RN_FB_PROD], @@ -661,6 +676,22 @@ const bundles = [ ], }, + /******* React Noop Flight Client Hooks (used for tests) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-noop-renderer/flight-hooks', + global: 'ReactNoopFlightHooks', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: [ + 'react', + 'scheduler', + 'expect', + 'react-noop-renderer/flight-modules', + ], + }, + /******* React Reconciler *******/ { bundleTypes: [NODE_DEV, NODE_PROD, NODE_PROFILING], @@ -705,6 +736,17 @@ const bundles = [ externals: ['react'], }, + /******* React Flight Client Hooks *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RECONCILER, + entry: 'react-client/flight-hooks', + global: 'ReactFlightHooks', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: false, + externals: ['react'], + }, + /******* Reconciler Reflection *******/ { moduleType: RENDERER_UTILS, diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index eb0eef9109837..35a11044db415 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -15,6 +15,7 @@ module.exports = [ 'react-dom/src/server/ReactDOMFizzServerNode.js', 'react-server-dom-webpack/writer.node.server', 'react-server-dom-webpack', + 'react-server-dom-webpack/flight-hooks', ], paths: [ 'react-dom', @@ -42,6 +43,7 @@ module.exports = [ 'react-dom/src/server/ReactDOMFizzServerBrowser.js', 'react-server-dom-webpack/writer.browser.server', 'react-server-dom-webpack', + 'react-server-dom-webpack/flight-hooks', ], paths: [ 'react-dom', @@ -111,6 +113,7 @@ module.exports = [ shortName: 'dom-relay', entryPoints: [ 'react-server-dom-relay', + 'react-server-dom-relay/flight-hooks', 'react-server-dom-relay/server', 'react-server-dom-relay/src/ReactDOMServerFB.js', ], @@ -139,6 +142,7 @@ module.exports = [ entryPoints: [ 'react-reconciler', 'react-client/flight', + 'react-client/flight-hooks', 'react-server', 'react-server/flight', ],