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
+
)
}
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()
+})