lib/opal/sprockets/server.rb



require 'rack/file'
require 'rack/urlmap'
require 'rack/builder'
require 'rack/directory'
require 'rack/showexceptions'
require 'opal/source_map'
require 'opal/sprockets/environment'
require 'erb'

module Opal

  class SourceMapServer
    def initialize sprockets
      @sprockets = sprockets
    end

    attr_reader :sprockets

    attr_writer :prefix

    def prefix
      @prefix ||= '/__opal_source_maps__'
    end

    def inspect
      "#<#{self.class}:#{object_id}>"
    end

    def call(env)
      path_info = env['PATH_INFO']

      if path_info =~ /\.js\.map$/
        path   = env['PATH_INFO'].gsub(/^\/|\.js\.map$/, '')
        asset  = sprockets[path]
        return [404, {}, []] if asset.nil?

        return [200, {"Content-Type" => "text/json"}, [$OPAL_SOURCE_MAPS[asset.pathname].to_s]]
      else
        return [200, {"Content-Type" => "text/text"}, [File.read(sprockets.resolve(path_info))]]
      end
    end
  end

  class Server

    attr_accessor :debug, :use_index, :index_path, :main, :public_root, :public_urls, :sprockets

    def initialize debug_or_options = {}
      unless Hash === debug_or_options
        warn "passing a boolean to control debug is deprecated.\n"+
             "Please pass an Hash instead: Server.new(debug: true)"
        options = {:debug => debug_or_options}
      else
        options = debug_or_options
      end

      @use_index   = true
      @public_root = nil
      @public_urls = ['/']
      @sprockets   = Environment.new
      @debug       = options.fetch(:debug, true)

      yield self if block_given?
      create_app
    end

    def public_dir=(dir)
      @public_root = dir
      @public_urls = ["/"]
    end

    def source_map=(enabled)
      Opal::Processor.source_map_enabled = enabled
    end

    def source_map_enabled
      Opal::Processor.source_map_enabled
    end

    def append_path path
      @sprockets.append_path path
    end

    def use_gem gem_name
      @sprockets.use_gem gem_name
    end

    def create_app
      server, sprockets = self, @sprockets

      @app = Rack::Builder.app do
        not_found = lambda { |env| [404, {}, []] }

        use Rack::Deflater
        use Rack::ShowExceptions
        map('/assets') { run sprockets }
        map(server.source_maps.prefix) { run server.source_maps } if server.source_map_enabled
        use Index, server if server.use_index
        run Rack::Static.new(not_found, :root => server.public_root, :urls => server.public_urls)
      end
    end

    def source_maps
      @source_maps ||= SourceMapServer.new(@sprockets)
    end

    def call(env)
      @app.call env
    end

    class Index

      def initialize(app, server)
        @app = app
        @server = server
        @index_path = server.index_path
      end

      def call(env)
        if %w[/ /index.html].include? env['PATH_INFO']
          [200, { 'Content-Type' => 'text/html' }, [html]]
        else
          @app.call env
        end
      end

      # Returns the html content for the root path. Supports ERB
      def html
        if @index_path
          raise "index does not exist: #{@index_path}" unless File.exist?(@index_path)
          Tilt.new(@index_path).render(self)
        else
          ::ERB.new(SOURCE).result binding
        end
      end

      def javascript_include_tag source
        if @server.debug
          assets = @server.sprockets[source].to_a

          raise "Cannot find asset: #{source}" if assets.empty?

          scripts = assets.map do |a|
            %Q{<script src="/assets/#{ a.logical_path }?body=1"></script>}
          end

          scripts.join "\n"
        else
          "<script src=\"/assets/#{source}.js\"></script>"
        end
      end

      SOURCE = <<-HTML
  <!DOCTYPE html>
  <html>
  <head>
    <title>Opal Server</title>
  </head>
  <body>
    <%= javascript_include_tag @server.main %>
  </body>
  </html>
      HTML
    end
  end
end