From 57bb5f062e9d3a60811cc56db4a98e51cc1147be Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 1 Dec 2021 18:16:49 +0900 Subject: [PATCH 01/84] wip: Create custom flight renderer with local version of react-server --- packages/hydrogen/package.json | 1 + .../src/framework/ReactFlight/Config.ts | 156 ++++++++++++++++++ .../src/framework/ReactFlight/Renderer.ts | 53 ++++++ .../plugins/vite-plugin-hydrogen-config.ts | 1 + yarn.lock | 4 + 5 files changed, 215 insertions(+) create mode 100644 packages/hydrogen/src/framework/ReactFlight/Config.ts create mode 100644 packages/hydrogen/src/framework/ReactFlight/Renderer.ts diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index 45576e8ef3..ed2e5ad6ac 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -96,6 +96,7 @@ "react-error-boundary": "^3.1.3", "react-helmet-async": "^1.0.9", "react-query": "^3.18.1", + "react-server": "link:../../../../react/build/node_modules/react-server", "react-ssr-prepass": "^1.4.0", "vite-plugin-inspect": "^0.3.6" } diff --git a/packages/hydrogen/src/framework/ReactFlight/Config.ts b/packages/hydrogen/src/framework/ReactFlight/Config.ts new file mode 100644 index 0000000000..1b3afd162e --- /dev/null +++ b/packages/hydrogen/src/framework/ReactFlight/Config.ts @@ -0,0 +1,156 @@ +import type {Writable} from 'stream'; +import type {ReactElement} from 'react'; + +const __WORKER__ = typeof process === 'undefined'; +// declare global { +// // eslint-disable-next-line no-var +// var __WORKER__: boolean; +// } + +type MightBeFlushable = { + flush?: () => void; + [key: string]: any; +}; + +// ReadableStreamController in browser? +export type Destination = Writable & MightBeFlushable; + +export type PrecomputedChunk = Uint8Array; +export type Chunk = string | Uint8Array; + +type WebpackMap = { + [filepath: string]: { + [name: string]: ModuleMetaData; + }; +}; + +export type BundlerConfig = WebpackMap; + +export type ReactModel = + | ReactElement + | string + | boolean + | number + | null + | Iterable + | {[key: string]: ReactModel}; + +// eslint-disable-next-line no-unused-vars +export type ModuleReference = { + $$typeof: Symbol; + filepath: string; + name: string; +}; + +export type ModuleMetaData = { + id: string; + chunks: Array; + name: string; +}; + +export type ModuleKey = string; + +const MODULE_TAG = Symbol.for('react.module.reference'); + +const textEncoder = __WORKER__ ? new TextEncoder() : null; + +const commonImplementation = { + getModuleKey(reference: ModuleReference): ModuleKey { + return reference.filepath + '#' + reference.name; + }, + isModuleReference(reference: Object): boolean { + return (reference as any).$$typeof === MODULE_TAG; + }, + resolveModuleMetaData( + config: BundlerConfig, + moduleReference: ModuleReference + ): ModuleMetaData { + return config[moduleReference.filepath][moduleReference.name]; + }, +}; + +const workerImplementation = { + ...commonImplementation, + close(destination: Destination) { + destination.close(); + }, + closeWithError(destination: Destination, error?: Error) { + if (typeof destination.error === 'function') { + destination.error(error); + } else { + destination.close(); + } + }, + scheduleWork(callback: () => void) { + callback(); + }, + beginWriting(destination: Destination) {}, + writeChunk( + destination: Destination, + chunk: PrecomputedChunk | Chunk + ): boolean { + destination.enqueue(chunk); + return destination.desiredSize > 0; + }, + completeWriting(destination: Destination) {}, + flushBuffered(destination: Destination) { + // WHATWG Streams do not yet have a way to flush the underlying + // transform streams. https://github.com/whatwg/streams/issues/960 + }, + + stringToChunk(content: string): Chunk { + return textEncoder!.encode(content); + }, + stringToPrecomputedChunk(content: string): PrecomputedChunk { + return textEncoder!.encode(content); + }, +}; + +const nodeImplementation = { + ...commonImplementation, + close(destination: Destination) { + destination.end(); + }, + closeWithError(destination: Destination, error?: Error) { + destination.destroy(error); + }, + scheduleWork(callback: () => void) { + setImmediate(callback); + }, + beginWriting(destination: Destination) { + // Older Node streams like http.createServer don't have this. + if (typeof destination.cork === 'function') { + destination.cork(); + } + }, + writeChunk( + destination: Destination, + chunk: Chunk | PrecomputedChunk + ): boolean { + const nodeBuffer = chunk as Buffer | string; // close enough + return destination.write(nodeBuffer); + }, + completeWriting(destination: Destination) { + // Older Node streams like http.createServer don't have this. + if (typeof destination.uncork === 'function') { + destination.uncork(); + } + }, + flushBuffered(destination: Destination) { + // If we don't have any more data to send right now. + // Flush whatever is in the buffer to the wire. + if (typeof destination.flush === 'function') { + // By convention the Zlib streams provide a flush function for this purpose. + // For Express, compression middleware adds this method. + destination.flush(); + } + }, + stringToChunk(content: string): Chunk { + return content; + }, + stringToPrecomputedChunk(content: string): PrecomputedChunk { + return Buffer.from(content, 'utf8'); + }, +}; + +export default __WORKER__ ? workerImplementation : nodeImplementation; diff --git a/packages/hydrogen/src/framework/ReactFlight/Renderer.ts b/packages/hydrogen/src/framework/ReactFlight/Renderer.ts new file mode 100644 index 0000000000..15868124d0 --- /dev/null +++ b/packages/hydrogen/src/framework/ReactFlight/Renderer.ts @@ -0,0 +1,53 @@ +import type {Writable} from 'stream'; +import type {BundlerConfig, ReactModel} from './Config'; + +// @ts-ignore +import createFlightRenderer from 'react-server/flight'; +import rendererConfig from './Config'; + +const {createRequest, startWork, startFlowing} = + createFlightRenderer(rendererConfig); + +function createDrainHandler(destination: any, request: any) { + return () => startFlowing(request, destination); +} + +type Options = { + onError?: (error: any) => void; +}; + +type Controls = { + pipe(destination: T): T; +}; + +function renderToPipeableStream( + model: ReactModel, + webpackMap: BundlerConfig, + options?: Options +): Controls { + const request = createRequest( + model, + webpackMap, + options ? options.onError : undefined + ); + let hasStartedFlowing = false; + startWork(request); + return { + pipe(destination: T): T { + if (hasStartedFlowing) { + throw new Error( + 'React currently only supports piping to one writable stream.' + ); + } + hasStartedFlowing = true; + startFlowing(request, destination); + (destination as any).on( + 'drain', + createDrainHandler(destination, request) + ); + return destination; + }, + }; +} + +export {renderToPipeableStream}; diff --git a/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts b/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts index 5779032bc2..0c3f526e2d 100644 --- a/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts +++ b/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts @@ -57,6 +57,7 @@ export default () => { 'react', 'react-dom', 'react-router-dom', + 'react-server', ], }, diff --git a/yarn.lock b/yarn.lock index cf7318f278..aad0266028 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11256,6 +11256,10 @@ react-router@5.2.1: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +"react-server@link:../../react/build/node_modules/react-server": + version "0.0.0" + uid "" + react-ssr-prepass@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/react-ssr-prepass/-/react-ssr-prepass-1.4.0.tgz#33a3db19414f0f8f9f3f781c88f760ae366b4f51" From 7ed7a73ba7063f4aff4a27b7edc47c0a4c9bc429 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 1 Dec 2021 21:57:29 +0900 Subject: [PATCH 02/84] wip: mock client manifest and add module reference to client components --- packages/hydrogen/src/entry-server.tsx | 41 ++------- .../framework/ClientMarker/ClientMarker.tsx | 86 ++++--------------- .../src/framework/ReactFlight/Config.ts | 12 ++- 3 files changed, 33 insertions(+), 106 deletions(-) diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 97a3502176..dcf28364ab 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -23,6 +23,8 @@ import {dehydrate} from 'react-query/hydration'; import {getCacheControlHeader} from './framework/cache'; import type {ServerResponse} from 'http'; +import {renderToPipeableStream as renderRSCFlight} from './framework/ReactFlight/Renderer'; + /** * react-dom/unstable-fizz provides different entrypoints based on runtime: * - `renderToReadableStream` for "browser" (aka worker) @@ -182,7 +184,7 @@ const renderHydrogen: ServerHandler = (App, hook) => { ) { const state = JSON.parse(url.searchParams.get('state') || '{}'); - const {ReactApp, componentResponse} = buildReactApp({ + const {ReactApp} = buildReactApp({ App, state, context, @@ -194,41 +196,12 @@ const renderHydrogen: ServerHandler = (App, hook) => { console.error('Fatal', error); }); - let didError: Error | undefined; - - const writer = new HydrationWriter(); - - const {pipe, abort} = renderToPipeableStream( - - - , - { - /** - * When hydrating, we have to wait until `onCompleteAll` to avoid having - * `template` and `script` tags inserted and rendered as part of the hydration response. - */ - onCompleteAll() { - // Tell React to start writing to the writer - pipe(writer); - - // Tell React that the writer is ready to drain, which sometimes results in a last "chunk" being written. - writer.drain(); - - response.statusCode = didError ? 500 : 200; - response.setHeader( - getCacheControlHeader({dev}), - componentResponse.cacheControlHeader - ); - response.end(generateWireSyntaxFromRenderedHtml(writer.toString())); - }, - onError(error: any) { - didError = error; - console.error(error); - }, - } + const {pipe} = renderRSCFlight( + , + {} // Empty manifest ); - setTimeout(abort, STREAM_ABORT_TIMEOUT_MS); + pipe(response); }; return { diff --git a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx index da1792bab7..aaaed7a3f0 100644 --- a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx +++ b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx @@ -1,6 +1,4 @@ -import React, {FunctionComponent, useContext} from 'react'; -import {HydrationContext} from '../Hydration/HydrationContext.server'; -import {renderReactProps} from '../Hydration/react-utils'; +import {FunctionComponent} from 'react'; interface ClientMarkerMeta { name: string; @@ -10,76 +8,26 @@ interface ClientMarkerMeta { } export function wrapInClientMarker(meta: ClientMarkerMeta) { - const {component, name} = meta; + const {component: Component} = meta; if ( - !component || - (typeof component !== 'function' && - !Object.prototype.hasOwnProperty.call(component, 'render')) + !Component || + (typeof Component !== 'function' && + !Object.prototype.hasOwnProperty.call(Component, 'render')) ) { // This is not a React component, return it as is. - return component; + return Component; } - // Use object syntax here to make sure the function name - // comes from the meta params for better error stacks. - const wrappedComponent = { - [name]: (props: any) => , - }[name]; - - // Relay component properties such as `Image.Fragment` - for (const [key, value] of Object.entries(component)) { - Object.defineProperty(wrappedComponent, key, { - enumerable: true, - value, - }); - } - - return wrappedComponent; -} - -function ClientMarker({ - props: allProps, - meta: {name, id, component: Component, named}, -}: { - meta: ClientMarkerMeta; - props: any; -}) { - const isHydrating = useContext(HydrationContext); - - if (!isHydrating) return ; - - let {children, ...props} = allProps; - - /** - * Convert props that happen to be React components to actual - * objects representing DOM elements. This is because we - * serialize props to JSON below, and React element Functions - * cannot be serialized. - */ - props = renderReactProps(props); - - /** - * Components ending in *Provider are special components to - * Hydrogen's RSC implementation. They are rendered during - * the hydration process in the state tree even though they - * don't output any DOM. This is key to supporting crossing - * the server/client context boundary. - */ - const shouldRenderDuringHydration = name.endsWith('Provider'); - - return ( - - {shouldRenderDuringHydration ? ( - {children} - ) : ( - children - )} - - ); + Object.defineProperty(Component, '$$moduleReference', { + enumerable: true, + value: { + $$typeof: Symbol.for('react.module.reference'), + filepath: meta.id, + name: meta.name, + named: meta.named, + }, + }); + + return Component; } diff --git a/packages/hydrogen/src/framework/ReactFlight/Config.ts b/packages/hydrogen/src/framework/ReactFlight/Config.ts index 1b3afd162e..421d21ce63 100644 --- a/packages/hydrogen/src/framework/ReactFlight/Config.ts +++ b/packages/hydrogen/src/framework/ReactFlight/Config.ts @@ -40,12 +40,14 @@ export type ModuleReference = { $$typeof: Symbol; filepath: string; name: string; + named?: boolean; }; export type ModuleMetaData = { id: string; - chunks: Array; + chunks?: Array; name: string; + named?: boolean; }; export type ModuleKey = string; @@ -62,10 +64,14 @@ const commonImplementation = { return (reference as any).$$typeof === MODULE_TAG; }, resolveModuleMetaData( - config: BundlerConfig, + config: BundlerConfig, // this is not used moduleReference: ModuleReference ): ModuleMetaData { - return config[moduleReference.filepath][moduleReference.name]; + return { + id: moduleReference.filepath, + name: moduleReference.name, + named: moduleReference.named, + }; }, }; From e1eba5ffd275418b5db60000535aa226f271d684 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 1 Dec 2021 22:42:58 +0900 Subject: [PATCH 03/84] wip: hack RSC resolution --- .../framework/ClientMarker/ClientMarker.tsx | 29 ++++++++++++------- .../src/framework/ReactFlight/Config.ts | 5 +++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx index aaaed7a3f0..a7bb14052f 100644 --- a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx +++ b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx @@ -8,7 +8,7 @@ interface ClientMarkerMeta { } export function wrapInClientMarker(meta: ClientMarkerMeta) { - const {component: Component} = meta; + const {component: Component, name} = meta; if ( !Component || @@ -19,15 +19,22 @@ export function wrapInClientMarker(meta: ClientMarkerMeta) { return Component; } - Object.defineProperty(Component, '$$moduleReference', { - enumerable: true, - value: { - $$typeof: Symbol.for('react.module.reference'), - filepath: meta.id, - name: meta.name, - named: meta.named, - }, - }); + // Use object syntax here to make sure the function name + // comes from the meta params for better error stacks. + const wrappedComponent = { + [name]: (props: any) => , + }[name]; - return Component; + return { + // React access the `render` function directly when encountring this type + $$typeof: Symbol.for('react.forward_ref'), + render: wrappedComponent, + + // RSC checks this hack instead of $$typeof + _$$typeof: Symbol.for('react.module.reference'), + // RSC payload + filepath: meta.id, + name: meta.name, + named: meta.named, + }; } diff --git a/packages/hydrogen/src/framework/ReactFlight/Config.ts b/packages/hydrogen/src/framework/ReactFlight/Config.ts index 421d21ce63..00e32eb31f 100644 --- a/packages/hydrogen/src/framework/ReactFlight/Config.ts +++ b/packages/hydrogen/src/framework/ReactFlight/Config.ts @@ -61,7 +61,10 @@ const commonImplementation = { return reference.filepath + '#' + reference.name; }, isModuleReference(reference: Object): boolean { - return (reference as any).$$typeof === MODULE_TAG; + return ( + ((reference as any)._$$typeof || (reference as any).$$typeof) === + MODULE_TAG + ); }, resolveModuleMetaData( config: BundlerConfig, // this is not used From a119de67a13023bf13e23a24dc767a765bab7667 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Dec 2021 16:07:14 +0900 Subject: [PATCH 04/84] wip: wrap component in Proxy to access original properties --- .../framework/ClientMarker/ClientMarker.tsx | 30 +++++++++++++------ .../src/framework/ReactFlight/Config.ts | 2 +- packages/hydrogen/src/utilities/object.ts | 18 +++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 packages/hydrogen/src/utilities/object.ts diff --git a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx index a7bb14052f..f031b2f84a 100644 --- a/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx +++ b/packages/hydrogen/src/framework/ClientMarker/ClientMarker.tsx @@ -1,4 +1,5 @@ -import {FunctionComponent} from 'react'; +import React, {FunctionComponent} from 'react'; +import {createObject} from '../../utilities/object'; interface ClientMarkerMeta { name: string; @@ -21,20 +22,31 @@ export function wrapInClientMarker(meta: ClientMarkerMeta) { // Use object syntax here to make sure the function name // comes from the meta params for better error stacks. - const wrappedComponent = { + const render = { [name]: (props: any) => , }[name]; - return { - // React access the `render` function directly when encountring this type + const componentRef = createObject({ + // React accesses the `render` function directly when encountring this type $$typeof: Symbol.for('react.forward_ref'), - render: wrappedComponent, + render, + }); - // RSC checks this hack instead of $$typeof - _$$typeof: Symbol.for('react.module.reference'), - // RSC payload + const rscDescriptor = createObject({ + // This custom type is checked in RSC renderer + $$typeof_rsc: Symbol.for('react.module.reference'), filepath: meta.id, name: meta.name, named: meta.named, - }; + }); + + return new Proxy(componentRef, { + get: (target, prop) => + // 1. Let React access the element/ref and type in SSR + (target as any)[prop] ?? + // 2. Check descriptor properties for RSC requests + (rscDescriptor as any)[prop] ?? + // 3. Fallback to custom component properties such as `Image.Fragment` + (Component as any)[prop], + }); } diff --git a/packages/hydrogen/src/framework/ReactFlight/Config.ts b/packages/hydrogen/src/framework/ReactFlight/Config.ts index 00e32eb31f..8574008c70 100644 --- a/packages/hydrogen/src/framework/ReactFlight/Config.ts +++ b/packages/hydrogen/src/framework/ReactFlight/Config.ts @@ -62,7 +62,7 @@ const commonImplementation = { }, isModuleReference(reference: Object): boolean { return ( - ((reference as any)._$$typeof || (reference as any).$$typeof) === + ((reference as any).$$typeof_rsc || (reference as any).$$typeof) === MODULE_TAG ); }, diff --git a/packages/hydrogen/src/utilities/object.ts b/packages/hydrogen/src/utilities/object.ts new file mode 100644 index 0000000000..bcc31c4d54 --- /dev/null +++ b/packages/hydrogen/src/utilities/object.ts @@ -0,0 +1,18 @@ +type Descriptor = Parameters[2]; + +// Create objects with null prototypes for faster access +export function createObject( + properties: T, + { + prototype = null, + ...descriptor + }: {prototype?: any} & Exclude = {} +) { + return Object.create( + prototype, + Object.entries(properties).reduce((acc, [key, value]) => { + acc[key] = {enumerable: true, ...descriptor, value}; + return acc; + }, {} as Record) + ) as T; +} From 46c9e0635f2353a8b8cd23a412e46eaf2cc9e43d Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Dec 2021 19:01:02 +0900 Subject: [PATCH 05/84] refactor: rename RSC server files --- packages/hydrogen/src/entry-server.tsx | 2 +- .../framework/ReactFlight/{Config.ts => ServerConfig.ts} | 2 ++ .../ReactFlight/{Renderer.ts => ServerRenderer.ts} | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) rename packages/hydrogen/src/framework/ReactFlight/{Config.ts => ServerConfig.ts} (97%) rename packages/hydrogen/src/framework/ReactFlight/{Renderer.ts => ServerRenderer.ts} (87%) diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index dcf28364ab..412653a468 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -23,7 +23,7 @@ import {dehydrate} from 'react-query/hydration'; import {getCacheControlHeader} from './framework/cache'; import type {ServerResponse} from 'http'; -import {renderToPipeableStream as renderRSCFlight} from './framework/ReactFlight/Renderer'; +import {renderToPipeableStream as renderRSCFlight} from './framework/ReactFlight/ServerRenderer'; /** * react-dom/unstable-fizz provides different entrypoints based on runtime: diff --git a/packages/hydrogen/src/framework/ReactFlight/Config.ts b/packages/hydrogen/src/framework/ReactFlight/ServerConfig.ts similarity index 97% rename from packages/hydrogen/src/framework/ReactFlight/Config.ts rename to packages/hydrogen/src/framework/ReactFlight/ServerConfig.ts index 8574008c70..f384f2730d 100644 --- a/packages/hydrogen/src/framework/ReactFlight/Config.ts +++ b/packages/hydrogen/src/framework/ReactFlight/ServerConfig.ts @@ -72,9 +72,11 @@ const commonImplementation = { ): ModuleMetaData { return { id: moduleReference.filepath, + // chunks: [], name: moduleReference.name, named: moduleReference.named, }; + // return config[moduleReference.filepath][moduleReference.name]; }, }; diff --git a/packages/hydrogen/src/framework/ReactFlight/Renderer.ts b/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts similarity index 87% rename from packages/hydrogen/src/framework/ReactFlight/Renderer.ts rename to packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts index 15868124d0..f2492992bb 100644 --- a/packages/hydrogen/src/framework/ReactFlight/Renderer.ts +++ b/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts @@ -1,9 +1,9 @@ import type {Writable} from 'stream'; -import type {BundlerConfig, ReactModel} from './Config'; +import type {BundlerConfig, ReactModel} from './ServerConfig'; // @ts-ignore import createFlightRenderer from 'react-server/flight'; -import rendererConfig from './Config'; +import rendererConfig from './ServerConfig'; const {createRequest, startWork, startFlowing} = createFlightRenderer(rendererConfig); @@ -22,12 +22,12 @@ type Controls = { function renderToPipeableStream( model: ReactModel, - webpackMap: BundlerConfig, + manifest?: BundlerConfig, options?: Options ): Controls { const request = createRequest( model, - webpackMap, + manifest || {}, options ? options.onError : undefined ); let hasStartedFlowing = false; From 31a1d5813d49c3b1ed888bfc132d7bdf8faaca8d Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Dec 2021 19:04:07 +0900 Subject: [PATCH 06/84] wip: add official RSC hydrator --- packages/hydrogen/package.json | 1 + .../src/framework/Hydration/Cache.client.ts | 166 ++---------------- .../src/framework/Hydration/client-imports.ts | 2 +- .../src/framework/ReactFlight/ClientConfig.ts | 107 +++++++++++ .../framework/ReactFlight/ClientHydrator.ts | 52 ++++++ .../plugins/vite-plugin-hydrogen-config.ts | 1 + yarn.lock | 4 + 7 files changed, 182 insertions(+), 151 deletions(-) create mode 100644 packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts create mode 100644 packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index ed2e5ad6ac..4f7822b759 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -96,6 +96,7 @@ "react-error-boundary": "^3.1.3", "react-helmet-async": "^1.0.9", "react-query": "^3.18.1", + "react-client": "link:../../../../react/build/node_modules/react-client", "react-server": "link:../../../../react/build/node_modules/react-server", "react-ssr-prepass": "^1.4.0", "vite-plugin-inspect": "^0.3.6" diff --git a/packages/hydrogen/src/framework/Hydration/Cache.client.ts b/packages/hydrogen/src/framework/Hydration/Cache.client.ts index 9fc0ab46a0..c88df9c1c1 100644 --- a/packages/hydrogen/src/framework/Hydration/Cache.client.ts +++ b/packages/hydrogen/src/framework/Hydration/Cache.client.ts @@ -1,9 +1,11 @@ -import {createElement, Fragment, ReactElement} from 'react'; -import {wrapPromise} from '../../utilities'; -import importClientComponent from './client-imports'; +// @ts-ignore +import {unstable_getCacheForType, unstable_useCacheRefresh} from 'react'; +import {createFromFetch} from '../ReactFlight/ClientHydrator'; +import type {FlightResponse} from '../ReactFlight/ClientConfig'; -const cache = new Map(); -const moduleCache = new Map(); +function createResponseCache() { + return new Map(); +} /** * Much of this is borrowed from React's demo implementation: @@ -13,159 +15,23 @@ const moduleCache = new Map(); */ export function useServerResponse(state: any) { const key = JSON.stringify(state); + const cache: ReturnType = + unstable_getCacheForType(createResponseCache); + let response = cache.get(key); if (response) { return response; } + response = createFromFetch(fetch('/react?state=' + encodeURIComponent(key))); cache.set(key, response); - return response; } -/** - * Similar to the RSC demo, `createFromFetch` wraps around a fetch call and throws - * promise events to the Suspense boundary until the content has loaded. - */ -function createFromFetch(fetchPromise: Promise) { - return wrapPromise( - fetchPromise - .then((response) => { - if (!response.ok) { - throw new Error(`Hydration request failed: ${response.statusText}`); - } - return response.text(); - }) - .then((payload) => { - return convertHydrationResponseToReactComponents(payload); - }) - .catch((e) => { - console.error(e); - }) - ); -} - -export async function convertHydrationResponseToReactComponents( - response: string -): Promise { - const manifest = createManifestFromWirePayload(response); - - /** - * Eager-load all the modules referenced in the manifest. Otherwise, - * Hydration errors crop up and show in the console. - */ - const modules = await eagerLoadModules(manifest); - - function isReactTuple(item: any) { - return item instanceof Array && item.length === 4 && item[0] === '$'; - } - - function isReactTupleOrString(item: any) { - return typeof item === 'string' || isReactTuple(item); - } - - function isReactTupleOrArrayOfTuples(item: any) { - return isReactTupleOrString(item) || isReactTupleOrString(item[0]); - } - - function wireSyntaxToReactElement(item: any, key?: number) { - if (typeof item === 'string') return item; - if (typeof item !== 'object') return null; - - // Assume it's an array of tuples, defined in the component as a fragment. - if (!isReactTuple(item)) { - return createElement(Fragment, { - children: item.map(wireSyntaxToReactElement), - }); - } - - let [, type, , props] = item; - const allProps = {...props}; - - /** - * Convert all props (including children) that may be serialized as tuples - * or arrays of tuples into React elements. - */ - Object.entries(allProps).map(([key, prop]) => { - if (prop instanceof Array && isReactTupleOrArrayOfTuples(prop)) { - /** - * - Array of children tuples - * - ...or a list of children, combo of strings and tuples, produced by dangerouslySetInnerHtml - */ - if (prop.every(isReactTupleOrString)) { - allProps[key] = prop.map(wireSyntaxToReactElement); - /** - * - Single tuple - */ - } else { - allProps[key] = wireSyntaxToReactElement(prop); - } - } - }); - - /** - * If the type is a module and not a React Symbol, reference the component - * listed in the manifest as `M` and lazy-load it. - * `lazy()` throws Suspense promises until the component has loaded. - */ - if (type.startsWith('@')) { - const module = manifest[type.replace('@', 'M')]; - const mod = modules[type.replace('@', 'M')]; - type = module.named ? mod[module.name] : mod.default; - } - - return createElement(type, {...allProps, key}); - } - - /** - * The manifest is listed as `J0` for some reason. This is how React does it. - * Maybe this is to support for additional model trees like `J1`? - * - * Regardless, this is guaranteed to exist from our server response. - */ - return wireSyntaxToReactElement(manifest.J0) as ReactElement; -} - -interface WireManifest { - J0: any; - [key: string]: any; -} - -function createManifestFromWirePayload(payload: string): WireManifest { - return payload.split('\n').reduce((memo, row) => { - const [key, ...values] = row.split(':'); - - if (key) { - memo[key] = JSON.parse(values.join(':')); - } - - return memo; - }, {} as Record) as WireManifest; -} - -async function eagerLoadModules(manifest: WireManifest) { - const modules = await Promise.all( - Object.entries(manifest) - .map(async ([key, module]) => { - if (!key.startsWith('M')) return; - if (moduleCache.has(module.id)) { - return moduleCache.get(module.id); - } - - const mod = await importClientComponent(module.id); - - moduleCache.set(module.id, mod); - return mod; - }) - .filter(Boolean) - ); - - return Object.keys(manifest) - .filter((key) => key.startsWith('M')) - .map((key, idx) => [key, modules[idx]]) - .reduce((memo, item) => { - memo[item[0]] = item[1]; - return memo; - }, {} as any); +export function useRefresh() { + const refreshCache = unstable_useCacheRefresh(); + return function refresh(key: string, seededResponse: any) { + refreshCache(createResponseCache, new Map([[key, seededResponse]])); + }; } diff --git a/packages/hydrogen/src/framework/Hydration/client-imports.ts b/packages/hydrogen/src/framework/Hydration/client-imports.ts index e0a0f9222b..99977466e7 100644 --- a/packages/hydrogen/src/framework/Hydration/client-imports.ts +++ b/packages/hydrogen/src/framework/Hydration/client-imports.ts @@ -1,7 +1,7 @@ // Transform relative paths to absolute in order // to match component IDs from ClientMarker. function normalizeComponentPaths( - componentObject: Record any)>, + componentObject: Record Promise)>, prefix: string ) { return Object.entries(componentObject).reduce((acc, [key, value]) => { diff --git a/packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts b/packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts new file mode 100644 index 0000000000..586c20eedf --- /dev/null +++ b/packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts @@ -0,0 +1,107 @@ +import importClientComponent from '../Hydration/client-imports'; +import type {ModuleReference, ModuleMetaData} from './ServerConfig'; + +export type UninitializedModel = string; +export type StringDecoder = unknown; + +export type JSONValue = + | number + | null + | boolean + | string + | {[key: string]: JSONValue} + | ReadonlyArray; + +type PendingChunk = { + _status: 0; + _value: null | Array<() => unknown>; + _response: FlightResponse; + then(resolve: () => unknown): void; +}; +type ResolvedModelChunk = { + _status: 1; + _value: UninitializedModel; + _response: FlightResponse; + then(resolve: () => unknown): void; +}; +type ResolvedModuleChunk = { + _status: 2; + _value: ModuleReference; + _response: FlightResponse; + then(resolve: () => unknown): void; +}; +type InitializedChunk = { + _status: 3; + _value: T; + _response: FlightResponse; + then(resolve: () => unknown): void; +}; +type ErroredChunk = { + _status: 4; + _value: Error; + _response: FlightResponse; + then(resolve: () => unknown): void; +}; + +type SomeChunk = + | PendingChunk + | ResolvedModelChunk + | ResolvedModuleChunk + | InitializedChunk + | ErroredChunk; + +type ResponseBase = { + _chunks: Map>; + readRoot(): T; +}; + +export type FlightResponse = ResponseBase & { + _partialRow: string; + _fromJSON: (key: string, value: JSONValue) => any; + _stringDecoder: StringDecoder; +}; + +const moduleCache = new Map(); + +export default { + supportsBinaryStreams: typeof TextDecoder !== 'undefined', + resolveModuleReference(idx: string) { + return idx; + }, + preloadModule({id}: ModuleMetaData) { + if (moduleCache.has(id)) return; + + function cacheResult | unknown>(mod: T) { + moduleCache.set(id, mod); + return mod; + } + + // Store the original promise first, then override cache with its result. + cacheResult(importClientComponent(id)).then(cacheResult, cacheResult); + }, + requireModule({id, name, named}: ModuleMetaData) { + if (moduleCache.has(id)) { + const mod = moduleCache.get(id); + + if (mod instanceof Promise || mod instanceof Error) { + // This module is being read but it's either still being + // downloaded or it has errored out. Pass it to Suspense. + throw mod; + } + + return mod[named ? name : 'default']; + } + }, + parseModel(response: FlightResponse, json: string) { + return JSON.parse(json, response._fromJSON); + }, + createStringDecoder() { + return new TextDecoder(); + }, + readPartialStringChunk(decoder: TextDecoder, buffer: Uint8Array) { + return decoder.decode(buffer, {stream: true}); + }, + readFinalStringChunk(decoder: TextDecoder, buffer: Uint8Array) { + return decoder.decode(buffer); + }, +}; diff --git a/packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts b/packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts new file mode 100644 index 0000000000..194225621b --- /dev/null +++ b/packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts @@ -0,0 +1,52 @@ +import type {FlightResponse} from './ClientConfig'; +// @ts-ignore +import createFlightHydrator from 'react-client/flight'; +import hydratorConfig from './ClientConfig'; + +const {createResponse, reportGlobalError, processBinaryChunk, close} = + createFlightHydrator(hydratorConfig); + +function startReadingFromStream( + response: FlightResponse, + stream: ReadableStream +): void { + const reader = stream.getReader(); + function progress({done, value}: any): void | Promise { + if (done) { + close(response); + return; + } + + processBinaryChunk(response, value as Uint8Array); + return reader.read().then(progress, error); + } + + function error(e: Error) { + reportGlobalError(response, e); + } + + reader.read().then(progress, error); +} + +export function createFromReadableStream( + stream: ReadableStream +): FlightResponse { + const response = createResponse() as FlightResponse; + startReadingFromStream(response, stream); + return response; +} + +export function createFromFetch(promiseForResponse: Promise) { + const response = createResponse() as FlightResponse; + + promiseForResponse.then( + function (r) { + startReadingFromStream(response, r.body!); + }, + function (e) { + reportGlobalError(response, e); + } + ); + + return response; +} diff --git a/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts b/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts index 0c3f526e2d..daabb67e20 100644 --- a/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts +++ b/packages/hydrogen/src/framework/plugins/vite-plugin-hydrogen-config.ts @@ -58,6 +58,7 @@ export default () => { 'react-dom', 'react-router-dom', 'react-server', + 'react-client/flight', ], }, diff --git a/yarn.lock b/yarn.lock index aad0266028..51d331a6b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11157,6 +11157,10 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +"react-client@link:../../react/build/node_modules/react-client": + version "0.0.0" + uid "" + react-dom@0.0.0-experimental-0cc724c77-20211125: version "0.0.0-experimental-0cc724c77-20211125" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-0cc724c77-20211125.tgz#f48ee44d803b7ba6a888f1cd5ef4402e84f46dba" From 0bada3296c955591168c457984258e8f28da416c Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 12:55:03 +0900 Subject: [PATCH 07/84] fix: Rename response.readRoot and remove explicit hydration --- packages/hydrogen/src/entry-client.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrogen/src/entry-client.tsx b/packages/hydrogen/src/entry-client.tsx index 8e5b97b059..b94f604b34 100644 --- a/packages/hydrogen/src/entry-client.tsx +++ b/packages/hydrogen/src/entry-client.tsx @@ -19,7 +19,7 @@ const renderHydrogen: ClientHandler = async (ClientWrapper) => { return; } - createRoot(root, {hydrate: true}).render( + createRoot(root).render( @@ -47,7 +47,7 @@ function Content({clientWrapper: ClientWrapper}: {clientWrapper: any}) { {/* @ts-ignore */} - {response.read()} + {response.readRoot()} From a49894251b746ff25bdee266296a45d23ff8cf12 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 13:06:57 +0900 Subject: [PATCH 08/84] wip: remove RR and Helmet providers from the server --- packages/hydrogen/src/entry-client.tsx | 37 +++++++------- packages/hydrogen/src/entry-server.tsx | 67 ++++++++++++-------------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/packages/hydrogen/src/entry-client.tsx b/packages/hydrogen/src/entry-client.tsx index b94f604b34..463ad23f64 100644 --- a/packages/hydrogen/src/entry-client.tsx +++ b/packages/hydrogen/src/entry-client.tsx @@ -1,13 +1,13 @@ import React, {Suspense, useState} from 'react'; // @ts-ignore import {createRoot} from 'react-dom'; -import {BrowserRouter} from 'react-router-dom'; +// import {BrowserRouter} from 'react-router-dom'; import type {ClientHandler} from './types'; import {ErrorBoundary} from 'react-error-boundary'; -import {HelmetProvider} from 'react-helmet-async'; +// import {HelmetProvider} from 'react-helmet-async'; import {useServerResponse} from './framework/Hydration/Cache.client'; -import {ServerStateProvider, ServerStateRouter} from './client'; -import {QueryProvider} from './hooks'; +// import {ServerStateProvider, ServerStateRouter} from './client'; +// import {QueryProvider} from './hooks'; const renderHydrogen: ClientHandler = async (ClientWrapper) => { const root = document.getElementById('root'); @@ -31,27 +31,26 @@ const renderHydrogen: ClientHandler = async (ClientWrapper) => { export default renderHydrogen; function Content({clientWrapper: ClientWrapper}: {clientWrapper: any}) { - const [serverState, setServerState] = useState({ + const [serverState /*, setServerState */] = useState({ pathname: window.location.pathname, search: window.location.search, }); const response = useServerResponse(serverState); return ( - - - - - - {/* @ts-ignore */} - {response.readRoot()} - - - - + // + // + // + // + // + {response.readRoot()} + // + // + // + // ); } diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 412653a468..5683c05927 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -8,12 +8,12 @@ import { import {renderToString} from 'react-dom/server'; import {getErrorMarkup} from './utilities/error'; import ssrPrepass from 'react-ssr-prepass'; -import {StaticRouter} from 'react-router-dom'; +// import {StaticRouter} from 'react-router-dom'; import type {ServerHandler} from './types'; import {HydrationContext} from './framework/Hydration/HydrationContext.server'; import type {ReactQueryHydrationContext} from './foundation/ShopifyProvider/types'; import {generateWireSyntaxFromRenderedHtml} from './framework/Hydration/wire.server'; -import {FilledContext, HelmetProvider} from 'react-helmet-async'; +// import {FilledContext, HelmetProvider} from 'react-helmet-async'; import {Html} from './framework/Hydration/Html'; import {HydrationWriter} from './framework/Hydration/writer.server'; import {Renderer, Hydrator, Streamer} from './types'; @@ -53,7 +53,7 @@ const renderHydrogen: ServerHandler = (App, hook) => { ? JSON.parse(url.searchParams?.get('state') ?? '{}') : {pathname: url.pathname, search: url.search}; - const {ReactApp, helmetContext, componentResponse} = buildReactApp({ + const {ReactApp, /*helmetContext,*/ componentResponse} = buildReactApp({ App, state, context, @@ -67,7 +67,7 @@ const renderHydrogen: ServerHandler = (App, hook) => { return {body: await componentResponse.customBody, url, componentResponse}; } - let params = {url, ...extractHeadElements(helmetContext)}; + let params = {url /*, ...extractHeadElements(helmetContext)*/}; /** * We allow the developer to "hook" into this process and mutate the params. @@ -224,38 +224,36 @@ function buildReactApp({ request: ServerComponentRequest; dev: boolean | undefined; }) { - const helmetContext = {} as FilledContext; + // const helmetContext = {} as FilledContext; const componentResponse = new ServerComponentResponse(); const ReactApp = (props: any) => ( - - - - - + // + + // ); - return {helmetContext, ReactApp, componentResponse}; + return {/*helmetContext,*/ ReactApp, componentResponse}; } -function extractHeadElements(helmetContext: FilledContext) { - const {helmet} = helmetContext; - - return { - base: helmet.base.toString(), - bodyAttributes: helmet.bodyAttributes.toString(), - htmlAttributes: helmet.htmlAttributes.toString(), - link: helmet.link.toString(), - meta: helmet.meta.toString(), - noscript: helmet.noscript.toString(), - script: helmet.script.toString(), - style: helmet.style.toString(), - title: helmet.title.toString(), - }; -} +// function extractHeadElements(helmetContext: FilledContext) { +// const {helmet} = helmetContext; + +// return { +// base: helmet.base.toString(), +// bodyAttributes: helmet.bodyAttributes.toString(), +// htmlAttributes: helmet.htmlAttributes.toString(), +// link: helmet.link.toString(), +// meta: helmet.meta.toString(), +// noscript: helmet.noscript.toString(), +// script: helmet.script.toString(), +// style: helmet.style.toString(), +// title: helmet.title.toString(), +// }; +// } function supportsReadableStream() { try { @@ -282,15 +280,10 @@ async function renderApp( ); } - const app = isReactHydrationRequest ? ( - - - - ) : ( - + return renderAppFromBufferedStream( + , + isReactHydrationRequest ); - - return renderAppFromBufferedStream(app, isReactHydrationRequest); } function renderAppFromBufferedStream( From 4bdac0f155130bb8bc8104349f3da7857b6b306a Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 13:33:10 +0900 Subject: [PATCH 09/84] wip: Add test app --- packages/dev/src/RSCTest/App.server.jsx | 21 +++++++++++++++++++++ packages/dev/src/RSCTest/C1.client.jsx | 3 +++ packages/dev/src/RSCTest/C2.client.jsx | 13 +++++++++++++ packages/dev/src/RSCTest/C3.client.jsx | 14 ++++++++++++++ packages/dev/src/RSCTest/CShared.jsx | 11 +++++++++++ packages/dev/src/entry-client.jsx | 9 ++++----- packages/dev/src/entry-server.jsx | 2 +- 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 packages/dev/src/RSCTest/App.server.jsx create mode 100644 packages/dev/src/RSCTest/C1.client.jsx create mode 100644 packages/dev/src/RSCTest/C2.client.jsx create mode 100644 packages/dev/src/RSCTest/C3.client.jsx create mode 100644 packages/dev/src/RSCTest/CShared.jsx diff --git a/packages/dev/src/RSCTest/App.server.jsx b/packages/dev/src/RSCTest/App.server.jsx new file mode 100644 index 0000000000..4cfd547fcf --- /dev/null +++ b/packages/dev/src/RSCTest/App.server.jsx @@ -0,0 +1,21 @@ +import {Suspense} from 'react'; + +// import LoadingFallback from './components/LoadingFallback'; +import C1 from './C1.client'; +import C2 from './C2.client'; +import CShared from './CShared'; + +export default function App({...serverState}) { + // const pages = import.meta.globEager('./pages/**/*.server.[jt]sx'); + + return ( + +
+
hello!
+ + + +
+
+ ); +} diff --git a/packages/dev/src/RSCTest/C1.client.jsx b/packages/dev/src/RSCTest/C1.client.jsx new file mode 100644 index 0000000000..ffa9240cc6 --- /dev/null +++ b/packages/dev/src/RSCTest/C1.client.jsx @@ -0,0 +1,3 @@ +export default function C1() { + return
c-1
; +} diff --git a/packages/dev/src/RSCTest/C2.client.jsx b/packages/dev/src/RSCTest/C2.client.jsx new file mode 100644 index 0000000000..e964c9d0eb --- /dev/null +++ b/packages/dev/src/RSCTest/C2.client.jsx @@ -0,0 +1,13 @@ +const onClick = () => window.alert('42'); + +export default function C2(props) { + const color = props.myProp2 ? 'blue' : 'gray'; + return ( + + ); +} diff --git a/packages/dev/src/RSCTest/C3.client.jsx b/packages/dev/src/RSCTest/C3.client.jsx new file mode 100644 index 0000000000..600d42b65f --- /dev/null +++ b/packages/dev/src/RSCTest/C3.client.jsx @@ -0,0 +1,14 @@ +const onClick = () => window.alert('42'); + +// NAMED EXPORT +export function C3(props) { + const color = props.myProp3 ? 'blue' : 'gray'; + return ( + + ); +} diff --git a/packages/dev/src/RSCTest/CShared.jsx b/packages/dev/src/RSCTest/CShared.jsx new file mode 100644 index 0000000000..44204324be --- /dev/null +++ b/packages/dev/src/RSCTest/CShared.jsx @@ -0,0 +1,11 @@ +import {C3} from './C3.client'; + +export default function CShared(props) { + return ( +
+ c-shared +

+ +
+ ); +} diff --git a/packages/dev/src/entry-client.jsx b/packages/dev/src/entry-client.jsx index 723df4e4a5..c103e4b466 100644 --- a/packages/dev/src/entry-client.jsx +++ b/packages/dev/src/entry-client.jsx @@ -1,12 +1,11 @@ import renderHydrogen from '@shopify/hydrogen/entry-client'; -import {ShopifyProvider} from '@shopify/hydrogen/client'; +// import {ShopifyProvider} from '@shopify/hydrogen/client'; -import shopifyConfig from '../shopify.config'; +// import shopifyConfig from '../shopify.config'; function ClientApp({children}) { - return ( - {children} - ); + return children; + // {children} } export default renderHydrogen(ClientApp); diff --git a/packages/dev/src/entry-server.jsx b/packages/dev/src/entry-server.jsx index aedbf60731..9254771bf7 100644 --- a/packages/dev/src/entry-server.jsx +++ b/packages/dev/src/entry-server.jsx @@ -1,6 +1,6 @@ import renderHydrogen from '@shopify/hydrogen/entry-server'; -import App from './App.server'; +import App from './RSCTest/App.server'; export default renderHydrogen(App, () => { // Custom hook From 7406bf217e1c90a6018933748d545d4a48b698c8 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 14:39:01 +0900 Subject: [PATCH 10/84] wip: remove hydration providers --- packages/hydrogen/src/entry-server.tsx | 60 ++++++++------------------ 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 5683c05927..0181a68947 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -10,9 +10,9 @@ import {getErrorMarkup} from './utilities/error'; import ssrPrepass from 'react-ssr-prepass'; // import {StaticRouter} from 'react-router-dom'; import type {ServerHandler} from './types'; -import {HydrationContext} from './framework/Hydration/HydrationContext.server'; +// import {HydrationContext} from './framework/Hydration/HydrationContext.server'; import type {ReactQueryHydrationContext} from './foundation/ShopifyProvider/types'; -import {generateWireSyntaxFromRenderedHtml} from './framework/Hydration/wire.server'; +// import {generateWireSyntaxFromRenderedHtml} from './framework/Hydration/wire.server'; // import {FilledContext, HelmetProvider} from 'react-helmet-async'; import {Html} from './framework/Hydration/Html'; import {HydrationWriter} from './framework/Hydration/writer.server'; @@ -61,7 +61,9 @@ const renderHydrogen: ServerHandler = (App, hook) => { dev, }); - const body = await renderApp(ReactApp, state, isReactHydrationRequest); + const body = isReactHydrationRequest + ? '' // TODO: Implement RSC without streaming -- Or wait until ReadableStream is supported + : await renderApp(ReactApp, state); if (componentResponse.customBody) { return {body: await componentResponse.customBody, url, componentResponse}; @@ -264,32 +266,23 @@ function supportsReadableStream() { } } -async function renderApp( - ReactApp: JSXElementConstructor, - state: any, - isReactHydrationRequest?: boolean -) { +async function renderApp(ReactApp: JSXElementConstructor, state: any) { /** * Temporary workaround until all Worker runtimes support ReadableStream */ if (isWorker && !supportsReadableStream()) { - return renderAppFromStringWithPrepass( - ReactApp, - state, - isReactHydrationRequest - ); + return renderAppFromStringWithPrepass(ReactApp, state); } - return renderAppFromBufferedStream( - , - isReactHydrationRequest - ); + return renderAppFromBufferedStream(ReactApp, state); } function renderAppFromBufferedStream( - app: JSX.Element, - isReactHydrationRequest?: boolean + ReactApp: JSXElementConstructor, + state: any ) { + const app = ; + return new Promise((resolve, reject) => { if (isWorker) { let isComplete = false; @@ -321,11 +314,7 @@ function renderAppFromBufferedStream( * to resolve and be processed by the rest of the pipeline. */ const res = new Response(stream); - if (isReactHydrationRequest) { - resolve(generateWireSyntaxFromRenderedHtml(await res.text())); - } else { - resolve(await res.text()); - } + resolve(await res.text()); } checkForResults(); @@ -344,11 +333,7 @@ function renderAppFromBufferedStream( // Tell React that the writer is ready to drain, which sometimes results in a last "chunk" being written. writer.drain(); - if (isReactHydrationRequest) { - resolve(generateWireSyntaxFromRenderedHtml(writer.toString())); - } else { - resolve(writer.toString()); - } + resolve(writer.toString()); }, onError(error: any) { console.error(error); @@ -370,18 +355,11 @@ function renderAppFromBufferedStream( */ async function renderAppFromStringWithPrepass( ReactApp: JSXElementConstructor, - state: any, - isReactHydrationRequest?: boolean + state: any ) { const hydrationContext: ReactQueryHydrationContext = {}; - const app = isReactHydrationRequest ? ( - - - - ) : ( - - ); + const app = ; await ssrPrepass(app); @@ -394,11 +372,7 @@ async function renderAppFromStringWithPrepass( hydrationContext.dehydratedState = dehydrate(hydrationContext.queryClient); } - const body = renderToString(app); - - return isReactHydrationRequest - ? generateWireSyntaxFromRenderedHtml(body) - : body; + return renderToString(app); } export default renderHydrogen; From 81e45488c7b47bed5e783518c4a00c79bd8da74e Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 14:42:32 +0900 Subject: [PATCH 11/84] wip: add renderToReadableStream for RSC --- packages/hydrogen/src/entry-server.tsx | 18 ++++--- .../framework/ReactFlight/ServerRenderer.ts | 49 ++++++++++++++++++- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 0181a68947..31d9a97402 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -23,7 +23,10 @@ import {dehydrate} from 'react-query/hydration'; import {getCacheControlHeader} from './framework/cache'; import type {ServerResponse} from 'http'; -import {renderToPipeableStream as renderRSCFlight} from './framework/ReactFlight/ServerRenderer'; +import { + rscRenderToPipeableStream, + rscRenderToReadableStream, +} from './framework/ReactFlight/ServerRenderer'; /** * react-dom/unstable-fizz provides different entrypoints based on runtime: @@ -198,12 +201,13 @@ const renderHydrogen: ServerHandler = (App, hook) => { console.error('Fatal', error); }); - const {pipe} = renderRSCFlight( - , - {} // Empty manifest - ); - - pipe(response); + if (rscRenderToPipeableStream) { + rscRenderToPipeableStream().pipe(response); + } else if (rscRenderToReadableStream) { + const stream = rscRenderToReadableStream(); + // TODO: How do we pipe the stream to the response? + return new Response(stream); + } }; return { diff --git a/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts b/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts index f2492992bb..c71b90a322 100644 --- a/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts +++ b/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts @@ -13,7 +13,7 @@ function createDrainHandler(destination: any, request: any) { } type Options = { - onError?: (error: any) => void; + onError?: (error: unknown) => void; }; type Controls = { @@ -50,4 +50,49 @@ function renderToPipeableStream( }; } -export {renderToPipeableStream}; +function renderToReadableStream( + model: ReactModel, + manifest?: BundlerConfig, + options?: Options +): ReadableStream { + const request = createRequest( + model, + manifest || {}, + options ? options.onError : undefined + ); + + const stream = new ReadableStream({ + start(controller) { + startWork(request); + }, + pull(controller) { + // Pull is called immediately even if the stream is not passed to anything. + // That's buffering too early. We want to start buffering once the stream + // is actually used by something so we can give it the best result possible + // at that point. + if (stream.locked) { + startFlowing(request, controller); + } + }, + cancel(reason) {}, + }); + + return stream; +} + +// This will be a build-time variable to trigger tree-shaking +const __WORKER__ = typeof process === 'undefined'; +// declare global { +// // eslint-disable-next-line no-var +// var __WORKER__: boolean; +// } + +const rscRenderToPipeableStream = __WORKER__ + ? undefined + : renderToPipeableStream; + +const rscRenderToReadableStream = __WORKER__ + ? renderToReadableStream + : undefined; + +export {rscRenderToPipeableStream, rscRenderToReadableStream}; From 2640a8c2eb33864376806ef2374ca43f5f5487b9 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 14:52:33 +0900 Subject: [PATCH 12/84] refactor: cleanup custom RSC code --- packages/hydrogen/src/entry-server.tsx | 2 - .../Hydration/ClientComponents.server.ts | 25 --- .../Hydration/HydrationContext.server.ts | 3 - .../Hydration/__tests__/react-utils.spec.tsx | 136 --------------- .../Hydration/__tests__/wire.spec.ts | 158 ------------------ .../src/framework/Hydration/react-utils.ts | 64 ------- .../src/framework/Hydration/wire.server.ts | 98 ----------- 7 files changed, 486 deletions(-) delete mode 100644 packages/hydrogen/src/framework/Hydration/ClientComponents.server.ts delete mode 100644 packages/hydrogen/src/framework/Hydration/HydrationContext.server.ts delete mode 100644 packages/hydrogen/src/framework/Hydration/__tests__/react-utils.spec.tsx delete mode 100644 packages/hydrogen/src/framework/Hydration/__tests__/wire.spec.ts delete mode 100644 packages/hydrogen/src/framework/Hydration/react-utils.ts delete mode 100644 packages/hydrogen/src/framework/Hydration/wire.server.ts diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 31d9a97402..4dd2b07145 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -10,9 +10,7 @@ import {getErrorMarkup} from './utilities/error'; import ssrPrepass from 'react-ssr-prepass'; // import {StaticRouter} from 'react-router-dom'; import type {ServerHandler} from './types'; -// import {HydrationContext} from './framework/Hydration/HydrationContext.server'; import type {ReactQueryHydrationContext} from './foundation/ShopifyProvider/types'; -// import {generateWireSyntaxFromRenderedHtml} from './framework/Hydration/wire.server'; // import {FilledContext, HelmetProvider} from 'react-helmet-async'; import {Html} from './framework/Hydration/Html'; import {HydrationWriter} from './framework/Hydration/writer.server'; diff --git a/packages/hydrogen/src/framework/Hydration/ClientComponents.server.ts b/packages/hydrogen/src/framework/Hydration/ClientComponents.server.ts deleted file mode 100644 index 5edcfa254d..0000000000 --- a/packages/hydrogen/src/framework/Hydration/ClientComponents.server.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface ClientModule { - name: string; - id: string; -} - -/** - * Track the client components discovered while rendering SSR output to wire syntax. - */ -export class ClientComponents { - modules: string[] = []; - - add(module: ClientModule): number { - this.modules.push(JSON.stringify(module)); - - return this.modules.length; - } - - indexOf(module: ClientModule) { - return this.modules.indexOf(JSON.stringify(module)) + 1; - } - - all() { - return this.modules.map((module) => JSON.parse(module)); - } -} diff --git a/packages/hydrogen/src/framework/Hydration/HydrationContext.server.ts b/packages/hydrogen/src/framework/Hydration/HydrationContext.server.ts deleted file mode 100644 index 066fa92278..0000000000 --- a/packages/hydrogen/src/framework/Hydration/HydrationContext.server.ts +++ /dev/null @@ -1,3 +0,0 @@ -import {createContext} from 'react'; - -export const HydrationContext = createContext(false); diff --git a/packages/hydrogen/src/framework/Hydration/__tests__/react-utils.spec.tsx b/packages/hydrogen/src/framework/Hydration/__tests__/react-utils.spec.tsx deleted file mode 100644 index 9cc2c9147b..0000000000 --- a/packages/hydrogen/src/framework/Hydration/__tests__/react-utils.spec.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import {renderReactProps} from '../react-utils'; - -function TestComponent() { - return

Hi

; -} - -function NestedComponent() { - return ; -} - -it('renders single react component', () => { - const props = { - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: { - $$typeof: Symbol.for('react.element'), - type: TestComponent, - props: {}, - }, - }, - }, - }; - - expect(JSON.stringify(renderReactProps(props))).toBe( - JSON.stringify({ - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: { - type: 'p', - key: null, - ref: null, - props: { - children: 'Hi', - }, - _owner: null, - _store: {}, - }, - }, - }, - }) - ); -}); - -it('renders nested react components', () => { - const props = { - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: { - $$typeof: Symbol.for('react.element'), - type: NestedComponent, - props: {}, - }, - }, - }, - }; - - expect(JSON.stringify(renderReactProps(props))).toBe( - JSON.stringify({ - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: { - type: 'p', - key: null, - ref: null, - props: { - children: 'Hi', - }, - _owner: null, - _store: {}, - }, - }, - }, - }) - ); -}); - -it('renders arrays of react components', () => { - const props = { - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: [ - { - $$typeof: Symbol.for('react.element'), - type: NestedComponent, - props: {}, - }, - { - $$typeof: Symbol.for('react.element'), - type: 'p', - props: {children: 'Hello'}, - }, - ], - }, - }, - }; - - expect(JSON.stringify(renderReactProps(props))).toBe( - JSON.stringify({ - stuff: { - $$typeof: Symbol.for('react.element'), - type: 'div', - props: { - children: [ - { - type: 'p', - key: null, - ref: null, - props: { - children: 'Hi', - }, - _owner: null, - _store: {}, - }, - { - type: 'p', - props: { - children: 'Hello', - }, - }, - ], - }, - }, - }) - ); -}); diff --git a/packages/hydrogen/src/framework/Hydration/__tests__/wire.spec.ts b/packages/hydrogen/src/framework/Hydration/__tests__/wire.spec.ts deleted file mode 100644 index 5eb5370682..0000000000 --- a/packages/hydrogen/src/framework/Hydration/__tests__/wire.spec.ts +++ /dev/null @@ -1,158 +0,0 @@ -import {generateWireSyntaxFromRenderedHtml} from '../wire.server'; - -it('renders normal html elements', () => { - const input = `

Hello!

`; - const output = `J0:["$","div",null,{"className":"foo","children":["$","p",null,{"id":"bar","children":"Hello!"}]}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -it('renders Client Components', () => { - const input = `
-
Hi
-
`; - - const output = `M1:{"name":"Counter","id":"assets/Counter.123.js","named":false} -J0:["$","div",null,{"className":"foo","children":["\\n ",["$","@1",null,{"hello":"world","children":"Hi"}],"\\n "]}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -it('renders Client Components with React components as props that are not children', () => { - const input = `
-
Hi
-
`; - - const output = `M1:{"name":"Counter","id":"assets/Counter.123.js","named":false} -J0:["$","div",null,{"className":"foo","children":["\\n ",["$","@1",null,{"sidebar":["$","div",null,{"children":["$","p",null,{"children":"Hello"}]}],"children":"Hi"}],"\\n "]}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -/** - * NOTE: This test assumes that the JSON props are serialized correctly. However, our mechanism for serializing - * these just doesn't... work at all. It's missing `type` primarily. Need to revisit this to ensure we can - * pass components as props to Client Components. - */ -it('renders Client Components with nested React components as props that are not children', () => { - const input = `
-
Hi
-
`; - - const output = `M1:{"name":"Counter","id":"assets/Counter.123.js","named":false} -M2:{"name":"Bar","id":"assets/Bar.123.js","named":false} -J0:["$","div",null,{"className":"foo","children":["\\n ",["$","@1",null,{"sidebar":["$","div",null,{"children":["$","@2",null,{"color":"red","children":"Hello"}]}],"children":"Hi"}],"\\n "]}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -it('renders Client Components with nested props that have multiple children', () => { - const input = `

Bar

Baz
`; - - const output = `J0:["$","div",null,{"className":"foo","children":[["$","p",null,{"key":0,"children":"Bar"}],["$","div",null,{"key":1,"children":"Baz"}]]}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -it('renders Client Components with nested props that have multiple children including text nodes', () => { - const input = `
Hi
`; - - const output = `M1:{"name":"Counter","id":"assets/Counter.123.js","named":false} -J0:["$","@1",null,{"sidebar":["$","div",null,{"children":[["$","p",null,{"children":"Hello"}]," to ",["$","a",null,{"href":"#","children":"you"}]]}],"children":"Hi"}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); - -it('renders named Client Components', () => { - const input = `
Hi
`; - - const output = `M1:{"name":"Counter","id":"assets/Counter.123.js","named":true} -J0:["$","@1",null,{"children":"Hi"}]`; - - expect(generateWireSyntaxFromRenderedHtml(input)).toBe(output); -}); diff --git a/packages/hydrogen/src/framework/Hydration/react-utils.ts b/packages/hydrogen/src/framework/Hydration/react-utils.ts deleted file mode 100644 index 2be420d610..0000000000 --- a/packages/hydrogen/src/framework/Hydration/react-utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {renderToString} from 'react-dom/server'; -/** - * Load `domToReact` from within the library so it doesn't try to load `htmlToDom` - * which seems to always resolve to the client version, even though we want server. - */ -import domToReact from 'html-react-parser/lib/dom-to-react'; -import htmlToDOM from 'html-dom-parser'; -import {ReactElement} from 'react'; - -/** - * Iterate through each prop in an object and render it to an object. - */ -export function renderReactProps(props: any) { - return Object.entries(props).reduce((memo, [key, prop]: [string, any]) => { - if (prop instanceof Array) { - memo[key] = prop.map((p) => renderReactProp(p)); - } else { - memo[key] = renderReactProp(prop); - } - - return memo; - }, {} as Record); -} - -/** - * If a prop is a React element (determined by the `$$typeof` property), - * check to see if it's a Function and call it. Otherwise, recursively - * render React props to support nested components. - */ -function renderReactProp(prop: any): any { - if ( - typeof prop === 'object' && - prop !== null && - prop['$$typeof'] === Symbol.for('react.element') - ) { - if (prop.type instanceof Function) { - /** - * We can't simply call prop.type(), since this does funky things - * with hooks, etc. Instead, we render it to string and convert - * it to a React object. This *still* isn't a perfect approach - * because the component isn't wrapped in the same context - * that the developer may have intended. However, we can - * set expectations as thus when passing components as - * props within Server Components. - */ - return parseReactFromString(renderToString(prop as ReactElement)); - } else { - return { - ...prop, - props: renderReactProps(prop.props), - }; - } - } - - return prop; -} - -export function parseReactFromString(input: string, options: any = {}) { - return domToReact( - // @ts-ignore - htmlToDOM(input, {lowerCaseTags: false, lowerCaseAttributeNames: false}), - options - ); -} diff --git a/packages/hydrogen/src/framework/Hydration/wire.server.ts b/packages/hydrogen/src/framework/Hydration/wire.server.ts deleted file mode 100644 index 14674759d9..0000000000 --- a/packages/hydrogen/src/framework/Hydration/wire.server.ts +++ /dev/null @@ -1,98 +0,0 @@ -import {ClientComponents} from './ClientComponents.server'; -import {parseReactFromString} from './react-utils'; - -/** - * Parse the HTML and convert it to wire syntax model. - */ -export function generateWireSyntaxFromRenderedHtml(html: string) { - const clientComponents = new ClientComponents(); - - const wireModel = parseReactFromString(html, { - library: { - // @ts-ignore - createElement: convertToWireSyntax, - }, - }); - - /** - * Converts each DOM element to React's "wire" syntax. (I came up with this name; not sure what it's called). - * This is a terse syntax that records the model tree as an array of tuples. It also lists the client - * components used with their corresponding IDs to import dynamically. - */ - function convertToWireSyntax(type: any, props?: any, children?: any) { - let componentType = type; - let componentProps = props; - - if (props && props['data-client-component']) { - const component = { - name: props['data-client-component'], - id: props['data-id'], - named: props['data-named'] === 'true', - }; - - const index = - clientComponents.indexOf(component) > 0 - ? clientComponents.indexOf(component) - : clientComponents.add(component); - - componentType = `@${index}`; - componentProps = JSON.parse(props['data-props']); - } - - convertComponentPropsToWireSyntax(componentProps ?? {}); - - if ( - isDomNode(children) || - (children instanceof Array && children.some(isDomNode)) - ) { - if (children instanceof Array) { - children = children.map((child) => { - if (typeof child === 'string') { - return child; - } - - const {children, ...props} = child.props; - return convertToWireSyntax(child.type, props, children); - }); - } else { - const {children: childrenChildren, ...childrenProps} = children.props; - children = convertToWireSyntax( - children.type, - childrenProps, - childrenChildren - ); - } - } - - /** - * TODO: The third position is actually supposed to be `key` I think. - * It's usually `null` which is what confused me. Find a way to pass - * through `key` here if we have one in `props`. - */ - return ['$', componentType, null, {...componentProps, children}]; - } - - function convertComponentPropsToWireSyntax( - componentProps: Record - ) { - Object.entries(componentProps).forEach(([key, prop]) => { - if (isDomNode(prop)) { - const {children, ...props} = prop.props; - componentProps[key] = convertToWireSyntax(prop.type, props, children); - } - }); - } - - return ( - clientComponents - .all() - .map((component, idx) => { - return `M${idx + 1}:${JSON.stringify(component)}`; - }) - .join('\n') + `\nJ0:${JSON.stringify(wireModel)}` - ).trim(); -} - -function isDomNode(item: any) { - return item !== null && typeof item === 'object' && '_owner' in item; -} From 46dea4796de298e3f235f5cabff83d62e65e1b06 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 15:01:17 +0900 Subject: [PATCH 13/84] refactor: move and rename files --- packages/hydrogen/src/entry-server.tsx | 2 +- packages/hydrogen/src/framework/Hydration/Cache.client.ts | 4 ++-- .../ClientConfig.ts => Hydration/rsc-client-config.ts} | 4 ++-- .../ClientHydrator.ts => Hydration/rsc-client-hydrator.ts} | 4 ++-- .../ServerConfig.ts => Hydration/rsc-server-config.ts} | 0 .../ServerRenderer.ts => Hydration/rsc-server-renderer.ts} | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename packages/hydrogen/src/framework/{ReactFlight/ClientConfig.ts => Hydration/rsc-client-config.ts} (95%) rename packages/hydrogen/src/framework/{ReactFlight/ClientHydrator.ts => Hydration/rsc-client-hydrator.ts} (91%) rename packages/hydrogen/src/framework/{ReactFlight/ServerConfig.ts => Hydration/rsc-server-config.ts} (100%) rename packages/hydrogen/src/framework/{ReactFlight/ServerRenderer.ts => Hydration/rsc-server-renderer.ts} (95%) diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 4dd2b07145..d84383aff4 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -24,7 +24,7 @@ import type {ServerResponse} from 'http'; import { rscRenderToPipeableStream, rscRenderToReadableStream, -} from './framework/ReactFlight/ServerRenderer'; +} from './framework/Hydration/rsc-server-renderer'; /** * react-dom/unstable-fizz provides different entrypoints based on runtime: diff --git a/packages/hydrogen/src/framework/Hydration/Cache.client.ts b/packages/hydrogen/src/framework/Hydration/Cache.client.ts index c88df9c1c1..86d1b4ecda 100644 --- a/packages/hydrogen/src/framework/Hydration/Cache.client.ts +++ b/packages/hydrogen/src/framework/Hydration/Cache.client.ts @@ -1,7 +1,7 @@ // @ts-ignore import {unstable_getCacheForType, unstable_useCacheRefresh} from 'react'; -import {createFromFetch} from '../ReactFlight/ClientHydrator'; -import type {FlightResponse} from '../ReactFlight/ClientConfig'; +import {createFromFetch} from '../Hydration/rsc-client-hydrator'; +import type {FlightResponse} from '../Hydration/rsc-client-config'; function createResponseCache() { return new Map(); diff --git a/packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts b/packages/hydrogen/src/framework/Hydration/rsc-client-config.ts similarity index 95% rename from packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts rename to packages/hydrogen/src/framework/Hydration/rsc-client-config.ts index 586c20eedf..453fd702a1 100644 --- a/packages/hydrogen/src/framework/ReactFlight/ClientConfig.ts +++ b/packages/hydrogen/src/framework/Hydration/rsc-client-config.ts @@ -1,5 +1,5 @@ -import importClientComponent from '../Hydration/client-imports'; -import type {ModuleReference, ModuleMetaData} from './ServerConfig'; +import importClientComponent from './client-imports'; +import type {ModuleReference, ModuleMetaData} from './rsc-server-config'; export type UninitializedModel = string; export type StringDecoder = unknown; diff --git a/packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts b/packages/hydrogen/src/framework/Hydration/rsc-client-hydrator.ts similarity index 91% rename from packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts rename to packages/hydrogen/src/framework/Hydration/rsc-client-hydrator.ts index 194225621b..a370264f84 100644 --- a/packages/hydrogen/src/framework/ReactFlight/ClientHydrator.ts +++ b/packages/hydrogen/src/framework/Hydration/rsc-client-hydrator.ts @@ -1,7 +1,7 @@ -import type {FlightResponse} from './ClientConfig'; +import type {FlightResponse} from './rsc-client-config'; // @ts-ignore import createFlightHydrator from 'react-client/flight'; -import hydratorConfig from './ClientConfig'; +import hydratorConfig from './rsc-client-config'; const {createResponse, reportGlobalError, processBinaryChunk, close} = createFlightHydrator(hydratorConfig); diff --git a/packages/hydrogen/src/framework/ReactFlight/ServerConfig.ts b/packages/hydrogen/src/framework/Hydration/rsc-server-config.ts similarity index 100% rename from packages/hydrogen/src/framework/ReactFlight/ServerConfig.ts rename to packages/hydrogen/src/framework/Hydration/rsc-server-config.ts diff --git a/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts b/packages/hydrogen/src/framework/Hydration/rsc-server-renderer.ts similarity index 95% rename from packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts rename to packages/hydrogen/src/framework/Hydration/rsc-server-renderer.ts index c71b90a322..ff9f7ac1dc 100644 --- a/packages/hydrogen/src/framework/ReactFlight/ServerRenderer.ts +++ b/packages/hydrogen/src/framework/Hydration/rsc-server-renderer.ts @@ -1,9 +1,9 @@ import type {Writable} from 'stream'; -import type {BundlerConfig, ReactModel} from './ServerConfig'; +import type {BundlerConfig, ReactModel} from './rsc-server-config'; // @ts-ignore import createFlightRenderer from 'react-server/flight'; -import rendererConfig from './ServerConfig'; +import rendererConfig from './rsc-server-config'; const {createRequest, startWork, startFlowing} = createFlightRenderer(rendererConfig); From f7f6b51a7d3431489129be187da8bf19d57e92eb Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Fri, 3 Dec 2021 15:08:51 +0900 Subject: [PATCH 14/84] wip: fix test app --- packages/dev/src/RSCTest/C2.client.jsx | 4 ++-- packages/dev/src/RSCTest/C3.client.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dev/src/RSCTest/C2.client.jsx b/packages/dev/src/RSCTest/C2.client.jsx index e964c9d0eb..adf640e3fd 100644 --- a/packages/dev/src/RSCTest/C2.client.jsx +++ b/packages/dev/src/RSCTest/C2.client.jsx @@ -1,10 +1,10 @@ const onClick = () => window.alert('42'); export default function C2(props) { - const color = props.myProp2 ? 'blue' : 'gray'; + const color = props.myProp2 ? 'bg-blue-500' : 'bg-gray-500'; return (