Sprockets integration into Padrino application

Posted: Apr 16, 2015

For one of my projects I decided to use Padrino instead of Rails. I worked with Sinatra, therefore I thought that Padrino would work for me as well. Actually, I guess even Sinatra matches my needs, but start of a project with Sinatra is slower, because you need to setup a lot of stuffs to start active development.

Anyway, for my application I needed Sprockets to get assets pipeline. May be I could use something else for that, but when you start a new project you have to pick up technologies which you know (or at least some of them). Padrino doesn’t provide a standard way to integrate Sprockets and it is perfectly fine. I tried a few gems mentioned here some of them partially worked and some of them didn’t work as I expected. Since Sprockets can be used with any Rack application I decided to integrate it without any gems.

What I did:

Create a new application to serve assets

# app/assets_app.rb

require 'sprockets'
require 'ejs'

module MyApp
  class Assets
    def initialize
      @sprockets = Sprockets::Environment.new
      @sprockets.append_path 'app/assets/javascripts'
      @sprockets.append_path 'app/assets/stylesheets'
      @sprockets.append_path 'app/assets/fonts'

      # any other paths which you need here

      @sprockets.context_class.class_eval do
        def asset_path(path, options = {})
          "/assets/#{path}"
        end
      end
    end

    def call(*args)
      @sprockets.call(*args)
    end

    def self.root
      Padrino.root('public')
    end

    def self.call(*args)
      MyApp::Assets.new.call(*args)
    end
  end
end

Mount the new app

# config/apps.rb

Padrino.mount(
  'MyApp::Assets',
  app_file: Padrino.root('app/assets_app.rb')
).to('/assets')

Note: this code should go before mounting of your main app.

Create new helper methods for assets

# app/helpers/assets_helper.rb

module MyApp
  class App
    module AssetTagsHelper
      def js_include_tag(*sources)
        options = sources.extract_options!.symbolize_keys
        options.reverse_merge!(type: 'text/javascript')
        sources.flatten.map { |source|
          content_tag(
            :script,
            nil,
            options.reverse_merge(
              src: resolve_path(source.to_s)
            )
          )
        }.join("\n").html_safe
      end

      def css_link_tag(*sources)
        options = sources.extract_options!.symbolize_keys
        options.reverse_merge!(media: 'screen', rel: 'stylesheet', type: 'text/css')
        sources.flatten.map { |source|
          tag(
            :link,
            options.reverse_merge(
              href: asset_path(:css, resolve_path(source.to_s))
            )
          )
        }.join("\n").html_safe
      end

      protected
        def resolve_path(source)
          if Padrino.env == :production
            minifest_file = Sprockets::ManifestUtils.find_directory_manifest(
              Padrino.root('public/assets')
            )

            manifest = Sprockets::Manifest.new(
              Sprockets::Environment.new,
              minifest_file
            )

            '/assets/' + manifest.assets[source]
          else
            asset_path('/assets/' + source)
          end
        end
    end

    helpers AssetTagsHelper
  end
end

Add Sprockets rake tasks to compile assets

# lib/tasks/assets.rake

require 'rake/sprocketstask'

Rake::SprocketsTask.new do |t|
  sprockets = Sprockets::Environment.new

  sprockets.append_path 'app/assets/javascripts'
  sprockets.append_path 'app/assets/stylesheets'
  sprockets.append_path 'app/assets/fonts'

  # any other paths which you need

  sprockets.context_class.class_eval do
    def asset_path(path, options = {})
      path = environment[path].digest_path

      "/assets/#{path}"
    end
  end

  t.environment = sprockets

  t.output = './public/assets'
  t.assets = %w( application.js application.css *.ttf *.woff *.woff2 )
end

To avoid duplications in appending paths to assets I moved that code to own class, actually that class finds asset paths in all gems and adds to sprockets.

Now in my layouts I use:

css_link_tag 'application.css'

and

js_include_tag 'application.js'

Everything works as I need. Since I don’t need big flexibility here, I am satisfied by this result without any additional gems.

I am ok to use additional gems for such kind of challenges, but usually, they aren't maintainable enough. When you use barely maintainable gems, you may run into the issue with dependencies of that gems. They may depend on obsolete versions of gems and it means you won't be able to upgrade some of your dependencies. Therefore, if you can do integration without any additional gems, it is better to go this way.

UPD: The demo code is here.