Class: ReactOnRails::Generators::BaseGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Includes:
GeneratorHelper, JsDependencyManager
Defined in:
lib/generators/react_on_rails/base_generator.rb

Constant Summary collapse

CONFIGURE_RSPEC_TO_COMPILE_ASSETS =
<<~STR
  # Ensure that if we are running js tests, we are using latest webpack assets
  # This will use the defaults of :js and :server_rendering meta tags
  # Requires config.build_test_command in config/initializers/react_on_rails.rb.
  # This is the default setup for React on Rails generated apps.
  ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
STR
CONFIGURE_MINITEST_TO_COMPILE_ASSETS =
<<~STR
  # Ensure that tests run against fresh webpack assets.
  ActiveSupport::TestCase.setup do
    ReactOnRails::TestHelper.ensure_assets_compiled
  end
STR

Constants included from JsDependencyManager

JsDependencyManager::BABEL_REACT_DEPENDENCIES, JsDependencyManager::CSS_DEPENDENCIES, JsDependencyManager::DEV_DEPENDENCIES, JsDependencyManager::PRO_DEPENDENCIES, JsDependencyManager::REACT_DEPENDENCIES, JsDependencyManager::RSC_DEPENDENCIES, JsDependencyManager::RSC_PACKAGE_VERSION_PIN, JsDependencyManager::RSC_REACT_VERSION_RANGE, JsDependencyManager::RSPACK_DEPENDENCIES, JsDependencyManager::RSPACK_DEV_DEPENDENCIES, JsDependencyManager::SWC_DEPENDENCIES, JsDependencyManager::TYPESCRIPT_DEPENDENCIES

Instance Method Summary collapse

Methods included from GeneratorHelper

#add_documentation_reference, #add_npm_dependencies, #component_extension, #copy_file_and_missing_parent_directories, #dest_dir_exists?, #dest_file_exists?, #destination_config_path, #detect_react_version, #empty_directory_with_keep_file, #gem_in_lockfile?, #keep_file, #mark_pro_gem_installed!, #package_json, #print_generator_messages, #pro_gem_installed?, #resolve_server_client_or_both_path, #root_route_present?, #setup_file_error, #shakapacker_version_9_or_higher?, #symlink_dest_file_to_dest_file, #use_pro?, #use_rsc?, #use_rsc_pro_mode?, #using_rspack?, #using_swc?

Instance Method Details

#add_base_gems_to_gemfileObject



267
268
269
# File 'lib/generators/react_on_rails/base_generator.rb', line 267

def add_base_gems_to_gemfile
  run "bundle"
end

#add_hello_world_routeObject



153
154
155
156
157
158
# File 'lib/generators/react_on_rails/base_generator.rb', line 153

def add_hello_world_route
  # RSC uses HelloServer instead of HelloWorld, but Redux still needs hello_world route
  return if use_rsc? && !options.redux?

  route "get 'hello_world', to: 'hello_world#index'"
end

#add_root_routeObject



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/generators/react_on_rails/base_generator.rb', line 108

def add_root_route
  return unless options.new_app?
  # add_root_route normally runs before copy_base_files as a generator action.
  # Guard against accidental double invocation (for example, if future
  # refactors trigger lazy initialization in generate_new_app_home_page?).
  return if defined?(@new_app_root_route_added)

  @new_app_root_route_added = false

  if preexisting_root_route?
    say_status :skip, "Root route already exists; keeping existing root route", :yellow
    return
  end

  routes_path = "config/routes.rb"
  routes_full_path = File.join(destination_root, routes_path)

  unless File.file?(routes_full_path)
    say_status :warn, "Could not inject root route; config/routes.rb was not found", :yellow
    return
  end

  # Support both LF and CRLF route files so new-app onboarding works on Windows checkouts too.
  routes_draw_declaration = /^\s*Rails\.application\.routes\.draw do\r?\n/
  unless File.read(routes_full_path).match?(routes_draw_declaration)
    say_status :warn, "Could not inject root route; config/routes.rb format was unexpected", :yellow
    return
  end

  inject_into_file routes_path,
                   %(  root to: "home#index"\n),
                   after: routes_draw_declaration
  if options[:pretend]
    @new_app_root_route_added = true
    return
  end

  if File.read(routes_full_path).include?('root to: "home#index"')
    @new_app_root_route_added = true
    return
  end

  say_status :warn, "Could not inject root route; config/routes.rb format was unexpected", :yellow
end

#append_to_spec_rails_helperObject



291
292
293
294
295
296
297
# File 'lib/generators/react_on_rails/base_generator.rb', line 291

def append_to_spec_rails_helper
  rspec_helper = preferred_rspec_helper_file
  add_configure_rspec_to_compile_assets(rspec_helper) if rspec_helper

  test_helper = File.join(destination_root, "test/test_helper.rb")
  add_configure_minitest_to_compile_assets(test_helper) if File.exist?(test_helper)
end

#copy_base_filesObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/generators/react_on_rails/base_generator.rb', line 167

def copy_base_files
  ensure_new_app_root_route_initialized

  base_path = "base/base/"
  base_files = %w[Procfile.dev
                  Procfile.dev-static-assets
                  Procfile.dev-prod-assets
                  .dev-services.yml.example
                  .env.example
                  bin/shakapacker-precompile-hook]

  # react_on_rails_default layout provides empty pack tags so React on Rails can
  # inject generated packs without requiring a hardcoded application.js pack entry.
  base_files << "app/views/layouts/react_on_rails_default.html.erb"

  # HelloWorld controller only when not using RSC (RSC uses HelloServer)
  # Exception: Redux still needs the HelloWorld controller even with RSC
  base_files << "app/controllers/hello_world_controller.rb" unless use_rsc? && !options.redux?
  base_files << "app/controllers/home_controller.rb" if generate_new_app_home_page?
  base_templates = %w[config/initializers/react_on_rails.rb]
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
  base_templates.each do |file|
    template("#{base_path}/#{file}.tt", file)
  end

  if generate_new_app_home_page?
    empty_directory("app/views/home")
    template("#{base_path}/app/views/home/index.html.erb.tt", "app/views/home/index.html.erb", home_page_config)
  end

  # Make the hook script executable (copy_file guarantees it exists)
  if options[:pretend]
    say_status :pretend, "Skipping chmod on shakapacker-precompile-hook in --pretend mode", :yellow
  else
    File.chmod(0o755, File.join(destination_root, "bin/shakapacker-precompile-hook"))
  end
end

#copy_js_bundle_filesObject



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/generators/react_on_rails/base_generator.rb', line 205

def copy_js_bundle_files
  base_path = "base/base/"
  base_files = %w[app/javascript/packs/server-bundle.js]

  # Skip HelloWorld CSS for Redux (uses HelloWorldApp) or RSC (uses HelloServer)
  unless options.redux? || use_rsc?
    base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
  end

  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
end

#copy_packer_configObject



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/generators/react_on_rails/base_generator.rb', line 241

def copy_packer_config
  base_path = "base/base/"
  config = "config/shakapacker.yml"

  if options.shakapacker_just_installed?
    say "Replacing Shakapacker default config with React on Rails version"
    # Shakapacker's installer just created this file from scratch (no pre-existing config).
    # Safe to overwrite silently with RoR's version-aware template (e.g., private_output_path).
    template("#{base_path}#{config}.tt", config, force: true)
  else
    say "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
    # Thor handles the conflict: prompts user interactively, or respects --force/--skip flags.
    template("#{base_path}#{config}.tt", config)
  end

  # Configure bundler-specific settings
  configure_rspack_in_shakapacker if using_rspack?

  # Always ensure precompile_hook is configured (Shakapacker 9.0+ only)
  configure_precompile_hook_in_shakapacker

  # For SSR bundles, configure Shakapacker private_output_path (9.0+ only)
  # This keeps Shakapacker and React on Rails server bundle paths in sync.
  configure_private_output_path_in_shakapacker
end

#copy_webpack_configObject



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/generators/react_on_rails/base_generator.rb', line 217

def copy_webpack_config
  # Cleanup must run before writing new webpack/rspack configs so we only
  # evaluate pre-existing stale entries, never files generated in this run.
  cleanup_stale_webpack_config_dir_for_rspack

  say "Adding #{using_rspack? ? 'Rspack' : 'Webpack'} config"
  base_path = "base/base"
  base_files = %w[babel.config.js
                  config/webpack/clientWebpackConfig.js
                  config/webpack/commonWebpackConfig.js
                  config/webpack/test.js
                  config/webpack/development.js
                  config/webpack/production.js
                  config/webpack/serverWebpackConfig.js
                  config/webpack/ServerClientOrBoth.js]
  config = { message: DOCS_REFERENCE_MESSAGE }
  base_files.each do |file|
    template("#{base_path}/#{file}.tt", destination_config_path(file), config)
  end

  # Handle webpack.config.js separately with smart replacement
  copy_webpack_main_config(base_path, config)
end

#create_react_directoriesObject



160
161
162
163
164
165
# File 'lib/generators/react_on_rails/base_generator.rb', line 160

def create_react_directories
  # Skip HelloWorld directory for Redux (uses HelloWorldApp) or RSC (uses HelloServer)
  return if options.redux? || use_rsc?

  empty_directory("app/javascript/src/HelloWorld/ror_components")
end

#update_gitignore_for_auto_registrationObject



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/generators/react_on_rails/base_generator.rb', line 271

def update_gitignore_for_auto_registration
  gitignore_path = File.join(destination_root, ".gitignore")
  return unless File.exist?(gitignore_path)

  gitignore_content = File.read(gitignore_path)

  additions = []
  additions << "**/generated/**" unless gitignore_content.include?("**/generated/**")
  additions << "ssr-generated" unless gitignore_content.include?("ssr-generated")
  additions << ".env" unless gitignore_content.match?(/^\.env$/)

  return if additions.empty?

  append_to_file ".gitignore" do
    lines = ["\n# React on Rails (generated and local files)"]
    lines.concat(additions)
    "#{lines.join("\n")}\n"
  end
end