diff --git a/lib/generators/rbui/base_generator.rb b/lib/generators/rbui/base_generator.rb index 1d2a63b..40e313c 100644 --- a/lib/generators/rbui/base_generator.rb +++ b/lib/generators/rbui/base_generator.rb @@ -7,8 +7,10 @@ class BaseGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Bas source_root File.join(__dir__, "templates") - def copy_templates - template "base_store_initializer.rb", "config/initializers/rbui.rb" + private + + def using_importmap? + File.exist?(Rails.root.join("config/importmap.rb")) && File.exist?(Rails.root.join("bin/importmap")) end end end diff --git a/lib/generators/rbui/component_generator.rb b/lib/generators/rbui/component_generator.rb index 8537302..ae81404 100644 --- a/lib/generators/rbui/component_generator.rb +++ b/lib/generators/rbui/component_generator.rb @@ -1,6 +1,6 @@ module RBUI module Generators - class ComponentGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Base : Object + class ComponentGenerator < RBUI::Generators::BaseGenerator namespace "rbui:component" source_root File.expand_path("../../..", __dir__) @@ -34,48 +34,83 @@ def copy_component_files def update_index_file index_path = File.join(destination_root, "app/components/rbui/index.js") - content = File.read(index_path) + rbui_index_content = File.read(index_path) - add_controller_registration(content) + updated_rbui_index_content = add_controller_registration(rbui_index_content) - File.write(index_path, content) + File.write(index_path, updated_rbui_index_content) end - def add_controller_registration(content) + def add_controller_registration(rbui_index_content) + valid_controllers = get_valid_controllers + + rbui_index_content = update_imports(rbui_index_content, valid_controllers) + update_registrations(rbui_index_content, valid_controllers) + # Uncomment the following line if you want to update exports + # rbui_index_content = update_exports(rbui_index_content, valid_controllers) + end + + def get_valid_controllers all_js_controllers = Dir.glob(File.join(destination_path, "**", "*_controller.js")) - # Collect all valid controller information - valid_controllers = all_js_controllers.map do |controller_file| - relative_path = Pathname.new(controller_file).relative_path_from(Pathname.new(destination_path)) - file_name = relative_path.basename(".js").to_s - component_name = file_name.sub(/_controller$/, "") - new_controller = "#{component_name.camelize}Controller" - new_path = "./#{relative_path.dirname}/#{file_name}" - registration_name = "rbui--#{component_name.dasherize}" - - { - import: "import #{new_controller} from \"#{new_path}\";", - registration: "application.register(\"#{registration_name}\", #{new_controller});", - export: "export { default as #{new_controller} } from \"#{new_path}\";" - } + all_js_controllers.map do |controller_file| + controller_info(controller_file) end + end + + def controller_info(controller_file) + # Get the relative path from the destination path to the controller file + relative_path = Pathname.new(controller_file).relative_path_from(Pathname.new(destination_path)) + + # Extract the file name without the .js extension + file_name = relative_path.basename(".js").to_s + + # Remove '_controller' suffix to get the component name + component_name = file_name.sub(/_controller$/, "") + + # Create the new controller name by camelizing the component name and adding 'Controller' + new_controller = "#{component_name.camelize}Controller" + + # Build the new import path + new_import_path = new_import_path("./#{relative_path.dirname}/#{file_name}") - # Update imports - imports = valid_controllers.map { |c| c[:import] }.sort - import_block = imports.join("\n") - content.sub!(/\/\/ Import all controller files.*?(?=\n\n)/m, "// Import all controller files\n#{import_block}") + # Create the registration name by dasherizing the component name and prefixing with 'rbui--' + registration_name = "rbui--#{component_name.dasherize}" - # Update registrations - registrations = valid_controllers.map { |c| c[:registration] }.sort - registration_block = registrations.join("\n") - content.sub!(/\/\/ Register all controllers.*?(?=\n\n)/m, "// Register all controllers\n#{registration_block}") + # Return a hash with import, registration, and export statements + { + # Import statement for importmaps + import: "import #{new_controller} from \"#{new_import_path}\";", - # Update exports - # exports = valid_controllers.map { |c| c[:export] }.sort - # export_block = exports.join("\n") - # content.sub!(/\/\/ Export all controllers.*?(?=\n\n)/m, "// Export all controllers so user of npm package can lazy load controllers\n#{export_block}") + # Registration statement for the Stimulus controller + registration: "application.register(\"#{registration_name}\", #{new_controller});", + + # Export statement for the controller + export: "export { default as #{new_controller} } from \"#{new_import_path}\";" + } + end + + def new_import_path(relative_path) + if using_importmap? + "rbui/#{relative_path.sub(/^\.\//, "")}" + else + relative_path + end + end + + def update_imports(content, controllers) + imports = controllers.map { |c| c[:import] }.sort.join("\n") + content.sub(/\/\/ Import all controller files.*?(?=\n\n)/m, "// Import all controller files\n#{imports}") + end + + def update_registrations(content, controllers) + registrations = controllers.map { |c| c[:registration] }.sort.join("\n") + content.sub(/\/\/ Register all controllers.*?(?=\n\n)/m, "// Register all controllers\n#{registrations}") + end - content + def update_exports(content, controllers) + exports = controllers.map { |c| c[:export] }.sort.join("\n") + content.sub(/\/\/ Export all controllers.*?(?=\n\n)/m, "// Export all controllers so user of npm package can lazy load controllers\n#{exports}") end def component diff --git a/lib/generators/rbui/install/install_generator.rb b/lib/generators/rbui/install/install_generator.rb index 5a98aa4..c054ece 100644 --- a/lib/generators/rbui/install/install_generator.rb +++ b/lib/generators/rbui/install/install_generator.rb @@ -1,11 +1,20 @@ +require "net/http" + +# TODO: make ejctectec components work without the gem module RBUI module Generators - class InstallGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Base : Object + class InstallGenerator < RBUI::Generators::BaseGenerator namespace "rbui:install" if defined?(Rails::Generators::Base) source_root File.expand_path("templates", __dir__) + def confirm_installation + return if yes?("You need tailwindcss installed. Continue? (y/n)") + say "Installation cancelled.", :red + exit + end + def add_phlex_rails say "Checking for Phlex Rails" if gem_installed?("phlex-rails") @@ -15,14 +24,16 @@ def add_phlex_rails run "bundle add phlex-rails" end - if yes?("Do you want to run the Phlex installer? (y/n)") - say "Run Phlex install" - run "bin/rails generate phlex:install" - end + say "run phlex install" + run "bin/rails generate phlex:install" end def install_stuff - if yes?("Do you want to set up the dev test data? (y/n)") + # make default option no + # extend the yes func to have a default option y/(n) and also allow for enter to accedpt the default + + if ENV["TEST_DATA"] == "true" + say "Do you want to set up the dev test data?" say "Add index controller" run "bin/rails generate controller static index --no-helper --no-assets --no-test-framework --no-jbuilder" @@ -30,6 +41,7 @@ def install_stuff run "bin/rails g phlex:view Static::Index" append_to_file "app/controllers/static_controller.rb", after: " def index" do + # remove view because phlex is removing view "\n render Static::IndexView" end @@ -41,65 +53,82 @@ def install_stuff end end - say "Checking for Tailwind CSS" - if gem_installed?("tailwindcss-rails") - say "Tailwind CSS is already installed", :green + tailwind_config_path = Rails.root.join("config/tailwind.config.js") + if !File.exist?(tailwind_config_path) + say "Tailwind CSS is required for RBUI", :red + end + + say "Add rbui initializer" + template "base_store_initializer.rb", "config/initializers/rbui.rb" + + if using_importmap? + say "Using importmaps, adding tailwind-animate" + run "bin/importmap pin tailwindcss-animate" + + # Remove the default pin + gsub_file "config/importmap.rb", /^pin "tailwindcss-animate".*$\n/, "" - if yes?("Do you want to run the Tailwind installer? (y/n)") - say "Run Tailwind install" - run "./bin/rails tailwindcss:install" + # Add the vendor-specific pin + append_to_file "config/importmap.rb" do + 'pin "tailwindcss-animate", to: "tailwindcss-animate.js", preload: true' + "\n" end - elsif yes?("Do you want us to install Tailwind CSS? (y/n)") - say "Adding Tailwind CSS" - run "./bin/bundle add tailwindcss-rails" - say "Run Tailwind install" - run "./bin/rails tailwindcss:install" + else + say "Not using importmaps, adding tailwind-animate via yarn" + run "yarn add tailwindcss-animate" end - say "Add tailwind animate" - run "yarn add tailwindcss-animate" - + # check if tailwind.config is in config dir or in root or ask to specify a path say "update tailwind.config.js" - template "tailwind.config.js", "config/tailwind.config.js", force: true + template "tailwind.config.js", "config/tailwind.config.js", force: true, assigns: {using_importmap: using_importmap?} say "Add CSS variables" template "application.tailwind.css", "app/assets/stylesheets/application.tailwind.css", force: true end def pin_rbui_js - importmap_binstub = Rails.root.join("bin/importmap") - importmap_config_path = Rails.root.join("config/importmap.rb") stimulus_path = Rails.root.join("app/javascript/application.js") package_name = "rbui-js" - if importmap_binstub.exist? + say "Add RBUI Stimulus controllers" + # run "mkdir -p app/javascript/controllers/rbui-js" + template "index.js", "app/components/rbui/index.js" + + if using_importmap? + gsub_file "app/components/rbui/index.js", /^import { application }.*$/ do + 'import { application } from "controllers/application";' + end + + append_to_file Rails.root.join("config/initializers/assets.rb") do + "Rails.application.config.assets.paths << Rails.root.join(\"app/components\")\n" + end + say "Pin #{package_name}" - append_to_file importmap_config_path do - # %(pin "rbui-js", to: "rbui-js.js"\n) - %(pin #{package_name}, to: "rbui-js.js"\n) + append_to_file Rails.root.join("config/importmap.rb") do + "pin_all_from \"app/components/rbui\", under: \"rbui\"\n" end + + run "bin/importmap pin #{package_name}" + append_to_file stimulus_path, "\nimport \"rbui\";\n" + + fix_import_maps! else say "Add rbui-js package" run "yarn add #{package_name}" - end - if stimulus_path.exist? - say "Add RBUI Stimulus controllers" - template "#{template_dir}/index.js", "#{destination_path}/index.js" unless File.exist?("#{destination_path}/index.js") append_to_file stimulus_path, "\nimport \"../components/rbui\";\n" + run "yarn build" - else - say "Default Stimulus location is missing: app/javascript/controllers/index.js", :red - say " Add to your Stimulus index.js:" - say " import \"#{package_name}\"" end end def include_rbui - say "Add RBUI to your global component layout" - insert_into_file "app/views/application_view.rb", after: "class ApplicationView < ApplicationComponent\n" do - " include RBUI\n" + message = "Include RBUI in your global component layout?\n This allows to call RBUI.Button {\"button\"} / RBUI::Button.new {\"button\"} with Button {\"button\"} (y/n)" + if yes?(message) + say "Add RBUI to your global component layout" + insert_into_file "app/views/application_view.rb", after: "class ApplicationView < ApplicationComponent\n" do + " include RBUI\n" + end end end @@ -119,6 +148,36 @@ def revoke private + def fix_import_maps! + importmap_config_path = Rails.root.join("config/importmap.rb") + + gsub_file importmap_config_path, /^pin "date-fns".*$/ do + 'pin "date-fns", to: "https://ga.jspm.io/npm:date-fns@3.3.1/index.mjs"' + end + run "bin/importmap pin @popperjs/core@2.11.8/+esm --from jsdelivr" + + run "mv vendor/javascript/@popperjs--core--+esm.js vendor/javascript/stupid-popper-lib-2024.js" + + append_to_file importmap_config_path do + 'pin "@popperjs/core", to: "stupid-popper-lib-2024.js"' + end + + uri = URI "https://ga.jspm.io/npm:chart.js@3.9.1/dist/chart.min.js" + request = Net::HTTP::Get.new uri + + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| + http.request(request) + } + + File.write(Rails.root.join("vendor/javascript/chart.js--auto.js"), response.body) if response.is_a?(Net::HTTPSuccess) + + append_to_file Rails.root.join("app/views/layouts/application.html.erb"), before: "" do + "" + end + end + def gem_installed?(name) Gem::Specification.find_all_by_name(name).any? end diff --git a/lib/generators/rbui/templates/index.js.tt b/lib/generators/rbui/install/templates/index.js.tt similarity index 100% rename from lib/generators/rbui/templates/index.js.tt rename to lib/generators/rbui/install/templates/index.js.tt diff --git a/lib/generators/rbui/install/templates/tailwind.config.js.tt b/lib/generators/rbui/install/templates/tailwind.config.js.tt index 9900696..a517a41 100644 --- a/lib/generators/rbui/install/templates/tailwind.config.js.tt +++ b/lib/generators/rbui/install/templates/tailwind.config.js.tt @@ -81,6 +81,10 @@ module.exports = { }, }, plugins: [ + <% if using_importmap? %> + require("../vendor/javascript/tailwindcss-animate"), + <% else %> require("tailwindcss-animate"), + <% end %> ], } diff --git a/lib/rbui.rb b/lib/rbui.rb index fe854de..e700557 100644 --- a/lib/rbui.rb +++ b/lib/rbui.rb @@ -45,6 +45,7 @@ def self.create_namespace_module # If you need to require generators (assuming they're needed) if defined?(Rails::Generators) + require_relative "generators/rbui/base_generator" require_relative "generators/rbui/install/install_generator" require_relative "generators/rbui/component_generator" end diff --git a/lib/rbui/base.rb b/lib/rbui/base.rb index 86d6c48..58f752f 100644 --- a/lib/rbui/base.rb +++ b/lib/rbui/base.rb @@ -4,7 +4,7 @@ module RBUI class Base < Phlex::HTML - TAILWIND_MERGER = ::TailwindMerge::Merger.new.freeze + TAILWIND_MERGER = ::TailwindMerge::Merger.new.freeze unless defined?(TAILWIND_MERGER) attr_reader :attrs