Skip to content

Commit

Permalink
go crazy with virtual module to route - break 404 status code
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Walker-GM committed Sep 4, 2024
1 parent 4b5783b commit 6c16faa
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 190 deletions.
18 changes: 0 additions & 18 deletions packages/vite/src/envs/__example__/pages/Home.tsx

This file was deleted.

91 changes: 11 additions & 80 deletions packages/vite/src/envs/__example__/web/src/Routes.tsx
Original file line number Diff line number Diff line change
@@ -1,91 +1,22 @@
import React from 'react'

import { Router, Route, Private, Set } from '@redwoodjs/router'

import BlogLayout from 'src/layouts/BlogLayout'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
import HomePage from 'src/pages/HomePage'
import { Router, Route } from '@redwoodjs/router'

import { useAuth } from './auth'

// NOTE(jgmw): These are typically injected by a babel plugin
import HomePage from './pages/HomePage/HomePage.jsx'
// import NotFoundPage from './pages/NotFoundPage/NotFoundPage.jsx'
import Test1Page from './pages/Test1Page/Test1Page.jsx'
import Test2Page from './pages/Test2Page/Test2Page.jsx'

const Routes = () => {
return (
<Router useAuth={useAuth}>
<Route path="/double" page={DoublePage} name="double" prerender />
<Route path="/login" page={LoginPage} name="login" />
<Route path="/signup" page={SignupPage} name="signup" />
<Route
path="/forgot-password"
page={ForgotPasswordPage}
name="forgotPassword"
/>
<Route
path="/reset-password"
page={ResetPasswordPage}
name="resetPassword"
/>
<Set
wrap={ScaffoldLayout}
title="Contacts"
titleTo="contacts"
buttonLabel="New Contact"
buttonTo="newContact"
>
<Route
path="/contacts/new"
page={ContactNewContactPage}
name="newContact"
prerender
/>
<Route
path="/contacts/{id:Int}/edit"
page={ContactEditContactPage}
name="editContact"
/>
<Route
path="/contacts/{id:Int}"
page={ContactContactPage}
name="contact"
/>
<Route path="/contacts" page={ContactContactsPage} name="contacts" />
</Set>
<Set
wrap={ScaffoldLayout}
title="Posts"
titleTo="posts"
buttonLabel="New Post"
buttonTo="newPost"
>
<Route path="/posts/new" page={PostNewPostPage} name="newPost" />
<Route
path="/posts/{id:Int}/edit"
page={PostEditPostPage}
name="editPost"
/>
<Route path="/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/posts" page={PostPostsPage} name="posts" />
</Set>
<Set wrap={BlogLayout}>
<Route
path="/waterfall/{id:Int}"
page={WaterfallPage}
prerender
name="waterfall"
/>
<Private unauthenticated="login">
<Route path="/profile" page={ProfilePage} name="profile" />
</Private>
<Route
path="/blog-post/{id:Int}"
page={BlogPostPage}
name="blogPost"
prerender
/>
<Route path="/contact" page={ContactUsPage} name="contactUs" />
<Route path="/about" page={AboutPage} name="about" prerender />
<Route path="/" page={HomePage} name="home" prerender />
{/* <Route notfound page={NotFoundPage} prerender /> */}
</Set>
<Route path="/test-2" page={Test2Page} name="test-2" />
<Route path="/test-1" page={Test1Page} name="test-1" />
<Route path="/" page={HomePage} name="home" />
{/* <Route notfound page={NotFoundPage} /> */}
</Router>
)
}
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,43 +1,14 @@
import React from 'react'

const HomePage = () => {
import { Like } from '../../components/Like/Like.jsx'
import { Links } from '../../components/Links/Links.jsx'

export default function Home() {
return (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
fontFamily: 'monospace',
}}
>
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
borderRight: '1px solid black',
paddingRight: '2em',
marginRight: '2em',
}}
>
<h1>200</h1>
</div>
<div>
<h1>Welcome home</h1>
</div>
</div>
<div>
<h1>Home</h1>
<Links />
<Like />
</div>
)
}

export default HomePage
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'

import { Links } from './Links.jsx'
import { Links } from '../../components/Links/Links.jsx'

export default function Home() {
export default function Test1Page() {
return (
<div>
<h1>Test 1</h1>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'

import { Links } from './Links.jsx'
import { Links } from '../../components/Links/Links.jsx'

export default function Home() {
export default function Test2Page() {
return (
<div>
<h1>Test 2</h1>
Expand Down
23 changes: 6 additions & 17 deletions packages/vite/src/envs/entry-ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import ReactClient from 'react-server-dom-webpack/client.edge'
import { injectRSCPayload } from 'rsc-html-stream/server'
import type { ModuleRunner } from 'vite/module-runner'

import { getPageForRoute } from './__example__/routes.js'
import { moduleMap } from './register/ssr.js'

export async function ssrHandler(opts: {
Expand All @@ -18,22 +17,12 @@ export async function ssrHandler(opts: {
(id: string) => import(/* @vite-ignore */ id),
)

// TODO: Make this a Plugin.
// Show off idea of "everything is a plugin."
// Determine if there's a valid page to render for the given URL.
// Get the page and response status from the URL
const url = new URL(req.url)
let notfound = false
let Page = await getPageForRoute({
pathname: url.pathname,
viteEnvRunner: viteEnvRunnerRSC,
})
if (!Page) {
notfound = true
const { default: notFoundPage } = await viteEnvRunnerRSC.import(
'virtual:redwoodjs-not-found-page',
)
Page = notFoundPage
}
const { default: Page } = await viteEnvRunnerRSC.import(
`virtual:redwoodjs-load-page-for-route?pathname=${url.pathname}`,
)

const { rscHandler } = await viteEnvRunnerRSC.import('src/envs/entry-rsc.tsx')
const rscResult = await rscHandler({ req, Page })

Expand All @@ -55,6 +44,6 @@ export async function ssrHandler(opts: {
const html = htmlStream.pipeThrough(injectRSCPayload(rscStream2))
return new Response(html, {
headers: { 'content-type': 'text/html' },
status: notfound ? 404 : 200,
status: 200,
})
}
134 changes: 100 additions & 34 deletions packages/vite/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,51 +105,116 @@ function vitePluginRSC_UseClient(): PluginOption {
}

function vitePlugin_Redwood_Router_NotFoundPage(): PluginOption {
const virtualModuleId = 'virtual:redwoodjs-not-found-page'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return [
{
name: vitePlugin_Redwood_Router_NotFoundPage.name,
async resolveId(source) {
if (source === 'virtual:redwoodjs-not-found-page') {
// TODO(jgmw): We must set the env var so this function picks up the mock project directory
process.env.RWJS_CWD = path.join(
import.meta.dirname,
'src',
'envs',
'__example__',
)

// Extract the routes from the router
const { getProjectRoutes } = await import('@redwoodjs/internal')
const routes = getProjectRoutes()
const notFoundRoute = routes.find((spec) => spec.isNotFound)
if (!notFoundRoute) {
// We fallback to loading the resolved virtual module which will provide a default implementation
return `\0virtual:redwoodjs-not-found-page`
}
if (source !== virtualModuleId) {
return undefined
}

// Extract the pages from the project structure
// TODO(jgmw): Not thrilled about using the deprecated function
const { processPagesDir } = await import('@redwoodjs/project-config')
const pages = processPagesDir()
const notFoundPage = pages.find(
(page) => page.constName === notFoundRoute.pageIdentifier,
)
if (!notFoundPage) {
// We fallback to loading the resolved virtual module which will provide a default implementation
return `\0virtual:redwoodjs-not-found-page`
}
// TODO(jgmw): We must set the env var so this function picks up the mock project directory
process.env.RWJS_CWD = path.join(
import.meta.dirname,
'src',
'envs',
'__example__',
)

// Extract the routes from the AST of the Routes.tsx file
const { getProjectRoutes } = await import('@redwoodjs/internal')
const routes = getProjectRoutes()

// Find the not found route
const notFoundRoute = routes.find((route) => route.isNotFound)
if (!notFoundRoute) {
return resolvedVirtualModuleId
}

// Extract the pages from the project structure
// TODO(jgmw): Not thrilled about using the deprecated function
const { processPagesDir } = await import('@redwoodjs/project-config')
const pages = processPagesDir()

// We return the path to page the user specified to handle 404s
return notFoundPage.path
const notFoundPage = pages.find(
(page) => page.constName === notFoundRoute.pageIdentifier,
)
if (!notFoundPage) {
return resolvedVirtualModuleId
}
return undefined

// We return the path to page the user specified to handle 404s
return notFoundPage.path
},
// Load provides a fallback to a default 404 page
load(id) {
if (id === '\0virtual:redwoodjs-not-found-page') {
return 'export default () => "default 404 page"'
if (id !== resolvedVirtualModuleId) {
return undefined
}
// This is the most basic 404 page
return 'export default () => "404"'
},
},
]
}

function vitePlugin_Redwood_LoadPageForRoute(): PluginOption {
const virtualModuleId = 'virtual:redwoodjs-load-page-for-route'
return [
{
name: vitePlugin_Redwood_LoadPageForRoute.name,
async resolveId(source) {
if (!source.startsWith(virtualModuleId)) {
return undefined
}

// TODO(jgmw): We must set the env var so this function picks up the mock project directory
process.env.RWJS_CWD = path.join(
import.meta.dirname,
'src',
'envs',
'__example__',
)

// Get the route from the pathname
const searchParams = new URLSearchParams(
source.substring(virtualModuleId.length),
)
const pathname = searchParams.get('pathname')
if (!pathname) {
throw new Error('No pathname provided')
}
return undefined

// Extract the routes from the AST of the Routes.tsx file
const { getProjectRoutes } = await import('@redwoodjs/internal')
const routes = getProjectRoutes()

const { processPagesDir } = await import('@redwoodjs/project-config')
const pages = processPagesDir()

const { matchPath } = await import('@redwoodjs/router')

for (const route of routes) {
// TODO(jgmw): Handle route params
const { match } = matchPath(route.pathDefinition, pathname)
if (match) {
const page = pages.find(
(page) => page.constName === route.pageIdentifier,
)
if (!page) {
throw new Error(
`Could not find page for route: ${route.pageIdentifier}`,
)
}

return page.path
}
}

// Fallback to switching the id to the not-found page module
return this.resolve('virtual:redwoodjs-not-found-page')
},
},
]
Expand All @@ -163,6 +228,7 @@ export default defineConfig({
vitePluginReact(),
vitePluginRSC(),
vitePluginSSR(),
vitePlugin_Redwood_LoadPageForRoute(),
vitePlugin_Redwood_Router_NotFoundPage(),
],
})

0 comments on commit 6c16faa

Please sign in to comment.