-
Notifications
You must be signed in to change notification settings - Fork 374
Pro RSC migration 3/3: React Server Components demo on webpack #729
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
Open
ihabadham
wants to merge
21
commits into
ihabadham/feature/pro-rsc/base
Choose a base branch
from
ihabadham/feature/pro-rsc/rsc-demo
base: ihabadham/feature/pro-rsc/base
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
2424b1c
Enable RSC support in Pro initializer
ihabadham 325f3e3
Add RSCWebpackPlugin to client webpack config
ihabadham 2f11ffe
Create rscWebpackConfig.js for the RSC bundle
ihabadham 1c4bef2
Wire RSC bundle into webpackConfig.js
ihabadham cf394ba
Add 'use client' to all registered component entry points
ihabadham 8e8c518
Add /server-components route, controller action, and view
ihabadham 02b8b1c
Add RSC demo components for /server-components page
ihabadham b770daa
Add RSC watcher to Procfile.dev and nav link to /server-components
ihabadham 138befb
Include RSC bundle in default webpack build
ihabadham bc716e0
Move ServerComponentsPage to ror_components for auto-discovery
ihabadham 649e0bd
Wire RSC loader for both SWC and Babel transpilers
ihabadham 1ac1b27
Disable stubTimers in Pro Node renderer for RSC streaming
ihabadham fd9faf1
Enable replayServerAsyncOperationLogs on Pro Node renderer
ihabadham 9e17d03
Refactor RSC demo to canonical RoR Pro data flow
ihabadham df039e2
Add RSCRoute + ErrorBoundary demo to RSC page
ihabadham f2d0d3d
Use refetchComponent explicitly in retry path
ihabadham 0b626bc
Address PR review feedback
ihabadham 09b113e
Add request + system specs for the RSC demo
ihabadham f23beba
Gate LiveActivity 300ms delay on RSC_SUSPENSE_DEMO_DELAY
ihabadham e2c76a5
Wrap RSCRoute in local Suspense to prevent whole-page collapse
ihabadham 86eed7a
Add request spec variant exercising populated CommentsFeed
ihabadham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,8 @@ def simple; end | |
|
|
||
| def rescript; end | ||
|
|
||
| def server_components; end | ||
|
|
||
| private | ||
|
|
||
| def set_comments | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <%= react_component("ServerComponentsPage", | ||
| prerender: false, | ||
| auto_load_bundle: true, | ||
| trace: Rails.env.development?, | ||
| id: "ServerComponentsPage-react-component-0") %> | ||
|
ihabadham marked this conversation as resolved.
Outdated
ihabadham marked this conversation as resolved.
Outdated
|
||
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/components/Footer/ror_components/Footer.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
...pp/bundles/comments/components/SimpleCommentScreen/ror_components/SimpleCommentScreen.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/rescript/ReScriptShow/ror_components/RescriptShow.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/startup/App/ror_components/App.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/startup/NavigationBarApp/ror_components/NavigationBarApp.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/startup/RouterApp/ror_components/RouterApp.client.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
client/app/bundles/comments/startup/RouterApp/ror_components/RouterApp.server.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
client/app/bundles/server-components/components/CommentsFeed.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // Server Component - fetches comments directly from the Rails API on the server. | ||
| // Uses marked for markdown rendering. Both fetch and marked stay server-side. | ||
|
|
||
| import React from 'react'; | ||
| import { Marked } from 'marked'; | ||
| import { gfmHeadingId } from 'marked-gfm-heading-id'; | ||
| import sanitizeHtml from 'sanitize-html'; | ||
| import _ from 'lodash'; | ||
| import TogglePanel from './TogglePanel'; | ||
|
|
||
| const marked = new Marked(); | ||
| marked.use(gfmHeadingId()); | ||
|
|
||
| function resolveRailsBaseUrl() { | ||
| if (process.env.RAILS_INTERNAL_URL) { | ||
| return process.env.RAILS_INTERNAL_URL; | ||
| } | ||
|
|
||
| // Local defaults are okay in development/test, but production should be explicit. | ||
| if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { | ||
| return 'http://localhost:3000'; | ||
| } | ||
|
|
||
| throw new Error('RAILS_INTERNAL_URL must be set outside development/test'); | ||
|
ihabadham marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| async function CommentsFeed() { | ||
| // Simulate network latency only when explicitly enabled for demos. | ||
| if (process.env.RSC_SUSPENSE_DEMO_DELAY === 'true') { | ||
| await new Promise((resolve) => { | ||
| setTimeout(resolve, 800); | ||
| }); | ||
| } | ||
|
|
||
| let recentComments = []; | ||
| try { | ||
| // Fetch comments directly from the Rails API — no client-side fetch needed | ||
| const baseUrl = resolveRailsBaseUrl(); | ||
| const controller = new AbortController(); | ||
| const timeoutId = setTimeout(() => controller.abort(), 5000); | ||
| const response = await fetch(`${baseUrl}/comments.json`, { signal: controller.signal }); | ||
| clearTimeout(timeoutId); | ||
|
ihabadham marked this conversation as resolved.
Outdated
|
||
| if (!response.ok) { | ||
| throw new Error(`Failed to fetch comments: ${response.status} ${response.statusText}`); | ||
| } | ||
| const data = await response.json(); | ||
| const comments = data.comments; | ||
|
|
||
| // Use lodash to process (stays on server) | ||
| const sortedComments = _.orderBy(comments, ['created_at'], ['desc']); | ||
| recentComments = _.take(sortedComments, 10); | ||
| } catch (error) { | ||
| // eslint-disable-next-line no-console | ||
| console.error('CommentsFeed failed to load comments', error); | ||
| return ( | ||
| <div className="bg-rose-50 border border-rose-200 rounded-lg p-6 text-center"> | ||
| <p className="text-rose-700">Could not load comments right now. Please try again later.</p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| if (recentComments.length === 0) { | ||
| return ( | ||
| <div className="bg-amber-50 border border-amber-200 rounded-lg p-6 text-center"> | ||
| <p className="text-amber-700"> | ||
| No comments yet. Add some comments from the{' '} | ||
| <a href="/" className="underline font-medium"> | ||
| home page | ||
| </a>{' '} | ||
| to see them rendered here by server components. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="space-y-3"> | ||
| {recentComments.map((comment) => { | ||
|
ihabadham marked this conversation as resolved.
Outdated
|
||
| // Render markdown on the server using marked + sanitize-html. | ||
| // sanitize-html strips any dangerous HTML before rendering. | ||
| // These libraries (combined ~200KB) never reach the client. | ||
| const rawHtml = marked.parse(comment.text || ''); | ||
|
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
|
||
| const safeHtml = sanitizeHtml(rawHtml, { | ||
| allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']), | ||
| allowedAttributes: { | ||
| ...sanitizeHtml.defaults.allowedAttributes, | ||
| img: ['src', 'alt', 'title', 'width', 'height'], | ||
| }, | ||
| allowedSchemes: ['https', 'http'], | ||
|
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
|
||
| }); | ||
|
ihabadham marked this conversation as resolved.
ihabadham marked this conversation as resolved.
|
||
|
|
||
| return ( | ||
| <div | ||
| key={comment.id} | ||
| className="bg-white border border-slate-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow" | ||
| > | ||
| <div className="flex items-center justify-between mb-2"> | ||
| <span className="font-semibold text-slate-800">{comment.author}</span> | ||
| <span className="text-xs text-slate-400"> | ||
| {new Date(comment.created_at).toLocaleDateString('en-US', { | ||
| month: 'short', | ||
| day: 'numeric', | ||
| year: 'numeric', | ||
| hour: '2-digit', | ||
| minute: '2-digit', | ||
| })} | ||
| </span> | ||
| </div> | ||
| <TogglePanel title="Show rendered markdown"> | ||
| {/* Content is sanitized via sanitize-html before rendering */} | ||
| {/* eslint-disable-next-line react/no-danger */} | ||
| <div | ||
| className="prose prose-sm prose-slate max-w-none" | ||
| dangerouslySetInnerHTML={{ __html: safeHtml }} | ||
| /> | ||
| </TogglePanel> | ||
| <p className="text-slate-600 text-sm mt-1">{comment.text}</p> | ||
| </div> | ||
| ); | ||
| })} | ||
| <p className="text-xs text-slate-400 text-center pt-2"> | ||
| {recentComments.length} comment{recentComments.length !== 1 ? 's' : ''} rendered on the server using{' '} | ||
| <code>marked</code> + <code>sanitize-html</code> (never sent to browser) | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default CommentsFeed; | ||
58 changes: 58 additions & 0 deletions
58
client/app/bundles/server-components/components/ServerInfo.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Server Component - uses Node.js os module, which only exists on the server. | ||
| // This component and its dependencies are never sent to the browser. | ||
|
|
||
| import React from 'react'; | ||
| import os from 'os'; | ||
| import _ from 'lodash'; | ||
|
|
||
| async function ServerInfo() { | ||
|
ihabadham marked this conversation as resolved.
Outdated
|
||
| const serverInfo = { | ||
| platform: os.platform(), | ||
| arch: os.arch(), | ||
| nodeVersion: process.version, | ||
| uptime: Math.floor(os.uptime() / 3600), | ||
| totalMemory: (os.totalmem() / (1024 * 1024 * 1024)).toFixed(1), | ||
| freeMemory: (os.freemem() / (1024 * 1024 * 1024)).toFixed(1), | ||
| cpus: os.cpus().length, | ||
| hostname: os.hostname(), | ||
|
ihabadham marked this conversation as resolved.
Outdated
ihabadham marked this conversation as resolved.
Outdated
|
||
| }; | ||
|
|
||
| // Using lodash on the server — this 70KB+ library stays server-side | ||
| const infoEntries = _.toPairs(serverInfo); | ||
| const grouped = _.chunk(infoEntries, 4); | ||
|
|
||
| const labels = { | ||
| platform: 'Platform', | ||
| arch: 'Architecture', | ||
| nodeVersion: 'Node.js', | ||
| uptime: 'Uptime (hrs)', | ||
| totalMemory: 'Total RAM (GB)', | ||
| freeMemory: 'Free RAM (GB)', | ||
| cpus: 'CPU Cores', | ||
| hostname: 'Hostname', | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="bg-gradient-to-br from-emerald-50 to-teal-50 border border-emerald-200 rounded-xl p-6"> | ||
| <p className="text-xs text-emerald-600 mb-4 font-medium"> | ||
| This data comes from the Node.js <code className="bg-emerald-100 px-1 rounded">os</code> module | ||
| — it runs only on the server. The <code className="bg-emerald-100 px-1 rounded">lodash</code> library | ||
| used to format it never reaches the browser. | ||
| </p> | ||
| <div className="grid md:grid-cols-2 gap-x-8 gap-y-1"> | ||
| {grouped.map((group) => ( | ||
| <div key={group.map(([k]) => k).join('-')} className="space-y-1"> | ||
| {group.map(([key, value]) => ( | ||
| <div key={key} className="flex justify-between py-1.5 border-b border-emerald-100 last:border-0"> | ||
| <span className="text-sm text-emerald-700 font-medium">{labels[key] || key}</span> | ||
| <span className="text-sm text-emerald-900 font-mono">{value}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default ServerInfo; | ||
40 changes: 40 additions & 0 deletions
40
client/app/bundles/server-components/components/TogglePanel.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { useState } from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
|
|
||
| const TogglePanel = ({ title, children }) => { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| return ( | ||
| <div className="border border-slate-200 rounded-lg overflow-hidden"> | ||
| <button | ||
|
ihabadham marked this conversation as resolved.
|
||
| type="button" | ||
| onClick={() => setIsOpen((prev) => !prev)} | ||
| className="w-full flex items-center justify-between px-4 py-2.5 bg-slate-50 hover:bg-slate-100 transition-colors text-left" | ||
| > | ||
| <span className="text-sm font-medium text-slate-700">{title}</span> | ||
| <svg | ||
| className={`w-4 h-4 text-slate-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} | ||
| fill="none" | ||
| viewBox="0 0 24 24" | ||
| stroke="currentColor" | ||
| > | ||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> | ||
| </svg> | ||
| </button> | ||
| {isOpen && ( | ||
| <div className="px-4 py-3 bg-white"> | ||
| {children} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| TogglePanel.propTypes = { | ||
| title: PropTypes.string.isRequired, | ||
| children: PropTypes.node.isRequired, | ||
| }; | ||
|
|
||
| export default TogglePanel; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.