Skip to content

Instantly share code, notes, and snippets.

@dac09
Created March 17, 2024 14:04
Show Gist options
  • Save dac09/38a87dc1fb04b852f0008b9945cbf51f to your computer and use it in GitHub Desktop.
Save dac09/38a87dc1fb04b852f0008b9945cbf51f to your computer and use it in GitHub Desktop.
How I modified the streaming server to build with client conditions and "redirect" server imports.
import fs from 'node:fs'
import path from 'node:path'
import { build as viteBuild } from 'vite'
import { cjsInterop } from 'vite-plugin-cjs-interop'
import { getPaths } from '@redwoodjs/project-config'
export async function buildForStreamingServer({
verbose = false,
clientEntryFiles,
serverEntryFiles,
}: {
verbose?: boolean
clientEntryFiles: Record<string, string>
serverEntryFiles: Record<string, string>
}) {
console.log('Starting streaming server build...\n')
const rwPaths = getPaths()
const rscBuildManifest: Record<string, any> = JSON.parse(
fs.readFileSync(
path.join(rwPaths.web.distRsc, 'server-build-manifest.json'),
'utf-8',
),
)
const serverEntriesPaths = Object.values(serverEntryFiles)
if (!rwPaths.web.viteConfig) {
throw new Error('Vite config not found')
}
await viteBuild({
configFile: rwPaths.web.viteConfig,
plugins: [
cjsInterop({
dependencies: ['@redwoodjs/**'],
}),
{
name: 'rw-resolve-redirect',
enforce: 'pre',
async resolveId(source, importer, _options) {
// Not sure how this is possible but TS says so
if (!importer) {
return null
}
// @TODO: ignore node_modules, this seems like a lot of processing otherwise
// Step 1: create path for lookup
const absPath = path.join(path.parse(importer).dir, source)
// Only do it for files with "use server" in src, ignore node_modules
// @MARK @TODO Temporary!!!!
const absPathWithHackedExtension = absPath + '.ts'
if (
!serverEntriesPaths.includes(absPathWithHackedExtension) ||
absPath.includes('node_modules')
) {
return null
}
const relToSrc = path.relative(
rwPaths.web.src,
absPathWithHackedExtension,
)
// Step 2: lookup in the manifest
// const manifest = await getManifest()
const fileId = rscBuildManifest[relToSrc].file
// Step 3, return fileId and external: true
return {
id: path.join('../../rsc', fileId),
external: true,
}
},
},
],
build: {
rollupOptions: {
input: {
...clientEntryFiles,
entries: rwPaths.web.entries as string,
},
},
outDir: rwPaths.web.distServer,
ssr: true,
emptyOutDir: true,
},
ssr: {
noExternal: /^(?!node:)/,
// Can't inline prisma client
external: ['@prisma/client'],
},
envFile: false,
logLevel: verbose ? 'info' : 'warn',
})
}
@dac09
Copy link
Author

dac09 commented Mar 17, 2024

What this is effectively achieving:

  • Build SSR with "node conditions": i.e. not react-server
  • Import redirection using the plugin: if you encounter a server entry (NOT a server component) change the import to what it is from the rsc build. In the RSA fixture, if you enounter "./actions" (it'll be in the serverEntries) - you need to redirect the import to the built rsc assets
  • when you build with client conditions (and noExternal things) - the final output will not contain "use client". In this example, check if HomePage has an import to "use-client" - it shouldn't.

We then need to launch the server with:

╰─ NODE_OPTIONS="--conditions=react-server" yarn rw serve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment