Skip to content

Commit

Permalink
fix(ssr): Get experimental ssr setup working properly (#8922)
Browse files Browse the repository at this point in the history
As title, addresses the following issues:
- fixes: no css styles
- fixes: DevFatalErrorPage breaks with “window” not defined during ssr
- fixes: ReferenceError: RWJS_DEBUG_ENV is not defined
- sets up `yarn rw dev` to use fe server when streaming experimen
enabled
- Also removes server context, no longer needed.

Note: Not needed for v6!
  • Loading branch information
dac09 committed Jul 18, 2023
1 parent b13caec commit abf229b
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 50 deletions.
59 changes: 59 additions & 0 deletions packages/cli/src/commands/__tests__/dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ describe('yarn rw dev', () => {
port: 8911,
debugPort: 18911,
},
experimental: {
streamingSsr: {
enabled: false,
},
},
})

await handler({
Expand All @@ -106,6 +111,45 @@ describe('yarn rw dev', () => {
expect(generateCommand.command).toEqual('yarn rw-gen-watch')
})

it('Should run api and FE dev server, when streaming experimental flag enabled', async () => {
getConfig.mockReturnValue({
web: {
port: 8910,
},
api: {
port: 8911,
debugPort: 18911,
},
experimental: {
streamingSsr: {
enabled: true, // <-- enable SSR/Streaming
},
},
})

await handler({
side: ['api', 'web'],
})

expect(generatePrismaClient).toHaveBeenCalledTimes(1)
const concurrentlyArgs = concurrently.mock.lastCall[0]

const webCommand = find(concurrentlyArgs, { name: 'web' })
const apiCommand = find(concurrentlyArgs, { name: 'api' })
const generateCommand = find(concurrentlyArgs, { name: 'gen' })

// Uses absolute path, so not doing a snapshot
expect(webCommand.command).toContain(
'yarn cross-env NODE_ENV=development rw-dev-fe'
)

expect(apiCommand.command).toMatchInlineSnapshot(
`"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
)

expect(generateCommand.command).toEqual('yarn rw-gen-watch')
})

it('Debug port passed in command line overrides TOML', async () => {
getConfig.mockReturnValue({
web: {
Expand All @@ -115,6 +159,11 @@ describe('yarn rw dev', () => {
port: 8911,
debugPort: 505050,
},
experimental: {
streamingSsr: {
enabled: false,
},
},
})

await handler({
Expand All @@ -140,6 +189,11 @@ describe('yarn rw dev', () => {
port: 8911,
debugPort: false,
},
experimental: {
streamingSsr: {
enabled: false,
},
},
})

await handler({
Expand All @@ -162,6 +216,11 @@ describe('yarn rw dev', () => {
api: {
port: 8911,
},
experimental: {
streamingSsr: {
enabled: false,
},
},
})

await handler({
Expand Down
31 changes: 25 additions & 6 deletions packages/cli/src/commands/devHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,31 @@ export const handler = async ({

const redwoodConfigPath = getConfigPath()

const webCommand =
getConfig().web.bundler !== 'webpack' // @NOTE: can't use enums, not TS
? `yarn cross-env NODE_ENV=development rw-vite-dev ${forward}`
: `yarn cross-env NODE_ENV=development RWJS_WATCH_NODE_MODULES=${
watchNodeModules ? '1' : ''
} webpack serve --config "${webpackDevConfig}" ${forward}`
const streamingSsrEnabled = getConfig().experimental.streamingSsr?.enabled

// @TODO (Streaming) Lot of temporary feature flags for started dev server.
// Written this way to make it easier to read

// 1. default: Vite (SPA)
let webCommand = `yarn cross-env NODE_ENV=development rw-vite-dev ${forward}`

// 2. Vite with SSR
if (streamingSsrEnabled) {
webCommand = `yarn cross-env NODE_ENV=development rw-dev-fe ${forward}`
}

// 3. Webpack (SPA): we will remove this override after v7
if (getConfig().web.bundler === 'webpack') {
if (streamingSsrEnabled) {
throw new Error(
'Webpack does not support SSR. Please switch your bundler to Vite in redwood.toml first'
)
} else {
webCommand = `yarn cross-env NODE_ENV=development RWJS_WATCH_NODE_MODULES=${
watchNodeModules ? '1' : ''
} webpack serve --config "${webpackDevConfig}" ${forward}`
}
}

/** @type {Record<string, import('concurrently').CommandObj>} */
const jobs = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { hydrateRoot, createRoot } from 'react-dom/client'

// TODO (STREAMING) This was marked "temporary workaround"
// Need to figure out why it's a temporary workaround and what we
// should do instead.
import { ServerContextProvider } from '@redwoodjs/web/dist/serverContext'

import App from './App'
import { Document } from './Document'

Expand All @@ -19,20 +14,15 @@ const redwoodAppElement = document.getElementById('redwood-app')
if (redwoodAppElement.children?.length > 0) {
hydrateRoot(
document,
<ServerContextProvider value={{}}>
<Document css={window.__assetMap?.()?.css}>
<App />
</Document>
</ServerContextProvider>
<Document css={window.__assetMap?.()?.css}>
<App />
</Document>
)
} else {
console.log('Rendering from scratch')
const root = createRoot(document)
root.render(
<ServerContextProvider value={{}}>
<Document css={window.__assetMap?.()?.css}>
<App />
</Document>
</ServerContextProvider>
<Document css={window.__assetMap?.()?.css}>
<App />
</Document>
)
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
import { LocationProvider } from '@redwoodjs/router'
import { ServerContextProvider } from '@redwoodjs/web/dist/serverContext'

import App from './App'
import { Document } from './Document'

interface Props {
routeContext: any
url: string
css: string[]
meta?: any[]
}

export const ServerEntry: React.FC<Props> = ({
routeContext,
url,
css,
meta,
}) => {
export const ServerEntry: React.FC<Props> = ({ url, css, meta }) => {
return (
<ServerContextProvider value={routeContext}>
<LocationProvider location={{ pathname: url }}>
<Document css={css} meta={meta}>
<App />
</Document>
</LocationProvider>
</ServerContextProvider>
<LocationProvider location={{ pathname: url }}>
<Document css={css} meta={meta}>
<App />
</Document>
</LocationProvider>
)
}
16 changes: 14 additions & 2 deletions packages/vite/src/devFeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,26 @@ globalThis.RWJS_ENV = {}
globalThis.__REDWOOD__PRERENDER_PAGES = {}

async function createServer() {
// Check CWD: make sure its the web/ directory
// Without this Postcss can misbehave, and its hard to trace why.
if (process.cwd() !== getPaths().web.base) {
console.error('⚠️ Warning: CWD is ', process.cwd())
console.warn('~'.repeat(50))
console.warn(
'The FE dev server cwd must be web/. Please use `yarn rw dev` or start the server from the web/ directory.'
)
console.log(`Changing cwd to ${getPaths().web.base}....`)
console.log()

process.chdir(getPaths().web.base)
}

const app = express()
const rwPaths = getPaths()

// TODO (STREAMING) When Streaming is released Vite will be the only bundler,
// and this file should always exist. So the error message needs to change
// (or be removed perhaps)
// @MARK: Vite is still experimental, and opt-in
if (!rwPaths.web.viteConfig) {
throw new Error(
'Vite config not found. You need to setup your project with Vite using `yarn rw setup vite`'
Expand All @@ -45,7 +58,6 @@ async function createServer() {
})

// use vite's connect instance as middleware
// if you use your own express router (express.Router()), you should use router.use
app.use(vite.middlewares)

app.use('*', async (req, res, next) => {
Expand Down
6 changes: 4 additions & 2 deletions packages/web/src/components/DevFatalErrorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
// making it fine for embedding inside this project.

// Stacktracey requires buffer, which Vite does not polyfill by default
window.Buffer = window.Buffer || require('buffer').Buffer
if (typeof window !== 'undefined') {
window.Buffer = window.Buffer || require('buffer').Buffer
}

import { useState } from 'react'

import StackTracey from 'stacktracey'

// RWJS_SRC_ROOT is defined and defaulted in webpack to the base path
const srcRoot = RWJS_DEBUG_ENV?.RWJS_SRC_ROOT || ''
const srcRoot = globalThis.RWJS_DEBUG_ENV?.RWJS_SRC_ROOT || ''

let appRoot: string

Expand Down
9 changes: 0 additions & 9 deletions packages/web/src/serverContext.tsx

This file was deleted.

0 comments on commit abf229b

Please sign in to comment.