Skip to content

Instantly share code, notes, and snippets.

@dac09
Last active February 16, 2024 11:08
Show Gist options
  • Save dac09/415e630c8784ad3b5eb3d348250f3967 to your computer and use it in GitHub Desktop.
Save dac09/415e630c8784ad3b5eb3d348250f3967 to your computer and use it in GitHub Desktop.
ExampleServer.mjs
// @ts-check
import express from 'express'
import { createServer as createViteServer } from 'vite'
import { renderToPipeableStream, renderToString } from 'react-dom/server'
import { PassThrough, Writable } from 'node:stream'
import React from 'react'
async function createServer() {
const app = express()
// Switch between prod and dev mode
const isProd = process.argv.slice(2)[0] !== 'dev'
console.log(`👉 \n ~ file: server.mjs:40 ~ isProd:`, isProd)
let ServerEntry
if (isProd) {
ServerEntry = (await import('./dist/entry.server.js')).ServerEntry
} else {
// do it in the handler block so that changes are picked up
}
const vite = await createViteServer({
configFile: './vite.config.ts',
server: { middlewareMode: true },
logLevel: 'info',
clearScreen: false,
appType: 'custom',
})
app.use(vite.middlewares)
app.use('*', async (req, res, next) => {
const myStream = new Writable({
write(chunk, encoding, next) {
const d = Date.now()
const chunkAsString = chunk.toString()
console.log(`👉 \n ~ chunk: ${d}`, chunkAsString)
const split = chunkAsString.split('</head>')
// If the closing tag exists
if (split.length > 1) {
const [beforeClosingHead, afterClosingHead] = split
const TEMP_STUFF_TO_INJECT = renderToString(
React.createElement('script', {
dangerouslySetInnerHTML: {
__html: `console.log('This is a react rendered paragraph')`,
},
})
)
const outputBuffer = Buffer.from(
[
beforeClosingHead,
TEMP_STUFF_TO_INJECT,
'</head>',
afterClosingHead,
].join('')
)
res.write(outputBuffer, encoding)
} else {
res.write(chunk, encoding)
}
// ✋ Cant do this because it'll cross React's streams
// res.write(`<script>'Imaginary apollo cache restore'; ${d};</script>`)
next()
},
final(x) {
console.log('xxxxx FINAL!', x)
// XXX
res.write('<script>console.log("FINAL!")</script>')
// We can inject code here, but it'll be after all the suspense boundaries have resolved.
// @TODO find all the html insertion callbacks, and inject here
// e.g. usage
/**
*
* import { injectBeforeFinal } from '@redwoodjs/web'
*
* injectBeforeFinal(() => {
* return `<script>'Imaginary apollo cache restore'; ${d};</script>`
* })
*/
// Neded to close the stream or it will hang
res.end()
},
})
const entryModule = await vite.ssrLoadModule('./src/entry.server.tsx')
ServerEntry = entryModule.ServerEntry
try {
const reactStream = renderToPipeableStream(
ServerEntry({
// url: currentPathName,
// css: FIXME_HardcodedIndexCss,
// meta: metaTags,
}),
{
// bootstrapScriptContent: `window.__assetMap = function() { return ${assetMap} }; `,
bootstrapModules: isProd ? [] : ['src/reactRefresh.js'],
onShellReady() {
res.setHeader('content-type', 'text/html; charset=utf-8')
// reactStream.pipe(myStream)
// reactStream.pipe(res)
},
onAllReady() {
reactStream.pipe(myStream)
},
}
)
// stream.pipe('xxxxx')
} catch (e) {
console.error(e)
// If an error is caught, let Vite fix the stack trace so it maps back to
// your actual source code.
// @TODO no vite here, because of if statement above
vite.ssrFixStacktrace(e)
next(e)
}
})
console.log(`Started server on http://localhost:${8822}`)
return await app.listen(8822)
}
let devApp = createServer()
process.stdin.on('data', async (data) => {
const str = data.toString().trim().toLowerCase()
if (str === 'rs' || str === 'restart') {
;(await devApp).close()
devApp = createServer()
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment