diff --git a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts index c53b180b0363..5e2f819ef472 100644 --- a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts @@ -72,8 +72,7 @@ describe('page auto loader correctly imports pages', () => { }) test('RSC specific code should not be added', () => { - expect(result?.code).not.toContain( - 'import { renderFromRscServer } from "@redwoodjs/vite/client"', - ) + expect(result?.code).not.toContain('DummyComponent') + expect(result?.code).not.toContain('= () => {}') }) }) diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts deleted file mode 100644 index b23d0a8d5b1f..000000000000 --- a/packages/vite/src/client.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { cache, use, useEffect, useState } from 'react' -import type { ReactElement } from 'react' - -import type { Options } from 'react-server-dom-webpack/client' -import { createFromFetch, encodeReply } from 'react-server-dom-webpack/client' - -import { StatusError } from './lib/StatusError.js' - -const checkStatus = async ( - responsePromise: Promise, -): Promise => { - const response = await responsePromise - - if (!response.ok) { - throw new StatusError(response.statusText, response.status) - } - - return response -} - -const BASE_PATH = '/rw-rsc/' - -type SetRerender = ( - rerender: (next: [Thenable, string]) => void, -) => () => void - -function fetchRSC( - rscId: string, - serializedProps: string, -): readonly [Thenable, SetRerender] { - console.log('fetchRSC serializedProps', serializedProps) - - let rerender: ((next: [Thenable, string]) => void) | undefined - - const setRerender: SetRerender = (fn) => { - rerender = fn - return () => { - rerender = undefined - } - } - - const searchParams = new URLSearchParams() - searchParams.set('props', serializedProps) - - const options: Options = { - // React will hold on to `callServer` and use that when it detects a - // server action is invoked (like `action={onSubmit}` in a
- // element). So for now at least we need to send it with every RSC - // request, so React knows what `callServer` method to use for server - // actions inside the RSC. - callServer: async function (rsaId: string, args: unknown[]) { - // `args` is often going to be an array with just a single element, - // and that element will be FormData - console.log('client.ts :: callServer rsfId', rsaId, 'args', args) - - const isMutating = !!mutationMode - const searchParams = new URLSearchParams() - searchParams.set('action_id', rsaId) - let id: string - - if (isMutating) { - id = rscId - searchParams.set('props', serializedProps) - } else { - id = '_' - } - - const response = fetch(BASE_PATH + id + '?' + searchParams, { - method: 'POST', - body: await encodeReply(args), - headers: { - 'rw-rsc': '1', - }, - }) - - // I'm not sure this recursive use of `options` is needed. I briefly - // tried without it, and things seemed to work. But keeping it for - // now, until we learn more. - const data = createFromFetch(response, options) - - if (isMutating) { - rerender?.([data, serializedProps]) - } - - return data - }, - } - - const prefetched = (globalThis as any).__WAKU_PREFETCHED__?.[rscId]?.[ - serializedProps - ] - - console.log( - 'fetchRSC before createFromFetch', - BASE_PATH + rscId + '?' + searchParams, - ) - - const response = - prefetched || - fetch(BASE_PATH + rscId + '?' + searchParams, { - headers: { - 'rw-rsc': '1', - }, - }) - const data = createFromFetch(checkStatus(response), options) - console.log('fetchRSC after createFromFetch. data:', data) - - return [data, setRerender] -} - -export function renderFromRscServer(rscId: string) { - console.log('serve rscId (renderFromRscServer)', rscId) - - if (typeof window === 'undefined') { - throw new Error( - 'renderFromRscServer should only be used in a real browser ' + - 'environment. Did you mean to use renderFromDist in clientSsr.ts ' + - 'instead?', - ) - } - - const cachedFetchRSC = cache(fetchRSC) - - // Create temporary client component that wraps the ServerComponent returned - // by the `createFromFetch` call. - const ServerComponent = (props: TProps) => { - console.log('ServerComponent', rscId, 'props', props) - - // FIXME we blindly expect JSON.stringify usage is deterministic - const serializedProps = JSON.stringify(props || {}) - const [data, setRerender] = cachedFetchRSC(rscId, serializedProps) - const [state, setState] = useState< - | [dataToOverride: Thenable, lastSerializedProps: string] - | undefined - >() - - // MARK Should this be useLayoutEffect? - useEffect(() => setRerender(setState), [setRerender]) - - let dataToReturn = data - - if (state) { - if (state[1] === serializedProps) { - dataToReturn = state[0] - } else { - setState(undefined) - } - } - - // `use()` will throw a `SuspenseException` as long as `dataToReturn` is - // unfulfilled. React internally tracks this promise and re-renders this - // component when the promise resolves. When the promise is resolved no - // exception will be thrown and the actual value of the promise will be - // returned instead - // The closest suspense boundary will render its fallback when the - // exception is thrown - return use(dataToReturn) - - // TODO (RSC): Might be an issue with `use` above with startTransition - // according to the waku sources I copied this from. We need to figure out - // if this is the right way to do things - } - - return ServerComponent -} - -let mutationMode = 0 - -export function mutate(fn: () => void) { - ++mutationMode - fn() - --mutationMode -} diff --git a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-route-auto-loader.test.ts b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-route-auto-loader.test.ts index 14fb0b7ebfcb..bfd710b0a09f 100644 --- a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-route-auto-loader.test.ts +++ b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-route-auto-loader.test.ts @@ -105,8 +105,8 @@ describe('rscRoutesAutoLoader', () => { ) // What we are interested in seeing here is: - // - The import of `renderFromRscServer` from `@redwoodjs/vite/client` - // - The call to `renderFromRscServer` for each page that wasn't already imported + // - Creation of `const EmptyUserNewEmptyUserPage = () => null;` etc for each page + // - The import of `dummyComponent` from `@redwoodjs/router/dist/dummyComponent` expect(output).toMatchInlineSnapshot(` "const EmptyUserNewEmptyUserPage = () => null; const EmptyUserEmptyUsersPage = () => null; @@ -364,9 +364,8 @@ describe('rscRoutesAutoLoader', () => { undefined, ) - // We don't have to add calls for the AboutPage as it was already imported - expect(output).not.toContain('renderFromDist("AboutPage")') - expect(output).not.toContain('renderFromRscServer("AboutPage")') + // We should't create a stub component for the AboutPage as it was already imported + expect(output).not.toContain('const AboutPage = () => null') }) }) diff --git a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-client.test.ts b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-client.test.ts index 65db8f607e31..f5c56f37fa69 100644 --- a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-client.test.ts +++ b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-client.test.ts @@ -107,9 +107,10 @@ describe('rscRoutesAutoLoader', () => { ) // What we are interested in seeing here is: - // - There's a CLIENT_REFERENCE + // - There's a registerClientReference import // - There's a Link export - // - There's a proper $$id + // - There's a registerClientReference call with the path to the built link + // component dist file expect(output).toMatchInlineSnapshot(` "import {registerClientReference} from "react-server-dom-webpack/server"; export const Link = registerClientReference(function() {throw new Error("Attempted to call Link() from the server but Link is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");},"/Users/tobbe/rw-app/web/dist/rsc/assets/rsc-link.js-13.mjs","Link") @@ -155,8 +156,8 @@ describe('rscRoutesAutoLoader', () => { ) // What we are interested in seeing here is: - // - The import of `renderFromRscServer` from `@redwoodjs/vite/client` - // - The call to `renderFromRscServer` for each page that wasn't already imported + // - The import of `registerClientReference` from `react-server-dom-webpack/server` + // - The export of all of the individual components expect(output).toMatchInlineSnapshot(` "import {registerClientReference} from "react-server-dom-webpack/server"; export const CheckmarkIcon = registerClientReference(function() {throw new Error("Attempted to call CheckmarkIcon() from the server but CheckmarkIcon is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");},"/Users/tobbe/rw-app/web/dist/rsc/assets/rsc-index.js-15.mjs","CheckmarkIcon")