Wire up bundler configs (#18334)
This allows different flight server and clients to have different configs depending on bundler to serialize and resolve modules.
Sebastian Markbåge committed
Mar 18, 2020 at 12:18 UTC
8206b4b864645881974d219ffb0233128748ba94
26 files changed
+266
-32
.eslintrc.js
+7
@@ -157,6 +157,13 @@ module.exports = {
157
nativeFabricUIManager: true,
158
},
159
},
160
+ {
161
+ files: ['packages/react-flight-dom-webpack/**/*.js'],
162
+ globals: {
163
+ '__webpack_chunk_load__': true,
164
+ '__webpack_require__': true,
165
+ },
166
+ },
167
],
168
169
globals: {
packages/react-client/src/ReactFlightClient.js
+7
@@ -9,6 +9,13 @@
9
10
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
11
12
+// import type {ModuleMetaData} from './ReactFlightClientHostConfig';
13
+
14
+// import {
15
+// preloadModule,
16
+// requireModule,
17
+// } from './ReactFlightClientHostConfig';
18
+
19
export type ReactModelRoot<T> = {|
20
model: T,
21
|};
packages/react-client/src/ReactFlightClientHostConfigNoStream.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export type StringDecoder = void;
11
+
12
+export const supportsBinaryStreams = false;
13
+
14
+export function createStringDecoder(): void {
15
+ throw new Error('Should never be called');
16
+}
17
+
18
+export function readPartialStringChunk(
19
+ decoder: StringDecoder,
20
+ buffer: Uint8Array,
21
+): string {
22
+ throw new Error('Should never be called');
23
+}
24
+
25
+export function readFinalStringChunk(
26
+ decoder: StringDecoder,
27
+ buffer: Uint8Array,
28
+): string {
29
+ throw new Error('Should never be called');
30
+}
packages/react-client/src/forks/ReactFlightClientHostConfig.custom.js
+5
@@ -24,6 +24,11 @@
24
// really an argument to a top-level wrapping function.
25
26
declare var $$$hostConfig: any;
27
+
28
+export opaque type ModuleMetaData = mixed; // eslint-disable-line no-undef
29
+export const preloadModule = $$$hostConfig.preloadModule;
30
+export const requireModule = $$$hostConfig.requireModule;
31
+
32
export opaque type Source = mixed; // eslint-disable-line no-undef
33
export opaque type StringDecoder = mixed; // eslint-disable-line no-undef
34
packages/react-client/src/forks/ReactFlightClientHostConfig.dom-browser.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientHostConfigBrowser';
11
+export * from 'react-flight-dom-webpack/src/ReactFlightClientWebpackBundlerConfig';
packages/react-client/src/forks/ReactFlightClientHostConfig.dom-relay.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from 'react-flight-dom-relay/src/ReactFlightDOMRelayClientHostConfig';
11
+export * from '../ReactFlightClientHostConfigNoStream';
packages/react-client/src/forks/ReactFlightClientHostConfig.dom.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientHostConfigBrowser';
11
+export * from 'react-flight-dom-webpack/src/ReactFlightClientWebpackBundlerConfig';
packages/react-flight-dom-relay/src/ReactFlightDOMRelayClientHostConfig.js
+5
-20
@@ -7,24 +7,9 @@
7
* @flow
8
*/
9
10
-export type StringDecoder = void;
10
+export {
11
+ preloadModule,
12
+ requireModule,
13
+} from 'ReactFlightDOMRelayClientIntegration';
14
12
-export const supportsBinaryStreams = false;
13
-
14
-export function createStringDecoder(): void {
15
- throw new Error('Should never be called');
16
-}
17
-
18
-export function readPartialStringChunk(
19
- decoder: StringDecoder,
20
- buffer: Uint8Array,
21
-): string {
22
- throw new Error('Should never be called');
23
-}
24
-
25
-export function readFinalStringChunk(
26
- decoder: StringDecoder,
27
- buffer: Uint8Array,
28
-): string {
29
- throw new Error('Should never be called');
30
-}
15
+export type {ModuleMetaData} from 'ReactFlightDOMRelayClientIntegration';
packages/react-flight-dom-relay/src/ReactFlightDOMRelayServer.js
+1
-1
@@ -13,7 +13,7 @@ import type {Destination} from './ReactFlightDOMRelayServerHostConfig';
13
import {createRequest, startWork} from 'react-server/src/ReactFlightServer';
14
15
function render(model: ReactModel, destination: Destination): void {
16
- let request = createRequest(model, destination);
16
+ let request = createRequest(model, destination, undefined);
17
startWork(request);
18
}
19
packages/react-flight-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js
+25
-4
@@ -9,13 +9,34 @@
9
10
import type {Request, ReactModel} from 'react-server/src/ReactFlightServer';
11
12
-import type {Destination} from 'ReactFlightDOMRelayServerIntegration';
12
+import type {
13
+ Destination,
14
+ ModuleReference,
15
+ ModuleMetaData,
16
+} from 'ReactFlightDOMRelayServerIntegration';
17
18
import {resolveModelToJSON} from 'react-server/src/ReactFlightServer';
19
16
-import {emitModel, emitError} from 'ReactFlightDOMRelayServerIntegration';
17
-
18
-export type {Destination} from 'ReactFlightDOMRelayServerIntegration';
20
+import {
21
+ emitModel,
22
+ emitError,
23
+ resolveResourceMetaData,
24
+} from 'ReactFlightDOMRelayServerIntegration';
25
+
26
+export type {
27
+ Destination,
28
+ ModuleReference,
29
+ ModuleMetaData,
30
+} from 'ReactFlightDOMRelayServerIntegration';
31
+
32
+export type BundlerConfig = void;
33
+
34
+export function resolveModuleMetaData(
35
+ config: BundlerConfig,
36
+ resource: ModuleReference,
37
+): ModuleMetaData {
38
+ return resolveResourceMetaData(resource);
39
+}
40
41
type JSONValue =
42
| string
packages/react-flight-dom-relay/src/__mocks__/ReactFlightDOMRelayClientIntegration.js
new
+25
@@ -0,0 +1,25 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+'use strict';
9
+
10
+function getFakeModule() {
11
+ return function FakeModule(props, data) {
12
+ return data;
13
+ };
14
+}
15
+
16
+const ReactFlightDOMRelayClientIntegration = {
17
+ preloadModule(jsResource) {
18
+ return null;
19
+ },
20
+ requireModule(jsResource) {
21
+ return getFakeModule();
22
+ },
23
+};
24
+
25
+module.exports = ReactFlightDOMRelayClientIntegration;
packages/react-flight-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js
+3
@@ -23,6 +23,9 @@ const ReactFlightDOMRelayServerIntegration = {
23
});
24
},
25
close(destination) {},
26
+ resolveResourceMetaData(resource) {
27
+ return resource;
28
+ },
29
};
30
31
module.exports = ReactFlightDOMRelayServerIntegration;
packages/react-flight-dom-webpack/src/ReactFlightClientWebpackBundlerConfig.js
new
+53
@@ -0,0 +1,53 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export type ModuleMetaData = {
11
+ id: string,
12
+ chunks: Array<string>,
13
+};
14
+
15
+type Thenable = {
16
+ then(resolve: () => mixed, reject: (mixed) => mixed): mixed,
17
+ ...
18
+};
19
+
20
+// The chunk cache contains all the chunks we've preloaded so far.
21
+// If they're still pending they're a thenable. This map also exists
22
+// in Webpack but unfortunately it's not exposed so we have to
23
+// replicate it in user space. null means that it has already loaded.
24
+const chunkCache: Map<string, null | Thenable> = new Map();
25
+
26
+// Returning null means that all dependencies are fulfilled and we
27
+// can synchronously require the module now. A thenable is returned
28
+// that when resolved, means we can try again.
29
+export function preloadModule(moduleData: ModuleMetaData): null | Thenable {
30
+ let moduleEntry = require.cache[moduleData.id];
31
+ if (moduleEntry) {
32
+ // Fast exit if this module has already been loaded.
33
+ return null;
34
+ }
35
+ let chunks = moduleData.chunks;
36
+ let anyRemainingThenable = null;
37
+ for (let i = 0; i < chunks.length; i++) {
38
+ let chunkId = chunks[i];
39
+ let entry = chunkCache.get(chunkId);
40
+ if (entry === undefined) {
41
+ anyRemainingThenable = __webpack_chunk_load__(chunkId);
42
+ chunkCache.set(chunkId, anyRemainingThenable);
43
+ anyRemainingThenable.then(chunkCache.set.bind(chunkCache, chunkId, null));
44
+ } else if (entry !== null) {
45
+ anyRemainingThenable = entry;
46
+ }
47
+ }
48
+ return anyRemainingThenable;
49
+}
50
+
51
+export function requireModule<T>(moduleData: ModuleMetaData): T {
52
+ return __webpack_require__(moduleData.id).default;
53
+}
packages/react-flight-dom-webpack/src/ReactFlightDOMServerBrowser.js
+6
-2
@@ -8,6 +8,7 @@
8
*/
9
10
import type {ReactModel} from 'react-server/src/ReactFlightServer';
11
+import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig';
12
13
import {
14
createRequest,
@@ -15,11 +16,14 @@ import {
16
startFlowing,
17
} from 'react-server/src/ReactFlightServer';
18
18
-function renderToReadableStream(model: ReactModel): ReadableStream {
19
+function renderToReadableStream(
20
+ model: ReactModel,
21
+ webpackMap: BundlerConfig,
22
+): ReadableStream {
23
let request;
24
return new ReadableStream({
25
start(controller) {
22
- request = createRequest(model, controller);
26
+ request = createRequest(model, controller, webpackMap);
27
startWork(request);
28
},
29
pull(controller) {
packages/react-flight-dom-webpack/src/ReactFlightDOMServerNode.js
+7
-2
@@ -8,6 +8,7 @@
8
*/
9
10
import type {ReactModel} from 'react-server/src/ReactFlightServer';
11
+import type {BundlerConfig} from './ReactFlightServerWebpackBundlerConfig';
12
import type {Writable} from 'stream';
13
14
import {
@@ -20,8 +21,12 @@ function createDrainHandler(destination, request) {
21
return () => startFlowing(request);
22
}
23
23
-function pipeToNodeWritable(model: ReactModel, destination: Writable): void {
24
- let request = createRequest(model, destination);
24
+function pipeToNodeWritable(
25
+ model: ReactModel,
26
+ destination: Writable,
27
+ webpackMap: BundlerConfig,
28
+): void {
29
+ let request = createRequest(model, destination, webpackMap);
30
destination.on('drain', createDrainHandler(destination, request));
31
startWork(request);
32
}
packages/react-flight-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js
new
+28
@@ -0,0 +1,28 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+type WebpackMap = {
11
+ [filename: string]: ModuleMetaData,
12
+};
13
+
14
+export type BundlerConfig = WebpackMap;
15
+
16
+export type ModuleReference = string;
17
+
18
+export type ModuleMetaData = {
19
+ id: string,
20
+ chunks: Array<string>,
21
+};
22
+
23
+export function resolveModuleMetaData(
24
+ config: BundlerConfig,
25
+ modulePath: ModuleReference,
26
+): ModuleMetaData {
27
+ return config[modulePath];
28
+}
packages/react-noop-renderer/src/ReactNoopFlightServer.js
+6
-1
@@ -47,7 +47,12 @@ const ReactNoopFlightServer = ReactFlightServer({
47
48
function render(model: ReactModel): Destination {
49
let destination: Destination = [];
50
- let request = ReactNoopFlightServer.createRequest(model, destination);
50
+ let bundlerConfig = undefined;
51
+ let request = ReactNoopFlightServer.createRequest(
52
+ model,
53
+ destination,
54
+ bundlerConfig,
55
+ );
56
ReactNoopFlightServer.startWork(request);
57
return destination;
58
}
packages/react-server/src/ReactFlightServer.js
+11
-1
@@ -7,7 +7,13 @@
7
* @flow
8
*/
9
10
-import type {Destination, Chunk} from './ReactFlightServerConfig';
10
+import type {
11
+ Destination,
12
+ Chunk,
13
+ BundlerConfig,
14
+ // ModuleReference,
15
+ // ModuleMetaData,
16
+} from './ReactFlightServerConfig';
17
18
import {
19
scheduleWork,
@@ -18,6 +24,7 @@ import {
24
close,
25
processModelChunk,
26
processErrorChunk,
27
+ // resolveModuleMetaData,
28
} from './ReactFlightServerConfig';
29
30
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
@@ -49,6 +56,7 @@ type Segment = {
56
57
export type Request = {
58
destination: Destination,
59
+ bundlerConfig: BundlerConfig,
60
nextChunkId: number,
61
pendingChunks: number,
62
pingedSegments: Array<Segment>,
@@ -61,10 +69,12 @@ export type Request = {
69
export function createRequest(
70
model: ReactModel,
71
destination: Destination,
72
+ bundlerConfig: BundlerConfig,
73
): Request {
74
let pingedSegments = [];
75
let request = {
76
destination,
77
+ bundlerConfig,
78
nextChunkId: 0,
79
pendingChunks: 0,
80
pingedSegments: pingedSegments,
packages/react-server/src/ReactFlightServerBundlerConfigCustom.js
new
+15
@@ -0,0 +1,15 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+declare var $$$hostConfig: any;
11
+
12
+export opaque type BundlerConfig = mixed; // eslint-disable-line no-undef
13
+export opaque type ModuleReference = mixed; // eslint-disable-line no-undef
14
+export opaque type ModuleMetaData = mixed; // eslint-disable-line no-undef
15
+export const resolveModuleMetaData = $$$hostConfig.resolveModuleMetaData;
packages/react-server/src/forks/ReactFlightServerConfig.custom.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from '../ReactFlightServerConfigStream';
11
+export * from '../ReactFlightServerBundlerConfigCustom';
packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from '../ReactFlightServerConfigStream';
11
+export * from 'react-flight-dom-webpack/src/ReactFlightServerWebpackBundlerConfig';
packages/react-server/src/forks/ReactFlightServerConfig.dom.js
+1
@@ -8,3 +8,4 @@
8
*/
9
10
export * from '../ReactFlightServerConfigStream';
11
+export * from 'react-flight-dom-webpack/src/ReactFlightServerWebpackBundlerConfig';
scripts/flow/environment.js
+3
@@ -66,3 +66,6 @@ declare module 'EventListener' {
66
...
67
};
68
}
69
+
70
+declare function __webpack_chunk_load__(id: string): {then(() => mixed): mixed};
71
+declare function __webpack_require__(id: string): {default: any};
scripts/flow/react-relay-hooks.js
+19
@@ -15,6 +15,11 @@ type JSONValue =
15
| {[key: string]: JSONValue}
16
| Array<JSONValue>;
17
18
+type Thenable = {
19
+ then(resolve: () => mixed, reject: (error: Error) => mixed): mixed,
20
+ ...
21
+};
22
+
23
declare module 'ReactFlightDOMRelayServerIntegration' {
24
declare export opaque type Destination;
25
declare export function emitModel(
@@ -29,4 +34,18 @@ declare module 'ReactFlightDOMRelayServerIntegration' {
34
stack: string,
35
): void;
36
declare export function close(destination: Destination): void;
37
+
38
+ declare export opaque type ModuleReference;
39
+ declare export opaque type ModuleMetaData;
40
+ declare export function resolveResourceMetaData(
41
+ resource: ModuleReference,
42
+ ): ModuleMetaData;
43
+}
44
+
45
+declare module 'ReactFlightDOMRelayClientIntegration' {
46
+ declare export opaque type ModuleMetaData;
47
+ declare export function preloadModule(
48
+ moduleData: ModuleMetaData,
49
+ ): null | Thenable;
50
+ declare export function requireModule<T>(moduleData: ModuleMetaData): T;
51
}
scripts/jest/setupHostConfigs.js
+3
@@ -34,6 +34,9 @@ jest.mock('react-server/flight', () => {
34
return config => {
35
jest.mock(shimServerStreamConfigPath, () => config);
36
jest.mock(shimServerFormatConfigPath, () => config);
37
+ jest.mock('react-server/src/ReactFlightServerBundlerConfigCustom', () => ({
38
+ resolveModuleMetaData: config.resolveModuleMetaData,
39
+ }));
40
jest.mock(shimFlightServerConfigPath, () =>
41
require.requireActual(
42
'react-server/src/forks/ReactFlightServerConfig.custom'
scripts/rollup/bundles.js
+1
-1
@@ -258,7 +258,7 @@ const bundles = [
258
moduleType: RENDERER,
259
entry: 'react-flight-dom-relay',
260
global: 'ReactFlightDOMRelayClient',
261
- externals: ['react'],
261
+ externals: ['react', 'ReactFlightDOMRelayClientIntegration'],
262
},
263
264
/******* React ART *******/