Implement react-server-dom-parcel (#31725)

This adds a new `react-server-dom-parcel-package`, which is an RSC
integration for the Parcel bundler. It is mostly copied from the
existing webpack/turbopack integrations, with some changes to utilize
Parcel runtime APIs for loading and executing bundles/modules.

See https://github.com/parcel-bundler/parcel/pull/10043 for the Parcel
side of this, which includes the plugin needed to generate client and
server references. https://github.com/parcel-bundler/rsc-examples also
includes examples of various ways to use RSCs with Parcel.

Differences from other integrations:

* Client and server modules are all part of the same graph, and we use
Parcel's
[environments](https://parceljs.org/plugin-system/transformer/#the-environment)
to distinguish them. The server is the Parcel build entry point, and it
imports and renders server components in route handlers. When a `"use
client"` directive is seen, the environment changes and Parcel creates a
new client bundle for the page, combining all client modules together.
CSS from both client and server components are also combined
automatically.
* There is no separate manifest file that needs to be passed around by
the user. A [Runtime](https://parceljs.org/plugin-system/runtime/)
plugin injects client and server references as needed into the relevant
bundles, and registers server action ids using `react-server-dom-parcel`
automatically.
* A special `<Resources>` component is also generated by Parcel to
render the `<script>` and `<link rel="stylesheet">` elements needed for
a page, using the relevant info from the bundle graph.

Note: I've already published a 0.0.x version of this package to npm for
testing purposes but happy to add whoever needs access to it as well.

### Questions

* How to test this in the React repo. I'll have integration tests in
Parcel, but setting up all the different mocks and environments to
simulate that here seems challenging. I could try to copy how
Webpack/Turbopack do it but it's a bit different.
* Where to put TypeScript types. Right now I have some ambient types in
my [example
repo](https://github.com/parcel-bundler/rsc-examples/blob/main/types.d.ts)
but it would be nice for users not to copy and paste these. Can I
include them in the package or do they need to maintained separately in
definitelytyped? I would really prefer not to have to maintain code in
three different repos ideally.

---------

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
This commit is contained in:
Devon Govett 2024-12-11 22:58:51 -05:00 committed by GitHub
parent a4964987dc
commit ca587425fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 5212 additions and 0 deletions

View File

@ -330,6 +330,7 @@ module.exports = {
'packages/react-server-dom-esm/**/*.js',
'packages/react-server-dom-webpack/**/*.js',
'packages/react-server-dom-turbopack/**/*.js',
'packages/react-server-dom-parcel/**/*.js',
'packages/react-server-dom-fb/**/*.js',
'packages/react-test-renderer/**/*.js',
'packages/react-debug-tools/**/*.js',
@ -481,6 +482,12 @@ module.exports = {
__turbopack_require__: 'readonly',
},
},
{
files: ['packages/react-server-dom-parcel/**/*.js'],
globals: {
parcelRequire: 'readonly',
},
},
{
files: ['packages/scheduler/**/*.js'],
globals: {

View File

@ -40,6 +40,7 @@ const stablePackages = {
'react-dom': ReactVersion,
'react-server-dom-webpack': ReactVersion,
'react-server-dom-turbopack': ReactVersion,
'react-server-dom-parcel': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.31.0',
'react-refresh': '0.16.0',

5
fixtures/flight-parcel/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.parcel-cache
.DS_Store
node_modules
dist
todos.json

View File

@ -0,0 +1,4 @@
{
"extends": "@parcel/config-default",
"runtimes": ["...", "@parcel/runtime-rsc"]
}

View File

@ -0,0 +1,51 @@
{
"name": "flight-parcel",
"private": true,
"workspaces": [
"examples/*"
],
"server": "dist/server.js",
"targets": {
"server": {
"source": "src/server.tsx",
"context": "react-server",
"outputFormat": "commonjs",
"includeNodeModules": {
"express": false
}
}
},
"scripts": {
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
"dev": "concurrently \"npm run dev:watch\" \"npm run dev:start\"",
"dev:watch": "NODE_ENV=development parcel watch",
"dev:start": "NODE_ENV=development node dist/server.js",
"build": "parcel build",
"start": "node dist/server.js"
},
"@parcel/resolver-default": {
"packageExports": true
},
"dependencies": {
"@parcel/config-default": "2.0.0-dev.1789",
"@parcel/runtime-rsc": "2.13.3-dev.3412",
"@types/parcel-env": "^0.0.6",
"@types/express": "*",
"@types/node": "^22.10.1",
"@types/react": "^19",
"@types/react-dom": "^19",
"concurrently": "^7.3.0",
"express": "^4.18.2",
"parcel": "2.0.0-dev.1787",
"process": "^0.11.10",
"react": "experimental",
"react-dom": "experimental",
"react-server-dom-parcel": "experimental",
"rsc-html-stream": "^0.0.4",
"ws": "^8.8.1"
},
"@parcel/bundler-default": {
"minBundleSize": 0
}
}

View File

@ -0,0 +1,21 @@
'use client';
import {ReactNode, useRef} from 'react';
export function Dialog({
trigger,
children,
}: {
trigger: ReactNode;
children: ReactNode;
}) {
let ref = useRef<HTMLDialogElement | null>(null);
return (
<>
<button onClick={() => ref.current?.showModal()}>{trigger}</button>
<dialog ref={ref} onSubmit={() => ref.current?.close()}>
{children}
</dialog>
</>
);
}

View File

@ -0,0 +1,18 @@
import {createTodo} from './actions';
export function TodoCreate() {
return (
<form action={createTodo}>
<label>
Title: <input name="title" />
</label>
<label>
Description: <textarea name="description" />
</label>
<label>
Due date: <input type="date" name="dueDate" />
</label>
<button>Add todo</button>
</form>
);
}

View File

@ -0,0 +1,25 @@
import {getTodo, updateTodo} from './actions';
export async function TodoDetail({id}: {id: number}) {
let todo = await getTodo(id);
if (!todo) {
return <p>Todo not found</p>;
}
return (
<form className="todo" action={updateTodo.bind(null, todo.id)}>
<label>
Title: <input name="title" defaultValue={todo.title} />
</label>
<label>
Description:{' '}
<textarea name="description" defaultValue={todo.description} />
</label>
<label>
Due date:{' '}
<input type="date" name="dueDate" defaultValue={todo.dueDate} />
</label>
<button type="submit">Update todo</button>
</form>
);
}

View File

@ -0,0 +1,37 @@
'use client';
import {startTransition, useOptimistic} from 'react';
import {deleteTodo, setTodoComplete, type Todo as ITodo} from './actions';
export function TodoItem({
todo,
isSelected,
}: {
todo: ITodo;
isSelected: boolean;
}) {
let [isOptimisticComplete, setOptimisticComplete] = useOptimistic(
todo.isComplete,
);
return (
<li data-selected={isSelected || undefined}>
<input
type="checkbox"
checked={isOptimisticComplete}
onChange={e => {
startTransition(async () => {
setOptimisticComplete(e.target.checked);
await setTodoComplete(todo.id, e.target.checked);
});
}}
/>
<a
href={`/todos/${todo.id}`}
aria-current={isSelected ? 'page' : undefined}>
{todo.title}
</a>
<button onClick={() => deleteTodo(todo.id)}>x</button>
</li>
);
}

View File

@ -0,0 +1,13 @@
import {TodoItem} from './TodoItem';
import {getTodos} from './actions';
export async function TodoList({id}: {id: number | undefined}) {
let todos = await getTodos();
return (
<ul className="todo-list">
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} isSelected={todo.id === id} />
))}
</ul>
);
}

View File

@ -0,0 +1,63 @@
body {
font-family: system-ui;
color-scheme: light dark;
}
form {
display: grid;
grid-template-columns: auto 1fr;
flex-direction: column;
max-width: 400px;
gap: 8px;
}
label {
display: contents;
}
main {
display: flex;
gap: 32px;
}
.todo-column {
width: 250px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 250px;
padding: 8px;
padding-right: 40px;
box-sizing: border-box;
}
.todo-list {
max-width: 250px;
padding: 0;
list-style: none;
padding-right: 32px;
border-right: 1px solid gray;
li {
display: flex;
gap: 8px;
padding: 8px;
border-radius: 8px;
accent-color: light-dark(black, white);
a {
color: inherit;
text-decoration: none;
width: 100%;
}
&[data-selected] {
background-color: light-dark(#222, #ddd);
color: light-dark(#ddd, #222);
accent-color: light-dark(white, black);
}
}
}

View File

@ -0,0 +1,35 @@
'use server-entry';
import './client';
import './Todos.css';
import {Resources} from '@parcel/runtime-rsc';
import {Dialog} from './Dialog';
import {TodoDetail} from './TodoDetail';
import {TodoCreate} from './TodoCreate';
import {TodoList} from './TodoList';
export async function Todos({id}: {id?: number}) {
return (
<html style={{colorScheme: 'dark light'}}>
<head>
<title>Todos</title>
<Resources />
</head>
<body>
<header>
<h1>Todos</h1>
<Dialog trigger="+">
<h2>Add todo</h2>
<TodoCreate />
</Dialog>
</header>
<main>
<div className="todo-column">
<TodoList id={id} />
</div>
{id != null ? <TodoDetail key={id} id={id} /> : <p>Select a todo</p>}
</main>
</body>
</html>
);
}

View File

@ -0,0 +1,75 @@
'use server';
import fs from 'fs/promises';
export interface Todo {
id: number;
title: string;
description: string;
dueDate: string;
isComplete: boolean;
}
export async function getTodos(): Promise<Todo[]> {
try {
let contents = await fs.readFile('todos.json', 'utf8');
return JSON.parse(contents);
} catch {
await fs.writeFile('todos.json', '[]');
return [];
}
}
export async function getTodo(id: number): Promise<Todo | undefined> {
let todos = await getTodos();
return todos.find(todo => todo.id === id);
}
export async function createTodo(formData: FormData) {
let todos = await getTodos();
let title = formData.get('title');
let description = formData.get('description');
let dueDate = formData.get('dueDate');
let id = todos.length > 0 ? Math.max(...todos.map(todo => todo.id)) + 1 : 0;
todos.push({
id,
title: typeof title === 'string' ? title : '',
description: typeof description === 'string' ? description : '',
dueDate: typeof dueDate === 'string' ? dueDate : new Date().toISOString(),
isComplete: false,
});
await fs.writeFile('todos.json', JSON.stringify(todos));
}
export async function updateTodo(id: number, formData: FormData) {
let todos = await getTodos();
let title = formData.get('title');
let description = formData.get('description');
let dueDate = formData.get('dueDate');
let todo = todos.find(todo => todo.id === id);
if (todo) {
todo.title = typeof title === 'string' ? title : '';
todo.description = typeof description === 'string' ? description : '';
todo.dueDate =
typeof dueDate === 'string' ? dueDate : new Date().toISOString();
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}
export async function setTodoComplete(id: number, isComplete: boolean) {
let todos = await getTodos();
let todo = todos.find(todo => todo.id === id);
if (todo) {
todo.isComplete = isComplete;
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}
export async function deleteTodo(id: number) {
let todos = await getTodos();
let index = todos.findIndex(todo => todo.id === id);
if (index >= 0) {
todos.splice(index, 1);
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}

View File

@ -0,0 +1,113 @@
'use client-entry';
import {
useState,
use,
startTransition,
useInsertionEffect,
ReactElement,
} from 'react';
import ReactDOM from 'react-dom/client';
import {
createFromReadableStream,
createFromFetch,
encodeReply,
setServerCallback,
} from 'react-server-dom-parcel/client';
import {rscStream} from 'rsc-html-stream/client';
// Stream in initial RSC payload embedded in the HTML.
let initialRSCPayload = createFromReadableStream<ReactElement>(rscStream);
let updateRoot:
| ((root: ReactElement, cb?: (() => void) | null) => void)
| null = null;
function Content() {
// Store the current root element in state, along with a callback
// to call once rendering is complete.
let [[root, cb], setRoot] = useState<[ReactElement, (() => void) | null]>([
use(initialRSCPayload),
null,
]);
updateRoot = (root, cb) => setRoot([root, cb ?? null]);
useInsertionEffect(() => cb?.());
return root;
}
// Hydrate initial page content.
startTransition(() => {
ReactDOM.hydrateRoot(document, <Content />);
});
// A very simple router. When we navigate, we'll fetch a new RSC payload from the server,
// and in a React transition, stream in the new page. Once complete, we'll pushState to
// update the URL in the browser.
async function navigate(pathname: string, push = false) {
let res = fetch(pathname, {
headers: {
Accept: 'text/x-component',
},
});
let root = await createFromFetch<ReactElement>(res);
startTransition(() => {
updateRoot!(root, () => {
if (push) {
history.pushState(null, '', pathname);
push = false;
}
});
});
}
// Intercept link clicks to perform RSC navigation.
document.addEventListener('click', e => {
let link = (e.target as Element).closest('a');
if (
link &&
link instanceof HTMLAnchorElement &&
link.href &&
(!link.target || link.target === '_self') &&
link.origin === location.origin &&
!link.hasAttribute('download') &&
e.button === 0 && // left clicks only
!e.metaKey && // open in new tab (mac)
!e.ctrlKey && // open in new tab (windows)
!e.altKey && // download
!e.shiftKey &&
!e.defaultPrevented
) {
e.preventDefault();
navigate(link.pathname, true);
}
});
// When the user clicks the back button, navigate with RSC.
window.addEventListener('popstate', e => {
navigate(location.pathname);
});
// Intercept HMR window reloads, and do it with RSC instead.
window.addEventListener('parcelhmrreload', e => {
e.preventDefault();
navigate(location.pathname);
});
// Setup a callback to perform server actions.
// This sends a POST request to the server, and updates the page with the response.
setServerCallback(async function (id: string, args: any[]) {
console.log('Handling server action', id, args);
const response = fetch(location.pathname, {
method: 'POST',
headers: {
Accept: 'text/x-component',
'rsc-action-id': id,
},
body: await encodeReply(args),
});
const {result, root} = await createFromFetch<{
root: JSX.Element;
result: any;
}>(response);
startTransition(() => updateRoot!(root));
return result;
});

View File

@ -0,0 +1,137 @@
// Server dependencies.
import express, {
type Request as ExpressRequest,
type Response as ExpressResponse,
} from 'express';
import {Readable} from 'node:stream';
import type {ReadableStream as NodeReadableStream} from 'stream/web';
import {
renderToReadableStream,
loadServerAction,
decodeReply,
decodeAction,
} from 'react-server-dom-parcel/server.edge';
import {injectRSCPayload} from 'rsc-html-stream/server';
// Client dependencies, used for SSR.
// These must run in the same environment as client components (e.g. same instance of React).
import {createFromReadableStream} from 'react-server-dom-parcel/client' with {env: 'react-client'};
import {renderToReadableStream as renderHTMLToReadableStream} from 'react-dom/server' with {env: 'react-client'};
import ReactClient, {ReactElement} from 'react' with {env: 'react-client'};
// Page components. These must have "use server-entry" so they are treated as code splitting entry points.
import {Todos} from './Todos';
const app = express();
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,POST');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'rsc-action');
next();
});
app.use(express.static('dist'));
app.get('/', async (req, res) => {
await render(req, res, <Todos />);
});
app.post('/', async (req, res) => {
await handleAction(req, res, <Todos />);
});
app.get('/todos/:id', async (req, res) => {
await render(req, res, <Todos id={Number(req.params.id)} />);
});
app.post('/todos/:id', async (req, res) => {
await handleAction(req, res, <Todos id={Number(req.params.id)} />);
});
async function render(
req: ExpressRequest,
res: ExpressResponse,
component: ReactElement,
actionResult?: any,
) {
// Render RSC payload.
let root: any = component;
if (actionResult) {
root = {result: actionResult, root};
}
let stream = renderToReadableStream(root);
if (req.accepts('text/html')) {
res.setHeader('Content-Type', 'text/html');
// Use client react to render the RSC payload to HTML.
let [s1, s2] = stream.tee();
let data = createFromReadableStream<ReactElement>(s1);
function Content() {
return ReactClient.use(data);
}
let htmlStream = await renderHTMLToReadableStream(<Content />);
let response = htmlStream.pipeThrough(injectRSCPayload(s2));
Readable.fromWeb(response as NodeReadableStream).pipe(res);
} else {
res.set('Content-Type', 'text/x-component');
Readable.fromWeb(stream as NodeReadableStream).pipe(res);
}
}
// Handle server actions.
async function handleAction(
req: ExpressRequest,
res: ExpressResponse,
component: ReactElement,
) {
let id = req.get('rsc-action-id');
let request = new Request('http://localhost' + req.url, {
method: 'POST',
headers: req.headers as any,
body: Readable.toWeb(req) as ReadableStream,
// @ts-ignore
duplex: 'half',
});
if (id) {
let action = await loadServerAction(id);
let body = req.is('multipart/form-data')
? await request.formData()
: await request.text();
let args = await decodeReply<any[]>(body);
let result = action.apply(null, args);
try {
// Wait for any mutations
await result;
} catch (x) {
// We handle the error on the client
}
await render(req, res, component, result);
} else {
// Form submitted by browser (progressive enhancement).
let formData = await request.formData();
let action = await decodeAction(formData);
try {
// Wait for any mutations
await action();
} catch (err) {
// TODO render error page?
}
await render(req, res, component);
}
}
let server = app.listen(3001);
console.log('Server listening on port 3001');
// Restart the server when it changes.
if (module.hot) {
module.hot.dispose(() => {
server.close();
});
module.hot.accept();
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"module": "esnext",
"isolatedModules": true,
"esModuleInterop": true,
"target": "es2022"
}
}

21
fixtures/flight-parcel/types.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
// TODO: move these into their respective packages.
declare module 'react-server-dom-parcel/client' {
export function createFromFetch<T>(res: Promise<Response>): Promise<T>;
export function createFromReadableStream<T>(stream: ReadableStream): Promise<T>;
export function encodeReply(value: any): Promise<string | URLSearchParams | FormData>;
type CallServerCallback = <T>(id: string, args: any[]) => Promise<T>;
export function setServerCallback(cb: CallServerCallback): void;
}
declare module 'react-server-dom-parcel/server.edge' {
export function renderToReadableStream(value: any): ReadableStream;
export function loadServerAction(id: string): Promise<(...args: any[]) => any>;
export function decodeReply<T>(body: string | FormData): Promise<T>;
export function decodeAction(body: FormData): Promise<(...args: any[]) => any>;
}
declare module '@parcel/runtime-rsc' {
export function Resources(): JSX.Element;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
/**
* 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
*/
export {default as rendererVersion} from 'shared/ReactVersion';
export const rendererPackageName = 'react-server-dom-parcel';
export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;

View File

@ -0,0 +1,17 @@
/**
* 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
*/
export {default as rendererVersion} from 'shared/ReactVersion';
export const rendererPackageName = 'react-server-dom-parcel';
export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

@ -0,0 +1,17 @@
/**
* 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
*/
export {default as rendererVersion} from 'shared/ReactVersion';
export const rendererPackageName = 'react-server-dom-parcel';
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

@ -0,0 +1,5 @@
# react-server-dom-parcel
Experimental React Flight bindings for DOM using Parcel.
**Use it at your own risk.**

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './src/client/ReactFlightDOMClientBrowser';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './src/client/ReactFlightDOMClientEdge';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './client.browser';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './src/client/ReactFlightDOMClientNode';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
throw new Error('Use react-server-dom-parcel/client instead.');

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-client.browser.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-client.browser.development.js');
}

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-client.edge.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-client.edge.development.js');
}

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./client.browser');

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-client.node.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-client.node.development.js');
}

View File

@ -0,0 +1,12 @@
/**
* 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
*/
'use strict';
throw new Error('Use react-server-dom-parcel/client instead.');

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-server.browser.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-server.browser.development.js');
}

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-server.edge.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-server.edge.development.js');
}

View File

@ -0,0 +1,6 @@
'use strict';
throw new Error(
'The React Server Writer cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.'
);

View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-parcel-server.node.production.js');
} else {
module.exports = require('./cjs/react-server-dom-parcel-server.node.development.js');
}

View File

@ -0,0 +1,86 @@
{
"name": "react-server-dom-parcel",
"description": "React Server Components bindings for DOM using Parcel. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
"version": "19.0.0",
"private": true,
"keywords": [
"react"
],
"homepage": "https://reactjs.org/",
"bugs": "https://github.com/facebook/react/issues",
"license": "MIT",
"files": [
"LICENSE",
"README.md",
"index.js",
"client.js",
"client.browser.js",
"client.edge.js",
"client.node.js",
"server.js",
"server.browser.js",
"server.edge.js",
"server.node.js",
"static.js",
"static.browser.js",
"static.edge.js",
"static.node.js",
"cjs/"
],
"exports": {
".": "./index.js",
"./client": {
"workerd": "./client.edge.js",
"deno": "./client.edge.js",
"worker": "./client.edge.js",
"node": "./client.node.js",
"edge-light": "./client.edge.js",
"browser": "./client.browser.js",
"default": "./client.browser.js"
},
"./client.browser": "./client.browser.js",
"./client.edge": "./client.edge.js",
"./client.node": "./client.node.js",
"./server": {
"react-server": {
"workerd": "./server.edge.js",
"deno": "./server.browser.js",
"node": "./server.node.js",
"edge-light": "./server.edge.js",
"browser": "./server.browser.js"
},
"default": "./server.js"
},
"./server.browser": "./server.browser.js",
"./server.edge": "./server.edge.js",
"./server.node": "./server.node.js",
"./static": {
"react-server": {
"workerd": "./static.edge.js",
"deno": "./static.browser.js",
"node": "./static.node.js",
"edge-light": "./static.edge.js",
"browser": "./static.browser.js"
},
"default": "./static.js"
},
"./static.browser": "./static.browser.js",
"./static.edge": "./static.edge.js",
"./static.node": "./static.node.js",
"./src/*": "./src/*.js",
"./package.json": "./package.json"
},
"main": "index.js",
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-server-dom-parcel"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}

View File

@ -0,0 +1,20 @@
/**
* 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
*/
export {
renderToReadableStream,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './src/server/react-flight-dom-server.browser';

View File

@ -0,0 +1,20 @@
/**
* 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
*/
export {
renderToReadableStream,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './src/server/react-flight-dom-server.edge';

View File

@ -0,0 +1,13 @@
/**
* 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
*/
throw new Error(
'The React Server cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.',
);

View File

@ -0,0 +1,21 @@
/**
* 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
*/
export {
renderToPipeableStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './src/server/react-flight-dom-server.node';

View File

@ -0,0 +1,126 @@
/**
* 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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
export type ServerReference<T: Function> = T & {
$$typeof: symbol,
$$id: string,
$$bound: null | Array<ReactClientValue>,
$$location?: Error,
};
// eslint-disable-next-line no-unused-vars
export type ClientReference<T> = {
$$typeof: symbol,
$$id: string,
$$name: string,
$$bundles: Array<string>,
};
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
export function isClientReference(reference: Object): boolean {
return reference.$$typeof === CLIENT_REFERENCE_TAG;
}
export function isServerReference(reference: Object): boolean {
return reference.$$typeof === SERVER_REFERENCE_TAG;
}
export function createClientReference<T>(
id: string,
exportName: string,
bundles: Array<string>,
): ClientReference<T> {
return {
$$typeof: CLIENT_REFERENCE_TAG,
$$id: id,
$$name: exportName,
$$bundles: bundles,
};
}
// $FlowFixMe[method-unbinding]
const FunctionBind = Function.prototype.bind;
// $FlowFixMe[method-unbinding]
const ArraySlice = Array.prototype.slice;
function bind(this: ServerReference<any>): any {
// $FlowFixMe[prop-missing]
const newFn = FunctionBind.apply(this, arguments);
if (this.$$typeof === SERVER_REFERENCE_TAG) {
if (__DEV__) {
const thisBind = arguments[0];
if (thisBind != null) {
console.error(
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
);
}
}
const args = ArraySlice.call(arguments, 1);
const $$typeof = {value: SERVER_REFERENCE_TAG};
const $$id = {value: this.$$id};
const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
return Object.defineProperties(
(newFn: any),
__DEV__
? {
$$typeof,
$$id,
$$bound,
$$location: {
value: this.$$location,
configurable: true,
},
bind: {value: bind, configurable: true},
}
: {
$$typeof,
$$id,
$$bound,
bind: {value: bind, configurable: true},
},
);
}
return newFn;
}
export function registerServerReference<T>(
reference: ServerReference<T>,
id: string,
exportName: string,
): ServerReference<T> {
const $$typeof = {value: SERVER_REFERENCE_TAG};
const $$id = {
value: id + '#' + exportName,
configurable: true,
};
const $$bound = {value: null, configurable: true};
return Object.defineProperties(
(reference: any),
__DEV__
? {
$$typeof,
$$id,
$$bound,
$$location: {
value: Error('react-stack-top-frame'),
configurable: true,
},
bind: {value: bind, configurable: true},
}
: {
$$typeof,
$$id,
$$bound,
bind: {value: bind, configurable: true},
},
);
}

View File

@ -0,0 +1,179 @@
/**
* 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 {Thenable} from 'shared/ReactTypes.js';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
import type {ServerReferenceId} from '../shared/ReactFlightClientConfigBundlerParcel';
import {
createResponse,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import {
processReply,
createServerReference as createServerReferenceImpl,
} from 'react-client/src/ReactFlightReplyClient';
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export type {TemporaryReferenceSet};
type CallServerCallback = <A, T>(id: string, args: A) => Promise<T>;
let callServer: CallServerCallback | null = null;
export function setServerCallback(fn: CallServerCallback) {
callServer = fn;
}
function callCurrentServerCallback<A, T>(
id: ServerReferenceId,
args: A,
): Promise<T> {
if (!callServer) {
throw new Error(
'No server callback has been registered. Call setServerCallback to register one.',
);
}
return callServer(id, args);
}
export function createServerReference<A: Iterable<any>, T>(
id: string,
exportName: string,
): (...A) => Promise<T> {
return createServerReferenceImpl(
id + '#' + exportName,
callCurrentServerCallback,
);
}
function startReadingFromStream(
response: FlightResponse,
stream: ReadableStream,
): void {
const reader = stream.getReader();
function progress({
done,
value,
}: {
done: boolean,
value: ?any,
...
}): void | Promise<void> {
if (done) {
close(response);
return;
}
const buffer: Uint8Array = (value: any);
processBinaryChunk(response, buffer);
return reader.read().then(progress).catch(error);
}
function error(e: any) {
reportGlobalError(response, e);
}
reader.read().then(progress).catch(error);
}
export type Options = {
temporaryReferences?: TemporaryReferenceSet,
replayConsoleLogs?: boolean,
environmentName?: string,
};
export function createFromReadableStream<T>(
stream: ReadableStream,
options?: Options,
): Thenable<T> {
const response: FlightResponse = createResponse(
null, // bundlerConfig
null, // serverReferenceConfig
null, // moduleLoading
callCurrentServerCallback,
undefined, // encodeFormAction
undefined, // nonce
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
);
startReadingFromStream(response, stream);
return getRoot(response);
}
export function createFromFetch<T>(
promiseForResponse: Promise<Response>,
options?: Options,
): Thenable<T> {
const response: FlightResponse = createResponse(
null, // bundlerConfig
null, // serverReferenceConfig
null, // moduleLoading
callCurrentServerCallback,
undefined, // encodeFormAction
undefined, // nonce
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
);
promiseForResponse.then(
function (r) {
startReadingFromStream(response, (r.body: any));
},
function (e) {
reportGlobalError(response, e);
},
);
return getRoot(response);
}
export function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
const abort = processReply(
value,
'', // formFieldPrefix
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}

View File

@ -0,0 +1,161 @@
/**
* 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 {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
import {
createResponse,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import {
processReply,
createServerReference as createServerReferenceImpl,
} from 'react-client/src/ReactFlightReplyClient';
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export type {TemporaryReferenceSet};
function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
'This would create a fetch waterfall. Try to use a Server Component ' +
'to pass data to Client Components instead.',
);
}
export function createServerReference<A: Iterable<any>, T>(
id: string,
exportName: string,
): (...A) => Promise<T> {
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
}
type EncodeFormActionCallback = <A>(
id: any,
args: Promise<A>,
) => ReactCustomFormAction;
export type Options = {
nonce?: string,
encodeFormAction?: EncodeFormActionCallback,
temporaryReferences?: TemporaryReferenceSet,
replayConsoleLogs?: boolean,
environmentName?: string,
};
function createResponseFromOptions(options?: Options) {
return createResponse(
null, // bundlerConfig
null, // serverReferenceConfig
null, // moduleLoading
noServerCall,
options ? options.encodeFormAction : undefined,
options && typeof options.nonce === 'string' ? options.nonce : undefined,
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
);
}
function startReadingFromStream(
response: FlightResponse,
stream: ReadableStream,
): void {
const reader = stream.getReader();
function progress({
done,
value,
}: {
done: boolean,
value: ?any,
...
}): void | Promise<void> {
if (done) {
close(response);
return;
}
const buffer: Uint8Array = (value: any);
processBinaryChunk(response, buffer);
return reader.read().then(progress).catch(error);
}
function error(e: any) {
reportGlobalError(response, e);
}
reader.read().then(progress).catch(error);
}
export function createFromReadableStream<T>(
stream: ReadableStream,
options?: Options,
): Thenable<T> {
const response: FlightResponse = createResponseFromOptions(options);
startReadingFromStream(response, stream);
return getRoot(response);
}
export function createFromFetch<T>(
promiseForResponse: Promise<Response>,
options?: Options,
): Thenable<T> {
const response: FlightResponse = createResponseFromOptions(options);
promiseForResponse.then(
function (r) {
startReadingFromStream(response, (r.body: any));
},
function (e) {
reportGlobalError(response, e);
},
);
return getRoot(response);
}
export function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
const abort = processReply(
value,
'',
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}

View File

@ -0,0 +1,77 @@
/**
* 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 {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
import type {Response} from 'react-client/src/ReactFlightClient';
import type {Readable} from 'stream';
import {
createResponse,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
'This would create a fetch waterfall. Try to use a Server Component ' +
'to pass data to Client Components instead.',
);
}
export function createServerReference<A: Iterable<any>, T>(
id: string,
exportName: string,
): (...A) => Promise<T> {
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
}
type EncodeFormActionCallback = <A>(
id: any,
args: Promise<A>,
) => ReactCustomFormAction;
export type Options = {
nonce?: string,
encodeFormAction?: EncodeFormActionCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
};
export function createFromNodeStream<T>(
stream: Readable,
options?: Options,
): Thenable<T> {
const response: Response = createResponse(
null, // bundlerConfig
null, // serverReferenceConfig
null, // moduleLoading
noServerCall,
options ? options.encodeFormAction : undefined,
options && typeof options.nonce === 'string' ? options.nonce : undefined,
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
undefined, // TODO: findSourceMapUrl
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
);
stream.on('data', chunk => {
processBinaryChunk(response, chunk);
});
stream.on('error', error => {
reportGlobalError(response, error);
});
stream.on('end', () => close(response));
return getRoot(response);
}

View File

@ -0,0 +1,214 @@
/**
* 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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ReactFormState, Thenable} from 'shared/ReactTypes';
import {
preloadModule,
requireModule,
resolveServerReference,
type ServerManifest,
type ServerReferenceId,
} from '../shared/ReactFlightClientConfigBundlerParcel';
import {
createRequest,
createPrerenderRequest,
startWork,
startFlowing,
stopFlowing,
abort,
} from 'react-server/src/ReactFlightServer';
import {
createResponse,
close,
getRoot,
} from 'react-server/src/ReactFlightReplyServer';
import {
decodeAction as decodeActionImpl,
decodeFormState as decodeFormStateImpl,
} from 'react-server/src/ReactFlightActionServer';
export {
createClientReference,
registerServerReference,
} from '../ReactFlightParcelReferences';
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export type {TemporaryReferenceSet};
type Options = {
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
export function renderToReadableStream(
model: ReactClientValue,
options?: Options,
): ReadableStream {
const request = createRequest(
model,
null,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
return stream;
}
type StaticResult = {
prelude: ReadableStream,
};
export function prerender(
model: ReactClientValue,
options?: Options,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
resolve({prelude: stream});
}
const request = createPrerenderRequest(
model,
null,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
const reason = (signal: any).reason;
abort(request, reason);
} else {
const listener = () => {
const reason = (signal: any).reason;
abort(request, reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
let serverManifest = {};
export function registerServerActions(manifest: ServerManifest) {
// This function is called by the bundler to register the manifest.
serverManifest = manifest;
}
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
form.append('0', body);
body = form;
}
const response = createResponse(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
body,
);
const root = getRoot<T>(response);
close(response);
return root;
}
export function decodeAction<T>(body: FormData): Promise<() => T> | null {
return decodeActionImpl(body, serverManifest);
}
export function decodeFormState<S>(
actionResult: S,
body: FormData,
): Promise<ReactFormState<S, ServerReferenceId> | null> {
return decodeFormStateImpl(actionResult, body, serverManifest);
}
export function loadServerAction<F: (...any[]) => any>(id: string): Promise<F> {
const reference = resolveServerReference<any>(serverManifest, id);
return Promise.resolve(reference)
.then(() => preloadModule(reference))
.then(() => {
const fn = requireModule(reference);
if (typeof fn !== 'function') {
throw new Error('Server actions must be functions');
}
return fn;
});
}

View File

@ -0,0 +1,214 @@
/**
* 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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ReactFormState, Thenable} from 'shared/ReactTypes';
import {
preloadModule,
requireModule,
resolveServerReference,
type ServerManifest,
type ServerReferenceId,
} from '../shared/ReactFlightClientConfigBundlerParcel';
import {
createRequest,
createPrerenderRequest,
startWork,
startFlowing,
stopFlowing,
abort,
} from 'react-server/src/ReactFlightServer';
import {
createResponse,
close,
getRoot,
} from 'react-server/src/ReactFlightReplyServer';
import {
decodeAction as decodeActionImpl,
decodeFormState as decodeFormStateImpl,
} from 'react-server/src/ReactFlightActionServer';
export {
createClientReference,
registerServerReference,
} from '../ReactFlightParcelReferences';
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export type {TemporaryReferenceSet};
type Options = {
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
identifierPrefix?: string,
signal?: AbortSignal,
temporaryReferences?: TemporaryReferenceSet,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
};
export function renderToReadableStream(
model: ReactClientValue,
options?: Options,
): ReadableStream {
const request = createRequest(
model,
null,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
return stream;
}
type StaticResult = {
prelude: ReadableStream,
};
export function prerender(
model: ReactClientValue,
options?: Options,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
resolve({prelude: stream});
}
const request = createPrerenderRequest(
model,
null,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
const reason = (signal: any).reason;
abort(request, reason);
} else {
const listener = () => {
const reason = (signal: any).reason;
abort(request, reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
let serverManifest = {};
export function registerServerActions(manifest: ServerManifest) {
// This function is called by the bundler to register the manifest.
serverManifest = manifest;
}
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
form.append('0', body);
body = form;
}
const response = createResponse(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
body,
);
const root = getRoot<T>(response);
close(response);
return root;
}
export function decodeAction<T>(body: FormData): Promise<() => T> | null {
return decodeActionImpl(body, serverManifest);
}
export function decodeFormState<S>(
actionResult: S,
body: FormData,
): Promise<ReactFormState<S, ServerReferenceId> | null> {
return decodeFormStateImpl(actionResult, body, serverManifest);
}
export function loadServerAction<F: (...any[]) => any>(id: string): Promise<F> {
const reference = resolveServerReference<any>(serverManifest, id);
return Promise.resolve(reference)
.then(() => preloadModule(reference))
.then(() => {
const fn = requireModule(reference);
if (typeof fn !== 'function') {
throw new Error('Server actions must be functions');
}
return fn;
});
}

View File

@ -0,0 +1,317 @@
/**
* 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 {
Request,
ReactClientValue,
} from 'react-server/src/ReactFlightServer';
import type {Destination} from 'react-server/src/ReactServerStreamConfigNode';
import type {Busboy} from 'busboy';
import type {Writable} from 'stream';
import type {ReactFormState, Thenable} from 'shared/ReactTypes';
import type {
ServerManifest,
ServerReferenceId,
} from '../shared/ReactFlightClientConfigBundlerParcel';
import {Readable} from 'stream';
import {
createRequest,
createPrerenderRequest,
startWork,
startFlowing,
stopFlowing,
abort,
} from 'react-server/src/ReactFlightServer';
import {
createResponse,
reportGlobalError,
close,
resolveField,
resolveFileInfo,
resolveFileChunk,
resolveFileComplete,
getRoot,
} from 'react-server/src/ReactFlightReplyServer';
import {
decodeAction as decodeActionImpl,
decodeFormState as decodeFormStateImpl,
} from 'react-server/src/ReactFlightActionServer';
import {
preloadModule,
requireModule,
resolveServerReference,
} from '../shared/ReactFlightClientConfigBundlerParcel';
export {
createClientReference,
registerServerReference,
} from '../ReactFlightParcelReferences';
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export type {TemporaryReferenceSet};
function createDrainHandler(destination: Destination, request: Request) {
return () => startFlowing(request, destination);
}
function createCancelHandler(request: Request, reason: string) {
return () => {
stopFlowing(request);
abort(request, new Error(reason));
};
}
type Options = {
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
temporaryReferences?: TemporaryReferenceSet,
};
type PipeableStream = {
abort(reason: mixed): void,
pipe<T: Writable>(destination: T): T,
};
export function renderToPipeableStream(
model: ReactClientValue,
options?: Options,
): PipeableStream {
const request = createRequest(
model,
null,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
let hasStartedFlowing = false;
startWork(request);
return {
pipe<T: Writable>(destination: T): T {
if (hasStartedFlowing) {
throw new Error(
'React currently only supports piping to one writable stream.',
);
}
hasStartedFlowing = true;
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on(
'error',
createCancelHandler(
request,
'The destination stream errored while writing data.',
),
);
destination.on(
'close',
createCancelHandler(request, 'The destination stream closed early.'),
);
return destination;
},
abort(reason: mixed) {
abort(request, reason);
},
};
}
function createFakeWritable(readable: any): Writable {
// The current host config expects a Writable so we create
// a fake writable for now to push into the Readable.
return ({
write(chunk) {
return readable.push(chunk);
},
end() {
readable.push(null);
},
destroy(error) {
readable.destroy(error);
},
}: any);
}
type PrerenderOptions = {
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
onError?: (error: mixed) => void,
onPostpone?: (reason: string) => void,
identifierPrefix?: string,
temporaryReferences?: TemporaryReferenceSet,
signal?: AbortSignal,
};
type StaticResult = {
prelude: Readable,
};
export function prerenderToNodeStream(
model: ReactClientValue,
options?: PrerenderOptions,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const readable: Readable = new Readable({
read() {
startFlowing(request, writable);
},
});
const writable = createFakeWritable(readable);
resolve({prelude: readable});
}
const request = createPrerenderRequest(
model,
null,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
const reason = (signal: any).reason;
abort(request, reason);
} else {
const listener = () => {
const reason = (signal: any).reason;
abort(request, reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
let serverManifest = {};
export function registerServerActions(manifest: ServerManifest) {
// This function is called by the bundler to register the manifest.
serverManifest = manifest;
}
export function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
const response = createResponse(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
busboyStream.on('field', (name, value) => {
if (pendingFiles > 0) {
// Because the 'end' event fires two microtasks after the next 'field'
// we would resolve files and fields out of order. To handle this properly
// we queue any fields we receive until the previous file is done.
queuedFields.push(name, value);
} else {
resolveField(response, name, value);
}
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
throw new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
);
}
pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
value.on('data', chunk => {
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
}
queuedFields.length = 0;
}
});
});
busboyStream.on('finish', () => {
close(response);
});
busboyStream.on('error', err => {
reportGlobalError(
response,
// $FlowFixMe[incompatible-call] types Error and mixed are incompatible
err,
);
});
return getRoot(response);
}
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
form.append('0', body);
body = form;
}
const response = createResponse(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
body,
);
const root = getRoot<T>(response);
close(response);
return root;
}
export function decodeAction<T>(body: FormData): Promise<() => T> | null {
return decodeActionImpl(body, serverManifest);
}
export function decodeFormState<S>(
actionResult: S,
body: FormData,
): Promise<ReactFormState<S, ServerReferenceId> | null> {
return decodeFormStateImpl(actionResult, body, serverManifest);
}
export function loadServerAction<F: (...any[]) => any>(id: string): Promise<F> {
const reference = resolveServerReference<any>(serverManifest, id);
return Promise.resolve(reference)
.then(() => preloadModule(reference))
.then(() => {
const fn = requireModule(reference);
if (typeof fn !== 'function') {
throw new Error('Server actions must be functions');
}
return fn;
});
}

View File

@ -0,0 +1,66 @@
/**
* 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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {ImportMetadata} from '../shared/ReactFlightImportMetadata';
import type {
ClientReference,
ServerReference,
} from '../ReactFlightParcelReferences';
export type {ClientReference, ServerReference};
export type ClientManifest = null;
export type ServerReferenceId = string;
export type ClientReferenceMetadata = ImportMetadata;
export type ClientReferenceKey = string;
export {
isClientReference,
isServerReference,
} from '../ReactFlightParcelReferences';
export function getClientReferenceKey(
reference: ClientReference<any>,
): ClientReferenceKey {
return reference.$$id + '#' + reference.$$name;
}
export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
clientReference: ClientReference<T>,
): ClientReferenceMetadata {
return [
clientReference.$$id,
clientReference.$$name,
clientReference.$$bundles,
];
}
export function getServerReferenceId<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): ServerReferenceId {
return serverReference.$$id;
}
export function getServerReferenceBoundArguments<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): null | Array<ReactClientValue> {
return serverReference.$$bound;
}
export function getServerReferenceLocation<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): void | Error {
return serverReference.$$location;
}

View File

@ -0,0 +1,21 @@
/**
* 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
*/
export {
renderToReadableStream,
prerender,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerBrowser';

View File

@ -0,0 +1,20 @@
/**
* 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
*/
export {
renderToReadableStream,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerBrowser';

View File

@ -0,0 +1,21 @@
/**
* 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
*/
export {
renderToReadableStream,
prerender,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerEdge';

View File

@ -0,0 +1,20 @@
/**
* 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
*/
export {
renderToReadableStream,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerEdge';

View File

@ -0,0 +1,22 @@
/**
* 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
*/
export {
renderToPipeableStream,
prerenderToNodeStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerNode';

View File

@ -0,0 +1,21 @@
/**
* 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
*/
export {
renderToPipeableStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
decodeFormState,
createClientReference,
registerServerReference,
createTemporaryReferenceSet,
registerServerActions,
loadServerAction,
} from './ReactFlightDOMServerNode';

View File

@ -0,0 +1,83 @@
/**
* 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 {Thenable} from 'shared/ReactTypes';
import type {ImportMetadata} from './ReactFlightImportMetadata';
import {ID, NAME, BUNDLES} from './ReactFlightImportMetadata';
export type ServerManifest = {
[string]: Array<string>,
};
export type SSRModuleMap = null;
export type ModuleLoading = null;
export type ServerConsumerModuleMap = null;
export type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = ImportMetadata;
// eslint-disable-next-line no-unused-vars
export opaque type ClientReference<T> = {
// Module id.
id: string,
// Export name.
name: string,
// List of bundle URLs, relative to the distDir.
bundles: Array<string>,
};
export function prepareDestinationForModule(
moduleLoading: ModuleLoading,
nonce: ?string,
metadata: ClientReferenceMetadata,
) {
return;
}
export function resolveClientReference<T>(
bundlerConfig: null,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
// Reference is already resolved during the build.
return {
id: metadata[ID],
name: metadata[NAME],
bundles: metadata[BUNDLES],
};
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
ref: ServerReferenceId,
): ClientReference<T> {
const idx = ref.lastIndexOf('#');
const id = ref.slice(0, idx);
const name = ref.slice(idx + 1);
const bundles = bundlerConfig[id];
if (!bundles) {
throw new Error('Invalid server action: ' + ref);
}
return {
id,
name,
bundles,
};
}
export function preloadModule<T>(
metadata: ClientReference<T>,
): null | Thenable<any> {
return Promise.all(metadata.bundles.map(url => parcelRequire.load(url)));
}
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata.id);
return moduleExports[metadata.name];
}

View File

@ -0,0 +1,20 @@
/**
* 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
*/
// This is the parsed shape of the wire format which is why it is
// condensed to only the essentialy information
export type ImportMetadata = [
/* id */ string,
/* name */ string,
/* bundles */ Array<string>,
];
export const ID = 0;
export const NAME = 1;
export const BUNDLES = 2;

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {prerender} from './src/server/react-flight-dom-server.browser';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {prerender} from './src/server/react-flight-dom-server.edge';

View File

@ -0,0 +1,13 @@
/**
* 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
*/
throw new Error(
'The React Server cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.',
);

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {prerenderToNodeStream} from './src/server/react-flight-dom-server.node';

View File

@ -0,0 +1,25 @@
/**
* 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 {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-parcel/src/server/ReactFlightServerConfigParcelBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';
export * from '../ReactFlightStackConfigV8';

View File

@ -0,0 +1,41 @@
/**
* 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 {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-parcel/src/server/ReactFlightServerConfigParcelBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
// For now, we get this from the global scope, but this will likely move to a module.
export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request | void> =
supportsRequestStorage ? new AsyncLocalStorage() : (null: any);
export const supportsComponentStorage: boolean =
__DEV__ && supportsRequestStorage;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
// We use the Node version but get access to async_hooks from a global.
import type {HookCallbacks, AsyncHook} from 'async_hooks';
export const createAsyncHook: HookCallbacks => AsyncHook =
typeof async_hooks === 'object'
? async_hooks.createHook
: function () {
return ({
enable() {},
disable() {},
}: any);
};
export const executionAsyncId: () => number =
typeof async_hooks === 'object' ? async_hooks.executionAsyncId : (null: any);
export * from '../ReactFlightServerConfigDebugNode';
export * from '../ReactFlightStackConfigV8';

View File

@ -0,0 +1,30 @@
/**
* 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 {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-parcel/src/server/ReactFlightServerConfigParcelBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsComponentStorage = __DEV__;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';
export * from '../ReactFlightStackConfigV8';

View File

@ -103,6 +103,11 @@ declare const __turbopack_require__: ((id: string) => any) & {
u: string => string,
};
declare var parcelRequire: {
(id: string): any,
load: (url: string) => Promise<mixed>,
};
declare module 'fs/promises' {
declare const access: (path: string, mode?: number) => Promise<void>;
declare const lstat: (

View File

@ -621,6 +621,70 @@ const bundles = [
externals: ['react', 'react-dom'],
},
/******* React Server DOM Parcel Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/src/server/react-flight-dom-server.browser',
name: 'react-server-dom-parcel-server.browser',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/src/server/react-flight-dom-server.node',
name: 'react-server-dom-parcel-server.node',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'util', 'async_hooks', 'react-dom'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/src/server/react-flight-dom-server.edge',
name: 'react-server-dom-parcel-server.edge',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'util', 'async_hooks', 'react-dom'],
},
/******* React Server DOM Parcel Client *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/client.browser',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/client.node',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-parcel/client.edge',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
/******* React Server DOM ESM Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],

View File

@ -64,6 +64,9 @@ module.exports = {
__turbopack_load__: 'readonly',
__turbopack_require__: 'readonly',
// Flight Parcel
parcelRequire: 'readonly',
// jest
expect: 'readonly',
jest: 'readonly',

View File

@ -62,6 +62,9 @@ module.exports = {
__turbopack_load__: 'readonly',
__turbopack_require__: 'readonly',
// Flight Parcel
parcelRequire: 'readonly',
// jest
expect: 'readonly',
jest: 'readonly',

View File

@ -64,6 +64,9 @@ module.exports = {
__turbopack_load__: 'readonly',
__turbopack_require__: 'readonly',
// Flight Parcel
parcelRequire: 'readonly',
// jest
expect: 'readonly',
jest: 'readonly',

View File

@ -189,6 +189,49 @@ module.exports = [
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-node-parcel',
entryPoints: [
'react-server-dom-parcel/client.node',
'react-server-dom-parcel/src/server/react-flight-dom-server.node',
],
paths: [
'react-dom',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
'react-dom/static.node',
'react-dom/src/server/react-dom-server.node',
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
'react-dom/src/server/ReactDOMFizzStaticNode.js',
'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
'react-server-dom-parcel',
'react-server-dom-parcel/client.node',
'react-server-dom-parcel/server',
'react-server-dom-parcel/server.node',
'react-server-dom-parcel/static',
'react-server-dom-parcel/static.node',
'react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js', // react-server-dom-parcel/client.node
'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel.js',
'react-server-dom-parcel/src/server/react-flight-dom-server.node',
'react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js', // react-server-dom-parcel/src/server/react-flight-dom-server.node
'react-server-dom-parcel/node-register',
'react-server-dom-parcel/src/ReactFlightParcelNodeRegister.js',
'react-devtools',
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-bun',
entryPoints: ['react-dom/src/server/react-dom-server.bun.js'],
@ -270,6 +313,40 @@ module.exports = [
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-browser-parcel',
entryPoints: [
'react-server-dom-parcel/client.browser',
'react-server-dom-parcel/src/server/react-flight-dom-server.browser',
],
paths: [
'react-dom',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom-bindings',
'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
'react-server-dom-parcel',
'react-server-dom-parcel/client',
'react-server-dom-parcel/client.browser',
'react-server-dom-parcel/server.browser',
'react-server-dom-parcel/static.browser',
'react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js', // react-server-dom-parcel/client.browser
'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel.js',
'react-server-dom-parcel/src/server/react-flight-dom-server.browser',
'react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js', // react-server-dom-parcel/src/server/react-flight-dom-server.browser
'react-devtools',
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
],
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-edge-webpack',
entryPoints: [
@ -352,6 +429,45 @@ module.exports = [
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-edge-parcel',
entryPoints: [
'react-server-dom-parcel/client.edge',
'react-server-dom-parcel/src/server/react-flight-dom-server.edge',
],
paths: [
'react-dom',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server.edge',
'react-dom/static.edge',
'react-dom/unstable_testing',
'react-dom/src/server/react-dom-server.edge',
'react-dom/src/server/ReactDOMFizzServerEdge.js', // react-dom/server.edge
'react-dom/src/server/ReactDOMFizzStaticEdge.js',
'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
'react-server-dom-parcel',
'react-server-dom-parcel/client.edge',
'react-server-dom-parcel/server.edge',
'react-server-dom-parcel/static.edge',
'react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-parcel/client.edge
'react-server-dom-parcel/src/shared/ReactFlightClientConfigBundlerParcel.js',
'react-server-dom-parcel/src/server/react-flight-dom-server.edge',
'react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js', // react-server-dom-parcel/src/server/react-flight-dom-server.edge
'react-devtools',
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-node-esm',
entryPoints: [