diff --git a/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/ServerRoutes.tsx b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/ServerRoutes.tsx new file mode 100644 index 000000000000..1ba9f57c8638 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/ServerRoutes.tsx @@ -0,0 +1,85 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Route } from '@redwoodjs/router/dist/Route' +import { Router } from '@redwoodjs/router/dist/server-router' +import { Set } from '@redwoodjs/router/dist/Set' + +import NavigationLayout from './layouts/NavigationLayout/NavigationLayout' +import ScaffoldLayout from './layouts/ScaffoldLayout/ScaffoldLayout' +import AboutPage from './pages/AboutPage/AboutPage' +import EmptyUserEmptyUsersPage from './pages/EmptyUser/EmptyUsersPage/EmptyUsersPage' +import EmptyUserNewEmptyUserPage from './pages/EmptyUser/NewEmptyUserPage/NewEmptyUserPage' +import HomePage from './pages/HomePage/HomePage' +import MultiCellPage from './pages/MultiCellPage/MultiCellPage' +import UserExampleNewUserExamplePage from './pages/UserExample/NewUserExamplePage/NewUserExamplePage' +import UserExampleUserExamplePage from './pages/UserExample/UserExamplePage/UserExamplePage' +import UserExampleUserExamplesPage from './pages/UserExample/UserExamplesPage/UserExamplesPage' + +const NotFoundPage = () => { + return
Not Found
+} + +const Routes = ({ location }) => { + return ( + + + + + + + + + + + + + + + + + + + + ) +} + +export default Routes diff --git a/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/entry.server.tsx b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/entry.server.tsx index 32fcd961f471..5636e61d4df6 100644 --- a/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/entry.server.tsx +++ b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/entry.server.tsx @@ -1,16 +1,22 @@ import type { TagDescriptor } from '@redwoodjs/web/dist/components/htmlTags' import { Document } from './Document' +import ServerRoutes from './ServerRoutes' interface Props { css: string[] meta?: TagDescriptor[] + location: { + pathname: string + hash?: string + search?: string + } } -export const ServerEntry: React.FC = ({ css, meta }) => { +export const ServerEntry: React.FC = ({ css, meta, location }) => { return ( -
App
+
) } diff --git a/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/layouts/NavigationLayout/NavigationLayout.tsx b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/layouts/NavigationLayout/NavigationLayout.tsx index 13eccddcd228..f26eec76bfa6 100644 --- a/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/layouts/NavigationLayout/NavigationLayout.tsx +++ b/__fixtures__/test-project-rsc-external-packages-and-cells/web/src/layouts/NavigationLayout/NavigationLayout.tsx @@ -1,12 +1,17 @@ -import { Link, routes } from '@redwoodjs/router' +import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes' import './NavigationLayout.css' type NavigationLayoutProps = { children?: React.ReactNode + rnd?: number } -const NavigationLayout = ({ children }: NavigationLayoutProps) => { +const Link = (props: any) => { + return {props.children} +} + +const NavigationLayout = ({ children, rnd }: NavigationLayoutProps) => { return (
+
{Math.round(rnd * 100)}
{children}
) diff --git a/packages/vite/src/clientSsr.ts b/packages/vite/src/clientSsr.ts index e0fb950b82f7..4fce7a4473a3 100644 --- a/packages/vite/src/clientSsr.ts +++ b/packages/vite/src/clientSsr.ts @@ -124,7 +124,7 @@ export function renderFromDist>( const id = resolveClientEntryForProd(filePath, clientEntries) - console.log('Proxy id', id) + console.log('clientSsr.ts::Proxy id', id) // id /Users/tobbe/tmp/test-project-rsc-external-packages-and-cells/web/dist/client/assets/rsc-AboutCounter.tsx-1-4kTKU8GC.mjs return { id, chunks: [id], name, async: true } }, diff --git a/packages/vite/src/lib/registerFwGlobalsAndShims.ts b/packages/vite/src/lib/registerFwGlobalsAndShims.ts index b25a335cf413..952739e79ceb 100644 --- a/packages/vite/src/lib/registerFwGlobalsAndShims.ts +++ b/packages/vite/src/lib/registerFwGlobalsAndShims.ts @@ -100,6 +100,8 @@ function registerFwGlobals() { * We have to call it early in the app's lifecycle, before code that depends on * it runs and do so at the server start in (src/devFeServer.ts and * src/runFeServer.ts). + * + * We generate the input to the shims in the `bundlerConfig` Proxies we have */ function registerFwShims() { if (!getConfig().experimental?.rsc?.enabled) { diff --git a/packages/vite/src/rsc/rscWorker.ts b/packages/vite/src/rsc/rscWorker.ts index 095f52595715..2df6cd9096c2 100644 --- a/packages/vite/src/rsc/rscWorker.ts +++ b/packages/vite/src/rsc/rscWorker.ts @@ -369,7 +369,7 @@ async function renderRsc(input: RenderInput): Promise { id = resolveClientEntryForProd(filePath, config) } - console.log('Proxy id', id) + console.log('rscWorker proxy id', id) // id /assets/rsc0-beb48afe.js return { id, chunks: [id], name, async: true } }, diff --git a/packages/vite/src/streaming/ssrModuleMap.ts b/packages/vite/src/streaming/ssrModuleMap.ts index 3ecfa7b7b6a8..a1cac27d3ef4 100644 --- a/packages/vite/src/streaming/ssrModuleMap.ts +++ b/packages/vite/src/streaming/ssrModuleMap.ts @@ -1,3 +1,5 @@ +import { makeFilePath } from '../utils' + type SSRModuleMap = null | { [clientId: string]: { [clientExportName: string]: ClientReferenceManifestEntry @@ -24,6 +26,8 @@ export const moduleMap: SSRModuleMap = new Proxy( {}, { get(_target, name: string) { + filePath = makeFilePath(filePath) + const manifestEntry: ClientReferenceManifestEntry = { id: filePath, chunks: [filePath], diff --git a/tasks/smoke-tests/rsc-external-packages-and-cells/tests/rsc-ssr.spec.ts b/tasks/smoke-tests/rsc-external-packages-and-cells/tests/rsc-ssr.spec.ts new file mode 100644 index 000000000000..4f208fabc4ab --- /dev/null +++ b/tasks/smoke-tests/rsc-external-packages-and-cells/tests/rsc-ssr.spec.ts @@ -0,0 +1,98 @@ +import { test, expect } from '@playwright/test' + +// UA taken from https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers +const BOT_USERAGENT = + 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36' + +test('Check that homepage has content fully rendered from the server, without JS', async ({ + browser, +}) => { + // Rendering as a bot here, to make sure we're getting the full page and not + // just some of it, with the rest streamed in later + const botContext = await browser.newContext({ + userAgent: BOT_USERAGENT, + // Even without JS, this should be a fully rendered page + javaScriptEnabled: false, + }) + + const page = await botContext.newPage() + + await page.goto('/') + + // Appears when the navigation layout has successfully rendered + await page.waitForSelector('main') + + // The NavigationLayout should have a random number in it + const rnd = await page.locator('div#rnd').innerHTML() + expect(rnd).toMatch(/\s*\d+\s*/) + + // expect there to only be one h1 heading element on the page + await expect(page.locator('h1')).toHaveCount(1) + await expect(page.locator('h1').first()).toHaveText('Hello Anonymous!!') + + // There should be a link to the about page + await expect(page.locator('a').getByText('About')).toBeVisible() + + await botContext.close() +}) + +test('Make sure navigation works even without JS', async ({ browser }) => { + // Rendering as a bot here, to make sure we're getting the full page and not + // just some of it, with the rest streamed in later + const botContext = await browser.newContext({ + userAgent: BOT_USERAGENT, + // Even without JS, this should be a fully rendered page + javaScriptEnabled: false, + }) + + const page = await botContext.newPage() + + await page.goto('/') + + // There should be a link to the about page + const aboutLink = page.locator('a').getByText('About') + expect(aboutLink).toBeVisible() + + // Clicking on the about link should take us to the about page + await aboutLink.click() + + expect(page.url()).toMatch(/\/about$/) + + // expect there to only be one h1 heading element on the page + await expect(page.locator('h1')).toHaveCount(1) + await expect(page.locator('h1').first()).toHaveText('About Redwood') + + await botContext.close() +}) + +test('The page should have a form button, but it should be non-interactive', async ({ + browser, +}) => { + // Rendering as a bot here, to make sure we're getting the full page and not + // just some of it, with the rest streamed in later + const botContext = await browser.newContext({ + userAgent: BOT_USERAGENT, + // Even without JS, this should be a fully rendered page + javaScriptEnabled: false, + }) + + const page = await botContext.newPage() + + await page.goto('/about') + + const paragraphs = page.locator('p') + + // Expect the count to be 0 when the page is first loaded + expect(paragraphs.getByText('Count: 0')).toBeVisible() + + await page.getByRole('button', { name: 'Increment' }).click() + + // The count should stay at 0, because the page should not be interactive + // (This is the SSRed version of the page, with no JS) + await expect(paragraphs.getByText('Count: 0')).toBeVisible() + + await expect(paragraphs.getByText('RSC on client: enabled')).toBeVisible() + await expect(paragraphs.getByText('RSC on server: enabled')).toBeVisible() + + await botContext.close() +})