From 92b3018d1c79291e5e6cdcb2f2d3b0a42badc9dd Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 19:24:57 +0300
Subject: [PATCH 01/56] Let Shakapacker choose SWC parsers
---
docs/oss/migrating/babel-to-swc-migration.md | 32 +++++--------------
.../spec/dummy/config/swc.config.js | 5 ---
2 files changed, 8 insertions(+), 29 deletions(-)
diff --git a/docs/oss/migrating/babel-to-swc-migration.md b/docs/oss/migrating/babel-to-swc-migration.md
index 7f746a67ee..2f2d6bad43 100644
--- a/docs/oss/migrating/babel-to-swc-migration.md
+++ b/docs/oss/migrating/babel-to-swc-migration.md
@@ -52,11 +52,6 @@ try {
const customConfig = {
options: {
jsc: {
- parser: {
- syntax: 'ecmascript',
- jsx: true,
- dynamicImport: true,
- },
transform: {
react: {
runtime: 'automatic',
@@ -140,13 +135,13 @@ If you need stable React Server Components support today:
### Features Migrated Successfully
-| Babel Feature | SWC Equivalent | Notes |
-| ------------------ | --------------------------------- | --------------------------- |
-| JSX Transform | `jsc.transform.react` | Automatic runtime supported |
-| React Fast Refresh | `jsc.transform.react.refresh` | Works in development mode |
-| Dynamic Imports | `jsc.parser.dynamicImport` | Fully supported |
-| Class Properties | Built-in | No config needed |
-| TypeScript | `jsc.parser.syntax: 'typescript'` | Native support |
+| Babel Feature | SWC Equivalent | Notes |
+| ------------------ | ------------------------------ | --------------------------- |
+| JSX Transform | `jsc.transform.react` | Automatic runtime supported |
+| React Fast Refresh | `jsc.transform.react.refresh` | Works in development mode |
+| Dynamic Imports | Shakapacker SWC parser default | Fully supported |
+| Class Properties | Built-in | No config needed |
+| TypeScript | Shakapacker SWC parser default | Native support |
### Features Requiring Different Approach
@@ -225,18 +220,7 @@ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
### Issue: TypeScript Files Not Transpiling
-**Solution**: For TypeScript files, update your SWC config to use TypeScript parser:
-
-```javascript
-jsc: {
- parser: {
- syntax: 'typescript',
- tsx: true,
- dynamicImport: true,
- },
- // ... rest of config
-}
-```
+**Solution**: Do not hardcode `jsc.parser` in `config/swc.config.js`. Shakapacker selects the SWC parser per file extension, using TypeScript mode for `.ts` and `.tsx` files. Keep custom settings under `jsc.transform`, `jsc.keepClassNames`, and other non-parser options unless the app has a specific parser feature to enable.
## Testing Results
diff --git a/react_on_rails/spec/dummy/config/swc.config.js b/react_on_rails/spec/dummy/config/swc.config.js
index eed3ef9bad..4781e16c41 100644
--- a/react_on_rails/spec/dummy/config/swc.config.js
+++ b/react_on_rails/spec/dummy/config/swc.config.js
@@ -12,11 +12,6 @@ try {
const customConfig = {
options: {
jsc: {
- parser: {
- syntax: 'ecmascript',
- jsx: true,
- dynamicImport: true,
- },
transform: {
react: {
runtime: 'automatic',
From d1fef75c2ada0dbbb9e9e4446bcd2909c33cc413 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 19:31:52 +0300
Subject: [PATCH 02/56] Wire dummy client into TypeScript checks
---
.../spec/dummy/client/app/types/assets.d.ts | 24 ++++++++++++++++++
react_on_rails/spec/dummy/package.json | 3 ++-
react_on_rails/spec/dummy/tsconfig.json | 25 +++++++++++++++++++
3 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 react_on_rails/spec/dummy/client/app/types/assets.d.ts
create mode 100644 react_on_rails/spec/dummy/tsconfig.json
diff --git a/react_on_rails/spec/dummy/client/app/types/assets.d.ts b/react_on_rails/spec/dummy/client/app/types/assets.d.ts
new file mode 100644
index 0000000000..f6736c83ed
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/types/assets.d.ts
@@ -0,0 +1,24 @@
+declare module '*.module.css' {
+ const classes: Record;
+ export default classes;
+}
+
+declare module '*.module.scss' {
+ const classes: Record;
+ export default classes;
+}
+
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.svg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.ttf' {
+ const src: string;
+ export default src;
+}
diff --git a/react_on_rails/spec/dummy/package.json b/react_on_rails/spec/dummy/package.json
index 35dfc39d85..c685b7a266 100644
--- a/react_on_rails/spec/dummy/package.json
+++ b/react_on_rails/spec/dummy/package.json
@@ -88,7 +88,8 @@
"build:dev:watch": "npx rescript build -w && RAILS_ENV=development NODE_ENV=development bin/shakapacker --watch",
"build:clean": "pnpm run build:rescript && rm -rf public/webpack || true",
"build:rescript": "npx rescript clean && npx rescript build -with-deps",
- "build:rescript:dev": "npx rescript build -w"
+ "build:rescript:dev": "npx rescript build -w",
+ "type-check": "tsc --noEmit --noErrorTruncation"
},
"version": "0.0.0"
}
diff --git a/react_on_rails/spec/dummy/tsconfig.json b/react_on_rails/spec/dummy/tsconfig.json
new file mode 100644
index 0000000000..738fd82591
--- /dev/null
+++ b/react_on_rails/spec/dummy/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es2018",
+ "allowJs": true,
+ "checkJs": false,
+ "skipLibCheck": true,
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "lib": ["dom", "es2020"]
+ },
+ "include": ["client/app/**/*"],
+ "exclude": [
+ "client/app/generated/**/*",
+ "client/app/packs/generated/**/*",
+ "client/app/**/*.res.js"
+ ]
+}
From 00a0b0f90d1002f578ca6ae7e78dab0d8b758ec5 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 19:41:11 +0300
Subject: [PATCH 03/56] Type selected dummy render example
---
.../dummy/client/app/components/EchoProps.jsx | 5 -----
.../dummy/client/app/components/EchoProps.tsx | 8 ++++++++
...redHtml.server.jsx => RenderedHtml.server.tsx} | 15 ++++++++++++---
3 files changed, 20 insertions(+), 8 deletions(-)
delete mode 100644 react_on_rails/spec/dummy/client/app/components/EchoProps.jsx
create mode 100644 react_on_rails/spec/dummy/client/app/components/EchoProps.tsx
rename react_on_rails/spec/dummy/client/app/startup/{RenderedHtml.server.jsx => RenderedHtml.server.tsx} (56%)
diff --git a/react_on_rails/spec/dummy/client/app/components/EchoProps.jsx b/react_on_rails/spec/dummy/client/app/components/EchoProps.jsx
deleted file mode 100644
index f64e288b3c..0000000000
--- a/react_on_rails/spec/dummy/client/app/components/EchoProps.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-const EchoProps = (props) => Props: {JSON.stringify(props)}
;
-
-export default EchoProps;
diff --git a/react_on_rails/spec/dummy/client/app/components/EchoProps.tsx b/react_on_rails/spec/dummy/client/app/components/EchoProps.tsx
new file mode 100644
index 0000000000..c5a41121c5
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/components/EchoProps.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+type EchoPropsProps = Record;
+
+const EchoProps = (props: EchoPropsProps) => Props: {JSON.stringify(props)}
;
+
+export type { EchoPropsProps };
+export default EchoProps;
diff --git a/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.jsx b/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.tsx
similarity index 56%
rename from react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.jsx
rename to react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.tsx
index 23070efab3..00844d9070 100644
--- a/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.jsx
+++ b/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.server.tsx
@@ -1,7 +1,9 @@
// Top level component for simple client side only rendering
import React from 'react';
import { renderToString } from 'react-dom/server';
-import EchoProps from '../components/EchoProps';
+import type { RailsContext, RenderFunction, ServerRenderResult } from 'react-on-rails/types';
+
+import EchoProps, { type EchoPropsProps } from '../components/EchoProps';
/*
* Export a function that takes the props and returns an object with { renderedHtml }
@@ -13,7 +15,14 @@ import EchoProps from '../components/EchoProps';
* And the use of renderToString would probably be done with React Router v4
*
*/
-export default (props, _railsContext) => {
- const renderedHtml = renderToString();
+const RenderedHtml = (
+ props: EchoPropsProps | undefined,
+ _railsContext?: RailsContext,
+): ServerRenderResult => {
+ const renderedHtml = renderToString();
return { renderedHtml };
};
+
+const renderFunction: RenderFunction = RenderedHtml;
+
+export default renderFunction;
From 2ecddad866f83410e2a148542bc97e86a3c5d68e Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:00:05 +0300
Subject: [PATCH 04/56] Type client rendered HTML example
---
.../{RenderedHtml.client.jsx => RenderedHtml.client.tsx} | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
rename react_on_rails/spec/dummy/client/app/startup/{RenderedHtml.client.jsx => RenderedHtml.client.tsx} (80%)
diff --git a/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.jsx b/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.tsx
similarity index 80%
rename from react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.jsx
rename to react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.tsx
index a035d10736..4ed6460a91 100644
--- a/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.jsx
+++ b/react_on_rails/spec/dummy/client/app/startup/RenderedHtml.client.tsx
@@ -1,7 +1,7 @@
// Top level component for simple client side only rendering
import React from 'react';
-import EchoProps from '../components/EchoProps';
+import EchoProps, { type EchoPropsProps } from '../components/EchoProps';
/*
* Export a function that takes the props and returns a ReactComponent.
@@ -19,4 +19,6 @@ import EchoProps from '../components/EchoProps';
// You may do either:
// export default (props, _railsContext) => () => ;
// or
-export default (props) => ;
+const RenderedHtml = (props: EchoPropsProps) => ;
+
+export default RenderedHtml;
From 0396ff4b5b5f5cbfd30ef378d0e63912d88af27e Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:06:49 +0300
Subject: [PATCH 05/56] Type manual renderer entrypoint
---
.../client/app/startup/ManualRenderApp.jsx | 20 -----------
.../client/app/startup/ManualRenderApp.tsx | 35 +++++++++++++++++++
2 files changed, 35 insertions(+), 20 deletions(-)
delete mode 100644 react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.jsx
create mode 100644 react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.tsx
diff --git a/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.jsx b/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.jsx
deleted file mode 100644
index 15db43a648..0000000000
--- a/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-import ReactDOMClient from 'react-dom/client';
-import { wrapElementInStrictMode } from '../strictModeSupport';
-
-export default (props, _railsContext, domNodeId) => {
- const reactElement = wrapElementInStrictMode(
-
-
Manual Render Example
-
If you can see this, you can register renderer functions.
-
,
- );
-
- const domNode = document.getElementById(domNodeId);
- if (props.prerender) {
- ReactDOMClient.hydrateRoot(domNode, reactElement);
- } else {
- const root = ReactDOMClient.createRoot(domNode);
- root.render(reactElement);
- }
-};
diff --git a/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.tsx b/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.tsx
new file mode 100644
index 0000000000..8dcb5f7f35
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/startup/ManualRenderApp.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import type { ReactElement } from 'react';
+import { createRoot, hydrateRoot } from 'react-dom/client';
+import type { RailsContext } from 'react-on-rails/types';
+
+import { wrapElementInStrictMode } from '../strictModeSupport';
+
+type ManualRenderProps = Record & {
+ prerender?: unknown;
+};
+
+type RendererFunction = (props: ManualRenderProps, railsContext: RailsContext, domNodeId: string) => void;
+
+type WrapElementInStrictMode = (reactElement: ReactElement) => ReactElement;
+
+const wrapStrictModeElement = wrapElementInStrictMode as WrapElementInStrictMode;
+
+const ManualRenderApp: RendererFunction = (props, _railsContext, domNodeId) => {
+ const reactElement = wrapStrictModeElement(
+
+
Manual Render Example
+
If you can see this, you can register renderer functions.
+
,
+ );
+
+ const domNode = document.getElementById(domNodeId) as HTMLElement;
+ if (props.prerender) {
+ hydrateRoot(domNode, reactElement);
+ } else {
+ const root = createRoot(domNode);
+ root.render(reactElement);
+ }
+};
+
+export default ManualRenderApp;
From 8aa8b61bc0216ac1495f50533d0dbcf083e4f465 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:13:12 +0300
Subject: [PATCH 06/56] Type manual render component
---
...nderComponent.jsx => ManualRenderComponent.tsx} | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
rename react_on_rails/spec/dummy/client/app/startup/{ManualRenderComponent.jsx => ManualRenderComponent.tsx} (84%)
diff --git a/react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.jsx b/react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.tsx
similarity index 84%
rename from react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.jsx
rename to react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.tsx
index 17c1f0fcf4..abcefd3640 100644
--- a/react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.jsx
+++ b/react_on_rails/spec/dummy/client/app/startup/ManualRenderComponent.tsx
@@ -1,5 +1,9 @@
-import PropTypes from 'prop-types';
import React from 'react';
+import type { CSSProperties } from 'react';
+
+type ManualRenderComponentProps = {
+ name: string;
+};
/**
* A simple component used to test the reactOnRailsPageLoaded() behavior
@@ -11,10 +15,10 @@ import React from 'react';
*
* Note: This tests the core package's manual rendering API, not Pro's async hydration.
*/
-const ManualRenderComponent = ({ name }) => {
+const ManualRenderComponent = ({ name }: ManualRenderComponentProps) => {
// Use inline styles to verify that hydration issues would cause CSS property
// format mismatches (camelCase in React vs kebab-case in server HTML)
- const containerStyle = {
+ const containerStyle: CSSProperties = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
@@ -33,8 +37,4 @@ const ManualRenderComponent = ({ name }) => {
);
};
-ManualRenderComponent.propTypes = {
- name: PropTypes.string.isRequired,
-};
-
export default ManualRenderComponent;
From ab91379cf35cd86dff3e54075d74f14755fbcef7 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:24:21 +0300
Subject: [PATCH 07/56] Type router dummy flow
---
...outerFirstPage.jsx => RouterFirstPage.tsx} | 0
.../{RouterLayout.jsx => RouterLayout.tsx} | 1 +
...terSecondPage.jsx => RouterSecondPage.tsx} | 0
.../app/routes/{routes.jsx => routes.tsx} | 4 +++-
.../client/app/startup/RouterApp.client.jsx | 15 ------------
.../client/app/startup/RouterApp.client.tsx | 24 +++++++++++++++++++
.../client/app/startup/RouterApp.server.jsx | 10 --------
.../client/app/startup/RouterApp.server.tsx | 20 ++++++++++++++++
8 files changed, 48 insertions(+), 26 deletions(-)
rename react_on_rails/spec/dummy/client/app/components/{RouterFirstPage.jsx => RouterFirstPage.tsx} (100%)
rename react_on_rails/spec/dummy/client/app/components/{RouterLayout.jsx => RouterLayout.tsx} (99%)
rename react_on_rails/spec/dummy/client/app/components/{RouterSecondPage.jsx => RouterSecondPage.tsx} (100%)
rename react_on_rails/spec/dummy/client/app/routes/{routes.jsx => routes.tsx} (84%)
delete mode 100644 react_on_rails/spec/dummy/client/app/startup/RouterApp.client.jsx
create mode 100644 react_on_rails/spec/dummy/client/app/startup/RouterApp.client.tsx
delete mode 100644 react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx
create mode 100644 react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx
diff --git a/react_on_rails/spec/dummy/client/app/components/RouterFirstPage.jsx b/react_on_rails/spec/dummy/client/app/components/RouterFirstPage.tsx
similarity index 100%
rename from react_on_rails/spec/dummy/client/app/components/RouterFirstPage.jsx
rename to react_on_rails/spec/dummy/client/app/components/RouterFirstPage.tsx
diff --git a/react_on_rails/spec/dummy/client/app/components/RouterLayout.jsx b/react_on_rails/spec/dummy/client/app/components/RouterLayout.tsx
similarity index 99%
rename from react_on_rails/spec/dummy/client/app/components/RouterLayout.jsx
rename to react_on_rails/spec/dummy/client/app/components/RouterLayout.tsx
index 2ff566b50f..87c1974176 100644
--- a/react_on_rails/spec/dummy/client/app/components/RouterLayout.jsx
+++ b/react_on_rails/spec/dummy/client/app/components/RouterLayout.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { Link, Route, Routes } from 'react-router-dom';
+
import RouterFirstPage from './RouterFirstPage';
import RouterSecondPage from './RouterSecondPage';
diff --git a/react_on_rails/spec/dummy/client/app/components/RouterSecondPage.jsx b/react_on_rails/spec/dummy/client/app/components/RouterSecondPage.tsx
similarity index 100%
rename from react_on_rails/spec/dummy/client/app/components/RouterSecondPage.jsx
rename to react_on_rails/spec/dummy/client/app/components/RouterSecondPage.tsx
diff --git a/react_on_rails/spec/dummy/client/app/routes/routes.jsx b/react_on_rails/spec/dummy/client/app/routes/routes.tsx
similarity index 84%
rename from react_on_rails/spec/dummy/client/app/routes/routes.jsx
rename to react_on_rails/spec/dummy/client/app/routes/routes.tsx
index d825dd988b..f01cb2cbf3 100644
--- a/react_on_rails/spec/dummy/client/app/routes/routes.jsx
+++ b/react_on_rails/spec/dummy/client/app/routes/routes.tsx
@@ -3,8 +3,10 @@ import { Routes, Route } from 'react-router-dom';
import RouterLayout from '../components/RouterLayout';
-export default (
+const routes = (
} />
);
+
+export default routes;
diff --git a/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.jsx b/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.jsx
deleted file mode 100644
index dd708538d5..0000000000
--- a/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { BrowserRouter } from 'react-router-dom';
-import routes from '../routes/routes';
-
-export default (props) => (
-
- {routes}
-
-);
diff --git a/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.tsx b/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.tsx
new file mode 100644
index 0000000000..5cf983cb02
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import type { ComponentProps } from 'react';
+import { BrowserRouter } from 'react-router-dom';
+
+import routes from '../routes/routes';
+
+type RouterAppProps = {
+ helloWorldData: {
+ name: string;
+ };
+} & Omit, 'children' | 'future'>;
+
+const routerFuture: ComponentProps['future'] = {
+ v7_startTransition: true,
+ v7_relativeSplatPath: true,
+};
+
+const RouterApp = (props: RouterAppProps) => (
+
+ {routes}
+
+);
+
+export default RouterApp;
diff --git a/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx b/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx
deleted file mode 100644
index d4c8675899..0000000000
--- a/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { StaticRouter } from 'react-router-dom/server';
-
-import routes from '../routes/routes';
-
-export default (props, railsContext) => () => (
-
- {routes}
-
-);
diff --git a/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx b/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx
new file mode 100644
index 0000000000..447340cf84
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import type { ComponentProps } from 'react';
+import { StaticRouter } from 'react-router-dom/server';
+import type { RailsContext } from 'react-on-rails/types';
+
+import routes from '../routes/routes';
+
+type RouterAppProps = {
+ helloWorldData: {
+ name: string;
+ };
+} & Omit, 'children' | 'location'>;
+
+const RouterApp = (props: RouterAppProps, railsContext: RailsContext) => () => (
+
+ {routes}
+
+);
+
+export default RouterApp;
From edc46101eef84cf8f2012bf449fc4c6bcf6bb370 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:26:18 +0300
Subject: [PATCH 08/56] Update router dummy docs links
---
docs/oss/api-reference/view-helpers-api.md | 2 +-
...to-bundling-file-system-based-automated-bundle-generation.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/oss/api-reference/view-helpers-api.md b/docs/oss/api-reference/view-helpers-api.md
index fda1a3718b..7a96cf5bde 100644
--- a/docs/oss/api-reference/view-helpers-api.md
+++ b/docs/oss/api-reference/view-helpers-api.md
@@ -113,7 +113,7 @@ Why would you want to take over mounting yourself? One use case is code splittin
[React Router](https://reactrouter.com/) is supported via manual integration, including server-side rendering. See:
1. [React on Rails docs for React Router](../building-features/react-router.md)
-2. Examples in [spec/dummy/app/views/react_router](https://github.com/shakacode/react_on_rails/tree/main/react_on_rails/spec/dummy/app/views/react_router) and follow to the JavaScript code in the [spec/dummy/client/app/startup/RouterApp.server.jsx](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx).
+2. Examples in [spec/dummy/app/views/react_router](https://github.com/shakacode/react_on_rails/tree/main/react_on_rails/spec/dummy/app/views/react_router) and follow to the JavaScript code in the [spec/dummy/client/app/startup/RouterApp.server.tsx](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx).
3. [React on Rails Pro loadable-components guide](../building-features/code-splitting.md) for modern code splitting with server-side rendering.
### TanStack Router
diff --git a/docs/oss/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md b/docs/oss/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md
index e8e39f1a2f..222f2687aa 100644
--- a/docs/oss/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md
+++ b/docs/oss/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md
@@ -509,7 +509,7 @@ _Screenshots show browser dev tools network analysis demonstrating the dramatic
If server rendering is enabled, the component will be registered for usage both in server and client rendering. To have separate definitions for client and server rendering, name the component files `ComponentName.server.jsx` and `ComponentName.client.jsx`. The `ComponentName.server.jsx` file will be used for server rendering and the `ComponentName.client.jsx` file for client rendering. If you don't want the component rendered on the server, you should only have the `ComponentName.client.jsx` file.
-> Example (dummy app): paired files such as [`ReduxApp.client.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx) and [`ReduxApp.server.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx), and [`RouterApp.client.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.jsx) and [`RouterApp.server.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.jsx).
+> Example (dummy app): paired files such as [`ReduxApp.client.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReduxApp.client.jsx) and [`ReduxApp.server.jsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReduxApp.server.jsx), and [`RouterApp.client.tsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.client.tsx) and [`RouterApp.server.tsx`](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/RouterApp.server.tsx).
Once generated, all server entrypoints will be imported into a file named `[ReactOnRails.configuration.server_bundle_js_file]-generated.js`, which in turn will be imported into a source file named the same as `ReactOnRails.configuration.server_bundle_js_file`. If your server bundling logic is such that your server bundle source entrypoint is not named the same as your `ReactOnRails.configuration.server_bundle_js_file` and changing it would be difficult, please let us know.
From 450de8f12cb17a67cee95c6122b06463ec6da18a Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:43:52 +0300
Subject: [PATCH 09/56] Type HelloWorld core dummy flow
---
pnpm-lock.yaml | 3 --
.../client/app/components/RailsContext.jsx | 46 -------------------
.../client/app/components/RailsContext.tsx | 46 +++++++++++++++++++
.../{HelloWorld.jsx => HelloWorld.tsx} | 38 ++++++++-------
.../{HelloWorldApp.jsx => HelloWorldApp.tsx} | 7 ++-
react_on_rails/spec/dummy/package.json | 1 -
6 files changed, 73 insertions(+), 68 deletions(-)
delete mode 100644 react_on_rails/spec/dummy/client/app/components/RailsContext.jsx
create mode 100644 react_on_rails/spec/dummy/client/app/components/RailsContext.tsx
rename react_on_rails/spec/dummy/client/app/startup/{HelloWorld.jsx => HelloWorld.tsx} (60%)
rename react_on_rails/spec/dummy/client/app/startup/{HelloWorldApp.jsx => HelloWorldApp.tsx} (75%)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db86320a4b..8983620c68 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -420,9 +420,6 @@ importers:
jquery-ujs:
specifier: ^1.2.2
version: 1.2.3(jquery@3.7.1)
- lodash:
- specifier: ^4.18.1
- version: 4.18.1
mini-css-extract-plugin:
specifier: ^2.4.4
version: 2.10.0(webpack@5.105.2)
diff --git a/react_on_rails/spec/dummy/client/app/components/RailsContext.jsx b/react_on_rails/spec/dummy/client/app/components/RailsContext.jsx
deleted file mode 100644
index 83568b8405..0000000000
--- a/react_on_rails/spec/dummy/client/app/components/RailsContext.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import _ from 'lodash';
-
-function renderContextRows(railsContext) {
- console.log('railsContext.serverSide is ', railsContext.serverSide);
- return _.transform(
- railsContext,
- (accum, value, key) => {
- if (key !== 'serverSide' && key !== 'componentSpecificMetadata') {
- const className = `js-${key}`;
- accum.push(
-
- |
- {key}:
- |
- {`${value}`} |
-
,
- );
- }
- },
- [],
- );
-}
-
-const RailsContext = ({ railsContext }) => (
-
-
-
- |
- key
- |
-
- value
- |
-
-
- {renderContextRows(railsContext)}
-
-);
-
-RailsContext.propTypes = {
- railsContext: PropTypes.object.isRequired,
-};
-
-export default RailsContext;
diff --git a/react_on_rails/spec/dummy/client/app/components/RailsContext.tsx b/react_on_rails/spec/dummy/client/app/components/RailsContext.tsx
new file mode 100644
index 0000000000..808469efa7
--- /dev/null
+++ b/react_on_rails/spec/dummy/client/app/components/RailsContext.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import type { RailsContext as RailsContextData } from 'react-on-rails/types';
+
+type RailsContextForDisplay = RailsContextData & Record;
+
+type RailsContextProps = {
+ railsContext: RailsContextForDisplay;
+};
+
+function renderContextRows(railsContext: RailsContextForDisplay) {
+ console.log('railsContext.serverSide is ', railsContext.serverSide);
+ return Object.entries(railsContext).reduce((accum, [key, value]) => {
+ if (key !== 'serverSide' && key !== 'componentSpecificMetadata') {
+ const className = `js-${key}`;
+ accum.push(
+
+ |
+ {key}:
+ |
+ {`${value}`} |
+
,
+ );
+ }
+
+ return accum;
+ }, []);
+}
+
+const RailsContext = ({ railsContext }: RailsContextProps) => (
+
+
+
+ |
+ key
+ |
+
+ value
+ |
+
+
+ {renderContextRows(railsContext)}
+
+);
+
+export type { RailsContextForDisplay, RailsContextProps };
+export default RailsContext;
diff --git a/react_on_rails/spec/dummy/client/app/startup/HelloWorld.jsx b/react_on_rails/spec/dummy/client/app/startup/HelloWorld.tsx
similarity index 60%
rename from react_on_rails/spec/dummy/client/app/startup/HelloWorld.jsx
rename to react_on_rails/spec/dummy/client/app/startup/HelloWorld.tsx
index 66a57c0350..95464c80c8 100644
--- a/react_on_rails/spec/dummy/client/app/startup/HelloWorld.jsx
+++ b/react_on_rails/spec/dummy/client/app/startup/HelloWorld.tsx
@@ -1,35 +1,40 @@
-import PropTypes from 'prop-types';
import React from 'react';
+import type { RailsContext as RailsContextData } from 'react-on-rails/types';
+
import RailsContext from '../components/RailsContext';
// Example of CSS modules...
import css from '../components/HelloWorld.module.scss';
+type HelloWorldData = Record & {
+ name: string;
+};
+
+type HelloWorldProps = Record & {
+ helloWorldData: HelloWorldData;
+ railsContext?: RailsContextData;
+};
+
+type HelloWorldState = HelloWorldData;
+
// Super simple example of the simplest possible React component
-class HelloWorld extends React.Component {
- static propTypes = {
- helloWorldData: PropTypes.shape({
- name: PropTypes.string,
- }).isRequired,
- railsContext: PropTypes.object,
- };
+class HelloWorld extends React.Component {
+ private nameDomRef: HTMLInputElement | null = null;
// Not necessary if we only call super, but we'll need to initialize state, etc.
- constructor(props) {
+ constructor(props: HelloWorldProps) {
super(props);
this.state = props.helloWorldData;
- this.setNameDomRef = this.setNameDomRef.bind(this);
- this.handleChange = this.handleChange.bind(this);
}
- handleChange() {
- const name = this.nameDomRef.value;
+ handleChange = () => {
+ const name = (this.nameDomRef as HTMLInputElement).value;
this.setState({ name });
- }
+ };
- setNameDomRef(nameDomNode) {
+ setNameDomRef = (nameDomNode: HTMLInputElement | null) => {
this.nameDomRef = nameDomNode;
- }
+ };
render() {
console.log(
@@ -53,4 +58,5 @@ class HelloWorld extends React.Component {
}
}
+export type { HelloWorldData, HelloWorldProps };
export default HelloWorld;
diff --git a/react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.jsx b/react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.tsx
similarity index 75%
rename from react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.jsx
rename to react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.tsx
index c6649c79d4..e60b4b3fb2 100644
--- a/react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.jsx
+++ b/react_on_rails/spec/dummy/client/app/startup/HelloWorldApp.tsx
@@ -1,6 +1,7 @@
// Top level component for simple client side only rendering
import React from 'react';
-import HelloWorld from './HelloWorld';
+
+import HelloWorld, { type HelloWorldProps } from './HelloWorld';
/*
* Export a function that takes the props and returns a ReactComponent.
@@ -11,4 +12,6 @@ import HelloWorld from './HelloWorld';
* Note, this is a fictional example, as you'd only use a Render-Function if you wanted to run
* some extra code, such as setting up Redux and React Router.
*/
-export default (props) => ;
+const HelloWorldApp = (props: HelloWorldProps) => ;
+
+export default HelloWorldApp;
diff --git a/react_on_rails/spec/dummy/package.json b/react_on_rails/spec/dummy/package.json
index c685b7a266..90168dd358 100644
--- a/react_on_rails/spec/dummy/package.json
+++ b/react_on_rails/spec/dummy/package.json
@@ -14,7 +14,6 @@
"create-react-class": "^15.6.3",
"jquery": "^3.5.1",
"jquery-ujs": "^1.2.2",
- "lodash": "^4.18.1",
"mini-css-extract-plugin": "^2.4.4",
"node-libs-browser": "^2.2.1",
"null-loader": "^4.0.0",
From 898d321753f7d3ed2e51f6709fc5a8ef8351c6a8 Mon Sep 17 00:00:00 2001
From: ihabadham
Date: Thu, 4 Jun 2026 20:47:39 +0300
Subject: [PATCH 10/56] Update HelloWorld dummy references
---
.../app/views/pages/client_side_hello_world.html.erb | 2 +-
.../app/views/pages/server_side_hello_world.html.erb | 10 +++++-----
.../spec/dummy/client/app/startup/HelloWorld.tsx | 3 +--
.../spec/dummy/client/app/startup/HelloWorldHooks.jsx | 2 +-
4 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/react_on_rails/spec/dummy/app/views/pages/client_side_hello_world.html.erb b/react_on_rails/spec/dummy/app/views/pages/client_side_hello_world.html.erb
index 06e291d79b..56e5578b0c 100644
--- a/react_on_rails/spec/dummy/app/views/pages/client_side_hello_world.html.erb
+++ b/react_on_rails/spec/dummy/app/views/pages/client_side_hello_world.html.erb
@@ -15,7 +15,7 @@
Setup
-
- Create component source: spec/dummy/client/app/components/HelloWorld.jsx
+ Create component source: spec/dummy/client/app/startup/HelloWorld.tsx
-
Expose the HelloWorld Component: spec/dummy/client/app/packs/client-bundle.js
diff --git a/react_on_rails/spec/dummy/app/views/pages/server_side_hello_world.html.erb b/react_on_rails/spec/dummy/app/views/pages/server_side_hello_world.html.erb
index 8dcb1ea96e..6633b1e478 100644
--- a/react_on_rails/spec/dummy/app/views/pages/server_side_hello_world.html.erb
+++ b/react_on_rails/spec/dummy/app/views/pages/server_side_hello_world.html.erb
@@ -15,7 +15,7 @@
<%= '
' %>
<%= '