Skip to content

Commit

Permalink
experimental feature flag for rsc (#8837)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe authored Jul 7, 2023
1 parent d4888a9 commit c7a5b13
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 0 deletions.
29 changes: 29 additions & 0 deletions packages/cli/src/commands/experimental/setupRsc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers'

import { getEpilogue } from './util'

export const command = 'setup-rsc'

export const description = 'Enable React Server Components (RSC)'

export const EXPERIMENTAL_TOPIC_ID = 5081

export const builder = (yargs) => {
yargs
.option('force', {
alias: 'f',
default: false,
description: 'Overwrite existing configuration',
type: 'boolean',
})
.epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true))
}

export const handler = async (options) => {
recordTelemetryAttributes({
command: ['experimental', command].join(' '),
force: options.force,
})
const { handler } = await import('./setupRscHandler.js')
return handler(options)
}
157 changes: 157 additions & 0 deletions packages/cli/src/commands/experimental/setupRscHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import fs from 'fs'
import path from 'path'

import { Listr } from 'listr2'

import { getConfig, getConfigPath } from '@redwoodjs/project-config'
import { errorTelemetry } from '@redwoodjs/telemetry'

import { getPaths, writeFile } from '../../lib'
import c from '../../lib/colors'
import { isTypeScriptProject } from '../../lib/project'

import {
command,
description,
EXPERIMENTAL_TOPIC_ID,
} from './setupStreamingSsr'
import { printTaskEpilogue } from './util'

export const handler = async ({ force, verbose }) => {
const rwPaths = getPaths()
const redwoodTomlPath = getConfigPath()
const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8')

const tasks = new Listr(
[
{
title: 'Check prerequisites',
task: () => {
if (!rwPaths.web.entryClient || !rwPaths.web.viteConfig) {
throw new Error('Vite needs to be setup before you can enable RSCs')
}

if (!getConfig().experimental?.streamingSsr?.enabled) {
throw new Error(
'The Streaming SSR experimental feature must be enabled before you can enable RSCs'
)
}

if (!isTypeScriptProject()) {
throw new Error(
'RSCs are only supported in TypeScript projects at this time'
)
}
},
},
{
title: 'Adding config to redwood.toml...',
task: (_ctx, task) => {
if (!configContent.includes('[experimental.rsc]')) {
writeFile(
redwoodTomlPath,
configContent.concat('\n[experimental.rsc]\n enabled = true\n'),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
if (force) {
task.output = 'Overwriting config in redwood.toml'

writeFile(
redwoodTomlPath,
configContent.replace(
// Enable if it's currently disabled
'\n[experimental.rsc]\n enabled = false\n',
'\n[experimental.rsc]\n enabled = true\n'
),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
task.skip(
'The [experimental.rsc] config block already exists in your `redwood.toml` file.'
)
}
}
},
options: { persistentOutput: true },
},
{
title: 'Adding entries.ts...',
task: async () => {
const entriesTemplate = fs.readFileSync(
path.resolve(__dirname, 'templates', 'rsc', 'entries.ts.template'),
'utf-8'
)
const entriesPath = path.join(rwPaths.web.src, 'entries.ts')

writeFile(entriesPath, entriesTemplate, {
overwriteExisting: force,
})
},
},
{
title: 'Updating App.tsx...',
task: async () => {
const appTemplate = fs.readFileSync(
path.resolve(__dirname, 'templates', 'rsc', 'App.tsx.template'),
'utf-8'
)
const appPath = rwPaths.web.app

writeFile(appPath, appTemplate, {
overwriteExisting: true,
})
},
},
{
title: 'Adding Counter.tsx...',
task: async () => {
const counterTemplate = fs.readFileSync(
path.resolve(__dirname, 'templates', 'rsc', 'Counter.tsx.template'),
'utf-8'
)
const counterPath = path.join(rwPaths.web.src, 'Counter.tsx')

writeFile(counterPath, counterTemplate, {
overwriteExisting: force,
})
},
},
{
title: 'Updating index.html...',
task: async () => {
let indexHtml = fs.readFileSync(rwPaths.web.html, 'utf-8')
indexHtml = indexHtml.replace(
'href="/favicon.png" />',
'href="/favicon.png" />\n <script type="module" src="entry.client.tsx"></script>'
)

writeFile(rwPaths.web.html, indexHtml, {
overwriteExisting: true,
})
},
},
{
task: () => {
printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID)
},
},
],
{
rendererOptions: { collapseSubtasks: false, persistentOutput: true },
renderer: verbose ? 'verbose' : 'default',
}
)

try {
await tasks.run()
} catch (e) {
errorTelemetry(process.argv, e.message)
console.error(c.error(e.message))
process.exit(e?.exitCode || 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Counter } from './Counter'

const App = ({ name = 'Anonymous' }) => {
return (
<div style={{ border: '3px red dashed', margin: '1em', padding: '1em' }}>
<h1>Hello {name}!!</h1>
<h3>This is a server component.</h3>
<Counter />
</div>
)
}

export default App
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client'

import React from 'react'

export const Counter = () => {
const [count, setCount] = React.useState(0)

return (
<div style={{ border: '3px blue dashed', margin: '1em', padding: '1em' }}>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<h3>This is a client component.</h3>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type GetEntry = (rscId: string) => Promise<
| React.FunctionComponent
| {
default: React.FunctionComponent
}
| null
>

export function defineEntries(getEntry: GetEntry) {
return {
getEntry,
}
}

export default defineEntries(
// getEntry
async (id) => {
switch (id) {
case 'App':
return import('./App')
default:
return null
}
}
)
3 changes: 3 additions & 0 deletions packages/project-config/src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ describe('getConfig', () => {
"apiSdk": undefined,
"enabled": false,
},
"rsc": {
"enabled": false,
},
"streamingSsr": {
"enabled": false,
},
Expand Down
6 changes: 6 additions & 0 deletions packages/project-config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ export interface Config {
streamingSsr: {
enabled: boolean
}
rsc: {
enabled: boolean
}
}
}

Expand Down Expand Up @@ -183,6 +186,9 @@ const DEFAULT_CONFIG: Config = {
streamingSsr: {
enabled: false,
},
rsc: {
enabled: false,
},
},
}

Expand Down
5 changes: 5 additions & 0 deletions packages/router/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ declare global {
*/
var RWJS_EXP_STREAMING_SSR: boolean

/**
* Is the experimental RSC feature enabled?
*/
var RWJS_EXP_RSC: boolean

namespace NodeJS {
interface Global {
/**
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export default function redwoodPluginVite(): PluginOption[] {
RWJS_EXP_STREAMING_SSR:
rwConfig.experimental.streamingSsr &&
rwConfig.experimental.streamingSsr.enabled,
RWJS_EXP_RSC: rwConfig.experimental?.rsc?.enabled,
},
RWJS_DEBUG_ENV: {
RWJS_SRC_ROOT: rwPaths.web.src,
Expand Down
3 changes: 3 additions & 0 deletions packages/web/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ declare global {
/** URL or absolute path to serverless functions */
RWJS_API_URL: string
RWJS_EXP_STREAMING_SSR: boolean
RWJS_EXP_RSC: boolean

__REDWOOD__APP_TITLE: string
__REDWOOD__APOLLO_STATE: NormalizedCacheObject
Expand All @@ -34,6 +35,8 @@ declare global {
var RWJS_SRC_ROOT: string
/** Flag for experimental Streaming and SSR support */
var RWJS_EXP_STREAMING_SSR: boolean
/** Flag for experimental RSC support */
var RWJS_EXP_RSC: boolean

namespace NodeJS {
interface Global {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ globalThis.RWJS_API_GRAPHQL_URL = RWJS_ENV.RWJS_API_GRAPHQL_URL as string
globalThis.RWJS_API_URL = RWJS_ENV.RWJS_API_URL as string
globalThis.__REDWOOD__APP_TITLE = RWJS_ENV.__REDWOOD__APP_TITLE as string
globalThis.RWJS_EXP_STREAMING_SSR = RWJS_ENV.RWJS_EXP_STREAMING_SSR as boolean
globalThis.RWJS_EXP_RSC = RWJS_ENV.RWJS_EXP_RSC as boolean

0 comments on commit c7a5b13

Please sign in to comment.