Class: PhlexKit::Generators::ComponentGenerator

Inherits:
Rails::Generators::NamedBase
  • Object
show all
Defined in:
lib/generators/phlex_kit/component/component_generator.rb

Overview

rails g phlex_kit:component button — the shadcn-style "eject": copies a component's folder (.rb parts + co-located .css + _controller.js) out of the gem and into the host app so the team owns and can edit the source. Appends its @import to application.css and wires config/application.rb (once) so the ejected copy fully shadows the gem's — Ruby, CSS, and JS alike.

Constant Summary collapse

COLLAPSE_LINE =

Marker that wire_host_app is idempotent against — presence means an earlier eject already wired config/application.rb.

%(Rails.autoloaders.main.collapse(Rails.root.join("app/components/phlex_kit/*")))

Instance Method Summary collapse

Instance Method Details

#confirmObject



71
72
73
74
75
76
77
78
79
# File 'lib/generators/phlex_kit/component/component_generator.rb', line 71

def confirm
  say_status :phlex_kit, "ejected phlex_kit/#{file_name} — your copy now shadows the gem (Ruby, CSS, JS)", :green
  foreign_behavior_owners.each do |owner|
    say_status :note,
      "#{file_name}'s behavior lives in the #{owner} component's controller " \
      "(still gem-owned) — `rails g phlex_kit:component #{owner}` to own it too",
      :yellow
  end
end

#eject_componentObject



19
20
21
22
23
24
25
# File 'lib/generators/phlex_kit/component/component_generator.rb', line 19

def eject_component
  unless File.directory?(File.join(self.class.source_root, file_name))
    say_status :error, "no PhlexKit component named '#{file_name}'", :red
    return
  end
  directory file_name, "app/components/phlex_kit/#{file_name}"
end

#wire_host_appObject

Make an ejected component fully editable, in one step. Two things are needed and neither is on by default in a host app:

1. Zeitwerk collapse — so app/components/phlex_kit/button/button.rb
 autoloads as PhlexKit::Button (not ::Button::Button). Fixes Ruby.
2. Host app/components ahead of the gem on Propshaft's asset path — so
 the ejected <name>.css and <name>_controller.js resolve to the host
 copy instead of the gem's. Without this the Ruby shadows but the CSS
 and JS silently keep serving the gem's originals.

Injected once (idempotent) right after config.load_defaults, where config.assets is available.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/generators/phlex_kit/component/component_generator.rb', line 47

def wire_host_app
  app_rb = "config/application.rb"
  unless File.exist?(app_rb)
    say_status :skip, "#{app_rb} not found — wire ejected components manually (see docs/05-PROPSHAFT-INSTALL.md)", :yellow
    return
  end
  if File.read(app_rb).include?(COLLAPSE_LINE)
    say_status :identical, "#{app_rb} already wired for ejected components", :blue
    return
  end
  inject_into_file app_rb, after: /^\s*config\.load_defaults.*\n/ do
    # Indent only non-empty lines so the blank separator carries no trailing space.
    <<~RUBY.gsub(/^(?=.)/, "    ")

      # PhlexKit: make ejected components under app/components/phlex_kit/
      # fully editable — collapse so <name>/<name>.rb autoloads as
      # PhlexKit::<Name>, and put the dir ahead of the gem on the asset path
      # so the ejected <name>.css and <name>_controller.js shadow the gem's.
      #{COLLAPSE_LINE}
      config.assets.paths.unshift(Rails.root.join("app/components"))
    RUBY
  end
end

#wire_importObject

Belt-and-braces: the gem manifest's relative import already resolves to the host copy once app/components is ahead on the asset path, but hosts that skipped/trimmed the manifest still need an explicit import.



30
31
32
33
34
35
# File 'lib/generators/phlex_kit/component/component_generator.rb', line 30

def wire_import
  css = "app/assets/stylesheets/application.css"
  line = %(@import url("phlex_kit/#{file_name}/#{file_name}.css");\n)
  return unless File.exist?(css)
  prepend_to_file(css, line) unless File.read(css).include?(line.strip)
end