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

Introduce RSC "live reload." #10932

Merged
merged 14 commits into from
Jul 25, 2024
4 changes: 4 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"bin": {
"rw-dev-fe": "./dist/devFeServer.js",
"rw-dev-rsc": "./dist/devRscServer.js",
Copy link
Contributor Author

@peterp peterp Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a command that will be executed by the Redwood CLI.

"rw-serve-fe": "./dist/runFeServer.js",
"rw-vite-build": "./bins/rw-vite-build.mjs",
"rw-vite-dev": "./bins/rw-vite-dev.mjs",
Expand Down Expand Up @@ -76,16 +77,19 @@
"acorn-loose": "8.4.0",
"buffer": "6.0.3",
"busboy": "^1.6.0",
"chokidar": "3.6.0",
"cookie": "0.6.0",
"core-js": "3.37.1",
"dotenv-defaults": "5.0.2",
"execa": "5.1.1",
"express": "4.19.2",
"find-my-way": "8.2.0",
"fs-extra": "11.2.0",
"http-proxy-middleware": "3.0.0",
"isbot": "5.1.13",
"react": "19.0.0-beta-04b058868c-20240508",
"react-server-dom-webpack": "19.0.0-beta-04b058868c-20240508",
"rimraf": "6.0.1",
"vite": "5.3.4",
"vite-plugin-cjs-interop": "2.1.1",
"vite-plugin-node-polyfills": "0.22.0",
Expand Down
131 changes: 131 additions & 0 deletions packages/vite/src/devRscServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import http from 'node:http'

import chokidar from 'chokidar'
import execa from 'execa'
import { rimraf } from 'rimraf'

import { getPaths } from '@redwoodjs/project-config'

import { buildFeServer } from './buildFeServer.js'

export const startDevServerWithLiveReload = async () => {
let child: execa.ExecaChildProcess
let client: http.ServerResponse

const rwjsPaths = getPaths()

const sendReloadEvent = () => {
if (typeof client !== 'undefined') {
client.write('event: reload\n')
client.write('data: \n\n')
}
}

const killCleanAndBuild = async () => {
try {
child.kill()
} catch (e) {
console.warn('[rsc-dev-server] could not shutdown server process', e)
}

await rimraf.rimraf(rwjsPaths.web.dist)
await rimraf.rimraf(rwjsPaths.api.dist)
await buildFeServer({ verbose: false, webDir: rwjsPaths.web.base })
}

const runFrontendServer = () => {
// NOTE: We explicitly run this via `execa.node`
// because we need this process to support IPC.
const child = execa.node(
'./node_modules/@redwoodjs/vite/dist/runFeServer.js',
{
cwd: rwjsPaths.base,
stdio: 'inherit',
env: {
NODE_ENV: process.env.NODE_ENV,
NODE_OPTIONS: '--conditions react-server',
},
},
)

child.addListener('message', (m) => {
if (m === 'server ready') {
sendReloadEvent()
}
})

return child
}

const watcher = chokidar.watch('(web|api)/src/**/*.{ts,js,jsx,tsx}', {
persistent: true,
ignored: ['node_modules', '.redwood'],
ignoreInitial: true,
cwd: rwjsPaths.base,
awaitWriteFinish: true,
})

process.stdin.on('data', async (data) => {
const str = data.toString().trim().toLowerCase()
if (str !== 'rs') {
return
}

console.log('[rsc-dev-server] restarting...')
await killCleanAndBuild()
child = runFrontendServer()

try {
await child
console.log('[rsc-dev-server] restarted')
} catch (e) {
console.error('[rsc-dev-server] error', e)
}
})

watcher
.on('ready', async () => {
await killCleanAndBuild()
child = runFrontendServer()

http
.createServer(async (_req, res) => {
client = res
res.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
connection: 'keep-alive',
'access-control-allow-origin': '*',
})
})
.listen(8913)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if hard coding this port is the correct approach, but could be OK for right now?

Copy link
Member

@Tobbe Tobbe Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good enough for now! 🙂


try {
await child
} catch (e) {
console.error('[rsc-dev-server] error', e)
}
})
.on('all', async () => {
console.log('[rsc-dev-server] restarting...')
await killCleanAndBuild()
child = runFrontendServer()

try {
await child
console.log('[rsc-dev-server] restarted')
} catch (e) {
console.error('[rsc-dev-server] error', e)
}
})
}

process.env.NODE_ENV = 'development'
startDevServerWithLiveReload()
.then(() => {
console.log('[rsc-dev-server] started')
})
.catch((e) => {
console.error('[rsc-dev-server] error', e)
process.exitCode = 1
})
5 changes: 5 additions & 0 deletions packages/vite/src/runFeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// fe-server. And it's already created, but this hasn't been moved over yet.

import path from 'node:path'
import process from 'node:process'
import url from 'node:url'

import { createServerAdapter } from '@whatwg-node/server'
Expand Down Expand Up @@ -199,6 +200,10 @@ export async function runFeServer() {
console.log(
`Started production FE server on http://localhost:${rwConfig.web.port}`,
)

if (typeof process.send !== 'undefined') {
process.send('server ready')
}
}

runFeServer()
16 changes: 15 additions & 1 deletion packages/vite/src/streaming/streamHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ globalThis.__webpack_require__ ||= (id) => {
};
`

const rscLiveReload = `\
// NOTE: This code is used during development to enable "live-reload."
window.addEventListener('load', () => {
const sse = new EventSource('http://localhost:8913');
sse.addEventListener('reload', () => {
window.location.reload();
});
window.addEventListener('beforeunload', () => {
sse.close();
});
// TODO: Handle disconnect / error states.
Tobbe marked this conversation as resolved.
Show resolved Hide resolved
});
`

export async function reactRenderToStreamResponse(
mwRes: MiddlewareResponse,
renderOptions: RenderToStreamArgs,
Expand Down Expand Up @@ -179,7 +193,7 @@ export async function reactRenderToStreamResponse(
bootstrapScriptContent:
// Only insert assetMap if client side JS will be loaded
jsBundles.length > 0
? `window.__REDWOOD__ASSET_MAP = ${assetMap}; ${rscWebpackShims}`
? `window.__REDWOOD__ASSET_MAP = ${assetMap}; ${rscWebpackShims}; ${rscLiveReload};`
: undefined,
bootstrapModules: jsBundles,
}
Expand Down
Loading
Loading