Class: ReactOnRails::Generators::BaseGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Includes:
GeneratorHelper, JsDependencyManager, ShakapackerPrecompileHookHelper
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::TAILWIND_DEPENDENCIES, JsDependencyManager::TYPESCRIPT_DEPENDENCIES

Instance Method Summary collapse

Methods included from JsDependencyManager

#add_css_dependencies, #add_dev_dependencies, #add_js_dependencies, #add_package, #add_packages, #add_react_dependencies, #add_react_on_rails_package, #add_rspack_dependencies, #add_typescript_dependencies, #install_js_dependencies, #setup_js_dependencies

Methods included from GeneratorHelper

#absolute_generator_path, #absolute_path_relative_to_destination, #add_documentation_reference, #add_npm_dependencies, #bundler_flag_given?, #component_extension, #configured_shakapacker_relative_path, #destination_config_path, #detect_react_version, #example_component_source_directory, #example_component_source_path, #explicit_bundler_choice, #extract_declared_layout_name, #gem_in_lockfile?, #html_comment_ranges, #inherited_application_layout_name, #javascript_relative_import_path, #layout_destination_path, #layout_file_links_tailwind_pack?, #layout_links_tailwind_pack?, #package_json, #path_inside_or_equal?, #print_generator_messages, #pro_gem_installed?, #range_overlaps_any?, #relative_tailwind_stylesheet_import_path, #resolve_server_client_or_both_path, #root_route_present?, #rsc_plugin_class_name, #rsc_plugin_import_path, #safe_generator_destination_path, #shakapacker_entrypoint_path, #shakapacker_path_config_value, #shakapacker_source_entry_path, #shakapacker_source_path, #shakapacker_stylesheet_path, #shakapacker_version_9_or_higher?, #stylesheet_dir_relative_path, #tailwind_css_source_directives, #tailwind_css_string, #tailwind_import_statement, #tailwind_pack_filename, #tailwind_pack_name, #tailwind_pack_path, #tailwind_source_statement, #tailwind_stylesheet_path, #unsafe_generator_destination_path?, #use_pro?, #use_rsc?, #use_tailwind?, #using_rspack?, #using_swc?

Instance Method Details

#add_base_gems_to_gemfileObject



333
334
335
# File 'lib/generators/react_on_rails/base_generator.rb', line 333

def add_base_gems_to_gemfile
  run "bundle"
end

#add_hello_world_routeObject



201
202
203
204
205
206
# File 'lib/generators/react_on_rails/base_generator.rb', line 201

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



156
157
158
159
160
161
162
163
164
165
166
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
# File 'lib/generators/react_on_rails/base_generator.rb', line 156

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



357
358
359
360
361
362
363
# File 'lib/generators/react_on_rails/base_generator.rb', line 357

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



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/generators/react_on_rails/base_generator.rb', line 250

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]

  # 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) }
  copy_react_on_rails_default_layout(base_path)
  warn_existing_hello_world_tailwind_layout
  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



286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/generators/react_on_rails/base_generator.rb', line 286

def copy_js_bundle_files
  base_path = "base/base/"
  copy_file("#{base_path}app/javascript/packs/server-bundle.js",
            shakapacker_entrypoint_path("server-bundle.js"))

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

  copy_file("#{base_path}app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css",
            File.join(example_component_source_directory("HelloWorld"),
                      "ror_components/HelloWorld.module.css"))
end

#copy_packer_configObject



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/generators/react_on_rails/base_generator.rb', line 208

def copy_packer_config
  # Rails generator actions run in method definition order.
  # Keep this before actions that call shakapacker_source_path or
  # shakapacker_source_entry_path; those helpers memoize on first read.
  if instance_variable_defined?(:@shakapacker_source_path) ||
     instance_variable_defined?(:@shakapacker_source_entry_path)
    raise Thor::Error, "copy_packer_config must run before path-dependent generator actions"
  end

  base_path = "base/base/"
  config = "config/shakapacker.yml"
  use_rspack = using_rspack?

  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 use_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_tailwind_filesObject



323
324
325
326
327
328
329
330
331
# File 'lib/generators/react_on_rails/base_generator.rb', line 323

def copy_tailwind_files
  return unless use_tailwind?

  base_path = "base/tailwind/"
  template("#{base_path}app/javascript/stylesheets/application.css.tt",
           tailwind_stylesheet_path)
  template("#{base_path}app/javascript/packs/react_on_rails_tailwind.js.tt",
           tailwind_pack_path)
end

#copy_webpack_configObject



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/generators/react_on_rails/base_generator.rb', line 299

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



243
244
245
246
247
248
# File 'lib/generators/react_on_rails/base_generator.rb', line 243

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

  empty_directory(File.join(example_component_source_directory("HelloWorld"), "ror_components"))
end

#update_gitignore_for_auto_registrationObject



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/generators/react_on_rails/base_generator.rb', line 337

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