mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Flight] Implement react-server-dom-turbopack (#27315)
stacked on #27314 Turbopack requires a different module loading strategy than Webpack and as such this PR implements a new package `react-server-dom-turbopack` which largely follows the `react-server-dom-webpack` but is implemented for this new bundler
This commit is contained in:
parent
701ac2e572
commit
f81c0f1ed9
|
|
@ -326,6 +326,7 @@ module.exports = {
|
|||
'packages/react-refresh/**/*.js',
|
||||
'packages/react-server-dom-esm/**/*.js',
|
||||
'packages/react-server-dom-webpack/**/*.js',
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
|
|
@ -427,6 +428,13 @@ module.exports = {
|
|||
__webpack_require__: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-server-dom-turbopack/**/*.js'],
|
||||
globals: {
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/scheduler/**/*.js'],
|
||||
globals: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 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/ReactFlightClientConfigBrowser';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackBrowser';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = false;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 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/ReactFlightClientConfigBrowser';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = true;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 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/ReactFlightClientConfigNode';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = true;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* 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/ReactFlightClientConfigNode';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerNode';
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = true;
|
||||
5
packages/react-server-dom-turbopack/README.md
Normal file
5
packages/react-server-dom-turbopack/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# react-server-dom-turbopack
|
||||
|
||||
Experimental React Flight bindings for DOM using Turbopack.
|
||||
|
||||
**Use it at your own risk.**
|
||||
10
packages/react-server-dom-turbopack/client.browser.js
Normal file
10
packages/react-server-dom-turbopack/client.browser.js
Normal 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/ReactFlightDOMClientBrowser';
|
||||
10
packages/react-server-dom-turbopack/client.edge.js
Normal file
10
packages/react-server-dom-turbopack/client.edge.js
Normal 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/ReactFlightDOMClientEdge';
|
||||
10
packages/react-server-dom-turbopack/client.js
vendored
Normal file
10
packages/react-server-dom-turbopack/client.js
vendored
Normal 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';
|
||||
10
packages/react-server-dom-turbopack/client.node.js
Normal file
10
packages/react-server-dom-turbopack/client.node.js
Normal 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/ReactFlightDOMClientNode';
|
||||
10
packages/react-server-dom-turbopack/client.node.unbundled.js
Normal file
10
packages/react-server-dom-turbopack/client.node.unbundled.js
Normal 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/ReactFlightDOMClientNode';
|
||||
3
packages/react-server-dom-turbopack/esm/package.json
Normal file
3
packages/react-server-dom-turbopack/esm/package.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
||||
10
packages/react-server-dom-turbopack/esm/react-server-dom-turbopack-node-loader.production.min.js
vendored
Normal file
10
packages/react-server-dom-turbopack/esm/react-server-dom-turbopack-node-loader.production.min.js
vendored
Normal 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/ReactFlightTurbopackNodeLoader.js';
|
||||
10
packages/react-server-dom-turbopack/index.js
vendored
Normal file
10
packages/react-server-dom-turbopack/index.js
vendored
Normal 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-turbopack/client instead.');
|
||||
10
packages/react-server-dom-turbopack/node-register.js
vendored
Normal file
10
packages/react-server-dom-turbopack/node-register.js
vendored
Normal 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
|
||||
*/
|
||||
|
||||
module.exports = require('./src/ReactFlightTurbopackNodeRegister');
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.browser.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.browser.development.js');
|
||||
}
|
||||
7
packages/react-server-dom-turbopack/npm/client.edge.js
Normal file
7
packages/react-server-dom-turbopack/npm/client.edge.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.edge.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.edge.development.js');
|
||||
}
|
||||
3
packages/react-server-dom-turbopack/npm/client.js
vendored
Normal file
3
packages/react-server-dom-turbopack/npm/client.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./client.browser');
|
||||
7
packages/react-server-dom-turbopack/npm/client.node.js
Normal file
7
packages/react-server-dom-turbopack/npm/client.node.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.node.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.node.development.js');
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.node.unbundled.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-client.node.unbundled.development.js');
|
||||
}
|
||||
3
packages/react-server-dom-turbopack/npm/esm/package.json
Normal file
3
packages/react-server-dom-turbopack/npm/esm/package.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
||||
12
packages/react-server-dom-turbopack/npm/index.js
vendored
Normal file
12
packages/react-server-dom-turbopack/npm/index.js
vendored
Normal 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-turbopack/client instead.');
|
||||
3
packages/react-server-dom-turbopack/npm/node-register.js
vendored
Normal file
3
packages/react-server-dom-turbopack/npm/node-register.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-node-register.js');
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.browser.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.browser.development.js');
|
||||
}
|
||||
7
packages/react-server-dom-turbopack/npm/server.edge.js
Normal file
7
packages/react-server-dom-turbopack/npm/server.edge.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.edge.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.edge.development.js');
|
||||
}
|
||||
6
packages/react-server-dom-turbopack/npm/server.js
vendored
Normal file
6
packages/react-server-dom-turbopack/npm/server.js
vendored
Normal 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.'
|
||||
);
|
||||
7
packages/react-server-dom-turbopack/npm/server.node.js
Normal file
7
packages/react-server-dom-turbopack/npm/server.node.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.node.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.node.development.js');
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-turbopack-server.node.unbundled.development.js');
|
||||
}
|
||||
95
packages/react-server-dom-turbopack/package.json
Normal file
95
packages/react-server-dom-turbopack/package.json
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"name": "react-server-dom-turbopack",
|
||||
"description": "React Server Components bindings for DOM using Turbopack. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
|
||||
"version": "18.2.0",
|
||||
"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",
|
||||
"client.node.unbundled.js",
|
||||
"server.js",
|
||||
"server.browser.js",
|
||||
"server.edge.js",
|
||||
"server.node.js",
|
||||
"server.node.unbundled.js",
|
||||
"node-register.js",
|
||||
"cjs/",
|
||||
"umd/",
|
||||
"esm/"
|
||||
],
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./client": {
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"./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"
|
||||
},
|
||||
"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",
|
||||
"./server.node.unbundled": "./server.node.unbundled.js",
|
||||
"./node-loader": "./esm/react-server-dom-turbopack-node-loader.production.min.js",
|
||||
"./node-register": "./node-register.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-turbopack"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn-loose": "^8.3.0",
|
||||
"neo-async": "^2.6.1",
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"loose-envify"
|
||||
]
|
||||
}
|
||||
}
|
||||
10
packages/react-server-dom-turbopack/server.browser.js
Normal file
10
packages/react-server-dom-turbopack/server.browser.js
Normal 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/ReactFlightDOMServerBrowser';
|
||||
10
packages/react-server-dom-turbopack/server.edge.js
Normal file
10
packages/react-server-dom-turbopack/server.edge.js
Normal 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/ReactFlightDOMServerEdge';
|
||||
13
packages/react-server-dom-turbopack/server.js
vendored
Normal file
13
packages/react-server-dom-turbopack/server.js
vendored
Normal 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.',
|
||||
);
|
||||
10
packages/react-server-dom-turbopack/server.node.js
Normal file
10
packages/react-server-dom-turbopack/server.node.js
Normal 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/ReactFlightDOMServerNode';
|
||||
10
packages/react-server-dom-turbopack/server.node.unbundled.js
Normal file
10
packages/react-server-dom-turbopack/server.node.unbundled.js
Normal 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/ReactFlightDOMServerNode';
|
||||
162
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerNode.js
vendored
Normal file
162
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerNode.js
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* 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,
|
||||
FulfilledThenable,
|
||||
RejectedThenable,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import type {ImportMetadata} from './shared/ReactFlightImportMetadata';
|
||||
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import {
|
||||
ID,
|
||||
CHUNKS,
|
||||
NAME,
|
||||
isAsyncImport,
|
||||
} from './shared/ReactFlightImportMetadata';
|
||||
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
export type SSRModuleMap = {
|
||||
[clientId: string]: {
|
||||
[clientExportName: string]: ClientReference<any>,
|
||||
},
|
||||
};
|
||||
|
||||
export type ServerManifest = void;
|
||||
|
||||
export type ServerReferenceId = string;
|
||||
|
||||
export opaque type ClientReferenceMetadata = ImportMetadata;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export opaque type ClientReference<T> = {
|
||||
specifier: string,
|
||||
name: string,
|
||||
async?: boolean,
|
||||
};
|
||||
|
||||
// The reason this function needs to defined here in this file instead of just
|
||||
// being exported directly from the WebpackDestination... file is because the
|
||||
// ClientReferenceMetadata is opaque and we can't unwrap it there.
|
||||
// This should get inlined and we could also just implement an unwrapping function
|
||||
// though that risks it getting used in places it shouldn't be. This is unfortunate
|
||||
// but currently it seems to be the best option we have.
|
||||
export function prepareDestinationForModule(
|
||||
moduleLoading: ModuleLoading,
|
||||
nonce: ?string,
|
||||
metadata: ClientReferenceMetadata,
|
||||
) {
|
||||
prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
|
||||
}
|
||||
|
||||
export function resolveClientReference<T>(
|
||||
bundlerConfig: SSRModuleMap,
|
||||
metadata: ClientReferenceMetadata,
|
||||
): ClientReference<T> {
|
||||
const moduleExports = bundlerConfig[metadata[ID]];
|
||||
let resolvedModuleData = moduleExports[metadata[NAME]];
|
||||
let name;
|
||||
if (resolvedModuleData) {
|
||||
// The potentially aliased name.
|
||||
name = resolvedModuleData.name;
|
||||
} else {
|
||||
// If we don't have this specific name, we might have the full module.
|
||||
resolvedModuleData = moduleExports['*'];
|
||||
if (!resolvedModuleData) {
|
||||
throw new Error(
|
||||
'Could not find the module "' +
|
||||
metadata[ID] +
|
||||
'" in the React SSR Manifest. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
name = metadata[NAME];
|
||||
}
|
||||
return {
|
||||
specifier: resolvedModuleData.specifier,
|
||||
name: name,
|
||||
async: isAsyncImport(metadata),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveServerReference<T>(
|
||||
bundlerConfig: ServerManifest,
|
||||
id: ServerReferenceId,
|
||||
): ClientReference<T> {
|
||||
const idx = id.lastIndexOf('#');
|
||||
const specifier = id.slice(0, idx);
|
||||
const name = id.slice(idx + 1);
|
||||
return {specifier, name};
|
||||
}
|
||||
|
||||
const asyncModuleCache: Map<string, Thenable<any>> = new Map();
|
||||
|
||||
export function preloadModule<T>(
|
||||
metadata: ClientReference<T>,
|
||||
): null | Thenable<any> {
|
||||
const existingPromise = asyncModuleCache.get(metadata.specifier);
|
||||
if (existingPromise) {
|
||||
if (existingPromise.status === 'fulfilled') {
|
||||
return null;
|
||||
}
|
||||
return existingPromise;
|
||||
} else {
|
||||
// $FlowFixMe[unsupported-syntax]
|
||||
let modulePromise: Promise<T> = import(metadata.specifier);
|
||||
if (metadata.async) {
|
||||
// If the module is async, it must have been a CJS module.
|
||||
// CJS modules are accessed through the default export in
|
||||
// Node.js so we have to get the default export to get the
|
||||
// full module exports.
|
||||
modulePromise = modulePromise.then(function (value) {
|
||||
return (value: any).default;
|
||||
});
|
||||
}
|
||||
modulePromise.then(
|
||||
value => {
|
||||
const fulfilledThenable: FulfilledThenable<mixed> =
|
||||
(modulePromise: any);
|
||||
fulfilledThenable.status = 'fulfilled';
|
||||
fulfilledThenable.value = value;
|
||||
},
|
||||
reason => {
|
||||
const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
|
||||
rejectedThenable.status = 'rejected';
|
||||
rejectedThenable.reason = reason;
|
||||
},
|
||||
);
|
||||
asyncModuleCache.set(metadata.specifier, modulePromise);
|
||||
return modulePromise;
|
||||
}
|
||||
}
|
||||
|
||||
export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
let moduleExports;
|
||||
// We assume that preloadModule has been called before, which
|
||||
// should have added something to the module cache.
|
||||
const promise: any = asyncModuleCache.get(metadata.specifier);
|
||||
if (promise.status === 'fulfilled') {
|
||||
moduleExports = promise.value;
|
||||
} else {
|
||||
throw promise.reason;
|
||||
}
|
||||
if (metadata.name === '*') {
|
||||
// This is a placeholder value that represents that the caller imported this
|
||||
// as a CommonJS module as is.
|
||||
return moduleExports;
|
||||
}
|
||||
if (metadata.name === '') {
|
||||
// This is a placeholder value that represents that the caller accessed the
|
||||
// default property of this if it was an ESM interop module.
|
||||
return moduleExports.default;
|
||||
}
|
||||
return moduleExports[metadata.name];
|
||||
}
|
||||
232
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack.js
vendored
Normal file
232
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack.js
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* 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,
|
||||
FulfilledThenable,
|
||||
RejectedThenable,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import type {
|
||||
ImportMetadata,
|
||||
ImportManifestEntry,
|
||||
} from './shared/ReactFlightImportMetadata';
|
||||
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import {
|
||||
ID,
|
||||
CHUNKS,
|
||||
NAME,
|
||||
isAsyncImport,
|
||||
} from './shared/ReactFlightImportMetadata';
|
||||
|
||||
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
export type SSRModuleMap = null | {
|
||||
[clientId: string]: {
|
||||
[clientExportName: string]: ClientReferenceManifestEntry,
|
||||
},
|
||||
};
|
||||
|
||||
export type ServerManifest = {
|
||||
[id: string]: ImportManifestEntry,
|
||||
};
|
||||
|
||||
export type ServerReferenceId = string;
|
||||
|
||||
export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
|
||||
export opaque type ClientReferenceMetadata = ImportMetadata;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export opaque type ClientReference<T> = ClientReferenceMetadata;
|
||||
|
||||
// The reason this function needs to defined here in this file instead of just
|
||||
// being exported directly from the TurbopackDestination... file is because the
|
||||
// ClientReferenceMetadata is opaque and we can't unwrap it there.
|
||||
// This should get inlined and we could also just implement an unwrapping function
|
||||
// though that risks it getting used in places it shouldn't be. This is unfortunate
|
||||
// but currently it seems to be the best option we have.
|
||||
export function prepareDestinationForModule(
|
||||
moduleLoading: ModuleLoading,
|
||||
nonce: ?string,
|
||||
metadata: ClientReferenceMetadata,
|
||||
) {
|
||||
prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
|
||||
}
|
||||
|
||||
export function resolveClientReference<T>(
|
||||
bundlerConfig: SSRModuleMap,
|
||||
metadata: ClientReferenceMetadata,
|
||||
): ClientReference<T> {
|
||||
if (bundlerConfig) {
|
||||
const moduleExports = bundlerConfig[metadata[ID]];
|
||||
let resolvedModuleData = moduleExports[metadata[NAME]];
|
||||
let name;
|
||||
if (resolvedModuleData) {
|
||||
// The potentially aliased name.
|
||||
name = resolvedModuleData.name;
|
||||
} else {
|
||||
// If we don't have this specific name, we might have the full module.
|
||||
resolvedModuleData = moduleExports['*'];
|
||||
if (!resolvedModuleData) {
|
||||
throw new Error(
|
||||
'Could not find the module "' +
|
||||
metadata[ID] +
|
||||
'" in the React SSR Manifest. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
name = metadata[NAME];
|
||||
}
|
||||
if (isAsyncImport(metadata)) {
|
||||
return [
|
||||
resolvedModuleData.id,
|
||||
resolvedModuleData.chunks,
|
||||
name,
|
||||
1 /* async */,
|
||||
];
|
||||
} else {
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function resolveServerReference<T>(
|
||||
bundlerConfig: ServerManifest,
|
||||
id: ServerReferenceId,
|
||||
): ClientReference<T> {
|
||||
let name = '';
|
||||
let resolvedModuleData = bundlerConfig[id];
|
||||
if (resolvedModuleData) {
|
||||
// The potentially aliased name.
|
||||
name = resolvedModuleData.name;
|
||||
} else {
|
||||
// We didn't find this specific export name but we might have the * export
|
||||
// which contains this name as well.
|
||||
// TODO: It's unfortunate that we now have to parse this string. We should
|
||||
// probably go back to encoding path and name separately on the client reference.
|
||||
const idx = id.lastIndexOf('#');
|
||||
if (idx !== -1) {
|
||||
name = id.slice(idx + 1);
|
||||
resolvedModuleData = bundlerConfig[id.slice(0, idx)];
|
||||
}
|
||||
if (!resolvedModuleData) {
|
||||
throw new Error(
|
||||
'Could not find the module "' +
|
||||
id +
|
||||
'" in the React Server Manifest. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: This needs to return async: true if it's an async module.
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
|
||||
}
|
||||
|
||||
// The chunk cache contains all the chunks we've preloaded so far.
|
||||
// If they're still pending they're a thenable. This map also exists
|
||||
// in Turbopack but unfortunately it's not exposed so we have to
|
||||
// replicate it in user space. null means that it has already loaded.
|
||||
const chunkCache: Map<string, null | Promise<any>> = new Map();
|
||||
|
||||
function requireAsyncModule(id: string): null | Thenable<any> {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
const promise = __turbopack_require__(id);
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
return null;
|
||||
} else if (promise.status === 'fulfilled') {
|
||||
// This module was already resolved earlier.
|
||||
return null;
|
||||
} else {
|
||||
// Instrument the Promise to stash the result.
|
||||
promise.then(
|
||||
value => {
|
||||
const fulfilledThenable: FulfilledThenable<mixed> = (promise: any);
|
||||
fulfilledThenable.status = 'fulfilled';
|
||||
fulfilledThenable.value = value;
|
||||
},
|
||||
reason => {
|
||||
const rejectedThenable: RejectedThenable<mixed> = (promise: any);
|
||||
rejectedThenable.status = 'rejected';
|
||||
rejectedThenable.reason = reason;
|
||||
},
|
||||
);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
function ignoreReject() {
|
||||
// We rely on rejected promises to be handled by another listener.
|
||||
}
|
||||
// Start preloading the modules since we might need them soon.
|
||||
// This function doesn't suspend.
|
||||
export function preloadModule<T>(
|
||||
metadata: ClientReference<T>,
|
||||
): null | Thenable<any> {
|
||||
const chunks = metadata[CHUNKS];
|
||||
const promises = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunkFilename = chunks[i];
|
||||
const entry = chunkCache.get(chunkFilename);
|
||||
if (entry === undefined) {
|
||||
const thenable = loadChunk(chunkFilename);
|
||||
promises.push(thenable);
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const resolve = chunkCache.set.bind(chunkCache, chunkFilename, null);
|
||||
thenable.then(resolve, ignoreReject);
|
||||
chunkCache.set(chunkFilename, thenable);
|
||||
} else if (entry !== null) {
|
||||
promises.push(entry);
|
||||
}
|
||||
}
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (promises.length === 0) {
|
||||
return requireAsyncModule(metadata[ID]);
|
||||
} else {
|
||||
return Promise.all(promises).then(() => {
|
||||
return requireAsyncModule(metadata[ID]);
|
||||
});
|
||||
}
|
||||
} else if (promises.length > 0) {
|
||||
return Promise.all(promises);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Actually require the module or suspend if it's not yet ready.
|
||||
// Increase priority if necessary.
|
||||
export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
let moduleExports = __turbopack_require__(metadata[ID]);
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
} else if (moduleExports.status === 'fulfilled') {
|
||||
// This Promise should've been instrumented by preloadModule.
|
||||
moduleExports = moduleExports.value;
|
||||
} else {
|
||||
throw moduleExports.reason;
|
||||
}
|
||||
}
|
||||
if (metadata[NAME] === '*') {
|
||||
// This is a placeholder value that represents that the caller imported this
|
||||
// as a CommonJS module as is.
|
||||
return moduleExports;
|
||||
}
|
||||
if (metadata[NAME] === '') {
|
||||
// This is a placeholder value that represents that the caller accessed the
|
||||
// default property of this if it was an ESM interop module.
|
||||
return moduleExports.__esModule ? moduleExports.default : moduleExports;
|
||||
}
|
||||
return moduleExports[metadata[NAME]];
|
||||
}
|
||||
12
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser.js
vendored
Normal file
12
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser.js
vendored
Normal 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
|
||||
*/
|
||||
|
||||
export function loadChunk(filename: string): Promise<mixed> {
|
||||
return __turbopack_load__(filename);
|
||||
}
|
||||
12
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer.js
vendored
Normal file
12
packages/react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer.js
vendored
Normal 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
|
||||
*/
|
||||
|
||||
export function loadChunk(filename: string): Promise<mixed> {
|
||||
return __turbopack_load__(filename);
|
||||
}
|
||||
18
packages/react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackBrowser.js
vendored
Normal file
18
packages/react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackBrowser.js
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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 type ModuleLoading = null;
|
||||
|
||||
export function prepareDestinationWithChunks(
|
||||
moduleLoading: ModuleLoading,
|
||||
chunks: mixed,
|
||||
nonce: ?string,
|
||||
) {
|
||||
// In the browser we don't need to prepare our destination since the browser is the Destination
|
||||
}
|
||||
32
packages/react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer.js
vendored
Normal file
32
packages/react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer.js
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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 {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
export type ModuleLoading = null | {
|
||||
prefix: string,
|
||||
crossOrigin?: 'use-credentials' | '',
|
||||
};
|
||||
|
||||
export function prepareDestinationWithChunks(
|
||||
moduleLoading: ModuleLoading,
|
||||
// Chunks are single-indexed filenames
|
||||
chunks: Array<string>,
|
||||
nonce: ?string,
|
||||
) {
|
||||
if (moduleLoading !== null) {
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
preinitScriptForSSR(
|
||||
moduleLoading.prefix + chunks[i],
|
||||
nonce,
|
||||
moduleLoading.crossOrigin,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
packages/react-server-dom-turbopack/src/ReactFlightDOMClientBrowser.js
vendored
Normal file
111
packages/react-server-dom-turbopack/src/ReactFlightDOMClientBrowser.js
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* 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 {
|
||||
createResponse,
|
||||
getRoot,
|
||||
reportGlobalError,
|
||||
processBinaryChunk,
|
||||
close,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import {
|
||||
processReply,
|
||||
createServerReference,
|
||||
} from 'react-client/src/ReactFlightReplyClient';
|
||||
|
||||
type CallServerCallback = <A, T>(string, args: A) => Promise<T>;
|
||||
|
||||
export type Options = {
|
||||
callServer?: CallServerCallback,
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options: void | Options) {
|
||||
return createResponse(
|
||||
null,
|
||||
null,
|
||||
options && options.callServer ? options.callServer : undefined,
|
||||
undefined, // nonce
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function createFromReadableStream<T>(
|
||||
stream: ReadableStream,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
startReadingFromStream(response, stream);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function encodeReply(
|
||||
value: ReactServerValue,
|
||||
): Promise<
|
||||
string | URLSearchParams | FormData,
|
||||
> /* We don't use URLSearchParams yet but maybe */ {
|
||||
return new Promise((resolve, reject) => {
|
||||
processReply(value, '', resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
createFromFetch,
|
||||
createFromReadableStream,
|
||||
encodeReply,
|
||||
createServerReference,
|
||||
};
|
||||
115
packages/react-server-dom-turbopack/src/ReactFlightDOMClientEdge.js
vendored
Normal file
115
packages/react-server-dom-turbopack/src/ReactFlightDOMClientEdge.js
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* 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 {
|
||||
SSRModuleMap,
|
||||
ModuleLoading,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
type SSRManifest = {
|
||||
moduleMap: SSRModuleMap,
|
||||
moduleLoading: ModuleLoading,
|
||||
};
|
||||
|
||||
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: any,
|
||||
callServer: any,
|
||||
): (...A) => Promise<T> {
|
||||
return createServerReferenceImpl(id, noServerCall);
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
ssrManifest: SSRManifest,
|
||||
nonce?: string,
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options: Options) {
|
||||
return createResponse(
|
||||
options.ssrManifest.moduleMap,
|
||||
options.ssrManifest.moduleLoading,
|
||||
noServerCall,
|
||||
typeof options.nonce === 'string' ? options.nonce : 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);
|
||||
}
|
||||
|
||||
function createFromReadableStream<T>(
|
||||
stream: ReadableStream,
|
||||
options: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
startReadingFromStream(response, stream);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
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 {createFromFetch, createFromReadableStream};
|
||||
75
packages/react-server-dom-turbopack/src/ReactFlightDOMClientNode.js
vendored
Normal file
75
packages/react-server-dom-turbopack/src/ReactFlightDOMClientNode.js
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* 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} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {
|
||||
SSRModuleMap,
|
||||
ModuleLoading,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
type SSRManifest = {
|
||||
moduleMap: SSRModuleMap,
|
||||
moduleLoading: ModuleLoading,
|
||||
};
|
||||
|
||||
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 type Options = {
|
||||
nonce?: string,
|
||||
};
|
||||
|
||||
export function createServerReference<A: Iterable<any>, T>(
|
||||
id: any,
|
||||
callServer: any,
|
||||
): (...A) => Promise<T> {
|
||||
return createServerReferenceImpl(id, noServerCall);
|
||||
}
|
||||
|
||||
function createFromNodeStream<T>(
|
||||
stream: Readable,
|
||||
ssrManifest: SSRManifest,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const response: Response = createResponse(
|
||||
ssrManifest.moduleMap,
|
||||
ssrManifest.moduleLoading,
|
||||
noServerCall,
|
||||
options && typeof options.nonce === 'string' ? options.nonce : undefined,
|
||||
);
|
||||
stream.on('data', chunk => {
|
||||
processBinaryChunk(response, chunk);
|
||||
});
|
||||
stream.on('error', error => {
|
||||
reportGlobalError(response, error);
|
||||
});
|
||||
stream.on('end', () => close(response));
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {createFromNodeStream};
|
||||
100
packages/react-server-dom-turbopack/src/ReactFlightDOMServerBrowser.js
vendored
Normal file
100
packages/react-server-dom-turbopack/src/ReactFlightDOMServerBrowser.js
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* 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 {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
|
||||
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
|
||||
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
abort,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
close,
|
||||
getRoot,
|
||||
} from 'react-server/src/ReactFlightReplyServer';
|
||||
|
||||
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
|
||||
|
||||
export {
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
} from './ReactFlightTurbopackReferences';
|
||||
|
||||
type Options = {
|
||||
identifierPrefix?: string,
|
||||
signal?: AbortSignal,
|
||||
context?: Array<[string, ServerContextJSONValue]>,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
};
|
||||
|
||||
function renderToReadableStream(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): ReadableStream {
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.context : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : 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> => {},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
return stream;
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
turbopackMap: ServerManifest,
|
||||
): Thenable<T> {
|
||||
if (typeof body === 'string') {
|
||||
const form = new FormData();
|
||||
form.append('0', body);
|
||||
body = form;
|
||||
}
|
||||
const response = createResponse(turbopackMap, '', body);
|
||||
close(response);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction};
|
||||
100
packages/react-server-dom-turbopack/src/ReactFlightDOMServerEdge.js
vendored
Normal file
100
packages/react-server-dom-turbopack/src/ReactFlightDOMServerEdge.js
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* 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 {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
|
||||
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
|
||||
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
abort,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
close,
|
||||
getRoot,
|
||||
} from 'react-server/src/ReactFlightReplyServer';
|
||||
|
||||
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
|
||||
|
||||
export {
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
} from './ReactFlightTurbopackReferences';
|
||||
|
||||
type Options = {
|
||||
identifierPrefix?: string,
|
||||
signal?: AbortSignal,
|
||||
context?: Array<[string, ServerContextJSONValue]>,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
};
|
||||
|
||||
function renderToReadableStream(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): ReadableStream {
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.context : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : 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> => {},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
return stream;
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
turbopackMap: ServerManifest,
|
||||
): Thenable<T> {
|
||||
if (typeof body === 'string') {
|
||||
const form = new FormData();
|
||||
form.append('0', body);
|
||||
body = form;
|
||||
}
|
||||
const response = createResponse(turbopackMap, '', body);
|
||||
close(response);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction};
|
||||
170
packages/react-server-dom-turbopack/src/ReactFlightDOMServerNode.js
vendored
Normal file
170
packages/react-server-dom-turbopack/src/ReactFlightDOMServerNode.js
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* 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 {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
|
||||
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
|
||||
import type {Busboy} from 'busboy';
|
||||
import type {Writable} from 'stream';
|
||||
import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
abort,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
reportGlobalError,
|
||||
close,
|
||||
resolveField,
|
||||
resolveFileInfo,
|
||||
resolveFileChunk,
|
||||
resolveFileComplete,
|
||||
getRoot,
|
||||
} from 'react-server/src/ReactFlightReplyServer';
|
||||
|
||||
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
|
||||
|
||||
export {
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
} from './ReactFlightTurbopackReferences';
|
||||
|
||||
function createDrainHandler(destination: Destination, request: Request) {
|
||||
return () => startFlowing(request, destination);
|
||||
}
|
||||
|
||||
type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
context?: Array<[string, ServerContextJSONValue]>,
|
||||
identifierPrefix?: string,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
abort(reason: mixed): void,
|
||||
pipe<T: Writable>(destination: T): T,
|
||||
};
|
||||
|
||||
function renderToPipeableStream(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): PipeableStream {
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.context : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : 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));
|
||||
return destination;
|
||||
},
|
||||
abort(reason: mixed) {
|
||||
abort(request, reason);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function decodeReplyFromBusboy<T>(
|
||||
busboyStream: Busboy,
|
||||
turbopackMap: ServerManifest,
|
||||
): Thenable<T> {
|
||||
const response = createResponse(turbopackMap, '');
|
||||
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);
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
turbopackMap: ServerManifest,
|
||||
): Thenable<T> {
|
||||
if (typeof body === 'string') {
|
||||
const form = new FormData();
|
||||
form.append('0', body);
|
||||
body = form;
|
||||
}
|
||||
const response = createResponse(turbopackMap, '', body);
|
||||
close(response);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
};
|
||||
93
packages/react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler.js
vendored
Normal file
93
packages/react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler.js
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* 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,
|
||||
ImportManifestEntry,
|
||||
} from './shared/ReactFlightImportMetadata';
|
||||
|
||||
import type {
|
||||
ClientReference,
|
||||
ServerReference,
|
||||
} from './ReactFlightTurbopackReferences';
|
||||
|
||||
export type {ClientReference, ServerReference};
|
||||
|
||||
export type ClientManifest = {
|
||||
[id: string]: ClientReferenceManifestEntry,
|
||||
};
|
||||
|
||||
export type ServerReferenceId = string;
|
||||
|
||||
export type ClientReferenceMetadata = ImportMetadata;
|
||||
export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
|
||||
|
||||
export type ClientReferenceKey = string;
|
||||
|
||||
export {
|
||||
isClientReference,
|
||||
isServerReference,
|
||||
} from './ReactFlightTurbopackReferences';
|
||||
|
||||
export function getClientReferenceKey(
|
||||
reference: ClientReference<any>,
|
||||
): ClientReferenceKey {
|
||||
return reference.$$async ? reference.$$id + '#async' : reference.$$id;
|
||||
}
|
||||
|
||||
export function resolveClientReferenceMetadata<T>(
|
||||
config: ClientManifest,
|
||||
clientReference: ClientReference<T>,
|
||||
): ClientReferenceMetadata {
|
||||
const modulePath = clientReference.$$id;
|
||||
let name = '';
|
||||
let resolvedModuleData = config[modulePath];
|
||||
if (resolvedModuleData) {
|
||||
// The potentially aliased name.
|
||||
name = resolvedModuleData.name;
|
||||
} else {
|
||||
// We didn't find this specific export name but we might have the * export
|
||||
// which contains this name as well.
|
||||
// TODO: It's unfortunate that we now have to parse this string. We should
|
||||
// probably go back to encoding path and name separately on the client reference.
|
||||
const idx = modulePath.lastIndexOf('#');
|
||||
if (idx !== -1) {
|
||||
name = modulePath.slice(idx + 1);
|
||||
resolvedModuleData = config[modulePath.slice(0, idx)];
|
||||
}
|
||||
if (!resolvedModuleData) {
|
||||
throw new Error(
|
||||
'Could not find the module "' +
|
||||
modulePath +
|
||||
'" in the React Client Manifest. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (clientReference.$$async === true) {
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
|
||||
} else {
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
483
packages/react-server-dom-turbopack/src/ReactFlightTurbopackNodeLoader.js
vendored
Normal file
483
packages/react-server-dom-turbopack/src/ReactFlightTurbopackNodeLoader.js
vendored
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
110
packages/react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister.js
vendored
Normal file
110
packages/react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister.js
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* 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) {
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
256
packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
vendored
Normal file
256
packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* 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>,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export type ClientReference<T> = {
|
||||
$$typeof: symbol,
|
||||
$$id: string,
|
||||
$$async: boolean,
|
||||
};
|
||||
|
||||
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 registerClientReference<T>(
|
||||
proxyImplementation: any,
|
||||
id: string,
|
||||
exportName: string,
|
||||
): ClientReference<T> {
|
||||
return registerClientReferenceImpl(
|
||||
proxyImplementation,
|
||||
id + '#' + exportName,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
function registerClientReferenceImpl<T>(
|
||||
proxyImplementation: any,
|
||||
id: string,
|
||||
async: boolean,
|
||||
): ClientReference<T> {
|
||||
return Object.defineProperties(proxyImplementation, {
|
||||
$$typeof: {value: CLIENT_REFERENCE_TAG},
|
||||
$$id: {value: id},
|
||||
$$async: {value: async},
|
||||
});
|
||||
}
|
||||
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const FunctionBind = Function.prototype.bind;
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const ArraySlice = Array.prototype.slice;
|
||||
function bind(this: ServerReference<any>) {
|
||||
// $FlowFixMe[unsupported-syntax]
|
||||
const newFn = FunctionBind.apply(this, arguments);
|
||||
if (this.$$typeof === SERVER_REFERENCE_TAG) {
|
||||
const args = ArraySlice.call(arguments, 1);
|
||||
newFn.$$typeof = SERVER_REFERENCE_TAG;
|
||||
newFn.$$id = this.$$id;
|
||||
newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args;
|
||||
}
|
||||
return newFn;
|
||||
}
|
||||
|
||||
export function registerServerReference<T>(
|
||||
reference: ServerReference<T>,
|
||||
id: string,
|
||||
exportName: null | string,
|
||||
): ServerReference<T> {
|
||||
return Object.defineProperties((reference: any), {
|
||||
$$typeof: {value: SERVER_REFERENCE_TAG},
|
||||
$$id: {value: exportName === null ? id : id + '#' + exportName},
|
||||
$$bound: {value: null},
|
||||
bind: {value: bind},
|
||||
});
|
||||
}
|
||||
|
||||
const PROMISE_PROTOTYPE = Promise.prototype;
|
||||
|
||||
const deepProxyHandlers = {
|
||||
get: function (target: Function, name: string, receiver: Proxy<Function>) {
|
||||
switch (name) {
|
||||
// These names are read by the Flight runtime if you end up using the exports object.
|
||||
case '$$typeof':
|
||||
// These names are a little too common. We should probably have a way to
|
||||
// have the Flight runtime extract the inner target instead.
|
||||
return target.$$typeof;
|
||||
case '$$id':
|
||||
return target.$$id;
|
||||
case '$$async':
|
||||
return target.$$async;
|
||||
case 'name':
|
||||
return target.name;
|
||||
case 'displayName':
|
||||
return undefined;
|
||||
// We need to special case this because createElement reads it if we pass this
|
||||
// reference.
|
||||
case 'defaultProps':
|
||||
return undefined;
|
||||
// Avoid this attempting to be serialized.
|
||||
case 'toJSON':
|
||||
return undefined;
|
||||
case Symbol.toPrimitive:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toPrimitive];
|
||||
case 'Provider':
|
||||
throw new Error(
|
||||
`Cannot render a Client Context Provider on the Server. ` +
|
||||
`Instead, you can export a Client Component wrapper ` +
|
||||
`that itself renders a Client Context Provider.`,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
const expression = String(target.name) + '.' + String(name);
|
||||
throw new Error(
|
||||
`Cannot access ${expression} on the server. ` +
|
||||
'You cannot dot into a client module from a server component. ' +
|
||||
'You can only pass the imported name through.',
|
||||
);
|
||||
},
|
||||
set: function () {
|
||||
throw new Error('Cannot assign to a client module from a server module.');
|
||||
},
|
||||
};
|
||||
|
||||
const proxyHandlers = {
|
||||
get: function (
|
||||
target: Function,
|
||||
name: string,
|
||||
receiver: Proxy<Function>,
|
||||
): $FlowFixMe {
|
||||
switch (name) {
|
||||
// These names are read by the Flight runtime if you end up using the exports object.
|
||||
case '$$typeof':
|
||||
return target.$$typeof;
|
||||
case '$$id':
|
||||
return target.$$id;
|
||||
case '$$async':
|
||||
return target.$$async;
|
||||
case 'name':
|
||||
return target.name;
|
||||
// We need to special case this because createElement reads it if we pass this
|
||||
// reference.
|
||||
case 'defaultProps':
|
||||
return undefined;
|
||||
// Avoid this attempting to be serialized.
|
||||
case 'toJSON':
|
||||
return undefined;
|
||||
case Symbol.toPrimitive:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toPrimitive];
|
||||
case '__esModule':
|
||||
// Something is conditionally checking which export to use. We'll pretend to be
|
||||
// an ESM compat module but then we'll check again on the client.
|
||||
const moduleId = target.$$id;
|
||||
target.default = registerClientReferenceImpl(
|
||||
(function () {
|
||||
throw new Error(
|
||||
`Attempted to call the default export of ${moduleId} 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.`,
|
||||
);
|
||||
}: any),
|
||||
target.$$id + '#',
|
||||
target.$$async,
|
||||
);
|
||||
return true;
|
||||
case 'then':
|
||||
if (target.then) {
|
||||
// Use a cached value
|
||||
return target.then;
|
||||
}
|
||||
if (!target.$$async) {
|
||||
// If this module is expected to return a Promise (such as an AsyncModule) then
|
||||
// we should resolve that with a client reference that unwraps the Promise on
|
||||
// the client.
|
||||
|
||||
const clientReference: ClientReference<any> =
|
||||
registerClientReferenceImpl(({}: any), target.$$id, true);
|
||||
const proxy = new Proxy(clientReference, proxyHandlers);
|
||||
|
||||
// Treat this as a resolved Promise for React's use()
|
||||
target.status = 'fulfilled';
|
||||
target.value = proxy;
|
||||
|
||||
const then = (target.then = registerClientReferenceImpl(
|
||||
(function then(resolve, reject: any) {
|
||||
// Expose to React.
|
||||
return Promise.resolve(resolve(proxy));
|
||||
}: any),
|
||||
// If this is not used as a Promise but is treated as a reference to a `.then`
|
||||
// export then we should treat it as a reference to that name.
|
||||
target.$$id + '#then',
|
||||
false,
|
||||
));
|
||||
return then;
|
||||
} else {
|
||||
// Since typeof .then === 'function' is a feature test we'd continue recursing
|
||||
// indefinitely if we return a function. Instead, we return an object reference
|
||||
// if we check further.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
let cachedReference = target[name];
|
||||
if (!cachedReference) {
|
||||
const reference: ClientReference<any> = registerClientReferenceImpl(
|
||||
(function () {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
`Attempted to call ${String(name)}() from the server but ${String(
|
||||
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.`,
|
||||
);
|
||||
}: any),
|
||||
target.$$id + '#' + name,
|
||||
target.$$async,
|
||||
);
|
||||
Object.defineProperty((reference: any), 'name', {value: name});
|
||||
cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
|
||||
}
|
||||
return cachedReference;
|
||||
},
|
||||
getPrototypeOf(target: Function): Object {
|
||||
// Pretend to be a Promise in case anyone asks.
|
||||
return PROMISE_PROTOTYPE;
|
||||
},
|
||||
set: function (): empty {
|
||||
throw new Error('Cannot assign to a client module from a server module.');
|
||||
},
|
||||
};
|
||||
|
||||
export function createClientModuleProxy<T>(
|
||||
moduleId: string,
|
||||
): ClientReference<T> {
|
||||
const clientReference: ClientReference<T> = registerClientReferenceImpl(
|
||||
({}: any),
|
||||
// Represents the whole Module object instead of a particular import.
|
||||
moduleId,
|
||||
false,
|
||||
);
|
||||
return new Proxy(clientReference, proxyHandlers);
|
||||
}
|
||||
1572
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
vendored
Normal file
1572
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1228
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
vendored
Normal file
1228
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
205
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
vendored
Normal file
205
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
// 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 clientExports;
|
||||
let turbopackMap;
|
||||
let turbopackModules;
|
||||
let React;
|
||||
let ReactDOMServer;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let use;
|
||||
|
||||
describe('ReactFlightDOMEdge', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.edge'),
|
||||
);
|
||||
|
||||
const TurbopackMock = require('./utils/TurbopackMock');
|
||||
clientExports = TurbopackMock.clientExports;
|
||||
turbopackMap = TurbopackMock.turbopackMap;
|
||||
turbopackModules = TurbopackMock.turbopackModules;
|
||||
React = require('react');
|
||||
ReactDOMServer = require('react-dom/server.edge');
|
||||
ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
|
||||
ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
|
||||
use = React.use;
|
||||
});
|
||||
|
||||
function passThrough(stream) {
|
||||
// Simulate more realistic network by splitting up and rejoining some chunks.
|
||||
// This lets us test that we don't accidentally rely on particular bounds of the chunks.
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
const reader = stream.getReader();
|
||||
let prevChunk = new Uint8Array(0);
|
||||
function push() {
|
||||
reader.read().then(({done, value}) => {
|
||||
if (done) {
|
||||
controller.enqueue(prevChunk);
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
const chunk = new Uint8Array(prevChunk.length + value.length);
|
||||
chunk.set(prevChunk, 0);
|
||||
chunk.set(value, prevChunk.length);
|
||||
if (chunk.length > 50) {
|
||||
controller.enqueue(chunk.subarray(0, chunk.length - 50));
|
||||
prevChunk = chunk.subarray(chunk.length - 50);
|
||||
} else {
|
||||
prevChunk = chunk;
|
||||
}
|
||||
push();
|
||||
});
|
||||
}
|
||||
push();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function readResult(stream) {
|
||||
const reader = stream.getReader();
|
||||
let result = '';
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) {
|
||||
return result;
|
||||
}
|
||||
result += Buffer.from(value).toString('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
it('should allow an alternative module mapping to be used for SSR', async () => {
|
||||
function ClientComponent() {
|
||||
return <span>Client Component</span>;
|
||||
}
|
||||
// The Client build may not have the same IDs as the Server bundles for the same
|
||||
// component.
|
||||
const ClientComponentOnTheClient = clientExports(ClientComponent);
|
||||
const ClientComponentOnTheServer = clientExports(ClientComponent);
|
||||
|
||||
// In the SSR bundle this module won't exist. We simulate this by deleting it.
|
||||
const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
|
||||
delete turbopackModules[clientId];
|
||||
|
||||
// Instead, we have to provide a translation from the client meta data to the SSR
|
||||
// meta data.
|
||||
const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
|
||||
const translationMap = {
|
||||
[clientId]: {
|
||||
'*': ssrMetadata,
|
||||
},
|
||||
};
|
||||
|
||||
function App() {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<App />,
|
||||
turbopackMap,
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
ssrManifest: {
|
||||
moduleMap: translationMap,
|
||||
moduleLoading: null,
|
||||
},
|
||||
});
|
||||
|
||||
function ClientRoot() {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToReadableStream(
|
||||
<ClientRoot />,
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual('<span>Client Component</span>');
|
||||
});
|
||||
|
||||
it('should encode long string in a compact format', async () => {
|
||||
const testString = '"\n\t'.repeat(500) + '🙃';
|
||||
const testString2 = 'hello'.repeat(400);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream({
|
||||
text: testString,
|
||||
text2: testString2,
|
||||
});
|
||||
const [stream1, stream2] = passThrough(stream).tee();
|
||||
|
||||
const serializedContent = await readResult(stream1);
|
||||
// The content should be compact an unescaped
|
||||
expect(serializedContent.length).toBeLessThan(4000);
|
||||
expect(serializedContent).not.toContain('\\n');
|
||||
expect(serializedContent).not.toContain('\\t');
|
||||
expect(serializedContent).not.toContain('\\"');
|
||||
expect(serializedContent).toContain('\t');
|
||||
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
stream2,
|
||||
{
|
||||
ssrManifest: {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
// Should still match the result when parsed
|
||||
expect(result.text).toBe(testString);
|
||||
expect(result.text2).toBe(testString2);
|
||||
});
|
||||
|
||||
// @gate enableBinaryFlight
|
||||
it('should be able to serialize any kind of typed array', async () => {
|
||||
const buffer = new Uint8Array([
|
||||
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
|
||||
]).buffer;
|
||||
const buffers = [
|
||||
buffer,
|
||||
new Int8Array(buffer, 1),
|
||||
new Uint8Array(buffer, 2),
|
||||
new Uint8ClampedArray(buffer, 2),
|
||||
new Int16Array(buffer, 2),
|
||||
new Uint16Array(buffer, 2),
|
||||
new Int32Array(buffer, 4),
|
||||
new Uint32Array(buffer, 4),
|
||||
new Float32Array(buffer, 4),
|
||||
new Float64Array(buffer, 0),
|
||||
new BigInt64Array(buffer, 0),
|
||||
new BigUint64Array(buffer, 0),
|
||||
new DataView(buffer, 3),
|
||||
];
|
||||
const stream = passThrough(
|
||||
ReactServerDOMServer.renderToReadableStream(buffers),
|
||||
);
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
ssrManifest: {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
},
|
||||
});
|
||||
expect(result).toEqual(buffers);
|
||||
});
|
||||
});
|
||||
254
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
vendored
Normal file
254
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/**
|
||||
* 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-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.edge'),
|
||||
);
|
||||
const TurbopackMock = require('./utils/TurbopackMock');
|
||||
serverExports = TurbopackMock.serverExports;
|
||||
turbopackServerMap = TurbopackMock.turbopackServerMap;
|
||||
React = require('react');
|
||||
ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
|
||||
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);
|
||||
}
|
||||
|
||||
// @gate enableFormActions
|
||||
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');
|
||||
});
|
||||
|
||||
// @gate enableFormActions
|
||||
it('can submit an imported server action without hydrating it', async () => {
|
||||
let foo = null;
|
||||
|
||||
const ServerModule = serverExports(function action(formData) {
|
||||
foo = formData.get('foo');
|
||||
return 'hi';
|
||||
});
|
||||
const serverAction = ReactServerDOMClient.createServerReference(
|
||||
ServerModule.$$id,
|
||||
);
|
||||
function App() {
|
||||
return (
|
||||
<form action={serverAction}>
|
||||
<input type="text" name="foo" defaultValue="bar" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
await readIntoContainer(ssrStream);
|
||||
|
||||
const form = container.firstChild;
|
||||
|
||||
expect(foo).toBe(null);
|
||||
|
||||
const result = await submit(form);
|
||||
|
||||
expect(result).toBe('hi');
|
||||
|
||||
expect(foo).toBe('bar');
|
||||
});
|
||||
|
||||
// @gate enableFormActions
|
||||
it('can submit a complex closure server action without hydrating it', async () => {
|
||||
let foo = null;
|
||||
|
||||
const serverAction = serverExports(function action(bound, formData) {
|
||||
foo = formData.get('foo') + bound.complex;
|
||||
return 'hello';
|
||||
});
|
||||
function App() {
|
||||
return (
|
||||
<form action={serverAction.bind(null, {complex: 'object'})}>
|
||||
<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('barobject');
|
||||
});
|
||||
|
||||
// @gate enableFormActions
|
||||
it('can submit a multiple complex closure server action without hydrating it', async () => {
|
||||
let foo = null;
|
||||
|
||||
const serverAction = serverExports(function action(bound, formData) {
|
||||
foo = formData.get('foo') + bound.complex;
|
||||
return 'hello' + bound.complex;
|
||||
});
|
||||
function App() {
|
||||
return (
|
||||
<form action={serverAction.bind(null, {complex: 'a'})}>
|
||||
<input type="text" name="foo" defaultValue="bar" />
|
||||
<button formAction={serverAction.bind(null, {complex: 'b'})} />
|
||||
<button formAction={serverAction.bind(null, {complex: 'c'})} />
|
||||
<input
|
||||
type="submit"
|
||||
formAction={serverAction.bind(null, {complex: 'd'})}
|
||||
/>
|
||||
</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.getElementsByTagName('button')[1]);
|
||||
|
||||
expect(result).toBe('helloc');
|
||||
expect(foo).toBe('barc');
|
||||
});
|
||||
});
|
||||
256
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
vendored
Normal file
256
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
// Don't wait before processing work on the server.
|
||||
// TODO: we can replace this with FlightServer.act().
|
||||
global.setImmediate = cb => cb();
|
||||
|
||||
let clientExports;
|
||||
let turbopackMap;
|
||||
let turbopackModules;
|
||||
let turbopackModuleLoading;
|
||||
let React;
|
||||
let ReactDOMServer;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let Stream;
|
||||
let use;
|
||||
|
||||
describe('ReactFlightDOMNode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.shared-subset'));
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.node'),
|
||||
);
|
||||
ReactServerDOMServer = require('react-server-dom-turbopack/server');
|
||||
|
||||
const TurbopackMock = require('./utils/TurbopackMock');
|
||||
clientExports = TurbopackMock.clientExports;
|
||||
turbopackMap = TurbopackMock.turbopackMap;
|
||||
turbopackModules = TurbopackMock.turbopackModules;
|
||||
turbopackModuleLoading = TurbopackMock.moduleLoading;
|
||||
|
||||
jest.resetModules();
|
||||
jest.unmock('react');
|
||||
jest.unmock('react-server-dom-turbopack/server');
|
||||
jest.mock('react-server-dom-turbopack/client', () =>
|
||||
require('react-server-dom-turbopack/client.node'),
|
||||
);
|
||||
|
||||
React = require('react');
|
||||
ReactDOMServer = require('react-dom/server.node');
|
||||
ReactServerDOMClient = require('react-server-dom-turbopack/client');
|
||||
Stream = require('stream');
|
||||
use = React.use;
|
||||
});
|
||||
|
||||
function readResult(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
const writable = new Stream.PassThrough();
|
||||
writable.setEncoding('utf8');
|
||||
writable.on('data', chunk => {
|
||||
buffer += chunk;
|
||||
});
|
||||
writable.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
writable.on('end', () => {
|
||||
resolve(buffer);
|
||||
});
|
||||
stream.pipe(writable);
|
||||
});
|
||||
}
|
||||
|
||||
it('should allow an alternative module mapping to be used for SSR', async () => {
|
||||
function ClientComponent() {
|
||||
return <span>Client Component</span>;
|
||||
}
|
||||
// The Client build may not have the same IDs as the Server bundles for the same
|
||||
// component.
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
ClientComponent,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
const ClientComponentOnTheServer = clientExports(ClientComponent);
|
||||
|
||||
// In the SSR bundle this module won't exist. We simulate this by deleting it.
|
||||
const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
|
||||
delete turbopackModules[clientId];
|
||||
|
||||
// Instead, we have to provide a translation from the client meta data to the SSR
|
||||
// meta data.
|
||||
const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
|
||||
const translationMap = {
|
||||
[clientId]: {
|
||||
'*': ssrMetadata,
|
||||
},
|
||||
};
|
||||
|
||||
function App() {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
turbopackMap,
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
|
||||
stream.pipe(readable);
|
||||
|
||||
let response;
|
||||
function ClientRoot() {
|
||||
if (!response) {
|
||||
response = ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: translationMap,
|
||||
moduleLoading: turbopackModuleLoading,
|
||||
});
|
||||
}
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot />,
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual(
|
||||
'<script src="/prefix/path/to/chunk.js" async=""></script><span>Client Component</span>',
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode long string in a compact format', async () => {
|
||||
const testString = '"\n\t'.repeat(500) + '🙃';
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream({
|
||||
text: testString,
|
||||
});
|
||||
|
||||
const readable = new Stream.PassThrough();
|
||||
|
||||
const stringResult = readResult(readable);
|
||||
const parsedResult = ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: turbopackMap,
|
||||
moduleLoading: turbopackModuleLoading,
|
||||
});
|
||||
|
||||
stream.pipe(readable);
|
||||
|
||||
const serializedContent = await stringResult;
|
||||
// The content should be compact an unescaped
|
||||
expect(serializedContent.length).toBeLessThan(2000);
|
||||
expect(serializedContent).not.toContain('\\n');
|
||||
expect(serializedContent).not.toContain('\\t');
|
||||
expect(serializedContent).not.toContain('\\"');
|
||||
expect(serializedContent).toContain('\t');
|
||||
|
||||
const result = await parsedResult;
|
||||
// Should still match the result when parsed
|
||||
expect(result.text).toBe(testString);
|
||||
});
|
||||
|
||||
// @gate enableBinaryFlight
|
||||
it('should be able to serialize any kind of typed array', async () => {
|
||||
const buffer = new Uint8Array([
|
||||
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
|
||||
]).buffer;
|
||||
const buffers = [
|
||||
buffer,
|
||||
new Int8Array(buffer, 1),
|
||||
new Uint8Array(buffer, 2),
|
||||
new Uint8ClampedArray(buffer, 2),
|
||||
new Int16Array(buffer, 2),
|
||||
new Uint16Array(buffer, 2),
|
||||
new Int32Array(buffer, 4),
|
||||
new Uint32Array(buffer, 4),
|
||||
new Float32Array(buffer, 4),
|
||||
new Float64Array(buffer, 0),
|
||||
new BigInt64Array(buffer, 0),
|
||||
new BigUint64Array(buffer, 0),
|
||||
new DataView(buffer, 3),
|
||||
];
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(buffers);
|
||||
const readable = new Stream.PassThrough();
|
||||
const promise = ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: turbopackMap,
|
||||
moduleLoading: turbopackModuleLoading,
|
||||
});
|
||||
stream.pipe(readable);
|
||||
const result = await promise;
|
||||
expect(result).toEqual(buffers);
|
||||
});
|
||||
|
||||
it('should allow accept a nonce option for Flight preinitialized scripts', async () => {
|
||||
function ClientComponent() {
|
||||
return <span>Client Component</span>;
|
||||
}
|
||||
// The Client build may not have the same IDs as the Server bundles for the same
|
||||
// component.
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
ClientComponent,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
const ClientComponentOnTheServer = clientExports(ClientComponent);
|
||||
|
||||
// In the SSR bundle this module won't exist. We simulate this by deleting it.
|
||||
const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
|
||||
delete turbopackModules[clientId];
|
||||
|
||||
// Instead, we have to provide a translation from the client meta data to the SSR
|
||||
// meta data.
|
||||
const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
|
||||
const translationMap = {
|
||||
[clientId]: {
|
||||
'*': ssrMetadata,
|
||||
},
|
||||
};
|
||||
const ssrManifest = {
|
||||
moduleMap: translationMap,
|
||||
moduleLoading: turbopackModuleLoading,
|
||||
};
|
||||
|
||||
function App() {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
turbopackMap,
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
let response;
|
||||
|
||||
stream.pipe(readable);
|
||||
|
||||
function ClientRoot() {
|
||||
if (response) return use(response);
|
||||
response = ReactServerDOMClient.createFromNodeStream(
|
||||
readable,
|
||||
ssrManifest,
|
||||
{
|
||||
nonce: 'r4nd0m',
|
||||
},
|
||||
);
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot />,
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual(
|
||||
'<script src="/prefix/path/to/chunk.js" async="" nonce="r4nd0m"></script><span>Client Component</span>',
|
||||
);
|
||||
});
|
||||
});
|
||||
232
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
vendored
Normal file
232
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
global.TextDecoder = require('util').TextDecoder;
|
||||
|
||||
// let serverExports;
|
||||
let turbopackServerMap;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
|
||||
describe('ReactFlightDOMReply', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.browser'),
|
||||
);
|
||||
const TurbopackMock = require('./utils/TurbopackMock');
|
||||
// serverExports = TurbopackMock.serverExports;
|
||||
turbopackServerMap = TurbopackMock.turbopackServerMap;
|
||||
ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
|
||||
ReactServerDOMClient = require('react-server-dom-turbopack/client');
|
||||
});
|
||||
|
||||
// This method should exist on File but is not implemented in JSDOM
|
||||
async function arrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
return resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function () {
|
||||
return reject(reader.error);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
it('can pass undefined as a reply', async () => {
|
||||
const body = await ReactServerDOMClient.encodeReply(undefined);
|
||||
const missing = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
turbopackServerMap,
|
||||
);
|
||||
expect(missing).toBe(undefined);
|
||||
|
||||
const body2 = await ReactServerDOMClient.encodeReply({
|
||||
array: [undefined, null, undefined],
|
||||
prop: undefined,
|
||||
});
|
||||
const object = await ReactServerDOMServer.decodeReply(
|
||||
body2,
|
||||
turbopackServerMap,
|
||||
);
|
||||
expect(object.array.length).toBe(3);
|
||||
expect(object.array[0]).toBe(undefined);
|
||||
expect(object.array[1]).toBe(null);
|
||||
expect(object.array[3]).toBe(undefined);
|
||||
expect(object.prop).toBe(undefined);
|
||||
// These should really be true but our deserialization doesn't currently deal with it.
|
||||
expect('3' in object.array).toBe(false);
|
||||
expect('prop' in object).toBe(false);
|
||||
});
|
||||
|
||||
it('can pass an iterable as a reply', async () => {
|
||||
const body = await ReactServerDOMClient.encodeReply({
|
||||
[Symbol.iterator]: function* () {
|
||||
yield 'A';
|
||||
yield 'B';
|
||||
yield 'C';
|
||||
},
|
||||
});
|
||||
const iterable = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
turbopackServerMap,
|
||||
);
|
||||
const items = [];
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const item of iterable) {
|
||||
items.push(item);
|
||||
}
|
||||
expect(items).toEqual(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
it('can pass weird numbers as a reply', async () => {
|
||||
const nums = [0, -0, Infinity, -Infinity, NaN];
|
||||
const body = await ReactServerDOMClient.encodeReply(nums);
|
||||
const nums2 = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
turbopackServerMap,
|
||||
);
|
||||
|
||||
expect(nums).toEqual(nums2);
|
||||
expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
|
||||
});
|
||||
|
||||
it('can pass a BigInt as a reply', async () => {
|
||||
const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
|
||||
const n = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
|
||||
|
||||
expect(n).toEqual(90071992547409910000n);
|
||||
});
|
||||
|
||||
it('can pass FormData as a reply', async () => {
|
||||
const formData = new FormData();
|
||||
formData.set('hello', 'world');
|
||||
formData.append('list', '1');
|
||||
formData.append('list', '2');
|
||||
formData.append('list', '3');
|
||||
const typedArray = new Uint8Array([0, 1, 2, 3]);
|
||||
const blob = new Blob([typedArray]);
|
||||
formData.append('blob', blob, 'filename.blob');
|
||||
|
||||
const body = await ReactServerDOMClient.encodeReply(formData);
|
||||
const formData2 = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
turbopackServerMap,
|
||||
);
|
||||
|
||||
expect(formData2).not.toBe(formData);
|
||||
expect(Array.from(formData2).length).toBe(5);
|
||||
expect(formData2.get('hello')).toBe('world');
|
||||
expect(formData2.getAll('list')).toEqual(['1', '2', '3']);
|
||||
const blob2 = formData.get('blob');
|
||||
expect(blob2.size).toBe(4);
|
||||
expect(blob2.name).toBe('filename.blob');
|
||||
expect(blob2.type).toBe('');
|
||||
const typedArray2 = new Uint8Array(await arrayBuffer(blob2));
|
||||
expect(typedArray2).toEqual(typedArray);
|
||||
});
|
||||
|
||||
it('can pass multiple Files in FormData', async () => {
|
||||
const typedArrayA = new Uint8Array([0, 1, 2, 3]);
|
||||
const typedArrayB = new Uint8Array([4, 5]);
|
||||
const blobA = new Blob([typedArrayA]);
|
||||
const blobB = new Blob([typedArrayB]);
|
||||
const formData = new FormData();
|
||||
formData.append('filelist', 'string');
|
||||
formData.append('filelist', blobA);
|
||||
formData.append('filelist', blobB);
|
||||
|
||||
const body = await ReactServerDOMClient.encodeReply(formData);
|
||||
const formData2 = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
turbopackServerMap,
|
||||
);
|
||||
|
||||
const filelist2 = formData2.getAll('filelist');
|
||||
expect(filelist2.length).toBe(3);
|
||||
expect(filelist2[0]).toBe('string');
|
||||
const blobA2 = filelist2[1];
|
||||
expect(blobA2.size).toBe(4);
|
||||
expect(blobA2.name).toBe('blob');
|
||||
expect(blobA2.type).toBe('');
|
||||
const typedArrayA2 = new Uint8Array(await arrayBuffer(blobA2));
|
||||
expect(typedArrayA2).toEqual(typedArrayA);
|
||||
const blobB2 = filelist2[2];
|
||||
expect(blobB2.size).toBe(2);
|
||||
expect(blobB2.name).toBe('blob');
|
||||
expect(blobB2.type).toBe('');
|
||||
const typedArrayB2 = new Uint8Array(await arrayBuffer(blobB2));
|
||||
expect(typedArrayB2).toEqual(typedArrayB);
|
||||
});
|
||||
|
||||
it('can pass two independent FormData with same keys', async () => {
|
||||
const formDataA = new FormData();
|
||||
formDataA.set('greeting', 'hello');
|
||||
const formDataB = new FormData();
|
||||
formDataB.set('greeting', 'hi');
|
||||
|
||||
const body = await ReactServerDOMClient.encodeReply({
|
||||
a: formDataA,
|
||||
b: formDataB,
|
||||
});
|
||||
const {a: formDataA2, b: formDataB2} =
|
||||
await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
|
||||
|
||||
expect(Array.from(formDataA2).length).toBe(1);
|
||||
expect(Array.from(formDataB2).length).toBe(1);
|
||||
expect(formDataA2.get('greeting')).toBe('hello');
|
||||
expect(formDataB2.get('greeting')).toBe('hi');
|
||||
});
|
||||
|
||||
it('can pass a Date as a reply', async () => {
|
||||
const d = new Date(1234567890123);
|
||||
const body = await ReactServerDOMClient.encodeReply(d);
|
||||
const d2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
|
||||
|
||||
expect(d).toEqual(d2);
|
||||
expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
|
||||
});
|
||||
|
||||
it('can pass a Map as a reply', async () => {
|
||||
const objKey = {obj: 'key'};
|
||||
const m = new Map([
|
||||
['hi', {greet: 'world'}],
|
||||
[objKey, 123],
|
||||
]);
|
||||
const body = await ReactServerDOMClient.encodeReply(m);
|
||||
const m2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
|
||||
|
||||
expect(m2 instanceof Map).toBe(true);
|
||||
expect(m2.size).toBe(2);
|
||||
expect(m2.get('hi').greet).toBe('world');
|
||||
expect(m2).toEqual(m);
|
||||
});
|
||||
|
||||
it('can pass a Set as a reply', async () => {
|
||||
const objKey = {obj: 'key'};
|
||||
const s = new Set(['hi', objKey]);
|
||||
|
||||
const body = await ReactServerDOMClient.encodeReply(s);
|
||||
const s2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
|
||||
|
||||
expect(s2 instanceof Set).toBe(true);
|
||||
expect(s2.size).toBe(2);
|
||||
expect(s2.has('hi')).toBe(true);
|
||||
expect(s2).toEqual(s);
|
||||
});
|
||||
});
|
||||
148
packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js
vendored
Normal file
148
packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const url = require('url');
|
||||
const Module = require('module');
|
||||
|
||||
let turbopackModuleIdx = 0;
|
||||
const turbopackServerModules = {};
|
||||
const turbopackClientModules = {};
|
||||
const turbopackErroredModules = {};
|
||||
const turbopackServerMap = {};
|
||||
const turbopackClientMap = {};
|
||||
global.__turbopack_require__ = function (id) {
|
||||
if (turbopackErroredModules[id]) {
|
||||
throw turbopackErroredModules[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;
|
||||
|
||||
exports.turbopackMap = turbopackClientMap;
|
||||
exports.turbopackModules = turbopackClientModules;
|
||||
exports.turbopackServerMap = turbopackServerMap;
|
||||
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 = {exports: {}};
|
||||
nodeCompile.call(mod, '"use client"', idx);
|
||||
return mod.exports;
|
||||
};
|
||||
|
||||
exports.clientExports = function clientExports(moduleExports, chunkUrl) {
|
||||
const chunks = [];
|
||||
if (chunkUrl !== undefined) {
|
||||
chunks.push(chunkUrl);
|
||||
}
|
||||
const idx = '' + turbopackModuleIdx++;
|
||||
turbopackClientModules[idx] = moduleExports;
|
||||
const path = url.pathToFileURL(idx).href;
|
||||
turbopackClientMap[path] = {
|
||||
id: idx,
|
||||
chunks,
|
||||
name: '*',
|
||||
};
|
||||
// We only add this if this test is testing ESM compat.
|
||||
if ('__esModule' in moduleExports) {
|
||||
turbopackClientMap[path + '#'] = {
|
||||
id: idx,
|
||||
chunks,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
if (typeof moduleExports.then === 'function') {
|
||||
moduleExports.then(
|
||||
asyncModuleExports => {
|
||||
for (const name in asyncModuleExports) {
|
||||
turbopackClientMap[path + '#' + name] = {
|
||||
id: idx,
|
||||
chunks,
|
||||
name: name,
|
||||
};
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
if ('split' in moduleExports) {
|
||||
// If we're testing module splitting, we encode this name in a separate module id.
|
||||
const splitIdx = '' + turbopackModuleIdx++;
|
||||
turbopackClientModules[splitIdx] = {
|
||||
s: moduleExports.split,
|
||||
};
|
||||
turbopackClientMap[path + '#split'] = {
|
||||
id: splitIdx,
|
||||
chunks,
|
||||
name: 's',
|
||||
};
|
||||
}
|
||||
const mod = {exports: {}};
|
||||
nodeCompile.call(mod, '"use client"', idx);
|
||||
return mod.exports;
|
||||
};
|
||||
|
||||
// This tests server to server references. There's another case of client to server references.
|
||||
exports.serverExports = function serverExports(moduleExports) {
|
||||
const idx = '' + turbopackModuleIdx++;
|
||||
turbopackServerModules[idx] = moduleExports;
|
||||
const path = url.pathToFileURL(idx).href;
|
||||
turbopackServerMap[path] = {
|
||||
id: idx,
|
||||
chunks: [],
|
||||
name: '*',
|
||||
};
|
||||
// We only add this if this test is testing ESM compat.
|
||||
if ('__esModule' in moduleExports) {
|
||||
turbopackServerMap[path + '#'] = {
|
||||
id: idx,
|
||||
chunks: [],
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
if ('split' in moduleExports) {
|
||||
// If we're testing module splitting, we encode this name in a separate module id.
|
||||
const splitIdx = '' + turbopackModuleIdx++;
|
||||
turbopackServerModules[splitIdx] = {
|
||||
s: moduleExports.split,
|
||||
};
|
||||
turbopackServerMap[path + '#split'] = {
|
||||
id: splitIdx,
|
||||
chunks: [],
|
||||
name: 's',
|
||||
};
|
||||
}
|
||||
const mod = {exports: moduleExports};
|
||||
nodeCompile.call(mod, '"use server"', idx);
|
||||
return mod.exports;
|
||||
};
|
||||
39
packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js
vendored
Normal file
39
packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 type ImportManifestEntry = {
|
||||
id: string,
|
||||
// chunks is an array of filenames
|
||||
chunks: Array<string>,
|
||||
name: string,
|
||||
};
|
||||
|
||||
// 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,
|
||||
/* chunk filenames */ Array<string>,
|
||||
/* name */ string,
|
||||
/* async */ 1,
|
||||
]
|
||||
| [/* id */ string, /* chunk filenames */ Array<string>, /* name */ string];
|
||||
|
||||
export const ID = 0;
|
||||
export const CHUNKS = 1;
|
||||
export const NAME = 2;
|
||||
// export const ASYNC = 3;
|
||||
|
||||
// This logic is correct because currently only include the 4th tuple member
|
||||
// when the module is async. If that changes we will need to actually assert
|
||||
// the value is true. We don't index into the 4th slot because flow does not
|
||||
// like the potential out of bounds access
|
||||
export function isAsyncImport(metadata: ImportMetadata): boolean {
|
||||
return metadata.length === 4;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
|
||||
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
|
||||
|
||||
export const supportsRequestStorage = false;
|
||||
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
|
||||
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> = supportsRequestStorage
|
||||
? new AsyncLocalStorage()
|
||||
: (null: any);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
|
||||
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
|
||||
|
||||
export const supportsRequestStorage = true;
|
||||
export const requestStorage: AsyncLocalStorage<Request> =
|
||||
new AsyncLocalStorage();
|
||||
|
|
@ -78,6 +78,11 @@ declare var __webpack_require__: ((id: string) => any) & {
|
|||
u: string => string,
|
||||
};
|
||||
|
||||
declare function __turbopack_load__(id: string): Promise<mixed>;
|
||||
declare var __turbopack_require__: ((id: string) => any) & {
|
||||
u: string => string,
|
||||
};
|
||||
|
||||
declare module 'fs/promises' {
|
||||
declare var access: (path: string, mode?: number) => Promise<void>;
|
||||
declare var lstat: (
|
||||
|
|
|
|||
|
|
@ -432,6 +432,109 @@ const bundles = [
|
|||
externals: ['url', 'module', 'react-server-dom-webpack/server'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Turbopack Server *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/server.browser',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'react-dom'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/server.node',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'util', 'async_hooks', 'react-dom'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/server.node.unbundled',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'util', 'async_hooks', 'react-dom'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/server.edge',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'util', 'async_hooks', 'react-dom'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Turbopack Client *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/client.browser',
|
||||
global: 'ReactServerDOMClient',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'react-dom'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-turbopack/client.node',
|
||||
global: 'ReactServerDOMClient',
|
||||
minifyWithProdErrorCodes: false,
|
||||
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,
|
||||
entry: 'react-server-dom-turbopack/client.edge',
|
||||
global: 'ReactServerDOMClient',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
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',
|
||||
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',
|
||||
global: 'ReactFlightWebpackNodeRegister',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['url', 'module', 'react-server-dom-turbopack/server'],
|
||||
},
|
||||
|
||||
/******* React Server DOM ESM Server *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ module.exports = {
|
|||
__webpack_chunk_load__: 'readonly',
|
||||
__webpack_require__: 'readonly',
|
||||
|
||||
// Flight Turbopack
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
|
||||
// jest
|
||||
expect: 'readonly',
|
||||
jest: 'readonly',
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ module.exports = {
|
|||
__webpack_chunk_load__: 'readonly',
|
||||
__webpack_require__: 'readonly',
|
||||
|
||||
// Flight Turbopack
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
|
||||
// jest
|
||||
expect: 'readonly',
|
||||
jest: 'readonly',
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ module.exports = {
|
|||
__webpack_chunk_load__: 'readonly',
|
||||
__webpack_require__: 'readonly',
|
||||
|
||||
// Flight Turbopack
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
|
||||
// jest
|
||||
expect: 'readonly',
|
||||
jest: 'readonly',
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ module.exports = {
|
|||
__webpack_chunk_load__: 'readonly',
|
||||
__webpack_require__: 'readonly',
|
||||
|
||||
// Flight Turbopack
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
|
||||
// jest
|
||||
jest: 'readonly',
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,115 @@ module.exports = [
|
|||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-node-webpack',
|
||||
entryPoints: [
|
||||
'react-server-dom-webpack/server.node',
|
||||
'react-server-dom-webpack/client.node',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom-bindings',
|
||||
'react-dom/client',
|
||||
'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-server-dom-webpack',
|
||||
'react-server-dom-webpack/client.node',
|
||||
'react-server-dom-webpack/server',
|
||||
'react-server-dom-webpack/server.node',
|
||||
'react-server-dom-webpack/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
|
||||
'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer.js',
|
||||
'react-server-dom-webpack/node-register',
|
||||
'react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js',
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'react-interactions',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-node-turbopack',
|
||||
entryPoints: [
|
||||
'react-server-dom-turbopack/server.node.unbundled',
|
||||
'react-server-dom-turbopack/client.node.unbundled',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom-bindings',
|
||||
'react-dom/client',
|
||||
'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-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/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node.unbundled
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node.unbundled
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerNode.js',
|
||||
'react-server-dom-turbopack/node-register',
|
||||
'react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister.js',
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'react-interactions',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-node-turbopack-bundled',
|
||||
entryPoints: [
|
||||
'react-server-dom-turbopack/server.node',
|
||||
'react-server-dom-turbopack/client.node',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom-bindings',
|
||||
'react-dom/client',
|
||||
'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-server-dom-turbopack',
|
||||
'react-server-dom-turbopack/client.node',
|
||||
'react-server-dom-turbopack/server',
|
||||
'react-server-dom-turbopack/server.node',
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMServerNode.js', // react-server-dom-turbopack/server.node
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer.js',
|
||||
'react-server-dom-turbopack/node-register',
|
||||
'react-server-dom-turbopack/src/ReactFlightTurbopackNodeRegister.js',
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'react-interactions',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-bun',
|
||||
entryPoints: ['react-dom', 'react-dom/src/server/react-dom-server.bun.js'],
|
||||
|
|
@ -127,6 +236,36 @@ module.exports = [
|
|||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-browser-turbopack',
|
||||
entryPoints: [
|
||||
'react-server-dom-turbopack/client.browser',
|
||||
'react-server-dom-turbopack/server.browser',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom/client',
|
||||
'react-dom/server',
|
||||
'react-dom/server.node',
|
||||
'react-dom-bindings',
|
||||
'react-server-dom-turbopack',
|
||||
'react-server-dom-turbopack/client',
|
||||
'react-server-dom-turbopack/client.browser',
|
||||
'react-server-dom-turbopack/server.browser',
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMClientBrowser.js', // react-server-dom-turbopack/client.browser
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMServerBrowser.js', // react-server-dom-turbopack/server.browser
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser.js',
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'react-interactions',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-edge-webpack',
|
||||
entryPoints: [
|
||||
|
|
@ -163,38 +302,33 @@ module.exports = [
|
|||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-node-webpack',
|
||||
shortName: 'dom-edge-turbopack',
|
||||
entryPoints: [
|
||||
'react-server-dom-webpack/server.node',
|
||||
'react-server-dom-webpack/client.node',
|
||||
'react-server-dom-turbopack/server.edge',
|
||||
'react-server-dom-turbopack/client.edge',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom/src/ReactDOMSharedSubset.js',
|
||||
'react-dom-bindings',
|
||||
'react-dom/client',
|
||||
'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-server-dom-webpack',
|
||||
'react-server-dom-webpack/client.node',
|
||||
'react-server-dom-webpack/server',
|
||||
'react-server-dom-webpack/server.node',
|
||||
'react-server-dom-webpack/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
|
||||
'react-server-dom-webpack/src/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
|
||||
'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer.js',
|
||||
'react-server-dom-webpack/node-register',
|
||||
'react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js',
|
||||
'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-server-dom-turbopack',
|
||||
'react-server-dom-turbopack/client.edge',
|
||||
'react-server-dom-turbopack/server.edge',
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.edge
|
||||
'react-server-dom-turbopack/src/ReactFlightDOMServerEdge.js', // react-server-dom-webpack/server.edge
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer.js',
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'react-interactions',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user