Remove turbopack unbundled/register/loader (#30756)

The unbundled form is just a way to show case a prototype for how an
unbundled version of RSC can work. It's not really intended for every
bundler combination to provide such a configuration.

There's no configuration of Turbopack that supports this mode atm and
possibly never will be since it's more of an integrated server/client
experience.

This removes the unbundled form and node register/loaders from the
turbopack build.
This commit is contained in:
Sebastian Markbåge
2024-08-21 09:58:31 -04:00
committed by GitHub
parent dd9117e313
commit 1228a28398
28 changed files with 38 additions and 1066 deletions

View File

@@ -1,16 +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
*/
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

@@ -9,7 +9,8 @@
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerNode';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

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

View File

@@ -1,3 +0,0 @@
{
"type": "module"
}

View File

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

View File

@@ -1,10 +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
*/
module.exports = require('./src/ReactFlightTurbopackNodeRegister');

View File

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

View File

@@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@@ -1,3 +0,0 @@
'use strict';
module.exports = require('./cjs/react-server-dom-turbopack-node-register.js');

View File

@@ -1,18 +0,0 @@
'use strict';
var s;
if (process.env.NODE_ENV === 'production') {
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.production.js');
} else {
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.development.js');
}
exports.renderToPipeableStream = s.renderToPipeableStream;
exports.decodeReplyFromBusboy = s.decodeReplyFromBusboy;
exports.decodeReply = s.decodeReply;
exports.decodeAction = s.decodeAction;
exports.decodeFormState = s.decodeFormState;
exports.registerServerReference = s.registerServerReference;
exports.registerClientReference = s.registerClientReference;
exports.createClientModuleProxy = s.createClientModuleProxy;
exports.createTemporaryReferenceSet = s.createTemporaryReferenceSet;

View File

@@ -1,12 +0,0 @@
'use strict';
var s;
if (process.env.NODE_ENV === 'production') {
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.production.js');
} else {
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.development.js');
}
if (s.prerenderToNodeStream) {
exports.prerenderToNodeStream = s.prerenderToNodeStream;
}

View File

@@ -16,20 +16,15 @@
"client.browser.js",
"client.edge.js",
"client.node.js",
"client.node.unbundled.js",
"server.js",
"server.browser.js",
"server.edge.js",
"server.node.js",
"server.node.unbundled.js",
"static.js",
"static.browser.js",
"static.edge.js",
"static.node.js",
"static.node.unbundled.js",
"node-register.js",
"cjs/",
"esm/"
"cjs/"
],
"exports": {
".": "./index.js",
@@ -37,11 +32,7 @@
"workerd": "./client.edge.js",
"deno": "./client.edge.js",
"worker": "./client.edge.js",
"node": {
"turbopack": "./client.node.js",
"webpack": "./client.node.js",
"default": "./client.node.unbundled.js"
},
"node": "./client.node.js",
"edge-light": "./client.edge.js",
"browser": "./client.browser.js",
"default": "./client.browser.js"
@@ -49,16 +40,11 @@
"./client.browser": "./client.browser.js",
"./client.edge": "./client.edge.js",
"./client.node": "./client.node.js",
"./client.node.unbundled": "./client.node.unbundled.js",
"./server": {
"react-server": {
"workerd": "./server.edge.js",
"deno": "./server.browser.js",
"node": {
"turbopack": "./server.node.js",
"webpack": "./server.node.js",
"default": "./server.node.unbundled.js"
},
"node": "./server.node.js",
"edge-light": "./server.edge.js",
"browser": "./server.browser.js"
},
@@ -67,16 +53,11 @@
"./server.browser": "./server.browser.js",
"./server.edge": "./server.edge.js",
"./server.node": "./server.node.js",
"./server.node.unbundled": "./server.node.unbundled.js",
"./static": {
"react-server": {
"workerd": "./static.edge.js",
"deno": "./static.browser.js",
"node": {
"turbopack": "./static.node.js",
"webpack": "./static.node.js",
"default": "./static.node.unbundled.js"
},
"node": "./static.node.js",
"edge-light": "./static.edge.js",
"browser": "./static.browser.js"
},
@@ -85,9 +66,6 @@
"./static.browser": "./static.browser.js",
"./static.edge": "./static.edge.js",
"./static.node": "./static.node.js",
"./static.node.unbundled": "./static.node.unbundled.js",
"./node-loader": "./esm/react-server-dom-turbopack-node-loader.production.js",
"./node-register": "./node-register.js",
"./src/*": "./src/*.js",
"./package.json": "./package.json"
},

View File

@@ -1,20 +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
*/
export {
renderToPipeableStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
decodeFormState,
registerServerReference,
registerClientReference,
createClientModuleProxy,
createTemporaryReferenceSet,
} from './src/server/react-flight-dom-server.node.unbundled';

View File

@@ -1,483 +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 * as acorn from 'acorn-loose';
type ResolveContext = {
conditions: Array<string>,
parentURL: string | void,
};
type ResolveFunction = (
string,
ResolveContext,
ResolveFunction,
) => {url: string} | Promise<{url: string}>;
type GetSourceContext = {
format: string,
};
type GetSourceFunction = (
string,
GetSourceContext,
GetSourceFunction,
) => Promise<{source: Source}>;
type TransformSourceContext = {
format: string,
url: string,
};
type TransformSourceFunction = (
Source,
TransformSourceContext,
TransformSourceFunction,
) => Promise<{source: Source}>;
type LoadContext = {
conditions: Array<string>,
format: string | null | void,
importAssertions: Object,
};
type LoadFunction = (
string,
LoadContext,
LoadFunction,
) => Promise<{format: string, shortCircuit?: boolean, source: Source}>;
type Source = string | ArrayBuffer | Uint8Array;
let warnedAboutConditionsFlag = false;
let stashedGetSource: null | GetSourceFunction = null;
let stashedResolve: null | ResolveFunction = null;
export async function resolve(
specifier: string,
context: ResolveContext,
defaultResolve: ResolveFunction,
): Promise<{url: string}> {
// We stash this in case we end up needing to resolve export * statements later.
stashedResolve = defaultResolve;
if (!context.conditions.includes('react-server')) {
context = {
...context,
conditions: [...context.conditions, 'react-server'],
};
if (!warnedAboutConditionsFlag) {
warnedAboutConditionsFlag = true;
// eslint-disable-next-line react-internal/no-production-logging
console.warn(
'You did not run Node.js with the `--conditions react-server` flag. ' +
'Any "react-server" override will only work with ESM imports.',
);
}
}
return await defaultResolve(specifier, context, defaultResolve);
}
export async function getSource(
url: string,
context: GetSourceContext,
defaultGetSource: GetSourceFunction,
): Promise<{source: Source}> {
// We stash this in case we end up needing to resolve export * statements later.
stashedGetSource = defaultGetSource;
return defaultGetSource(url, context, defaultGetSource);
}
function addLocalExportedNames(names: Map<string, string>, node: any) {
switch (node.type) {
case 'Identifier':
names.set(node.name, node.name);
return;
case 'ObjectPattern':
for (let i = 0; i < node.properties.length; i++)
addLocalExportedNames(names, node.properties[i]);
return;
case 'ArrayPattern':
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (element) addLocalExportedNames(names, element);
}
return;
case 'Property':
addLocalExportedNames(names, node.value);
return;
case 'AssignmentPattern':
addLocalExportedNames(names, node.left);
return;
case 'RestElement':
addLocalExportedNames(names, node.argument);
return;
case 'ParenthesizedExpression':
addLocalExportedNames(names, node.expression);
return;
}
}
function transformServerModule(
source: string,
body: any,
url: string,
loader: LoadFunction,
): string {
// If the same local name is exported more than once, we only need one of the names.
const localNames: Map<string, string> = new Map();
const localTypes: Map<string, string> = new Map();
for (let i = 0; i < body.length; i++) {
const node = body[i];
switch (node.type) {
case 'ExportAllDeclaration':
// If export * is used, the other file needs to explicitly opt into "use server" too.
break;
case 'ExportDefaultDeclaration':
if (node.declaration.type === 'Identifier') {
localNames.set(node.declaration.name, 'default');
} else if (node.declaration.type === 'FunctionDeclaration') {
if (node.declaration.id) {
localNames.set(node.declaration.id.name, 'default');
localTypes.set(node.declaration.id.name, 'function');
} else {
// TODO: This needs to be rewritten inline because it doesn't have a local name.
}
}
continue;
case 'ExportNamedDeclaration':
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
const declarations = node.declaration.declarations;
for (let j = 0; j < declarations.length; j++) {
addLocalExportedNames(localNames, declarations[j].id);
}
} else {
const name = node.declaration.id.name;
localNames.set(name, name);
if (node.declaration.type === 'FunctionDeclaration') {
localTypes.set(name, 'function');
}
}
}
if (node.specifiers) {
const specifiers = node.specifiers;
for (let j = 0; j < specifiers.length; j++) {
const specifier = specifiers[j];
localNames.set(specifier.local.name, specifier.exported.name);
}
}
continue;
}
}
if (localNames.size === 0) {
return source;
}
let newSrc = source + '\n\n;';
newSrc +=
'import {registerServerReference} from "react-server-dom-turbopack/server";\n';
localNames.forEach(function (exported, local) {
if (localTypes.get(local) !== 'function') {
// We first check if the export is a function and if so annotate it.
newSrc += 'if (typeof ' + local + ' === "function") ';
}
newSrc += 'registerServerReference(' + local + ',';
newSrc += JSON.stringify(url) + ',';
newSrc += JSON.stringify(exported) + ');\n';
});
return newSrc;
}
function addExportNames(names: Array<string>, node: any) {
switch (node.type) {
case 'Identifier':
names.push(node.name);
return;
case 'ObjectPattern':
for (let i = 0; i < node.properties.length; i++)
addExportNames(names, node.properties[i]);
return;
case 'ArrayPattern':
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (element) addExportNames(names, element);
}
return;
case 'Property':
addExportNames(names, node.value);
return;
case 'AssignmentPattern':
addExportNames(names, node.left);
return;
case 'RestElement':
addExportNames(names, node.argument);
return;
case 'ParenthesizedExpression':
addExportNames(names, node.expression);
return;
}
}
function resolveClientImport(
specifier: string,
parentURL: string,
): {url: string} | Promise<{url: string}> {
// Resolve an import specifier as if it was loaded by the client. This doesn't use
// the overrides that this loader does but instead reverts to the default.
// This resolution algorithm will not necessarily have the same configuration
// as the actual client loader. It should mostly work and if it doesn't you can
// always convert to explicit exported names instead.
const conditions = ['node', 'import'];
if (stashedResolve === null) {
throw new Error(
'Expected resolve to have been called before transformSource',
);
}
return stashedResolve(specifier, {conditions, parentURL}, stashedResolve);
}
async function parseExportNamesInto(
body: any,
names: Array<string>,
parentURL: string,
loader: LoadFunction,
): Promise<void> {
for (let i = 0; i < body.length; i++) {
const node = body[i];
switch (node.type) {
case 'ExportAllDeclaration':
if (node.exported) {
addExportNames(names, node.exported);
continue;
} else {
const {url} = await resolveClientImport(node.source.value, parentURL);
const {source} = await loader(
url,
{format: 'module', conditions: [], importAssertions: {}},
loader,
);
if (typeof source !== 'string') {
throw new Error('Expected the transformed source to be a string.');
}
let childBody;
try {
childBody = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
continue;
}
await parseExportNamesInto(childBody, names, url, loader);
continue;
}
case 'ExportDefaultDeclaration':
names.push('default');
continue;
case 'ExportNamedDeclaration':
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
const declarations = node.declaration.declarations;
for (let j = 0; j < declarations.length; j++) {
addExportNames(names, declarations[j].id);
}
} else {
addExportNames(names, node.declaration.id);
}
}
if (node.specifiers) {
const specifiers = node.specifiers;
for (let j = 0; j < specifiers.length; j++) {
addExportNames(names, specifiers[j].exported);
}
}
continue;
}
}
}
async function transformClientModule(
body: any,
url: string,
loader: LoadFunction,
): Promise<string> {
const names: Array<string> = [];
await parseExportNamesInto(body, names, url, loader);
if (names.length === 0) {
return '';
}
let newSrc =
'import {registerClientReference} from "react-server-dom-turbopack/server";\n';
for (let i = 0; i < names.length; i++) {
const name = names[i];
if (name === 'default') {
newSrc += 'export default ';
newSrc += 'registerClientReference(function() {';
newSrc +=
'throw new Error(' +
JSON.stringify(
`Attempted to call the default export of ${url} from the server ` +
`but it's on the client. It's not possible to invoke a client function from ` +
`the server, it can only be rendered as a Component or passed to props of a ` +
`Client Component.`,
) +
');';
} else {
newSrc += 'export const ' + name + ' = ';
newSrc += 'registerClientReference(function() {';
newSrc +=
'throw new Error(' +
JSON.stringify(
`Attempted to call ${name}() from the server but ${name} is on the client. ` +
`It's not possible to invoke a client function from the server, it can ` +
`only be rendered as a Component or passed to props of a Client Component.`,
) +
');';
}
newSrc += '},';
newSrc += JSON.stringify(url) + ',';
newSrc += JSON.stringify(name) + ');\n';
}
return newSrc;
}
async function loadClientImport(
url: string,
defaultTransformSource: TransformSourceFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
if (stashedGetSource === null) {
throw new Error(
'Expected getSource to have been called before transformSource',
);
}
// TODO: Validate that this is another module by calling getFormat.
const {source} = await stashedGetSource(
url,
{format: 'module'},
stashedGetSource,
);
const result = await defaultTransformSource(
source,
{format: 'module', url},
defaultTransformSource,
);
return {format: 'module', source: result.source};
}
async function transformModuleIfNeeded(
source: string,
url: string,
loader: LoadFunction,
): Promise<string> {
// Do a quick check for the exact string. If it doesn't exist, don't
// bother parsing.
if (
source.indexOf('use client') === -1 &&
source.indexOf('use server') === -1
) {
return source;
}
let body;
try {
body = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
return source;
}
let useClient = false;
let useServer = false;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type !== 'ExpressionStatement' || !node.directive) {
break;
}
if (node.directive === 'use client') {
useClient = true;
}
if (node.directive === 'use server') {
useServer = true;
}
}
if (!useClient && !useServer) {
return source;
}
if (useClient && useServer) {
throw new Error(
'Cannot have both "use client" and "use server" directives in the same file.',
);
}
if (useClient) {
return transformClientModule(body, url, loader);
}
return transformServerModule(source, body, url, loader);
}
export async function transformSource(
source: Source,
context: TransformSourceContext,
defaultTransformSource: TransformSourceFunction,
): Promise<{source: Source}> {
const transformed = await defaultTransformSource(
source,
context,
defaultTransformSource,
);
if (context.format === 'module') {
const transformedSource = transformed.source;
if (typeof transformedSource !== 'string') {
throw new Error('Expected source to have been transformed to a string.');
}
const newSrc = await transformModuleIfNeeded(
transformedSource,
context.url,
(url: string, ctx: LoadContext, defaultLoad: LoadFunction) => {
return loadClientImport(url, defaultTransformSource);
},
);
return {source: newSrc};
}
return transformed;
}
export async function load(
url: string,
context: LoadContext,
defaultLoad: LoadFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
const result = await defaultLoad(url, context, defaultLoad);
if (result.format === 'module') {
if (typeof result.source !== 'string') {
throw new Error('Expected source to have been loaded into a string.');
}
const newSrc = await transformModuleIfNeeded(
result.source,
url,
defaultLoad,
);
return {format: 'module', source: newSrc};
}
return result;
}

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
*/
const acorn = require('acorn-loose');
const url = require('url');
const Module = require('module');
module.exports = function register() {
const Server: any = require('react-server-dom-turbopack/server');
const registerServerReference = Server.registerServerReference;
const createClientModuleProxy = Server.createClientModuleProxy;
// $FlowFixMe[prop-missing] found when upgrading Flow
const originalCompile = Module.prototype._compile;
// $FlowFixMe[prop-missing] found when upgrading Flow
Module.prototype._compile = function (
this: any,
content: string,
filename: string,
): void {
// Do a quick check for the exact string. If it doesn't exist, don't
// bother parsing.
if (
content.indexOf('use client') === -1 &&
content.indexOf('use server') === -1
) {
return originalCompile.apply(this, arguments);
}
let body;
try {
body = acorn.parse(content, {
ecmaVersion: '2024',
sourceType: 'source',
}).body;
} catch (x) {
console['error']('Error parsing %s %s', url, x.message);
return originalCompile.apply(this, arguments);
}
let useClient = false;
let useServer = false;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type !== 'ExpressionStatement' || !node.directive) {
break;
}
if (node.directive === 'use client') {
useClient = true;
}
if (node.directive === 'use server') {
useServer = true;
}
}
if (!useClient && !useServer) {
return originalCompile.apply(this, arguments);
}
if (useClient && useServer) {
throw new Error(
'Cannot have both "use client" and "use server" directives in the same file.',
);
}
if (useClient) {
const moduleId: string = (url.pathToFileURL(filename).href: any);
this.exports = createClientModuleProxy(moduleId);
}
if (useServer) {
originalCompile.apply(this, arguments);
const moduleId: string = (url.pathToFileURL(filename).href: any);
const exports = this.exports;
// This module is imported server to server, but opts in to exposing functions by
// reference. If there are any functions in the export.
if (typeof exports === 'function') {
// The module exports a function directly,
registerServerReference(
(exports: any),
moduleId,
// Represents the whole Module object instead of a particular import.
null,
);
} else {
const keys = Object.keys(exports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = exports[keys[i]];
if (typeof value === 'function') {
registerServerReference((value: any), moduleId, key);
}
}
}
}
};
};

View File

@@ -30,7 +30,7 @@ let Suspense;
let ReactServerScheduler;
let reactServerAct;
describe('ReactFlightDOM', () => {
describe('ReactFlightTurbopackDOM', () => {
beforeEach(() => {
// For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
// This can be thought of as essentially being the React Server Components scope with react-server
@@ -43,7 +43,7 @@ describe('ReactFlightDOM', () => {
// Simulate the condition resolution
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.node.unbundled'),
require('react-server-dom-turbopack/server.node'),
);
jest.mock('react', () => require('react/react.react-server'));

View File

@@ -23,7 +23,7 @@ let ReactServerDOMClient;
let ReactServerScheduler;
let reactServerAct;
describe('ReactFlightDOMBrowser', () => {
describe('ReactFlightTurbopackDOMBrowser', () => {
beforeEach(() => {
jest.resetModules();

View File

@@ -28,7 +28,7 @@ let ReactServerDOMServer;
let ReactServerDOMClient;
let use;
describe('ReactFlightDOMEdge', () => {
describe('ReactFlightTurbopackDOMEdge', () => {
beforeEach(() => {
jest.resetModules();

View File

@@ -1,147 +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.
*
* @emails react-core
*/
'use strict';
import {insertNodesAndExecuteScripts} from 'react-dom/src/test-utils/FizzTestUtils';
// Polyfills for test environment
global.ReadableStream =
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;
// Don't wait before processing work on the server.
// TODO: we can replace this with FlightServer.act().
global.setTimeout = cb => cb();
let container;
let serverExports;
let turbopackServerMap;
let React;
let ReactDOMServer;
let ReactServerDOMServer;
let ReactServerDOMClient;
describe('ReactFlightDOMForm', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
jest.mock('react', () => require('react/react.react-server'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.edge'),
);
ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
const TurbopackMock = require('./utils/TurbopackMock');
serverExports = TurbopackMock.serverExports;
turbopackServerMap = TurbopackMock.turbopackServerMap;
__unmockReact();
jest.resetModules();
React = require('react');
ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
ReactDOMServer = require('react-dom/server.edge');
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
});
async function POST(formData) {
const boundAction = await ReactServerDOMServer.decodeAction(
formData,
turbopackServerMap,
);
return boundAction();
}
function submit(submitter) {
const form = submitter.form || submitter;
if (!submitter.form) {
submitter = undefined;
}
const submitEvent = new Event('submit', {bubbles: true, cancelable: true});
submitEvent.submitter = submitter;
const returnValue = form.dispatchEvent(submitEvent);
if (!returnValue) {
return;
}
const action =
(submitter && submitter.getAttribute('formaction')) || form.action;
if (!/\s*javascript:/i.test(action)) {
const method = (submitter && submitter.formMethod) || form.method;
const encType = (submitter && submitter.formEnctype) || form.enctype;
if (method === 'post' && encType === 'multipart/form-data') {
let formData;
if (submitter) {
const temp = document.createElement('input');
temp.name = submitter.name;
temp.value = submitter.value;
submitter.parentNode.insertBefore(temp, submitter);
formData = new FormData(form);
temp.parentNode.removeChild(temp);
} else {
formData = new FormData(form);
}
return POST(formData);
}
throw new Error('Navigate to: ' + action);
}
}
async function readIntoContainer(stream) {
const reader = stream.getReader();
let result = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
result += Buffer.from(value).toString('utf8');
}
const temp = document.createElement('div');
temp.innerHTML = result;
insertNodesAndExecuteScripts(temp, container, null);
}
it('can submit a passed server action without hydrating it', async () => {
let foo = null;
const serverAction = serverExports(function action(formData) {
foo = formData.get('foo');
return 'hello';
});
function App() {
return (
<form action={serverAction}>
<input type="text" name="foo" defaultValue="bar" />
</form>
);
}
const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
ssrManifest: {
moduleMap: null,
moduleLoading: null,
},
});
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
await readIntoContainer(ssrStream);
const form = container.firstChild;
expect(foo).toBe(null);
const result = await submit(form);
expect(result).toBe('hello');
expect(foo).toBe('bar');
});
});

View File

@@ -24,7 +24,7 @@ let use;
let ReactServerScheduler;
let reactServerAct;
describe('ReactFlightDOMNode', () => {
describe('ReactFlightTurbopackDOMNode', () => {
beforeEach(() => {
jest.resetModules();

View File

@@ -23,7 +23,7 @@ let ReactServerDOMServer;
let ReactServerDOMClient;
let ReactServerScheduler;
describe('ReactFlightDOMReply', () => {
describe('ReactFlightTurbopackDOMReply', () => {
beforeEach(() => {
jest.resetModules();

View File

@@ -20,7 +20,7 @@ let turbopackServerMap;
let ReactServerDOMServer;
let ReactServerDOMClient;
describe('ReactFlightDOMReply', () => {
describe('ReactFlightDOMTurbopackReply', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution

View File

@@ -8,7 +8,6 @@
'use strict';
const url = require('url');
const Module = require('module');
let turbopackModuleIdx = 0;
const turbopackServerModules = {};
@@ -23,21 +22,9 @@ global.__turbopack_require__ = function (id) {
return turbopackClientModules[id] || turbopackServerModules[id];
};
const previousCompile = Module.prototype._compile;
const register = require('react-server-dom-turbopack/node-register');
// Register node compile
register();
const nodeCompile = Module.prototype._compile;
if (previousCompile === nodeCompile) {
throw new Error(
'Expected the Node loader to register the _compile extension',
);
}
Module.prototype._compile = previousCompile;
const Server = require('react-server-dom-turbopack/server');
const registerServerReference = Server.registerServerReference;
const createClientModuleProxy = Server.createClientModuleProxy;
exports.turbopackMap = turbopackClientMap;
exports.turbopackModules = turbopackClientModules;
@@ -46,20 +33,6 @@ exports.moduleLoading = {
prefix: '/prefix/',
};
exports.clientModuleError = function clientModuleError(moduleError) {
const idx = '' + turbopackModuleIdx++;
turbopackErroredModules[idx] = moduleError;
const path = url.pathToFileURL(idx).href;
turbopackClientMap[path] = {
id: idx,
chunks: [],
name: '*',
};
const mod = new Module();
nodeCompile.call(mod, '"use client"', idx);
return mod.exports;
};
exports.clientExports = function clientExports(moduleExports, chunkUrl) {
const chunks = [];
if (chunkUrl !== undefined) {
@@ -107,9 +80,7 @@ exports.clientExports = function clientExports(moduleExports, chunkUrl) {
name: 's',
};
}
const mod = new Module();
nodeCompile.call(mod, '"use client"', idx);
return mod.exports;
return createClientModuleProxy(path);
};
// This tests server to server references. There's another case of client to server references.
@@ -142,8 +113,25 @@ exports.serverExports = function serverExports(moduleExports) {
name: 's',
};
}
const mod = new Module();
mod.exports = moduleExports;
nodeCompile.call(mod, '"use server"', idx);
return mod.exports;
if (typeof exports === 'function') {
// The module exports a function directly,
registerServerReference(
(exports: any),
idx,
// Represents the whole Module object instead of a particular import.
null,
);
} else {
const keys = Object.keys(exports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = exports[keys[i]];
if (typeof value === 'function') {
registerServerReference((value: any), idx, key);
}
}
}
return moduleExports;
};

View File

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

View File

@@ -1,20 +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
*/
export {
renderToPipeableStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
decodeFormState,
registerServerReference,
registerClientReference,
createClientModuleProxy,
createTemporaryReferenceSet,
} from './ReactFlightDOMServerNode';

View File

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

View File

@@ -534,18 +534,6 @@ const bundles = [
wrapWithModuleBoundaries: false,
externals: ['react', 'util', 'async_hooks', 'react-dom'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry:
'react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled',
name: 'react-server-dom-turbopack-server.node.unbundled',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'util', 'async_hooks', 'react-dom'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
@@ -577,15 +565,6 @@ const bundles = [
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-turbopack/client.node.unbundled',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
@@ -596,35 +575,6 @@ const bundles = [
externals: ['react', 'react-dom'],
},
/******* React Server DOM Turbopack Plugin *******/
// There is no plugin the moment because Turbopack
// does not expose a plugin interface yet.
/******* React Server DOM Turbopack Node.js Loader *******/
{
bundleTypes: [ESM_PROD],
moduleType: RENDERER_UTILS,
entry: 'react-server-dom-turbopack/node-loader',
condition: 'react-server',
global: 'ReactServerTurbopackNodeLoader',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['acorn'],
},
/******* React Server DOM Turbopack Node.js CommonJS Loader *******/
{
bundleTypes: [NODE_ES2015],
moduleType: RENDERER_UTILS,
entry: 'react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister',
name: 'react-server-dom-turbopack-node-register',
condition: 'react-server',
global: 'ReactFlightWebpackNodeRegister',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['url', 'module', 'react-server-dom-turbopack/server'],
},
/******* React Server DOM ESM Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],

View File

@@ -104,49 +104,6 @@ module.exports = [
},
{
shortName: 'dom-node-turbopack',
entryPoints: [
'react-server-dom-turbopack/client.node.unbundled',
'react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled',
],
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-turbopack',
'react-server-dom-turbopack/client.node.unbundled',
'react-server-dom-turbopack/server',
'react-server-dom-turbopack/server.node.unbundled',
'react-server-dom-turbopack/static',
'react-server-dom-turbopack/static.node.unbundled',
'react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-turbopack/client.node.unbundled
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerNode.js',
'react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled',
'react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js', // react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled
'react-server-dom-turbopack/node-register',
'react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister.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-node-turbopack-bundled',
entryPoints: [
'react-server-dom-turbopack/client.node',
'react-server-dom-turbopack/src/server/react-flight-dom-server.node',