-
Notifications
You must be signed in to change notification settings - Fork 0
Rspack spike #2
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
base: main
Are you sure you want to change the base?
Rspack spike #2
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Procfile for development using HMR (SSR enabled) | ||
| # You can run these commands in separate shells | ||
| rails: bundle exec rails s -p 3000 | ||
| wp-client: WEBPACK_SERVE=true bin/shakapacker-dev-server | ||
| wp-server: SERVER_BUNDLE_ONLY=true bin/shakapacker --watch |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Procfile for development with production assets | ||
| # Uses production-optimized, precompiled assets with development environment | ||
| # Uncomment additional processes as needed for your app | ||
|
|
||
| rails: bundle exec rails s -p 3001 | ||
| # sidekiq: bundle exec sidekiq -C config/sidekiq.yml | ||
| # redis: redis-server | ||
| # mailcatcher: mailcatcher --foreground |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| web: bin/rails server -p 3000 | ||
| js: bin/shakapacker --watch |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class HelloWorldController < ApplicationController | ||
| layout "hello_world" | ||
|
|
||
| def index | ||
| @hello_world_props = { name: "Stranger" } | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /* eslint no-console:0 */ | ||
| // This file is automatically compiled by Webpack, along with any other files | ||
| // present in this directory. You're encouraged to place your actual application logic in | ||
| // a relevant structure within app/javascript and only use these pack files to reference | ||
| // that code so it'll be compiled. | ||
| // | ||
| // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate | ||
| // layout file, like app/views/layouts/application.html.erb | ||
|
|
||
| // Uncomment to copy all static images under ./images to the output folder and reference | ||
| // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) | ||
| // or the `imagePath` JavaScript helper below. | ||
| // | ||
| // const images = require.context('./images', true) | ||
| // const imagePath = (name) => images(name, true) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // import statement added by react_on_rails:generate_packs rake task | ||
| import "./../generated/server-bundle-generated.js" | ||
| // Placeholder comment - auto-generated imports will be prepended here by react_on_rails:generate_packs |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .bright { | ||
| color: green; | ||
| font-weight: bold; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* eslint-disable import/prefer-default-export */ | ||
|
|
||
| import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; | ||
|
|
||
| // Action interface | ||
| export interface UpdateNameAction { | ||
| type: typeof HELLO_WORLD_NAME_UPDATE; | ||
| text: string; | ||
| } | ||
|
|
||
| // Union type for all actions | ||
| export type HelloWorldAction = UpdateNameAction; | ||
|
|
||
| // Action creator with proper TypeScript typing | ||
| export const updateName = (text: string): UpdateNameAction => ({ | ||
| type: HELLO_WORLD_NAME_UPDATE, | ||
| text, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .bright { | ||
| color: green; | ||
| font-weight: bold; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React from 'react'; | ||
| import * as style from './HelloWorld.module.css'; | ||
| import type { PropsFromRedux } from '../containers/HelloWorldContainer'; | ||
|
|
||
| // Component props are inferred from Redux container | ||
| type HelloWorldProps = PropsFromRedux; | ||
|
|
||
| const HelloWorld: React.FC<HelloWorldProps> = ({ name, updateName }) => ( | ||
| <div> | ||
| <h3> | ||
| Hello, | ||
| {name}! | ||
| </h3> | ||
| <hr /> | ||
| <form> | ||
| <label className={style.bright} htmlFor="name"> | ||
| Say hello to: | ||
| <input id="name" type="text" value={name} onChange={(e) => updateName(e.target.value)} /> | ||
| </label> | ||
| </form> | ||
| </div> | ||
| ); | ||
|
|
||
| export default HelloWorld; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /* eslint-disable import/prefer-default-export */ | ||
|
|
||
| export const HELLO_WORLD_NAME_UPDATE = 'HELLO_WORLD_NAME_UPDATE' as const; | ||
|
|
||
| // Action type for TypeScript | ||
| export type HelloWorldActionType = typeof HELLO_WORLD_NAME_UPDATE; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Simple example of a React "smart" component | ||
|
|
||
| import { connect, ConnectedProps } from 'react-redux'; | ||
| import HelloWorld from '../components/HelloWorld'; | ||
| import * as actions from '../actions/helloWorldActionCreators'; | ||
| import type { HelloWorldState } from '../reducers/helloWorldReducer'; | ||
|
|
||
| // Which part of the Redux global state does our component want to receive as props? | ||
| const mapStateToProps = (state: HelloWorldState) => ({ name: state.name }); | ||
|
|
||
| // Create the connector | ||
| const connector = connect(mapStateToProps, actions); | ||
|
|
||
| // Infer the props from Redux state and actions | ||
| export type PropsFromRedux = ConnectedProps<typeof connector>; | ||
|
|
||
| // Don't forget to actually use connect! | ||
| // Note that we don't export HelloWorld, but the redux "connected" version of it. | ||
| // See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples | ||
| export default connector(HelloWorld); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { combineReducers } from 'redux'; | ||
| import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; | ||
| import { HelloWorldAction } from '../actions/helloWorldActionCreators'; | ||
|
|
||
| // State interface | ||
| export interface HelloWorldState { | ||
| name: string; | ||
| } | ||
|
|
||
| // Individual reducer with TypeScript types | ||
| const name = (state: string = '', action: HelloWorldAction): string => { | ||
| switch (action.type) { | ||
| case HELLO_WORLD_NAME_UPDATE: | ||
| return action.text; | ||
| default: | ||
| return state; | ||
| } | ||
| }; | ||
|
|
||
| const helloWorldReducer = combineReducers<HelloWorldState>({ name }); | ||
|
|
||
| export default helloWorldReducer; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { useMemo, type FC } from 'react'; | ||
| import { Provider } from 'react-redux'; | ||
|
|
||
| import configureStore, { type RailsProps } from '../store/helloWorldStore'; | ||
| import HelloWorldContainer from '../containers/HelloWorldContainer'; | ||
|
|
||
| // Props interface matches what Rails will pass from the controller | ||
| interface HelloWorldAppProps extends RailsProps {} | ||
|
|
||
| // See documentation for https://github.com/reactjs/react-redux. | ||
| // This is how you get props from the Rails view into the redux store. | ||
| // This code here binds your smart component to the redux store. | ||
| const HelloWorldApp: FC<HelloWorldAppProps> = (props) => { | ||
| const store = useMemo(() => configureStore(props), [props]); | ||
|
|
||
| return ( | ||
| <Provider store={store}> | ||
| <HelloWorldContainer /> | ||
| </Provider> | ||
| ); | ||
| }; | ||
|
|
||
| export default HelloWorldApp; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import HelloWorldApp from './HelloWorldApp.client'; | ||
| // This could be specialized for server rendering | ||
| // For example, if using React Router, we'd have the SSR setup here. | ||
|
|
||
| export default HelloWorldApp; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { createStore } from 'redux'; | ||
| import type { Store, PreloadedState } from 'redux'; | ||
| import helloWorldReducer from '../reducers/helloWorldReducer'; | ||
| import type { HelloWorldState } from '../reducers/helloWorldReducer'; | ||
|
|
||
| // Rails props interface - customize based on your Rails controller | ||
| export interface RailsProps { | ||
| name: string; | ||
| [key: string]: any; // Allow additional props from Rails | ||
| } | ||
|
|
||
| // Store type | ||
| export type HelloWorldStore = Store<HelloWorldState>; | ||
|
|
||
| const configureStore = (railsProps: RailsProps): HelloWorldStore => | ||
| createStore(helloWorldReducer, railsProps as PreloadedState<HelloWorldState>); | ||
|
|
||
| export default configureStore; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // TypeScript definitions for CSS modules | ||
| declare module "*.module.css" { | ||
| const classes: { [key: string]: string }; | ||
| export default classes; | ||
| } | ||
|
|
||
| declare module "*.module.scss" { | ||
| const classes: { [key: string]: string }; | ||
| export default classes; | ||
| } | ||
|
|
||
| declare module "*.module.sass" { | ||
| const classes: { [key: string]: string }; | ||
| export default classes; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| <h1>Hello World</h1> | ||
| <%= react_component("HelloWorldApp", props: @hello_world_props, prerender: true) %> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify component registration for SSR. Ensure 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -nC2 "ReactOnRails\.register\((\s*)\{?.*HelloWorldApp" app/javascript || echo "Registration not found"
rg -n "HelloWorldApp" app/javascript | wc -lLength of output: 24 Register HelloWorldApp with ReactOnRails for SSR. No ReactOnRails.register for HelloWorldApp was found under app/javascript (script output: "Registration not found"; "HelloWorldApp" occurrences: 5). app/views/hello_world/index.html.erb uses prerender: true — add the ReactOnRails registration for HelloWorldApp in both the server and client entrypoints under app/javascript so SSR can locate the component. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>ReactOnRailsWithShakapacker</title> | ||
| <%= csrf_meta_tags %> | ||
|
|
||
| <!-- Empty pack tags - React on Rails injects component CSS/JS here --> | ||
| <%= stylesheet_pack_tag %> | ||
| <%= javascript_pack_tag %> | ||
| </head> | ||
|
|
||
| <body> | ||
| <%= yield %> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // The source code including full typescript support is available at: | ||
| // https://github.com/shakacode/react_on_rails_demo_ssr_hmr/blob/master/babel.config.js | ||
|
|
||
| module.exports = function (api) { | ||
| const defaultConfigFunc = require('shakapacker/package/babel/preset.js') | ||
| const resultConfig = defaultConfigFunc(api) | ||
| const isProductionEnv = api.env('production') | ||
|
|
||
| const changesOnDefault = { | ||
| presets: [ | ||
| [ | ||
| '@babel/preset-react', | ||
| { | ||
| development: !isProductionEnv, | ||
| useBuiltIns: true, | ||
| runtime: 'automatic' | ||
| } | ||
| ] | ||
| ].filter(Boolean), | ||
| plugins: [ | ||
| // Enable React Refresh (Fast Refresh) only when webpack-dev-server is running (HMR mode) | ||
| // This prevents React Refresh from trying to connect when using static compilation | ||
| !isProductionEnv && process.env.WEBPACK_SERVE && 'react-refresh/babel', | ||
| isProductionEnv && ['babel-plugin-transform-react-remove-prop-types', | ||
| { | ||
| removeImport: true | ||
| } | ||
| ] | ||
| ].filter(Boolean), | ||
| } | ||
|
|
||
| resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets] | ||
| resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ] | ||
|
|
||
| return resultConfig | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Absolute path gems will break CI and other machines—gate via ENV and provide fallback
Local paths are non-portable. Add ENV-gated overrides with a default to published gems.
Apply this diff:
Alternatively, keep Gemfile clean and use Bundler locals:
bundle config local.react_on_rails ../../react_on_railsandbundle config local.shakapacker /path/to/shakapacker.Please confirm CI can bundle without local paths.
🏁 Script executed:
Length of output: 435
Absolute local gem paths will break CI — gate via ENV or use Bundler locals
Gemfile contains local paths for react_on_rails and shakapacker (lines 59–61); .bundle/config not found — CI/other machines will fail to bundle.
Apply this diff:
Alternatively set per-developer bundler locals:
bundle config local.react_on_rails ../../react_on_railsandbundle config local.shakapacker /path/to/shakapacker.📝 Committable suggestion
🤖 Prompt for AI Agents