Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0966fc9
Add TypeScript support to React on Rails install generator
justin808 Sep 17, 2025
e505bba
Fix linting issues and exclude templates from ESLint
justin808 Sep 17, 2025
31e9f3e
Fix Prettier formatting issues in TypeScript templates
justin808 Sep 17, 2025
e470dda
Complete Redux TypeScript implementation for generators
justin808 Sep 17, 2025
0a3c0fb
Fix Redux generator tests and include missing GeneratorHelper
justin808 Sep 17, 2025
c5e03f1
Update lib/generators/react_on_rails/install_generator.rb
justin808 Sep 17, 2025
edb82bd
Address PR feedback: Fix type imports, security, and add CSS modules
justin808 Sep 17, 2025
1df67f5
Fix TypeScript generator tests and lint issues
justin808 Sep 18, 2025
68e601f
Trigger CI rebuild
justin808 Sep 18, 2025
9ea4952
Fix generator method scoping and indentation after rebase
justin808 Sep 18, 2025
535face
Fix Prettier formatting issues in documentation files
justin808 Sep 18, 2025
abb3e72
Update lib/generators/react_on_rails/install_generator.rb
justin808 Sep 18, 2025
79a41e9
Update lib/generators/react_on_rails/install_generator.rb
justin808 Sep 18, 2025
9eb5110
Update lib/generators/react_on_rails/install_generator.rb
justin808 Sep 18, 2025
bc5c1d7
Update lib/generators/react_on_rails/install_generator.rb
justin808 Sep 18, 2025
1f61210
Update lib/generators/react_on_rails/templates/base/base/app/javascri…
justin808 Sep 18, 2025
81f8da8
Apply suggestions from code review
justin808 Sep 18, 2025
0283efd
Address alexeyr-ci2 PR review suggestions
justin808 Sep 18, 2025
4ed7a0f
Fix require order and improve React component performance
justin808 Sep 18, 2025
054dff3
Fix Prettier formatting issues
justin808 Sep 18, 2025
b037c6a
Fix TypeScript test expectations to match improved code
justin808 Sep 18, 2025
622eecc
SECURITY: Fix command injection vulnerabilities in generators
justin808 Sep 18, 2025
cc1135e
Improve bin/dev generated script with hello_world default route
justin808 Sep 18, 2025
2ca96fd
Fix RuboCop violations in bin/dev template
justin808 Sep 18, 2025
ec7ffd3
Fix bin/dev test to handle default route functionality
justin808 Sep 18, 2025
1708714
Add CHANGELOG entry for TypeScript generator improvements
justin808 Sep 18, 2025
b597275
Add PR link and author attribution to CHANGELOG
justin808 Sep 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th

Changes since the last non-beta release.

#### Enhanced TypeScript Generator Support

**πŸ”§ Generator Improvements**

- **Modern TypeScript patterns**: Generators now produce more idiomatic TypeScript code with improved type inference instead of explicit type annotations [PR 1786](https://github.com/shakacode/react_on_rails/pull/1786) by [justin808](https://github.com/justin808)
- **Optimized tsconfig.json**: Updated compiler options to use `"moduleResolution": "bundler"` for better bundler compatibility
- **Enhanced Redux TypeScript integration**: Improved type safety and modern React patterns (useMemo, type-only imports)
- **Smart bin/dev defaults**: Generated `bin/dev` script now automatically navigates to `/hello_world` route for immediate component visibility

**πŸ” Security Enhancements**

- **Fixed command injection vulnerabilities**: Replaced unsafe string interpolation in generator package installation commands with secure array-based system calls
- **Improved input validation**: Enhanced package manager validation and argument sanitization across all generators

**🎯 Developer Experience**

- **Better component templates**: Removed unnecessary type annotations while maintaining type safety through TypeScript's inference
- **Cleaner generated code**: Streamlined templates following modern React and TypeScript best practices
- **Improved helper methods**: Added reusable `component_extension` helper for consistent file extension handling

### [16.0.0] - 2025-09-16

**React on Rails v16 is a major release that modernizes the library with ESM support, removes legacy Webpacker compatibility, and introduces significant performance improvements. This release builds on the foundation of v14 with enhanced RSC (React Server Components) support and streamlined configuration.**
Expand Down
3 changes: 3 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const config = tsEslint.config([
// fixtures
'**/fixtures/',
'**/.yalc/**/*',
// generator templates - exclude TypeScript templates that need tsconfig.json
'**/templates/**/*.tsx',
'**/templates/**/*.ts',
]),
{
files: ['**/*.[jt]s', '**/*.[jt]sx', '**/*.[cm][jt]s'],
Expand Down
14 changes: 7 additions & 7 deletions lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ def add_js_dependencies
def install_js_dependencies
# Detect which package manager to use
success = if File.exist?(File.join(destination_root, "yarn.lock"))
run "yarn install"
system("yarn", "install")
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
run "pnpm install"
system("pnpm", "install")
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
File.exist?(File.join(destination_root, "package.json"))
# Use npm for package-lock.json or as default fallback
run "npm install"
system("npm", "install")
else
true # No package manager detected, skip
end
Expand Down Expand Up @@ -173,7 +173,7 @@ def add_react_on_rails_package
return if add_npm_dependencies(react_on_rails_pkg)

puts "Using direct npm commands as fallback"
success = run "npm install #{react_on_rails_pkg.join(' ')}"
success = system("npm", "install", *react_on_rails_pkg)
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
end

Expand All @@ -189,7 +189,7 @@ def add_react_dependencies
]
return if add_npm_dependencies(react_deps)

success = run "npm install #{react_deps.join(' ')}"
success = system("npm", "install", *react_deps)
handle_npm_failure("React dependencies", react_deps) unless success
end

Expand All @@ -203,7 +203,7 @@ def add_css_dependencies
]
return if add_npm_dependencies(css_deps)

success = run "npm install #{css_deps.join(' ')}"
success = system("npm", "install", *css_deps)
handle_npm_failure("CSS dependencies", css_deps) unless success
end

Expand All @@ -215,7 +215,7 @@ def add_dev_dependencies
]
return if add_npm_dependencies(dev_deps, dev: true)

success = run "npm install --save-dev #{dev_deps.join(' ')}"
success = system("npm", "install", "--save-dev", *dev_deps)
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
end

Expand Down
4 changes: 4 additions & 0 deletions lib/generators/react_on_rails/generator_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ def copy_file_and_missing_parent_directories(source_file, destination_file = nil
def add_documentation_reference(message, source)
"#{message} \n#{source}"
end

def component_extension(options)
options.typescript? ? "tsx" : "jsx"
end
end
107 changes: 103 additions & 4 deletions lib/generators/react_on_rails/install_generator.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "rails/generators"
require "json"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Require FileUtils before using it.

create_css_module_types calls FileUtils.mkdir_p but fileutils isn’t required in this file.

Apply this diff:

 require "rails/generators"
 require "json"
+require "fileutils"
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require "json"
require "rails/generators"
require "json"
require "fileutils"
πŸ€– Prompt for AI Agents
In lib/generators/react_on_rails/install_generator.rb around line 4, FileUtils
is used in create_css_module_types but not required; add require "fileutils"
(for example immediately after the existing require "json") so FileUtils.mkdir_p
calls succeed.

require_relative "generator_helper"
require_relative "generator_messages"

Expand All @@ -20,6 +21,13 @@ class InstallGenerator < Rails::Generators::Base
desc: "Install Redux package and Redux version of Hello World Example. Default: false",
aliases: "-R"

# --typescript
class_option :typescript,
type: :boolean,
default: false,
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
aliases: "-T"

# --ignore-warnings
class_option :ignore_warnings,
type: :boolean,
Expand Down Expand Up @@ -58,11 +66,16 @@ def print_generator_messages

def invoke_generators
ensure_shakapacker_installed
invoke "react_on_rails:base"
if options.typescript?
install_typescript_dependencies
create_css_module_types
create_typescript_config
end
invoke "react_on_rails:base", [], { typescript: options.typescript? }
if options.redux?
invoke "react_on_rails:react_with_redux"
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
else
invoke "react_on_rails:react_no_redux"
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript? }
end
end

Expand Down Expand Up @@ -311,10 +324,96 @@ def missing_package_manager?
false
end

def install_typescript_dependencies
puts Rainbow("πŸ“ Installing TypeScript dependencies...").yellow

# Install TypeScript and React type definitions
typescript_packages = %w[
typescript
@types/react
@types/react-dom
@babel/preset-typescript
]

# Try using GeneratorHelper first (package manager agnostic)
return if add_npm_dependencies(typescript_packages, dev: true)

# Fallback to npm if GeneratorHelper fails
success = system("npm", "install", "--save-dev", *typescript_packages)
return if success

warning = <<~MSG.strip
⚠️ Failed to install TypeScript dependencies automatically.

Please run manually:
npm install --save-dev #{typescript_packages.join(' ')}
MSG
GeneratorMessages.add_warning(warning)
end

def create_css_module_types
puts Rainbow("πŸ“ Creating CSS module type definitions...").yellow

# Ensure the types directory exists
FileUtils.mkdir_p("app/javascript/types")

css_module_types_content = <<~TS.strip
// 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;
}
TS

File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
puts Rainbow("βœ… Created CSS module type definitions").green
end
Comment on lines +355 to +380
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

CSS Modules d.ts mismatches namedExports=true (project convention).

Per project learning, CSS Modules use named exports with import * as style from './file.module.css'. The current typings export a default, which will mislead TS. Use export = classes to support namespace imports.

Apply this diff:

-        css_module_types_content = <<~TS.strip
-          // 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;
-          }
-        TS
+        css_module_types_content = <<~TS.strip
+          // TypeScript definitions for CSS modules (supports `import * as styles from ...`)
+          declare module "*.module.css" {
+            const classes: { readonly [key: string]: string };
+            export = classes;
+          }
+
+          declare module "*.module.scss" {
+            const classes: { readonly [key: string]: string };
+            export = classes;
+          }
+
+          declare module "*.module.sass" {
+            const classes: { readonly [key: string]: string };
+            export = classes;
+          }
+        TS

Note: If you prefer strict per-file typings, consider adding a typed‑CSS‑modules generator later.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
puts Rainbow("πŸ“ Creating CSS module type definitions...").yellow
# Ensure the types directory exists
FileUtils.mkdir_p("app/javascript/types")
css_module_types_content = <<~TS.strip
// 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;
}
TS
File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
puts Rainbow("βœ… Created CSS module type definitions").green
end
puts Rainbow("πŸ“ Creating CSS module type definitions...").yellow
# Ensure the types directory exists
FileUtils.mkdir_p("app/javascript/types")
css_module_types_content = <<~TS.strip
// TypeScript definitions for CSS modules (supports `import * as styles from ...`)
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export = classes;
}
declare module "*.module.scss" {
const classes: { readonly [key: string]: string };
export = classes;
}
declare module "*.module.sass" {
const classes: { readonly [key: string]: string };
export = classes;
}
TS
File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
puts Rainbow("βœ… Created CSS module type definitions").green
end
πŸ€– Prompt for AI Agents
In lib/generators/react_on_rails/install_generator.rb around lines 346 to 371,
the generated CSS module type file currently uses "export default classes" which
conflicts with the project's namedExports=true convention; update the generated
declarations to use CommonJS-style namespace exports so namespace imports work.
Replace each "export default classes;" with "export = classes;" (keep the const
classes: { [key: string]: string }; declaration) for all "*.module.css",
"*.module.scss", and "*.module.sass" blocks and write that updated content to
app/javascript/types/css-modules.d.ts.


def create_typescript_config
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create_typescript_config method is defined but never called. This means TypeScript projects won't get a tsconfig.json file, which is essential for TypeScript compilation.

Copilot uses AI. Check for mistakes.
if File.exist?("tsconfig.json")
puts Rainbow("⚠️ tsconfig.json already exists, skipping creation").yellow
return
end

tsconfig_content = {
"compilerOptions" => {
"target" => "es2018",
"allowJs" => true,
"skipLibCheck" => true,
"strict" => true,
"noUncheckedIndexedAccess" => true,
"forceConsistentCasingInFileNames" => true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed.

"noFallthroughCasesInSwitch" => true,
"module" => "esnext",
"moduleResolution" => "bundler",
"resolveJsonModule" => true,
"isolatedModules" => true,
Copy link
Collaborator

@alexeyr-ci2 alexeyr-ci2 Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not desirable in my opinion, it simply makes some TS code not compile which the user may want to compile.

"noEmit" => true,
"jsx" => "react-jsx"
},
"include" => [
"app/javascript/**/*"
]
}

File.write("tsconfig.json", JSON.pretty_generate(tsconfig_content))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we always running in an empty project or could the user could already have tsconfig.json?

puts Rainbow("βœ… Created tsconfig.json").green
end

# Removed: Shakapacker auto-installation logic (now explicit dependency)

# Removed: Shakapacker 8+ is now required as explicit dependency
# rubocop:enable Metrics/ClassLength
end
# rubocop:enable Metrics/ClassLength
end
end
20 changes: 16 additions & 4 deletions lib/generators/react_on_rails/react_no_redux_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ class ReactNoReduxGenerator < Rails::Generators::Base
Rails::Generators.hide_namespace(namespace)
source_root(File.expand_path("templates", __dir__))

class_option :typescript,
type: :boolean,
default: false,
desc: "Generate TypeScript files"

def copy_base_files
base_js_path = "base/base"
base_files = %w[app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx
app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx
app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css]
base_files.each { |file| copy_file("#{base_js_path}/#{file}", file) }

# Determine which component files to copy based on TypeScript option
component_files = [
"app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
"app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
"app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
]

component_files.each do |file|
copy_file("#{base_js_path}/#{file}", file)
end
end

def create_appropriate_templates
Expand Down
Loading
Loading