@samitouri / QOS-React / commits / 8206b4b864

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 *******/