require "view_component" require "action_cable/engine" require "listen" module Lookbook class Engine < Rails::Engine isolate_namespace Lookbook config.autoload_paths << File.expand_path(root.join("app/components")) initializer "lookbook.viewcomponent.config" do Lookbook.config.preview_paths += config.view_component.preview_paths Lookbook.config.preview_controller ||= config.view_component.preview_controller Lookbook.config.components_path = config.view_component.view_component_path if config.view_component.view_component_path.present? Lookbook.config.listen_paths += Lookbook.config.preview_paths Lookbook.config.listen_paths << Lookbook.config.components_path end initializer "lookbook.parser.tags" do Lookbook::Parser.define_tags(Engine.tags) end initializer "lookbook.assets.serve" do config.app_middleware.use( Rack::Static, urls: ["/lookbook-assets"], root: root.join("public").to_s ) end config.before_configuration do config.lookbook = Lookbook.config end config.after_initialize do @preview_controller = Lookbook.config.preview_controller.constantize @preview_controller.include(Lookbook::PreviewController) parser.after_parse do |registry| Preview.load!(registry.all(:class)) reload_ui end if Gem::Version.new(Rails.version) >= Gem::Version.new("6.1.3.1") # Rails.application.server is only available for newer Rails versions Rails.application.server do init_listeners end else # Fallback for older Rails versions - don't start listeners if running in a rake task. unless prevent_listening? init_listeners end end parser.parse do run_hooks(:after_initialize) end end at_exit do if listeners.any? Lookbook.logger.debug "Stopping listeners" stop_listeners end run_hooks(:before_exit) end class << self def init_listeners config = Lookbook.config return unless config.listen == true listen_paths = PathUtils.normalize_all(config.listen_paths) if listen_paths.any? preview_listener = Listen.to(*listen_paths, only: /\.(#{config.listen_extensions.join("|")})$/, wait_for_delay: 0.5, force_polling: config.listen_use_polling) do |modified, added, removed| parser.parse do run_hooks(:after_change, {modified: modified, added: added, removed: removed}) end end register_listener(preview_listener) end page_paths = PathUtils.normalize_all(config.page_paths) if page_paths.any? page_listener = Listen.to(*page_paths, only: /\.(html.*|md.*)$/, force_polling: config.listen_use_polling) do |modified, added, removed| changes = {modified: modified, added: added, removed: removed} reload_ui run_hooks(:after_change, changes) end register_listener(page_listener) end end def websocket config = Lookbook.config return @websocket unless @websocket.nil? return unless config.auto_refresh == true && config.listen == true && !Rails.env.test? Lookbook.logger.info "Initializing websocket" cable = ActionCable::Server::Configuration.new cable.cable = {adapter: "async"}.with_indifferent_access cable.mount_path = nil cable.connection_class = -> { Lookbook::Connection } cable.logger = Lookbook.logger @websocket ||= if Gem::Version.new(Rails.version) >= Gem::Version.new(6.0) ActionCable::Server::Base.new(config: cable) else ws = ActionCable::Server::Base.new ws.config = cable ws end end def websocket_mount_path "#{mounted_path}/cable".gsub("//", "/") if websocket? end def websocket? websocket.present? end def mounted_path routes.find_script_name({}) end def parser preview_paths = PathUtils.normalize_all(Lookbook.config.preview_paths) @parser ||= Lookbook::Parser.new(preview_paths) end def log_level Lookbook.logger.level end def app_name name = if Gem::Version.new(Rails.version) >= Gem::Version.new("6.1") Rails.application.class.module_parent_name else Rails.application.class.parent_name end name.underscore end def register_listener(listener) listener.start listeners << listener end def listeners @listeners ||= [] end def stop_listeners listeners.each { |listener| listener.stop } end def run_hooks(event_name, *args) hooks.for_event(event_name).each do |hook| hook.call(Lookbook, *args) end end def reload_ui websocket&.broadcast("reload", {}) end def prevent_listening? Rails.env.test? || running_in_rake_task? end def running_in_rake_task? if defined?(Rake) && Rake.respond_to?(:application) File.basename($0) == "rake" || Rake.application.top_level_tasks.any? else false end end def panels @panels ||= PanelStore.init_from_config end def inputs @inputs ||= InputStore.init_from_config end def tags @tags ||= TagStore.init_from_config end def hooks @hooks ||= HookStore.init_from_config end attr_reader :preview_controller end end end