lib/lookbook/engine.rb



require "rails"
require "view_component"
require "action_cable/engine"
require "listen"

module Lookbook
  class << self
    def config
      @config ||= Engine.config.lookbook
    end

    def logger
      @logger ||= Rails.logger
    end

    def version
      Lookbook::VERSION
    end
  end

  class Engine < Rails::Engine
    isolate_namespace Lookbook

    config.lookbook = ActiveSupport::OrderedOptions.new
    config.lookbook.listen_paths ||= []
    config.lookbook.preview_paths ||= []
    config.lookbook.page_paths ||= ["test/components/docs"]

    initializer "view_component.set_configs" do
      options = config.lookbook
      vc_options = config.view_component

      options.project_name ||= options.project_name == false ? nil : options.project_name || "Lookbook"
      options.auto_refresh = true if options.auto_refresh.nil?
      options.sort_examples = false if options.sort_examples.nil?
      options.debug = false unless options.debug == true

      options.preview_paths = options.preview_paths.map(&:to_s)
      options.preview_paths += vc_options.preview_paths

      options.page_paths = options.page_paths.map(&:to_s)
      options.page_controller = "Lookbook::PageController" if options.page_controller.nil?
      options.page_route ||= "pages"
      options.page_options ||= {}.with_indifferent_access

      options.markdown_options = Markdown::DEFAULT_OPTIONS.merge(options.markdown_options || {})

      options.preview_controller = vc_options.preview_controller if options.preview_controller.nil?
      options.preview_srcdoc = false if options.preview_srcdoc.nil?
      options.preview_display_params ||= {}.with_indifferent_access

      options.listen = Rails.env.development? if options.listen.nil?
      options.listen_paths = options.listen_paths.map(&:to_s)
      options.listen_paths += options.preview_paths
      options.listen_paths << (vc_options.view_component_path || Rails.root.join("app/components"))
      options.listen_paths.filter! { |path| Dir.exist? path }

      options.cable_mount_path ||= "/lookbook-cable"
      options.cable_logger ||= Rails.logger

      options.runtime_parsing = !Rails.env.production? if options.runtime_parsing.nil?
      options.parser_registry_path ||= Rails.root.join("tmp/storage/.yardoc")

      options.experimental_features = false unless options.experimental_features.present?
    end

    initializer "lookbook.parser.tags" do
      Lookbook::Parser.define_tags
    end

    initializer "lookbook.assets.serve" do
      config.app_middleware.use(
        Rack::Static,
        urls: ["/lookbook-assets"], root: Lookbook::Engine.root.join("public").to_s
      )
    end

    config.after_initialize do
      preview_controller = Lookbook.config.preview_controller.constantize
      preview_controller.class_eval { include Lookbook::PreviewController }

      if config.lookbook.listen
        @preview_listener = Listen.to(*config.lookbook.listen_paths, only: /\.(rb|html.*)$/) do |modified, added, removed|
          begin
            parser.parse
          rescue
          end
          Lookbook::Preview.clear_cache
          Lookbook::Engine.websocket&.broadcast("reload", {
            modified: modified,
            removed: removed,
            added: added
          })
        end
        @preview_listener.start

        if Lookbook::Features.enabled?(:pages)
          @page_listener = Listen.to(*config.lookbook.page_paths.filter { |dir| Dir.exist? dir }, only: /\.(html.*|md.*)$/) do |modified, added, removed|
            Lookbook::Engine.websocket&.broadcast("reload", {
              modified: modified,
              removed: removed,
              added: added
            })
          end
          @page_listener.start
        end

        if config.lookbook.runtime_parsing
          parser.parse
        else
          unless File.exist?(config.lookbook.parser_registry_path)
            Lookbook.logger.warn "
              Runtime parsing is disabled but no registry file has been found.
              Did you run `rake lookbook:preparse` before starting the app?
              Expected to find registry file at #{config.lookbook.parser_registry_path}
            "
          end
        end
      end
    end

    at_exit do
      @preview_listener&.stop
      @page_listener&.stop
    end

    class << self
      def websocket
        if config.lookbook.auto_refresh
          cable = ActionCable::Server::Configuration.new
          cable.cable = {adapter: "async"}.with_indifferent_access
          cable.mount_path = config.lookbook.cable_mount_path
          cable.connection_class = -> { Lookbook::Connection }
          cable.logger = config.lookbook.cable_logger

          @websocket ||= if Rails.version.to_f >= 6.0
            ActionCable::Server::Base.new(config: cable)
          else
            websocket ||= ActionCable::Server::Base.new
            websocket.config = cable
            websocket
          end
        end
      end

      def websocket_mount_path
        "#{mounted_path}#{config.lookbook.cable_mount_path}" if websocket
      end

      def mounted_path
        Lookbook::Engine.routes.find_script_name({})
      end

      def parser
        @parser ||= Lookbook::Parser.new(config.lookbook.preview_paths, config.lookbook.parser_registry_path)
      end
    end
  end
end