[Flight] Delete Server Context (#28225)

Server Context was never documented, and has been deprecated in
https://github.com/facebook/react/pull/27424.

This PR removes it completely, including the implementation code.

Notably, `useContext` is removed from the shared subset, so importing it
from a React Server environment would now should be a build error in
environments that are able to enforce that.
This commit is contained in:
dan
2024-02-05 22:39:15 +00:00
committed by GitHub
parent 280baaf7e2
commit c8865a2cda
47 changed files with 89 additions and 1365 deletions
-6
View File
@@ -48,8 +48,6 @@ import {
REACT_POSTPONE_TYPE,
} from 'shared/ReactSymbols';
import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
export type {CallServerCallback};
type UninitializedModel = string;
@@ -634,10 +632,6 @@ function parseModelString(
// Symbol
return Symbol.for(value.slice(2));
}
case 'P': {
// Server Context Provider
return getOrCreateServerContext(value.slice(2)).Provider;
}
case 'F': {
// Server Reference
const id = parseInt(value.slice(2), 16);
+39 -387
View File
@@ -122,32 +122,6 @@ describe('ReactFlight', () => {
jest.restoreAllMocks();
});
function createServerContext(globalName, defaultValue, withStack) {
let ctx;
expect(() => {
ctx = React.createServerContext(globalName, defaultValue);
}).toErrorDev(
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
{withoutStack: !withStack},
);
return ctx;
}
function createServerServerContext(globalName, defaultValue, withStack) {
let ctx;
expect(() => {
ctx = ReactServer.createServerContext(globalName, defaultValue);
}).toErrorDev(
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
{withoutStack: !withStack},
);
return ctx;
}
function clientReference(value) {
return Object.defineProperties(
function () {
@@ -1042,18 +1016,50 @@ describe('ReactFlight', () => {
]);
});
it('should warn in DEV if a a client reference is passed to useContext()', () => {
it('should error if useContext is called()', () => {
function ServerComponent() {
return ReactServer.useContext();
}
const errors = [];
ReactNoopFlightServer.render(<ServerComponent />, {
onError(x) {
errors.push(x.message);
},
});
expect(errors).toEqual(['ReactServer.useContext is not a function']);
});
it('should error if a context without a client reference is passed to use()', () => {
const Context = React.createContext();
function ServerComponent() {
return ReactServer.use(Context);
}
const errors = [];
ReactNoopFlightServer.render(<ServerComponent />, {
onError(x) {
errors.push(x.message);
},
});
expect(errors).toEqual([
'Cannot read a Client Context from a Server Component.',
]);
});
it('should error if a client reference is passed to use()', () => {
const Context = React.createContext();
const ClientContext = clientReference(Context);
function ServerComponent() {
return ReactServer.useContext(ClientContext);
return ReactServer.use(ClientContext);
}
expect(() => {
const transport = ReactNoopFlightServer.render(<ServerComponent />);
ReactNoopFlightClient.read(transport);
}).toErrorDev('Cannot read a Client Context from a Server Component.', {
withoutStack: true,
const errors = [];
ReactNoopFlightServer.render(<ServerComponent />, {
onError(x) {
errors.push(x.message);
},
});
expect(errors).toEqual([
'Cannot read a Client Context from a Server Component.',
]);
});
describe('Hooks', () => {
@@ -1149,360 +1155,6 @@ describe('ReactFlight', () => {
});
});
describe('ServerContext', () => {
// @gate enableServerContext
it('supports basic createServerContext usage', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'hello from server',
);
function Foo() {
const context = ReactServer.useContext(ServerContext);
return <div>{context}</div>;
}
const transport = ReactNoopFlightServer.render(<Foo />);
await act(async () => {
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
expect(ReactNoop).toMatchRenderedOutput(<div>hello from server</div>);
});
// @gate enableServerContext
it('propagates ServerContext providers in flight', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'default',
);
function Foo() {
return (
<div>
<ServerContext.Provider value="hi this is server">
<Bar />
</ServerContext.Provider>
</div>
);
}
function Bar() {
const context = ReactServer.useContext(ServerContext);
return context;
}
const transport = ReactNoopFlightServer.render(<Foo />);
await act(async () => {
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
expect(ReactNoop).toMatchRenderedOutput(<div>hi this is server</div>);
});
// @gate enableServerContext
it('errors if you try passing JSX through ServerContext value', () => {
const ServerContext = createServerServerContext('ServerContext', {
foo: {
bar: <span>hi this is default</span>,
},
});
function Foo() {
return (
<div>
<ServerContext.Provider
value={{
foo: {
bar: <span>hi this is server</span>,
},
}}>
<Bar />
</ServerContext.Provider>
</div>
);
}
function Bar() {
const context = ReactServer.useContext(ServerContext);
return context.foo.bar;
}
expect(() => {
ReactNoopFlightServer.render(<Foo />);
}).toErrorDev('React elements are not allowed in ServerContext', {
withoutStack: true,
});
});
// @gate enableServerContext
it('propagates ServerContext and cleans up the providers in flight', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'default',
);
function Foo() {
return (
<>
<ServerContext.Provider value="hi this is server outer">
<ServerContext.Provider value="hi this is server">
<Bar />
</ServerContext.Provider>
<ServerContext.Provider value="hi this is server2">
<Bar />
</ServerContext.Provider>
<Bar />
</ServerContext.Provider>
<ServerContext.Provider value="hi this is server outer2">
<Bar />
</ServerContext.Provider>
<Bar />
</>
);
}
function Bar() {
const context = ReactServer.useContext(ServerContext);
return <span>{context}</span>;
}
const transport = ReactNoopFlightServer.render(<Foo />);
await act(async () => {
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>hi this is server</span>
<span>hi this is server2</span>
<span>hi this is server outer</span>
<span>hi this is server outer2</span>
<span>default</span>
</>,
);
});
// @gate enableServerContext
it('propagates ServerContext providers in flight after suspending', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'default',
);
function Foo() {
return (
<div>
<ServerContext.Provider value="hi this is server">
<React.Suspense fallback={'Loading'}>
<Bar />
</React.Suspense>
</ServerContext.Provider>
</div>
);
}
let resolve;
const promise = new Promise(res => {
resolve = () => {
promise.unsuspend = true;
res();
};
});
function Bar() {
if (!promise.unsuspend) {
Scheduler.log('suspended');
throw promise;
}
Scheduler.log('rendered');
const context = ReactServer.useContext(ServerContext);
return context;
}
const transport = ReactNoopFlightServer.render(<Foo />);
assertLog(['suspended']);
await act(async () => {
resolve();
await promise;
jest.runAllImmediates();
});
assertLog(['rendered']);
await act(async () => {
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
expect(ReactNoop).toMatchRenderedOutput(<div>hi this is server</div>);
});
// @gate enableServerContext
it('serializes ServerContext to client', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'default',
);
const ClientContext = createServerContext('ServerContext', 'default');
function ClientBar() {
Scheduler.log('ClientBar');
const context = React.useContext(ClientContext);
return <span>{context}</span>;
}
const Bar = clientReference(ClientBar);
function Foo() {
return (
<ServerContext.Provider value="hi this is server">
<Bar />
</ServerContext.Provider>
);
}
const model = {
foo: <Foo />,
};
const transport = ReactNoopFlightServer.render(model);
assertLog([]);
await act(async () => {
const flightModel = await ReactNoopFlightClient.read(transport);
ReactNoop.render(flightModel.foo);
});
assertLog(['ClientBar']);
expect(ReactNoop).toMatchRenderedOutput(<span>hi this is server</span>);
expect(() => {
createServerContext('ServerContext', 'default');
}).toThrow('ServerContext: ServerContext already defined');
});
// @gate enableServerContext
it('takes ServerContext from the client for refetching use cases', async () => {
const ServerContext = createServerServerContext(
'ServerContext',
'default',
);
function Bar() {
return <span>{ReactServer.useContext(ServerContext)}</span>;
}
const transport = ReactNoopFlightServer.render(<Bar />, {
context: [['ServerContext', 'Override']],
});
await act(async () => {
const flightModel = await ReactNoopFlightClient.read(transport);
ReactNoop.render(flightModel);
});
expect(ReactNoop).toMatchRenderedOutput(<span>Override</span>);
});
// @gate enableServerContext
it('sets default initial value when defined lazily on server or client', async () => {
let ServerContext;
function inlineLazyServerContextInitialization() {
if (!ServerContext) {
ServerContext = createServerServerContext('ServerContext', 'default');
}
return ServerContext;
}
let ClientContext;
function inlineContextInitialization() {
if (!ClientContext) {
ClientContext = createServerContext('ServerContext', 'default', true);
}
return ClientContext;
}
function ClientBaz() {
const context = inlineContextInitialization();
const value = React.useContext(context);
return <div>{value}</div>;
}
const Baz = clientReference(ClientBaz);
function Bar() {
return (
<article>
<div>
{ReactServer.useContext(inlineLazyServerContextInitialization())}
</div>
<Baz />
</article>
);
}
function ServerApp() {
const Context = inlineLazyServerContextInitialization();
return (
<>
<Context.Provider value="test">
<Bar />
</Context.Provider>
<Bar />
</>
);
}
function ClientApp({serverModel}) {
return (
<>
{serverModel}
<ClientBaz />
</>
);
}
const transport = ReactNoopFlightServer.render(<ServerApp />);
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', () => require('react/react.react-server'));
jest.mock('react-noop-renderer/flight-modules', () => flightModules);
ReactServer = require('react');
ReactNoopFlightServer = require('react-noop-renderer/flight-server');
__unmockReact();
jest.resetModules();
jest.mock('react-noop-renderer/flight-modules', () => flightModules);
React = require('react');
ReactNoop = require('react-noop-renderer');
ReactNoopFlightClient = require('react-noop-renderer/flight-client');
act = require('internal-test-utils').act;
Scheduler = require('scheduler');
await act(async () => {
const serverModel = await ReactNoopFlightClient.read(transport);
ReactNoop.render(<ClientApp serverModel={serverModel} />);
});
expect(ClientContext).not.toBe(ServerContext);
expect(ReactNoop).toMatchRenderedOutput(
<>
<article>
<div>test</div>
<div>test</div>
</article>
<article>
<div>default</div>
<div>default</div>
</article>
<div>default</div>
</>,
);
});
});
// @gate enableTaint
it('errors when a tainted object is serialized', async () => {
function UserClient({user}) {
+2 -80
View File
@@ -3353,62 +3353,6 @@ describe('ReactDOMFizzServer', () => {
]);
});
// @gate enableServerContext
it('supports ServerContext', async () => {
let ServerContext;
function inlineLazyServerContextInitialization() {
if (!ServerContext) {
expect(() => {
ServerContext = React.createServerContext('ServerContext', 'default');
}).toErrorDev(
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
);
}
return ServerContext;
}
function Foo() {
React.useState(); // component stack generation shouldn't reinit
inlineLazyServerContextInitialization();
return (
<>
<ServerContext.Provider value="hi this is server outer">
<ServerContext.Provider value="hi this is server">
<Bar />
</ServerContext.Provider>
<ServerContext.Provider value="hi this is server2">
<Bar />
</ServerContext.Provider>
<Bar />
</ServerContext.Provider>
<ServerContext.Provider value="hi this is server outer2">
<Bar />
</ServerContext.Provider>
<Bar />
</>
);
}
function Bar() {
const context = React.useContext(inlineLazyServerContextInitialization());
return <span>{context}</span>;
}
await act(() => {
const {pipe} = renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual([
<span>hi this is server</span>,
<span>hi this is server2</span>,
<span>hi this is server outer</span>,
<span>hi this is server outer2</span>,
<span>default</span>,
]);
});
it('Supports iterable', async () => {
const Immutable = require('immutable');
@@ -5886,39 +5830,18 @@ describe('ReactDOMFizzServer', () => {
expect(getVisibleChildren(container)).toEqual('ABC');
});
// @gate enableServerContext
it('basic use(context)', async () => {
const ContextA = React.createContext('default');
const ContextB = React.createContext('B');
let ServerContext;
expect(() => {
ServerContext = React.createServerContext('ServerContext', 'default');
}).toErrorDev(
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
{withoutStack: true},
);
function Client() {
return use(ContextA) + use(ContextB);
}
function ServerComponent() {
return use(ServerContext);
}
function Server() {
return (
<ServerContext.Provider value="C">
<ServerComponent />
</ServerContext.Provider>
);
}
function App() {
return (
<>
<ContextA.Provider value="A">
<Client />
</ContextA.Provider>
<Server />
</>
);
}
@@ -5927,16 +5850,15 @@ describe('ReactDOMFizzServer', () => {
const {pipe} = renderToPipeableStream(<App />);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
expect(getVisibleChildren(container)).toEqual('AB');
// Hydration uses a different renderer runtime (Fiber instead of Fizz).
// We reset _currentRenderer here to not trigger a warning about multiple
// renderers concurrently using these contexts
ContextA._currentRenderer = null;
ServerContext._currentRenderer = null;
ReactDOMClient.hydrateRoot(container, <App />);
await waitForAll([]);
expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
expect(getVisibleChildren(container)).toEqual('AB');
});
it('use(promise) in multiple components', async () => {
-2
View File
@@ -11,7 +11,6 @@
import {
REACT_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_ELEMENT_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
@@ -44,7 +43,6 @@ export function typeOf(object: any): mixed {
const $$typeofType = type && type.$$typeof;
switch ($$typeofType) {
case REACT_SERVER_CONTEXT_TYPE:
case REACT_CONTEXT_TYPE:
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
@@ -15,7 +15,6 @@
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ServerContextJSONValue} from 'shared/ReactTypes';
import {saveModule} from 'react-noop-renderer/flight-modules';
@@ -70,7 +69,6 @@ const ReactNoopFlightServer = ReactFlightServer({
type Options = {
onError?: (error: mixed) => void,
context?: Array<[string, ServerContextJSONValue]>,
identifierPrefix?: string,
};
@@ -81,7 +79,6 @@ function render(model: ReactClientValue, options?: Options): Destination {
model,
bundlerConfig,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
);
ReactNoopFlightServer.startWork(request);
+4 -17
View File
@@ -27,7 +27,6 @@ import {
REACT_PORTAL_TYPE,
REACT_LAZY_TYPE,
REACT_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
import isArray from 'shared/isArray';
@@ -577,10 +576,7 @@ function createChildReconciler(
return createChild(returnFiber, unwrapThenable(thenable), lanes);
}
if (
newChild.$$typeof === REACT_CONTEXT_TYPE ||
newChild.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = (newChild: any);
return createChild(
returnFiber,
@@ -667,10 +663,7 @@ function createChildReconciler(
);
}
if (
newChild.$$typeof === REACT_CONTEXT_TYPE ||
newChild.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = (newChild: any);
return updateSlot(
returnFiber,
@@ -756,10 +749,7 @@ function createChildReconciler(
);
}
if (
newChild.$$typeof === REACT_CONTEXT_TYPE ||
newChild.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = (newChild: any);
return updateFromMap(
existingChildren,
@@ -1442,10 +1432,7 @@ function createChildReconciler(
);
}
if (
newChild.$$typeof === REACT_CONTEXT_TYPE ||
newChild.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = (newChild: any);
return reconcileChildFibersImpl(
returnFiber,
@@ -73,8 +73,6 @@ export const CacheContext: ReactContext<Cache> = enableCache
_currentValue: (null: any),
_currentValue2: (null: any),
_threadCount: 0,
_defaultValue: (null: any),
_globalName: (null: any),
}
: (null: any);
+1 -5
View File
@@ -46,7 +46,6 @@ import {
} from 'shared/ReactFeatureFlags';
import {
REACT_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_MEMO_CACHE_SENTINEL,
} from 'shared/ReactSymbols';
@@ -1072,10 +1071,7 @@ function use<T>(usable: Usable<T>): T {
// This is a thenable.
const thenable: Thenable<T> = (usable: any);
return useThenable(thenable);
} else if (
usable.$$typeof === REACT_CONTEXT_TYPE ||
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<T> = (usable: any);
return readContext(context);
}
@@ -51,8 +51,6 @@ export const HostTransitionContext: ReactContext<TransitionStatus | null> = {
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
_defaultValue: (null: any),
_globalName: (null: any),
};
function requiredContext<Value>(c: Value | null): Value {
+2 -18
View File
@@ -44,11 +44,9 @@ import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue';
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
import {
enableLazyContextPropagation,
enableServerContext,
enableFormActions,
enableAsyncActions,
} from 'shared/ReactFeatureFlags';
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
import {
getHostTransitionProvider,
HostTransitionContext,
@@ -153,28 +151,14 @@ export function popProvider(
const currentValue = valueCursor.current;
if (isPrimaryRenderer) {
if (
enableServerContext &&
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue = context._defaultValue;
} else {
context._currentValue = currentValue;
}
context._currentValue = currentValue;
if (__DEV__) {
const currentRenderer = rendererCursorDEV.current;
pop(rendererCursorDEV, providerFiber);
context._currentRenderer = currentRenderer;
}
} else {
if (
enableServerContext &&
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue2 = context._defaultValue;
} else {
context._currentValue2 = currentValue;
}
context._currentValue2 = currentValue;
if (__DEV__) {
const currentRenderer2 = renderer2CursorDEV.current;
pop(renderer2CursorDEV, providerFiber);
@@ -16,7 +16,7 @@ import type {ClientManifest} from './ReactFlightServerConfigESMBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
import type {Busboy} from 'busboy';
import type {Writable} from 'stream';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import {
createRequest,
@@ -54,7 +54,6 @@ function createDrainHandler(destination: Destination, request: Request) {
type Options = {
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
context?: Array<[string, ServerContextJSONValue]>,
identifierPrefix?: string,
};
@@ -72,7 +71,6 @@ function renderToPipeableStream(
model,
moduleBasePath,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -8,7 +8,7 @@
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
@@ -36,7 +36,6 @@ export {
type Options = {
identifierPrefix?: string,
signal?: AbortSignal,
context?: Array<[string, ServerContextJSONValue]>,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
@@ -50,7 +49,6 @@ function renderToReadableStream(
model,
turbopackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -8,7 +8,7 @@
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
@@ -36,7 +36,6 @@ export {
type Options = {
identifierPrefix?: string,
signal?: AbortSignal,
context?: Array<[string, ServerContextJSONValue]>,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
@@ -50,7 +49,6 @@ function renderToReadableStream(
model,
turbopackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -16,7 +16,7 @@ import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
import type {Busboy} from 'busboy';
import type {Writable} from 'stream';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import {
createRequest,
@@ -51,7 +51,6 @@ function createDrainHandler(destination: Destination, request: Request) {
type Options = {
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
context?: Array<[string, ServerContextJSONValue]>,
identifierPrefix?: string,
};
@@ -69,7 +68,6 @@ function renderToPipeableStream(
model,
turbopackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -8,7 +8,7 @@
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
@@ -40,7 +40,6 @@ export {
type Options = {
identifierPrefix?: string,
signal?: AbortSignal,
context?: Array<[string, ServerContextJSONValue]>,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
@@ -54,7 +53,6 @@ function renderToReadableStream(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -8,7 +8,7 @@
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
@@ -40,7 +40,6 @@ export {
type Options = {
identifierPrefix?: string,
signal?: AbortSignal,
context?: Array<[string, ServerContextJSONValue]>,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
@@ -54,7 +53,6 @@ function renderToReadableStream(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -16,7 +16,7 @@ import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
import type {Busboy} from 'busboy';
import type {Writable} from 'stream';
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
import type {Thenable} from 'shared/ReactTypes';
import {
createRequest,
@@ -63,7 +63,6 @@ function createCancelHandler(request: Request, reason: string) {
type Options = {
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
context?: Array<[string, ServerContextJSONValue]>,
identifierPrefix?: string,
};
@@ -81,7 +80,6 @@ function renderToPipeableStream(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.context : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
);
@@ -641,54 +641,6 @@ describe('ReactFlightDOMBrowser', () => {
expect(container.innerHTML).toBe('ABC');
});
// @gate enableServerContext
it('basic use(context)', async () => {
let ContextA;
let ContextB;
expect(() => {
ContextA = React.createServerContext('ContextA', '');
ContextB = React.createServerContext('ContextB', 'B');
}).toErrorDev(
[
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
],
{withoutStack: true},
);
function ServerComponent() {
return ReactServer.use(ContextA) + ReactServer.use(ContextB);
}
function Server() {
return (
<ContextA.Provider value="A">
<ServerComponent />
</ContextA.Provider>
);
}
const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
const response = ReactServerDOMClient.createFromReadableStream(stream);
function Client() {
return use(response);
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
// Client uses a different renderer.
// We reset _currentRenderer here to not trigger a warning about multiple
// renderers concurrently using this context
ContextA._currentRenderer = null;
root.render(<Client />);
});
expect(container.innerHTML).toBe('AB');
});
it('use(promise) in multiple components', async () => {
function Child({prefix}) {
return (
+1 -5
View File
@@ -40,7 +40,6 @@ import {
} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
import {
REACT_SERVER_CONTEXT_TYPE,
REACT_CONTEXT_TYPE,
REACT_MEMO_CACHE_SENTINEL,
} from 'shared/ReactSymbols';
@@ -745,10 +744,7 @@ function use<T>(usable: Usable<T>): T {
// This is a thenable.
const thenable: Thenable<T> = (usable: any);
return unwrapThenable(thenable);
} else if (
usable.$$typeof === REACT_CONTEXT_TYPE ||
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<T> = (usable: any);
return readContext(context);
}
+2 -11
View File
@@ -9,7 +9,6 @@
import type {ReactContext} from 'shared/ReactTypes';
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
import {isPrimaryRenderer} from './ReactFizzConfig';
let rendererSigil;
@@ -246,11 +245,7 @@ export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
}
if (isPrimaryRenderer) {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue = value;
}
prevSnapshot.context._currentValue = value;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
@@ -266,11 +261,7 @@ export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
}
} else {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue2 = value;
}
prevSnapshot.context._currentValue2 = value;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
+1 -5
View File
@@ -129,7 +129,6 @@ import {
REACT_MEMO_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_SCOPE_TYPE,
REACT_OFFSCREEN_TYPE,
REACT_POSTPONE_TYPE,
@@ -2270,10 +2269,7 @@ function renderNodeDestructive(
);
}
if (
maybeUsable.$$typeof === REACT_CONTEXT_TYPE ||
maybeUsable.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
if (maybeUsable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<ReactNodeList> = (maybeUsable: any);
return renderNodeDestructive(
request,
+21 -37
View File
@@ -9,13 +9,12 @@
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {Request} from './ReactFlightServer';
import type {ReactServerContext, Thenable, Usable} from 'shared/ReactTypes';
import type {Thenable, Usable} from 'shared/ReactTypes';
import type {ThenableState} from './ReactFlightThenable';
import {
REACT_SERVER_CONTEXT_TYPE,
REACT_MEMO_CACHE_SENTINEL,
REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import {readContext as readContextImpl} from './ReactFlightNewContext';
import {createThenableState, trackUsedThenable} from './ReactFlightThenable';
import {isClientReference} from './ReactFlightServerConfig';
@@ -44,29 +43,6 @@ export function getThenableStateAfterSuspending(): null | ThenableState {
return state;
}
function readContext<T>(context: ReactServerContext<T>): T {
if (__DEV__) {
if (context.$$typeof !== REACT_SERVER_CONTEXT_TYPE) {
if (isClientReference(context)) {
console.error('Cannot read a Client Context from a Server Component.');
} else {
console.error(
'Only createServerContext is supported in Server Components.',
);
}
}
if (currentRequest === null) {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
}
return readContextImpl(context);
}
export const HooksDispatcher: Dispatcher = {
useMemo<T>(nextCreate: () => T): T {
return nextCreate();
@@ -77,8 +53,8 @@ export const HooksDispatcher: Dispatcher = {
useDebugValue(): void {},
useDeferredValue: (unsupportedHook: any),
useTransition: (unsupportedHook: any),
readContext,
useContext: readContext,
readContext: (unsupportedContext: any),
useContext: (unsupportedContext: any),
useReducer: (unsupportedHook: any),
useRef: (unsupportedHook: any),
useState: (unsupportedHook: any),
@@ -111,6 +87,10 @@ function unsupportedRefresh(): void {
);
}
function unsupportedContext(): void {
throw new Error('Cannot read a Client Context from a Server Component.');
}
function useId(): string {
if (currentRequest === null) {
throw new Error('useId can only be used while React is rendering');
@@ -138,18 +118,22 @@ function use<T>(usable: Usable<T>): T {
thenableState = createThenableState();
}
return trackUsedThenable(thenableState, thenable, index);
} else if (usable.$$typeof === REACT_SERVER_CONTEXT_TYPE) {
const context: ReactServerContext<T> = (usable: any);
return readContext(context);
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
unsupportedContext();
}
}
if (__DEV__) {
if (isClientReference(usable)) {
console.error('Cannot use() an already resolved Client Reference.');
if (isClientReference(usable)) {
if (usable.value != null && usable.value.$$typeof === REACT_CONTEXT_TYPE) {
// Show a more specific message since it's a common mistake.
throw new Error('Cannot read a Client Context from a Server Component.');
} else {
throw new Error('Cannot use() an already resolved Client Reference.');
}
} else {
throw new Error(
// eslint-disable-next-line react-internal/safe-string-coercion
'An unsupported type was passed to use(): ' + String(usable),
);
}
// eslint-disable-next-line react-internal/safe-string-coercion
throw new Error('An unsupported type was passed to use(): ' + String(usable));
}
-269
View File
@@ -1,269 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
ReactServerContext,
ServerContextJSONValue,
} from 'shared/ReactTypes';
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
import {isPrimaryRenderer} from './ReactFlightServerConfig';
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
// Used to store the parent path of all context overrides in a shared linked list.
// Forming a reverse tree.
type ContextNode<T: ServerContextJSONValue> = {
parent: null | ContextNode<any>,
depth: number, // Short hand to compute the depth of the tree at this node.
context: ReactServerContext<T>,
parentValue: T,
value: T,
};
// The structure of a context snapshot is an implementation of this file.
// Currently, it's implemented as tracking the current active node.
export opaque type ContextSnapshot = null | ContextNode<any>;
export const rootContextSnapshot: ContextSnapshot = null;
// We assume that this runtime owns the "current" field on all ReactContext instances.
// This global (actually thread local) state represents what state all those "current",
// fields are currently in.
let currentActiveSnapshot: ContextSnapshot = null;
function popNode(prev: ContextNode<any>): void {
if (isPrimaryRenderer) {
prev.context._currentValue = prev.parentValue;
} else {
prev.context._currentValue2 = prev.parentValue;
}
}
function pushNode(next: ContextNode<any>): void {
if (isPrimaryRenderer) {
next.context._currentValue = next.value;
} else {
next.context._currentValue2 = next.value;
}
}
function popToNearestCommonAncestor(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
if (prev === next) {
// We've found a shared ancestor. We don't need to pop nor reapply this one or anything above.
} else {
popNode(prev);
const parentPrev = prev.parent;
const parentNext = next.parent;
if (parentPrev === null) {
if (parentNext !== null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
} else {
if (parentNext === null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
popToNearestCommonAncestor(parentPrev, parentNext);
// On the way back, we push the new ones that weren't common.
pushNode(next);
}
}
}
function popAllPrevious(prev: ContextNode<any>): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev !== null) {
popAllPrevious(parentPrev);
}
}
function pushAllNext(next: ContextNode<any>): void {
const parentNext = next.parent;
if (parentNext !== null) {
pushAllNext(parentNext);
}
pushNode(next);
}
function popPreviousToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (parentPrev.depth === next.depth) {
// We found the same level. Now we just need to find a shared ancestor.
popToNearestCommonAncestor(parentPrev, next);
} else {
// We must still be deeper.
popPreviousToCommonLevel(parentPrev, next);
}
}
function popNextToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
const parentNext = next.parent;
if (parentNext === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (prev.depth === parentNext.depth) {
// We found the same level. Now we just need to find a shared ancestor.
popToNearestCommonAncestor(prev, parentNext);
} else {
// We must still be deeper.
popNextToCommonLevel(prev, parentNext);
}
pushNode(next);
}
// Perform context switching to the new snapshot.
// To make it cheap to read many contexts, while not suspending, we make the switch eagerly by
// updating all the context's current values. That way reads, always just read the current value.
// At the cost of updating contexts even if they're never read by this subtree.
export function switchContext(newSnapshot: ContextSnapshot): void {
// The basic algorithm we need to do is to pop back any contexts that are no longer on the stack.
// We also need to update any new contexts that are now on the stack with the deepest value.
// The easiest way to update new contexts is to just reapply them in reverse order from the
// perspective of the backpointers. To avoid allocating a lot when switching, we use the stack
// for that. Therefore this algorithm is recursive.
// 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go.
// 2) Then we find the nearest common ancestor from there. Popping old contexts as we go.
// 3) Then we reapply new contexts on the way back up the stack.
const prev = currentActiveSnapshot;
const next = newSnapshot;
if (prev !== next) {
if (prev === null) {
// $FlowFixMe[incompatible-call]: This has to be non-null since it's not equal to prev.
pushAllNext(next);
} else if (next === null) {
popAllPrevious(prev);
} else if (prev.depth === next.depth) {
popToNearestCommonAncestor(prev, next);
} else if (prev.depth > next.depth) {
popPreviousToCommonLevel(prev, next);
} else {
popNextToCommonLevel(prev, next);
}
currentActiveSnapshot = next;
}
}
export function pushProvider<T: ServerContextJSONValue>(
context: ReactServerContext<T>,
nextValue: T,
): ContextSnapshot {
let prevValue;
if (isPrimaryRenderer) {
prevValue = context._currentValue;
context._currentValue = nextValue;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer = rendererSigil;
}
} else {
prevValue = context._currentValue2;
context._currentValue2 = nextValue;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
const prevNode = currentActiveSnapshot;
const newNode: ContextNode<T> = {
parent: prevNode,
depth: prevNode === null ? 0 : prevNode.depth + 1,
context: context,
parentValue: prevValue,
value: nextValue,
};
currentActiveSnapshot = newNode;
return newNode;
}
export function popProvider(): ContextSnapshot {
const prevSnapshot = currentActiveSnapshot;
if (prevSnapshot === null) {
throw new Error(
'Tried to pop a Context at the root of the app. This is a bug in React.',
);
}
if (isPrimaryRenderer) {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue = value;
}
} else {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue2 = value;
}
}
return (currentActiveSnapshot = prevSnapshot.parent);
}
export function getActiveContext(): ContextSnapshot {
return currentActiveSnapshot;
}
export function readContext<T>(context: ReactServerContext<T>): T {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
return value;
}
+4 -166
View File
@@ -15,7 +15,6 @@ import {
enableBinaryFlight,
enablePostpone,
enableTaint,
enableServerContext,
enableServerComponentKeys,
} from 'shared/ReactFeatureFlags';
@@ -46,17 +45,13 @@ import type {
HintCode,
HintModel,
} from './ReactFlightServerConfig';
import type {ContextSnapshot} from './ReactFlightNewContext';
import type {ThenableState} from './ReactFlightThenable';
import type {
ReactProviderType,
ServerContextJSONValue,
Wakeable,
Thenable,
PendingThenable,
FulfilledThenable,
RejectedThenable,
ReactServerContext,
} from 'shared/ReactTypes';
import type {LazyComponent} from 'react/src/ReactLazy';
@@ -82,13 +77,6 @@ import {
resetHooksForRequest,
} from './ReactFlightHooks';
import {DefaultCacheDispatcher} from './flight/ReactFlightServerCache';
import {
pushProvider,
popProvider,
switchContext,
getActiveContext,
rootContextSnapshot,
} from './ReactFlightNewContext';
import {
getIteratorFn,
@@ -98,7 +86,6 @@ import {
REACT_LAZY_TYPE,
REACT_MEMO_TYPE,
REACT_POSTPONE_TYPE,
REACT_PROVIDER_TYPE,
} from 'shared/ReactSymbols';
import {
@@ -110,7 +97,6 @@ import {
objectName,
} from 'shared/ReactSerializationErrors';
import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import ReactServerSharedInternals from './ReactServerSharedInternals';
import isArray from 'shared/isArray';
@@ -153,7 +139,6 @@ export type ReactClientValue =
// subtype, so the receiver can only accept once of these.
| React$Element<string>
| React$Element<ClientReference<any> & any>
| ReactServerContext<any>
| string
| boolean
| number
@@ -184,7 +169,6 @@ type Task = {
toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
keyPath: null | string, // parent server component keys
implicitSlot: boolean, // true if the root server component of this sequence had a null key
context: ContextSnapshot,
thenableState: ThenableState | null,
};
@@ -209,7 +193,6 @@ export type Request = {
writtenSymbols: Map<symbol, number>,
writtenClientReferences: Map<ClientReferenceKey, number>,
writtenServerReferences: Map<ServerReference<any>, number>,
writtenProviders: Map<string, number>,
writtenObjects: WeakMap<Reference, number>, // -1 means "seen" but not outlined.
identifierPrefix: string,
identifierCount: number,
@@ -266,7 +249,6 @@ export function createRequest(
model: ReactClientValue,
bundlerConfig: ClientManifest,
onError: void | ((error: mixed) => ?string),
context?: Array<[string, ServerContextJSONValue]>,
identifierPrefix?: string,
onPostpone: void | ((reason: string) => void),
): Request {
@@ -307,7 +289,6 @@ export function createRequest(
writtenSymbols: new Map(),
writtenClientReferences: new Map(),
writtenServerReferences: new Map(),
writtenProviders: new Map(),
writtenObjects: new WeakMap(),
identifierPrefix: identifierPrefix || '',
identifierCount: 1,
@@ -316,15 +297,7 @@ export function createRequest(
onPostpone: onPostpone === undefined ? defaultPostponeHandler : onPostpone,
};
request.pendingChunks++;
const rootContext = createRootContext(context);
const rootTask = createTask(
request,
model,
null,
false,
rootContext,
abortSet,
);
const rootTask = createTask(request, model, null, false, abortSet);
pingedTasks.push(rootTask);
return request;
}
@@ -340,14 +313,6 @@ export function resolveRequest(): null | Request {
return null;
}
function createRootContext(
reqContext?: Array<[string, ServerContextJSONValue]>,
) {
return importServerContexts(reqContext);
}
const POP = {};
function serializeThenable(
request: Request,
task: Task,
@@ -359,7 +324,6 @@ function serializeThenable(
null,
task.keyPath, // the server component sequence continues through Promise-as-a-child.
task.implicitSlot,
task.context,
request.abortableTasks,
);
@@ -735,33 +699,6 @@ function renderElement(
case REACT_MEMO_TYPE: {
return renderElement(request, task, type.type, key, ref, props);
}
case REACT_PROVIDER_TYPE: {
if (enableServerContext) {
task.context = pushProvider(type._context, props.value);
if (__DEV__) {
const extraKeys = Object.keys(props).filter(value => {
if (value === 'children' || value === 'value') {
return false;
}
return true;
});
if (extraKeys.length !== 0) {
console.error(
'ServerContext can only have a value prop and children. Found: %s',
JSON.stringify(extraKeys),
);
}
}
return renderClientElement(
task,
type,
key,
// Rely on __popProvider being serialized last to pop the provider.
{value: props.value, children: props.children, __pop: POP},
);
}
// Fallthrough
}
}
}
throw new Error(
@@ -783,17 +720,13 @@ function createTask(
model: ReactClientValue,
keyPath: null | string,
implicitSlot: boolean,
context: ContextSnapshot,
abortSet: Set<Task>,
): Task {
const id = request.nextChunkId++;
if (typeof model === 'object' && model !== null) {
// If we're about to write this into a new task we can assign it an ID early so that
// any other references can refer to the value we're about to write.
if (
enableServerComponentKeys &&
(keyPath !== null || implicitSlot || context !== rootContextSnapshot)
) {
if (enableServerComponentKeys && (keyPath !== null || implicitSlot)) {
// If we're in some kind of context we can't necessarily reuse this object depending
// what parent components are used.
} else {
@@ -806,7 +739,6 @@ function createTask(
model,
keyPath,
implicitSlot,
context,
ping: () => pingTask(request, task),
toJSON: function (
this:
@@ -850,26 +782,6 @@ function createTask(
);
}
}
if (
enableServerContext &&
parent[0] === REACT_ELEMENT_TYPE &&
parent[1] &&
(parent[1]: any).$$typeof === REACT_PROVIDER_TYPE &&
parentPropertyName === '3'
) {
insideContextProps = value;
} else if (
insideContextProps === parent &&
parentPropertyName === 'value'
) {
isInsideContextValue = true;
} else if (
insideContextProps === parent &&
parentPropertyName === 'children'
) {
isInsideContextValue = false;
}
}
return renderModel(request, task, parent, parentPropertyName, value);
},
@@ -899,10 +811,6 @@ function serializeSymbolReference(name: string): string {
return '$S' + name;
}
function serializeProviderReference(name: string): string {
return '$P' + name;
}
function serializeNumber(number: number): string | number {
if (Number.isFinite(number)) {
if (number === 0 && 1 / number === -Infinity) {
@@ -1004,7 +912,6 @@ function outlineModel(request: Request, value: ReactClientValue): number {
value,
null, // The way we use outlining is for reusing an object.
false, // It makes no sense for that use case to be contextual.
rootContextSnapshot, // Therefore we don't pass any contextual information along.
request.abortableTasks,
);
retryTask(request, newTask);
@@ -1124,8 +1031,6 @@ function escapeStringValue(value: string): string {
}
}
let insideContextProps = null;
let isInsideContextValue = false;
let modelRoot: null | ReactClientValue = false;
function renderModel(
@@ -1169,7 +1074,6 @@ function renderModel(
task.model,
task.keyPath,
task.implicitSlot,
task.context,
request.abortableTasks,
);
const ping = newTask.ping;
@@ -1252,19 +1156,12 @@ function renderModelDestructive(
if (typeof value === 'object') {
switch ((value: any).$$typeof) {
case REACT_ELEMENT_TYPE: {
if (__DEV__) {
if (enableServerContext && isInsideContextValue) {
console.error('React elements are not allowed in ServerContext');
}
}
const writtenObjects = request.writtenObjects;
const existingId = writtenObjects.get(value);
if (existingId !== undefined) {
if (
enableServerComponentKeys &&
(task.keyPath !== null ||
task.implicitSlot ||
task.context !== rootContextSnapshot)
(task.keyPath !== null || task.implicitSlot)
) {
// If we're in some kind of context we can't reuse the result of this render or
// previous renders of this element. We only reuse elements if they're not wrapped
@@ -1341,9 +1238,7 @@ function renderModelDestructive(
if (existingId !== undefined) {
if (
enableServerComponentKeys &&
(task.keyPath !== null ||
task.implicitSlot ||
task.context !== rootContextSnapshot)
(task.keyPath !== null || task.implicitSlot)
) {
// If we're in some kind of context we can't reuse the result of this render or
// previous renders of this element. We only reuse Promises if they're not wrapped
@@ -1366,29 +1261,6 @@ function renderModelDestructive(
return serializePromiseID(promiseId);
}
if (enableServerContext) {
if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
const providerKey = ((value: any): ReactProviderType<any>)._context
._globalName;
const writtenProviders = request.writtenProviders;
let providerId = writtenProviders.get(providerKey);
if (providerId === undefined) {
request.pendingChunks++;
providerId = request.nextChunkId++;
writtenProviders.set(providerKey, providerId);
emitProviderChunk(request, providerId, providerKey);
}
return serializeByValueID(providerId);
} else if (value === POP) {
task.context = popProvider();
if (__DEV__) {
insideContextProps = null;
isInsideContextValue = false;
}
return (undefined: any);
}
}
if (existingId !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
@@ -1752,16 +1624,6 @@ function emitSymbolChunk(request: Request, id: number, name: string): void {
request.completedImportChunks.push(processedChunk);
}
function emitProviderChunk(
request: Request,
id: number,
contextName: string,
): void {
const contextReference = serializeProviderReference(contextName);
const processedChunk = encodeReferenceChunk(request, id, contextReference);
request.completedRegularChunks.push(processedChunk);
}
function emitModelChunk(request: Request, id: number, json: string): void {
const row = id.toString(16) + ':' + json + '\n';
const processedChunk = stringToChunk(row);
@@ -1776,8 +1638,6 @@ function retryTask(request: Request, task: Task): void {
return;
}
const prevContext = getActiveContext();
switchContext(task.context);
try {
// Track the root so we know that we have to emit this object even though it
// already has an ID. This is needed because we might see this object twice
@@ -1848,10 +1708,6 @@ function retryTask(request: Request, task: Task): void {
task.status = ERRORED;
const digest = logRecoverableError(request, x);
emitErrorChunk(request, task.id, digest, x);
} finally {
if (enableServerContext) {
switchContext(prevContext);
}
}
}
@@ -2061,21 +1917,3 @@ export function abort(request: Request, reason: mixed): void {
fatalError(request, error);
}
}
function importServerContexts(
contexts?: Array<[string, ServerContextJSONValue]>,
) {
if (enableServerContext && contexts) {
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);
}
const importedContext = getActiveContext();
switchContext(prevContext);
return importedContext;
}
return rootContextSnapshot;
}
-1
View File
@@ -22,7 +22,6 @@ export {
createElement,
createFactory,
createRef,
createServerContext,
use,
forwardRef,
isValidElement,
-1
View File
@@ -43,7 +43,6 @@ export {
createElement,
createFactory,
createRef,
createServerContext,
use,
forwardRef,
isValidElement,
-2
View File
@@ -62,7 +62,6 @@ import {
useOptimistic,
} from './ReactHooks';
import {createServerContext} from './ReactServerContext';
import ReactSharedInternals from './ReactSharedInternalsClient';
import {startTransition} from './ReactStartTransition';
import {act} from './ReactAct';
@@ -81,7 +80,6 @@ export {
Component,
PureComponent,
createContext,
createServerContext,
forwardRef,
lazy,
memo,
-4
View File
@@ -31,10 +31,6 @@ export function createContext<T>(defaultValue: T): ReactContext<T> {
// These are circular
Provider: (null: any),
Consumer: (null: any),
// Add these to use same hidden class in VM as ServerContext
_defaultValue: (null: any),
_globalName: (null: any),
};
context.Provider = {
@@ -24,12 +24,10 @@ import {
} from 'shared/ReactSymbols';
import {cloneElement, createElement, isValidElement} from './ReactElement';
import {createRef} from './ReactCreateRef';
import {createServerContext} from './ReactServerContext';
import {
use,
useId,
useCallback,
useContext,
useDebugValue,
useMemo,
getCacheSignal,
@@ -66,7 +64,6 @@ export {
cloneElement,
createElement,
createRef,
createServerContext,
use,
forwardRef,
isValidElement,
@@ -81,7 +78,6 @@ export {
postpone as unstable_postpone,
useId,
useCallback,
useContext,
useDebugValue,
useMemo,
version,
+1 -11
View File
@@ -23,15 +23,7 @@ import {
} from 'shared/ReactSymbols';
import {cloneElement, createElement, isValidElement} from './ReactElement';
import {createRef} from './ReactCreateRef';
import {createServerContext} from './ReactServerContext';
import {
use,
useId,
useCallback,
useContext,
useDebugValue,
useMemo,
} from './ReactHooks';
import {use, useId, useCallback, useDebugValue, useMemo} from './ReactHooks';
import {forwardRef} from './ReactForwardRef';
import {lazy} from './ReactLazy';
import {memo} from './ReactMemo';
@@ -56,7 +48,6 @@ export {
cloneElement,
createElement,
createRef,
createServerContext,
use,
forwardRef,
isValidElement,
@@ -66,7 +57,6 @@ export {
startTransition,
useId,
useCallback,
useContext,
useDebugValue,
useMemo,
version,
-109
View File
@@ -1,109 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {
REACT_PROVIDER_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
} from 'shared/ReactSymbols';
import type {
ReactServerContext,
ServerContextJSONValue,
} from 'shared/ReactTypes';
import {enableServerContext} from 'shared/ReactFeatureFlags';
import {ContextRegistry} from './ReactServerContextRegistry';
export function createServerContext<T: ServerContextJSONValue>(
globalName: string,
defaultValue: T,
): ReactServerContext<T> {
if (!enableServerContext) {
throw new Error('Not implemented.');
}
if (__DEV__) {
console.error(
'Server Context is deprecated and will soon be removed. ' +
'It was never documented and we have found it not to be useful ' +
'enough to warrant the downside it imposes on all apps.',
);
}
let wasDefined = true;
if (!ContextRegistry[globalName]) {
wasDefined = false;
const context: ReactServerContext<T> = {
$$typeof: REACT_SERVER_CONTEXT_TYPE,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
_defaultValue: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
_globalName: globalName,
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) {
let hasWarnedAboutUsingConsumer;
context._currentRenderer = null;
context._currentRenderer2 = null;
Object.defineProperties(
context,
({
Consumer: {
get() {
if (!hasWarnedAboutUsingConsumer) {
console.error(
'Consumer pattern is not supported by ReactServerContext',
);
hasWarnedAboutUsingConsumer = true;
}
return null;
},
},
}: any),
);
}
ContextRegistry[globalName] = context;
}
const context = ContextRegistry[globalName];
if (context._defaultValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
context._defaultValue = defaultValue;
if (
context._currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue = defaultValue;
}
if (
context._currentValue2 === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue2 = defaultValue;
}
} else if (wasDefined) {
throw new Error(`ServerContext: ${globalName} already defined`);
}
return context;
}
@@ -1,14 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactServerContext} from 'shared/ReactTypes';
export const ContextRegistry: {
[globalName: string]: ReactServerContext<any>,
} = {};
@@ -11,8 +11,6 @@ import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
import ReactCurrentActQueue from './ReactCurrentActQueue';
import ReactCurrentOwner from './ReactCurrentOwner';
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
import {enableServerContext} from 'shared/ReactFeatureFlags';
import {ContextRegistry} from './ReactServerContextRegistry';
const ReactSharedInternals = {
ReactCurrentDispatcher,
@@ -26,8 +24,4 @@ if (__DEV__) {
ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue;
}
if (enableServerContext) {
ReactSharedInternals.ContextRegistry = ContextRegistry;
}
export default ReactSharedInternals;
@@ -8,8 +8,6 @@
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
import ReactCurrentOwner from './ReactCurrentOwner';
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
import {enableServerContext} from 'shared/ReactFeatureFlags';
import {ContextRegistry} from './ReactServerContextRegistry';
const ReactSharedInternals = {
ReactCurrentDispatcher,
@@ -20,8 +18,4 @@ if (__DEV__) {
ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
}
if (enableServerContext) {
ReactSharedInternals.ContextRegistry = ContextRegistry;
}
export default ReactSharedInternals;
@@ -12,8 +12,6 @@ import ReactCurrentActQueue from '../ReactCurrentActQueue';
import ReactCurrentOwner from '../ReactCurrentOwner';
import ReactDebugCurrentFrame from '../ReactDebugCurrentFrame';
import ReactCurrentBatchConfig from '../ReactCurrentBatchConfig';
import {enableServerContext} from 'shared/ReactFeatureFlags';
import {ContextRegistry} from '../ReactServerContextRegistry';
const ReactSharedInternalsClient = {
ReactCurrentDispatcher,
@@ -34,8 +32,4 @@ if (__DEV__) {
ReactSharedInternalsClient.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
}
if (enableServerContext) {
ReactSharedInternalsClient.ContextRegistry = ContextRegistry;
}
export default ReactSharedInternalsClient;
-2
View File
@@ -240,8 +240,6 @@ export const enableAsyncDebugInfo = __EXPERIMENTAL__;
// Track which Fiber(s) schedule render work.
export const enableUpdaterTracking = __PROFILE__;
export const enableServerContext = __EXPERIMENTAL__;
// Internal only.
export const enableGetInspectorDataForInstanceInProduction = false;
@@ -1,77 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactServerContext} from 'shared/ReactTypes';
import {
REACT_PROVIDER_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
const ContextRegistry = ReactSharedInternals.ContextRegistry;
export function getOrCreateServerContext(
globalName: string,
): ReactServerContext<any> {
if (!ContextRegistry[globalName]) {
const context: ReactServerContext<any> = {
$$typeof: REACT_SERVER_CONTEXT_TYPE,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
_currentValue2: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
_defaultValue: REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
_globalName: globalName,
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) {
let hasWarnedAboutUsingConsumer;
context._currentRenderer = null;
context._currentRenderer2 = null;
Object.defineProperties(
context,
({
Consumer: {
get() {
if (!hasWarnedAboutUsingConsumer) {
console.error(
'Consumer pattern is not supported by ReactServerContext',
);
hasWarnedAboutUsingConsumer = true;
}
return null;
},
},
}: any),
);
}
ContextRegistry[globalName] = context;
}
return ContextRegistry[globalName];
}
-6
View File
@@ -19,9 +19,6 @@ export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider');
export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
export const REACT_SERVER_CONTEXT_TYPE: symbol = Symbol.for(
'react.server_context',
);
export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense');
export const REACT_SUSPENSE_LIST_TYPE: symbol = Symbol.for(
@@ -41,9 +38,6 @@ export const REACT_CACHE_TYPE: symbol = Symbol.for('react.cache');
export const REACT_TRACING_MARKER_TYPE: symbol = Symbol.for(
'react.tracing_marker',
);
export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED: symbol = Symbol.for(
'react.default_value',
);
export const REACT_MEMO_CACHE_SENTINEL: symbol = Symbol.for(
'react.memo_cache_sentinel',
-14
View File
@@ -62,22 +62,8 @@ export type ReactContext<T> = {
// This value may be added by application code
// to improve DEV tooling display names
displayName?: string,
// only used by ServerContext
_defaultValue: T,
_globalName: string,
};
export type ServerContextJSONValue =
| string
| boolean
| number
| null
| $ReadOnlyArray<ServerContextJSONValue>
| {+[key: string]: ServerContextJSONValue};
export type ReactServerContext<T: any> = ReactContext<T>;
export type ReactPortal = {
$$typeof: symbol | number,
key: null | string,
@@ -80,7 +80,6 @@ export const allowConcurrentByDefault = true;
export const enableCustomElementPropertySupport = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
export const enableServerContext = false;
export const enableTransitionTracing = false;
@@ -66,7 +66,6 @@ export const allowConcurrentByDefault = false;
export const enableCustomElementPropertySupport = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
export const enableServerContext = false;
export const enableTransitionTracing = false;
@@ -66,7 +66,6 @@ export const allowConcurrentByDefault = false;
export const enableCustomElementPropertySupport = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
export const enableServerContext = false;
export const enableTransitionTracing = false;
@@ -64,7 +64,6 @@ export const enableUnifiedSyncLane = true;
export const allowConcurrentByDefault = true;
export const consoleManagedByDevToolsDuringStrictMode = false;
export const enableServerContext = false;
export const enableTransitionTracing = false;
@@ -66,7 +66,6 @@ export const allowConcurrentByDefault = true;
export const enableCustomElementPropertySupport = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
export const enableServerContext = false;
export const enableTransitionTracing = false;
@@ -100,7 +100,6 @@ export const disableTextareaChildren = __EXPERIMENTAL__;
export const allowConcurrentByDefault = true;
export const consoleManagedByDevToolsDuringStrictMode = true;
export const enableServerContext = false;
export const useModernStrictMode = false;
export const enableFizzExternalRuntime = true;
+1 -11
View File
@@ -24,14 +24,9 @@ import {
REACT_LAZY_TYPE,
REACT_CACHE_TYPE,
REACT_TRACING_MARKER_TYPE,
REACT_SERVER_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import {
enableServerContext,
enableTransitionTracing,
enableCache,
} from './ReactFeatureFlags';
import {enableTransitionTracing, enableCache} from './ReactFeatureFlags';
// Keep in sync with react-reconciler/getComponentNameFromFiber
function getWrappedName(
@@ -127,11 +122,6 @@ export default function getComponentNameFromType(type: mixed): string | null {
return null;
}
}
case REACT_SERVER_CONTEXT_TYPE:
if (enableServerContext) {
const context2 = ((type: any): ReactContext<any>);
return (context2.displayName || context2._globalName) + '.Provider';
}
}
}
return null;
+3 -1
View File
@@ -486,5 +486,7 @@
"498": "Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.",
"499": "Only plain objects, and a few built-ins, can be passed to Server Actions. Classes or null prototypes are not supported.",
"500": "React expected a headers state to exist when emitEarlyPreloads was called but did not find it. This suggests emitEarlyPreloads was called more than once per request. This is a bug in React.",
"501": "The render was aborted with postpone when the shell is incomplete. Reason: %s"
"501": "The render was aborted with postpone when the shell is incomplete. Reason: %s",
"502": "Cannot read a Client Context from a Server Component.",
"503": "Cannot use() an already resolved Client Reference."
}