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.