react_on_rails 16.0.0 → 16.0.1.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +117 -77
- data/CLAUDE.md +14 -2
- data/Gemfile.lock +1 -1
- data/LICENSE.md +15 -1
- data/README.md +68 -18
- data/eslint.config.ts +3 -0
- data/knip.ts +20 -9
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +7 -7
- data/lib/generators/react_on_rails/generator_helper.rb +4 -0
- data/lib/generators/react_on_rails/generator_messages.rb +2 -2
- data/lib/generators/react_on_rails/install_generator.rb +115 -7
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +16 -4
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +83 -14
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +12 -24
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +10 -6
- data/lib/react_on_rails/dev/server_manager.rb +185 -28
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/helper.rb +9 -78
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +6 -2
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/tasks/doctor.rake +51 -0
- data/lib/tasks/generate_packs.rake +127 -4
- data/package-lock.json +11984 -0
- metadata +21 -6
- data/lib/generators/react_on_rails/bin/dev +0 -46
@@ -38,7 +38,7 @@ module GeneratorMessages
|
|
38
38
|
@output = []
|
39
39
|
end
|
40
40
|
|
41
|
-
def helpful_message_after_installation(component_name: "HelloWorld")
|
41
|
+
def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world")
|
42
42
|
process_manager_section = build_process_manager_section
|
43
43
|
testing_section = build_testing_section
|
44
44
|
package_manager = detect_package_manager
|
@@ -62,7 +62,7 @@ module GeneratorMessages
|
|
62
62
|
./bin/dev prod # Production-like mode for testing
|
63
63
|
./bin/dev help # See all available options
|
64
64
|
|
65
|
-
3. Visit: #{Rainbow('http://localhost:3000
|
65
|
+
3. Visit: #{Rainbow(route ? "http://localhost:3000/#{route}" : 'http://localhost:3000').cyan.underline}
|
66
66
|
✨ KEY FEATURES:
|
67
67
|
─────────────────────────────────────────────────────────────────────────
|
68
68
|
• Auto-registration enabled - Your layout only needs:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "json"
|
4
5
|
require_relative "generator_helper"
|
5
6
|
require_relative "generator_messages"
|
6
7
|
|
@@ -20,6 +21,13 @@ module ReactOnRails
|
|
20
21
|
desc: "Install Redux package and Redux version of Hello World Example. Default: false",
|
21
22
|
aliases: "-R"
|
22
23
|
|
24
|
+
# --typescript
|
25
|
+
class_option :typescript,
|
26
|
+
type: :boolean,
|
27
|
+
default: false,
|
28
|
+
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
|
29
|
+
aliases: "-T"
|
30
|
+
|
23
31
|
# --ignore-warnings
|
24
32
|
class_option :ignore_warnings,
|
25
33
|
type: :boolean,
|
@@ -58,11 +66,16 @@ module ReactOnRails
|
|
58
66
|
|
59
67
|
def invoke_generators
|
60
68
|
ensure_shakapacker_installed
|
61
|
-
|
69
|
+
if options.typescript?
|
70
|
+
install_typescript_dependencies
|
71
|
+
create_css_module_types
|
72
|
+
create_typescript_config
|
73
|
+
end
|
74
|
+
invoke "react_on_rails:base", [], { typescript: options.typescript? }
|
62
75
|
if options.redux?
|
63
|
-
invoke "react_on_rails:react_with_redux"
|
76
|
+
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
|
64
77
|
else
|
65
|
-
invoke "react_on_rails:react_no_redux"
|
78
|
+
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript? }
|
66
79
|
end
|
67
80
|
end
|
68
81
|
|
@@ -136,11 +149,13 @@ module ReactOnRails
|
|
136
149
|
end
|
137
150
|
|
138
151
|
def add_bin_scripts
|
139
|
-
|
152
|
+
# Copy bin scripts from templates
|
153
|
+
template_bin_path = "#{__dir__}/templates/base/base/bin"
|
154
|
+
directory template_bin_path, "bin"
|
140
155
|
|
141
156
|
# Make these and only these files executable
|
142
157
|
files_to_copy = []
|
143
|
-
Dir.chdir(
|
158
|
+
Dir.chdir(template_bin_path) do
|
144
159
|
files_to_copy.concat(Dir.glob("*"))
|
145
160
|
end
|
146
161
|
files_to_become_executable = files_to_copy.map { |filename| "bin/#{filename}" }
|
@@ -149,7 +164,14 @@ module ReactOnRails
|
|
149
164
|
end
|
150
165
|
|
151
166
|
def add_post_install_message
|
152
|
-
|
167
|
+
# Determine what route will be created by the generator
|
168
|
+
route = "hello_world" # This is the hardcoded route from base_generator.rb
|
169
|
+
component_name = options.redux? ? "HelloWorldApp" : "HelloWorld"
|
170
|
+
|
171
|
+
GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation(
|
172
|
+
component_name: component_name,
|
173
|
+
route: route
|
174
|
+
))
|
153
175
|
end
|
154
176
|
|
155
177
|
def shakapacker_loaded_in_process?(gem_name)
|
@@ -302,10 +324,96 @@ module ReactOnRails
|
|
302
324
|
false
|
303
325
|
end
|
304
326
|
|
327
|
+
def install_typescript_dependencies
|
328
|
+
puts Rainbow("📝 Installing TypeScript dependencies...").yellow
|
329
|
+
|
330
|
+
# Install TypeScript and React type definitions
|
331
|
+
typescript_packages = %w[
|
332
|
+
typescript
|
333
|
+
@types/react
|
334
|
+
@types/react-dom
|
335
|
+
@babel/preset-typescript
|
336
|
+
]
|
337
|
+
|
338
|
+
# Try using GeneratorHelper first (package manager agnostic)
|
339
|
+
return if add_npm_dependencies(typescript_packages, dev: true)
|
340
|
+
|
341
|
+
# Fallback to npm if GeneratorHelper fails
|
342
|
+
success = system("npm", "install", "--save-dev", *typescript_packages)
|
343
|
+
return if success
|
344
|
+
|
345
|
+
warning = <<~MSG.strip
|
346
|
+
⚠️ Failed to install TypeScript dependencies automatically.
|
347
|
+
|
348
|
+
Please run manually:
|
349
|
+
npm install --save-dev #{typescript_packages.join(' ')}
|
350
|
+
MSG
|
351
|
+
GeneratorMessages.add_warning(warning)
|
352
|
+
end
|
353
|
+
|
354
|
+
def create_css_module_types
|
355
|
+
puts Rainbow("📝 Creating CSS module type definitions...").yellow
|
356
|
+
|
357
|
+
# Ensure the types directory exists
|
358
|
+
FileUtils.mkdir_p("app/javascript/types")
|
359
|
+
|
360
|
+
css_module_types_content = <<~TS.strip
|
361
|
+
// TypeScript definitions for CSS modules
|
362
|
+
declare module "*.module.css" {
|
363
|
+
const classes: { [key: string]: string };
|
364
|
+
export default classes;
|
365
|
+
}
|
366
|
+
|
367
|
+
declare module "*.module.scss" {
|
368
|
+
const classes: { [key: string]: string };
|
369
|
+
export default classes;
|
370
|
+
}
|
371
|
+
|
372
|
+
declare module "*.module.sass" {
|
373
|
+
const classes: { [key: string]: string };
|
374
|
+
export default classes;
|
375
|
+
}
|
376
|
+
TS
|
377
|
+
|
378
|
+
File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
|
379
|
+
puts Rainbow("✅ Created CSS module type definitions").green
|
380
|
+
end
|
381
|
+
|
382
|
+
def create_typescript_config
|
383
|
+
if File.exist?("tsconfig.json")
|
384
|
+
puts Rainbow("⚠️ tsconfig.json already exists, skipping creation").yellow
|
385
|
+
return
|
386
|
+
end
|
387
|
+
|
388
|
+
tsconfig_content = {
|
389
|
+
"compilerOptions" => {
|
390
|
+
"target" => "es2018",
|
391
|
+
"allowJs" => true,
|
392
|
+
"skipLibCheck" => true,
|
393
|
+
"strict" => true,
|
394
|
+
"noUncheckedIndexedAccess" => true,
|
395
|
+
"forceConsistentCasingInFileNames" => true,
|
396
|
+
"noFallthroughCasesInSwitch" => true,
|
397
|
+
"module" => "esnext",
|
398
|
+
"moduleResolution" => "bundler",
|
399
|
+
"resolveJsonModule" => true,
|
400
|
+
"isolatedModules" => true,
|
401
|
+
"noEmit" => true,
|
402
|
+
"jsx" => "react-jsx"
|
403
|
+
},
|
404
|
+
"include" => [
|
405
|
+
"app/javascript/**/*"
|
406
|
+
]
|
407
|
+
}
|
408
|
+
|
409
|
+
File.write("tsconfig.json", JSON.pretty_generate(tsconfig_content))
|
410
|
+
puts Rainbow("✅ Created tsconfig.json").green
|
411
|
+
end
|
412
|
+
|
305
413
|
# Removed: Shakapacker auto-installation logic (now explicit dependency)
|
306
414
|
|
307
415
|
# Removed: Shakapacker 8+ is now required as explicit dependency
|
416
|
+
# rubocop:enable Metrics/ClassLength
|
308
417
|
end
|
309
|
-
# rubocop:enable Metrics/ClassLength
|
310
418
|
end
|
311
419
|
end
|
@@ -11,12 +11,24 @@ module ReactOnRails
|
|
11
11
|
Rails::Generators.hide_namespace(namespace)
|
12
12
|
source_root(File.expand_path("templates", __dir__))
|
13
13
|
|
14
|
+
class_option :typescript,
|
15
|
+
type: :boolean,
|
16
|
+
default: false,
|
17
|
+
desc: "Generate TypeScript files"
|
18
|
+
|
14
19
|
def copy_base_files
|
15
20
|
base_js_path = "base/base"
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
|
22
|
+
# Determine which component files to copy based on TypeScript option
|
23
|
+
component_files = [
|
24
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
|
25
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
|
26
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
|
27
|
+
]
|
28
|
+
|
29
|
+
component_files.each do |file|
|
30
|
+
copy_file("#{base_js_path}/#{file}", file)
|
31
|
+
end
|
20
32
|
end
|
21
33
|
|
22
34
|
def create_appropriate_templates
|
@@ -1,13 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require_relative "generator_helper"
|
5
|
+
require_relative "generator_messages"
|
4
6
|
|
5
7
|
module ReactOnRails
|
6
8
|
module Generators
|
7
9
|
class ReactWithReduxGenerator < Rails::Generators::Base
|
10
|
+
include GeneratorHelper
|
11
|
+
|
8
12
|
Rails::Generators.hide_namespace(namespace)
|
9
13
|
source_root(File.expand_path("templates", __dir__))
|
10
14
|
|
15
|
+
class_option :typescript,
|
16
|
+
type: :boolean,
|
17
|
+
default: false,
|
18
|
+
desc: "Generate TypeScript files",
|
19
|
+
aliases: "-T"
|
20
|
+
|
11
21
|
def create_redux_directories
|
12
22
|
# Create auto-registration directory structure for Redux
|
13
23
|
empty_directory("app/javascript/src/HelloWorldApp/ror_components")
|
@@ -19,17 +29,18 @@ module ReactOnRails
|
|
19
29
|
|
20
30
|
def copy_base_files
|
21
31
|
base_js_path = "redux/base"
|
32
|
+
ext = component_extension(options)
|
22
33
|
|
23
34
|
# Copy Redux-connected component to auto-registration structure
|
24
|
-
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client
|
25
|
-
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client
|
26
|
-
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server
|
27
|
-
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server
|
35
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.#{ext}",
|
36
|
+
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
|
37
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",
|
38
|
+
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.#{ext}")
|
28
39
|
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
|
29
40
|
"app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
|
30
41
|
|
31
42
|
# Update import paths in client component
|
32
|
-
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client
|
43
|
+
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
|
33
44
|
gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
|
34
45
|
gsub_file(ror_client_file, "../containers/HelloWorldContainer",
|
35
46
|
"../containers/HelloWorldContainer")
|
@@ -37,12 +48,15 @@ module ReactOnRails
|
|
37
48
|
|
38
49
|
def copy_base_redux_files
|
39
50
|
base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
redux_extension = options.typescript? ? "ts" : "js"
|
52
|
+
|
53
|
+
# Copy Redux infrastructure files with appropriate extension
|
54
|
+
%W[actions/helloWorldActionCreators.#{redux_extension}
|
55
|
+
containers/HelloWorldContainer.#{redux_extension}
|
56
|
+
constants/helloWorldConstants.#{redux_extension}
|
57
|
+
reducers/helloWorldReducer.#{redux_extension}
|
58
|
+
store/helloWorldStore.#{redux_extension}
|
59
|
+
components/HelloWorld.#{component_extension(options)}].each do |file|
|
46
60
|
copy_file("#{base_hello_world_path}/#{file}",
|
47
61
|
"app/javascript/src/HelloWorldApp/#{file}")
|
48
62
|
end
|
@@ -60,15 +74,70 @@ module ReactOnRails
|
|
60
74
|
end
|
61
75
|
|
62
76
|
def add_redux_npm_dependencies
|
63
|
-
|
77
|
+
# Add Redux dependencies as regular dependencies
|
78
|
+
regular_packages = %w[redux react-redux]
|
79
|
+
|
80
|
+
# Try using GeneratorHelper first (package manager agnostic)
|
81
|
+
success = add_npm_dependencies(regular_packages)
|
82
|
+
|
83
|
+
# Fallback to package manager detection if GeneratorHelper fails
|
84
|
+
return if success
|
85
|
+
|
86
|
+
package_manager = GeneratorMessages.detect_package_manager
|
87
|
+
return unless package_manager
|
88
|
+
|
89
|
+
install_packages_with_fallback(regular_packages, dev: false, package_manager: package_manager)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def install_packages_with_fallback(packages, dev:, package_manager:)
|
95
|
+
install_args = build_install_args(package_manager, dev, packages)
|
96
|
+
|
97
|
+
success = system(*install_args)
|
98
|
+
return if success
|
99
|
+
|
100
|
+
install_command = install_args.join(" ")
|
101
|
+
warning = <<~MSG.strip
|
102
|
+
⚠️ Failed to install Redux dependencies automatically.
|
103
|
+
|
104
|
+
Please run manually:
|
105
|
+
#{install_command}
|
106
|
+
MSG
|
107
|
+
GeneratorMessages.add_warning(warning)
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_install_args(package_manager, dev, packages)
|
111
|
+
# Security: Validate package manager to prevent command injection
|
112
|
+
allowed_package_managers = %w[npm yarn pnpm bun].freeze
|
113
|
+
unless allowed_package_managers.include?(package_manager)
|
114
|
+
raise ArgumentError, "Invalid package manager: #{package_manager}"
|
115
|
+
end
|
116
|
+
|
117
|
+
base_commands = {
|
118
|
+
"npm" => %w[npm install],
|
119
|
+
"yarn" => %w[yarn add],
|
120
|
+
"pnpm" => %w[pnpm add],
|
121
|
+
"bun" => %w[bun add]
|
122
|
+
}
|
123
|
+
|
124
|
+
base_args = base_commands[package_manager].dup
|
125
|
+
base_args << dev_flag_for(package_manager) if dev
|
126
|
+
base_args + packages
|
127
|
+
end
|
128
|
+
|
129
|
+
def dev_flag_for(package_manager)
|
130
|
+
case package_manager
|
131
|
+
when "npm", "pnpm" then "--save-dev"
|
132
|
+
when "yarn", "bun" then "--dev"
|
133
|
+
end
|
64
134
|
end
|
65
135
|
|
66
136
|
def add_redux_specific_messages
|
67
137
|
# Override the generic messages with Redux-specific instructions
|
68
|
-
require_relative "generator_messages"
|
69
138
|
GeneratorMessages.output.clear
|
70
139
|
GeneratorMessages.add_info(
|
71
|
-
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp")
|
140
|
+
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp", route: "hello_world")
|
72
141
|
)
|
73
142
|
end
|
74
143
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import * as style from './HelloWorld.module.css';
|
3
|
+
|
4
|
+
interface HelloWorldProps {
|
5
|
+
name: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
const HelloWorld: React.FC<HelloWorldProps> = (props) => {
|
9
|
+
const [name, setName] = useState(props.name);
|
10
|
+
|
11
|
+
return (
|
12
|
+
<div>
|
13
|
+
<h3>Hello, {name}!</h3>
|
14
|
+
<hr />
|
15
|
+
<form>
|
16
|
+
<label className={style.bright} htmlFor="name">
|
17
|
+
Say hello to:
|
18
|
+
<input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
19
|
+
</label>
|
20
|
+
</form>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
};
|
24
|
+
|
25
|
+
export default HelloWorld;
|
@@ -18,29 +18,17 @@
|
|
18
18
|
# 3. Extend ReactOnRails::Dev classes in your Rails app for advanced customization
|
19
19
|
# 4. Use classes directly: ReactOnRails::Dev::ServerManager.start(:development, "Custom.procfile")
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
21
|
+
require "bundler/setup"
|
22
|
+
require "react_on_rails/dev"
|
23
|
+
|
24
|
+
# Default route configuration
|
25
|
+
# This is set by the ReactOnRails installer to point to your generated component.
|
26
|
+
# Change this to your preferred default route, or pass --route=<route> to override.
|
27
|
+
DEFAULT_ROUTE = "hello_world"
|
29
28
|
|
30
29
|
# Main execution
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
when "kill"
|
37
|
-
ReactOnRails::Dev::ServerManager.kill_processes
|
38
|
-
when "help", "--help", "-h"
|
39
|
-
ReactOnRails::Dev::ServerManager.show_help
|
40
|
-
when "hmr", nil
|
41
|
-
ReactOnRails::Dev::ServerManager.start(:development, "Procfile.dev")
|
42
|
-
else
|
43
|
-
puts "Unknown argument: #{ARGV[0]}"
|
44
|
-
puts "Run 'bin/dev help' for usage information"
|
45
|
-
exit 1
|
46
|
-
end
|
30
|
+
# Add the default route to ARGV if no --route option is provided
|
31
|
+
argv_with_defaults = ARGV.dup
|
32
|
+
argv_with_defaults.push("--route", DEFAULT_ROUTE) unless argv_with_defaults.any? { |arg| arg.start_with?("--route") }
|
33
|
+
|
34
|
+
ReactOnRails::Dev::ServerManager.run_from_command_line(argv_with_defaults)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
2
|
+
|
3
|
+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
|
4
|
+
|
5
|
+
// Action interface
|
6
|
+
export interface UpdateNameAction {
|
7
|
+
type: typeof HELLO_WORLD_NAME_UPDATE;
|
8
|
+
text: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
// Union type for all actions
|
12
|
+
export type HelloWorldAction = UpdateNameAction;
|
13
|
+
|
14
|
+
// Action creator with proper TypeScript typing
|
15
|
+
export const updateName = (text: string): UpdateNameAction => ({
|
16
|
+
type: HELLO_WORLD_NAME_UPDATE,
|
17
|
+
text,
|
18
|
+
});
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import * as style from './HelloWorld.module.css';
|
3
|
+
import type { PropsFromRedux } from '../containers/HelloWorldContainer';
|
4
|
+
|
5
|
+
// Component props are inferred from Redux container
|
6
|
+
type HelloWorldProps = PropsFromRedux;
|
7
|
+
|
8
|
+
const HelloWorld: React.FC<HelloWorldProps> = ({ name, updateName }) => (
|
9
|
+
<div>
|
10
|
+
<h3>
|
11
|
+
Hello,
|
12
|
+
{name}!
|
13
|
+
</h3>
|
14
|
+
<hr />
|
15
|
+
<form>
|
16
|
+
<label className={style.bright} htmlFor="name">
|
17
|
+
Say hello to:
|
18
|
+
<input id="name" type="text" value={name} onChange={(e) => updateName(e.target.value)} />
|
19
|
+
</label>
|
20
|
+
</form>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
|
24
|
+
export default HelloWorld;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// Simple example of a React "smart" component
|
2
|
+
|
3
|
+
import { connect, ConnectedProps } from 'react-redux';
|
4
|
+
import HelloWorld from '../components/HelloWorld';
|
5
|
+
import * as actions from '../actions/helloWorldActionCreators';
|
6
|
+
import type { HelloWorldState } from '../reducers/helloWorldReducer';
|
7
|
+
|
8
|
+
// Which part of the Redux global state does our component want to receive as props?
|
9
|
+
const mapStateToProps = (state: HelloWorldState) => ({ name: state.name });
|
10
|
+
|
11
|
+
// Create the connector
|
12
|
+
const connector = connect(mapStateToProps, actions);
|
13
|
+
|
14
|
+
// Infer the props from Redux state and actions
|
15
|
+
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
16
|
+
|
17
|
+
// Don't forget to actually use connect!
|
18
|
+
// Note that we don't export HelloWorld, but the redux "connected" version of it.
|
19
|
+
// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
|
20
|
+
export default connector(HelloWorld);
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { combineReducers } from 'redux';
|
2
|
+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
|
3
|
+
import { HelloWorldAction } from '../actions/helloWorldActionCreators';
|
4
|
+
|
5
|
+
// State interface
|
6
|
+
export interface HelloWorldState {
|
7
|
+
name: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Individual reducer with TypeScript types
|
11
|
+
const name = (state: string = '', action: HelloWorldAction): string => {
|
12
|
+
switch (action.type) {
|
13
|
+
case HELLO_WORLD_NAME_UPDATE:
|
14
|
+
return action.text;
|
15
|
+
default:
|
16
|
+
return state;
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
const helloWorldReducer = combineReducers<HelloWorldState>({ name });
|
21
|
+
|
22
|
+
export default helloWorldReducer;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { useMemo, type FC } from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
|
4
|
+
import configureStore, { type RailsProps } from '../store/helloWorldStore';
|
5
|
+
import HelloWorldContainer from '../containers/HelloWorldContainer';
|
6
|
+
|
7
|
+
// Props interface matches what Rails will pass from the controller
|
8
|
+
interface HelloWorldAppProps extends RailsProps {}
|
9
|
+
|
10
|
+
// See documentation for https://github.com/reactjs/react-redux.
|
11
|
+
// This is how you get props from the Rails view into the redux store.
|
12
|
+
// This code here binds your smart component to the redux store.
|
13
|
+
const HelloWorldApp: FC<HelloWorldAppProps> = (props) => {
|
14
|
+
const store = useMemo(() => configureStore(props), [props]);
|
15
|
+
|
16
|
+
return (
|
17
|
+
<Provider store={store}>
|
18
|
+
<HelloWorldContainer />
|
19
|
+
</Provider>
|
20
|
+
);
|
21
|
+
};
|
22
|
+
|
23
|
+
export default HelloWorldApp;
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { createStore } from 'redux';
|
2
|
+
import type { Store, PreloadedState } from 'redux';
|
3
|
+
import helloWorldReducer from '../reducers/helloWorldReducer';
|
4
|
+
import type { HelloWorldState } from '../reducers/helloWorldReducer';
|
5
|
+
|
6
|
+
// Rails props interface - customize based on your Rails controller
|
7
|
+
export interface RailsProps {
|
8
|
+
name: string;
|
9
|
+
[key: string]: any; // Allow additional props from Rails
|
10
|
+
}
|
11
|
+
|
12
|
+
// Store type
|
13
|
+
export type HelloWorldStore = Store<HelloWorldState>;
|
14
|
+
|
15
|
+
const configureStore = (railsProps: RailsProps): HelloWorldStore =>
|
16
|
+
createStore(helloWorldReducer, railsProps as PreloadedState<HelloWorldState>);
|
17
|
+
|
18
|
+
export default configureStore;
|
@@ -175,7 +175,7 @@ module ReactOnRails
|
|
175
175
|
end
|
176
176
|
|
177
177
|
msg = <<~MSG
|
178
|
-
ReactOnRails: Your current version of
|
178
|
+
ReactOnRails: Your current version of shakapacker \
|
179
179
|
does not support async script loading, which may cause performance issues. Please either:
|
180
180
|
1. Use :sync or :defer loading strategy instead of :async
|
181
181
|
2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
|
@@ -284,8 +284,10 @@ module ReactOnRails
|
|
284
284
|
if ReactOnRails::PackerUtils.using_packer?
|
285
285
|
packer_public_output_path = ReactOnRails::PackerUtils.packer_public_output_path
|
286
286
|
# rubocop:disable Layout/LineLength
|
287
|
+
packer_name = ReactOnRails::PackerUtils.packer_type&.upcase_first
|
288
|
+
|
287
289
|
Rails.logger.warn "Error configuring config/initializers/react_on_rails. Define neither the generated_assets_dirs nor " \
|
288
|
-
"the generated_assets_dir when using #{
|
290
|
+
"the generated_assets_dir when using #{packer_name}. This is defined by " \
|
289
291
|
"public_output_path specified in #{ReactOnRails::PackerUtils.packer_type}.yml = #{packer_public_output_path}."
|
290
292
|
# rubocop:enable Layout/LineLength
|
291
293
|
return
|
@@ -331,15 +333,17 @@ module ReactOnRails
|
|
331
333
|
end
|
332
334
|
|
333
335
|
def compile_command_conflict_message
|
336
|
+
packer_name = ReactOnRails::PackerUtils.packer_type.upcase_first
|
337
|
+
packer_type = ReactOnRails::PackerUtils.packer_type
|
334
338
|
<<~MSG
|
335
339
|
|
336
|
-
React on Rails and #{
|
340
|
+
React on Rails and #{packer_name} error in configuration!
|
337
341
|
In order to use config/react_on_rails.rb config.build_production_command,
|
338
|
-
you must edit config/#{
|
339
|
-
'#{
|
342
|
+
you must edit config/#{packer_type}.yml to include this value in the default configuration:
|
343
|
+
'#{packer_type}_precompile: false'
|
340
344
|
|
341
345
|
Alternatively, remove the config/react_on_rails.rb config.build_production_command and the
|
342
|
-
default bin/#{
|
346
|
+
default bin/#{packer_type} script will be used for assets:precompile.
|
343
347
|
|
344
348
|
MSG
|
345
349
|
end
|