Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ssr): Get experimental ssr setup working properly #8922

Merged
merged 4 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading