Module: Roda::RodaPlugins::Assets
- Defined in:
- lib/roda/plugins/assets.rb
Overview
The assets plugin adds support for rendering your CSS and javascript asset files on the fly in development, and compiling them to a single, compressed file in production.
This uses the render plugin for rendering the assets, and the render plugin uses tilt internally, so you can use any template engine supported by tilt for your assets. Tilt ships with support for the following asset template engines, assuming the necessary libaries are installed:
- css
-
Less, Sass, Scss
- js
-
CoffeeScript
You can also use opal as a javascript template engine, assuming it is installed.
Usage
When loading the plugin, use the :css and :js options to set the source file(s) to use for CSS and javascript assets:
plugin :assets, :css => 'some_file.scss', :js => 'some_file.coffee'
This will look for the following files:
assets/css/some_file.scss
assets/js/some_file.coffee
The values for the :css and :js options can be arrays to load multiple files. If you want to change the paths where asset files are stored, see the Options section below.
Serving
In your routes, call the r.assets
method to add a route to your assets, which will make your app serve the rendered assets:
route do |r|
r.assets
end
You should generally call r.assets
inside the route block itself, and not under any branches of the routing tree.
Views
In your layout view, use the assets method to add links to your CSS and javascript assets:
<%= assets(:css) %>
<%= assets(:js) %>
You can add attributes to the tags by using an options hash:
<%= assets(:css, :media => 'print') %>
The assets method will respect the application’s :add_script_name option, if it set it will automatically prefix the path with the SCRIPT_NAME for the request.
Asset Groups
The asset plugin supports groups for the cases where you have different css/js files for your front end and back end. To use asset groups, you pass a hash for the :css and/or :js options:
plugin :assets, :css => {:frontend => 'some_frontend_file.scss',
:backend => 'some_backend_file.scss'}
This expects the following directory structure for your assets:
assets/css/frontend/some_frontend_file.scss
assets/css/backend/some_backend_file.scss
If you want do not want to force that directory structure when using asset groups, you can use the :group_subdirs => false
option.
In your view code use an array argument in your call to assets:
<%= assets([:css, :frontend]) %>
Nesting
Asset groups also supporting nesting, though that should only be needed in fairly large applications. You can use a nested hash when loading the plugin:
plugin :assets,
:css => {:frontend => {:dashboard => 'some_frontend_file.scss'}}
and an extra entry per nesting level when creating the tags.
<%= assets([:css, :frontend, :dashboard]) %>
Caching
The assets plugin uses the caching plugin internally, and will set the Last-Modified header to the modified timestamp of the asset source file when rendering the asset.
If you have assets that include other asset files, such as using @import in a sass file, you need to specify the dependencies for your assets so that the assets plugin will correctly pick up changes. You can do this using the :dependencies option to the plugin, which takes a hash where the keys are paths to asset files, and values are arrays of paths to dependencies of those asset files:
app.plugin :assets,
:dependencies=>{'assets/css/bootstrap.scss'=>Dir['assets/css/bootstrap/' '**/*.scss']}
Asset Compilation
In production, you are generally going to want to compile your assets into a single file, with you can do by calling compile_assets after loading the plugin:
plugin :assets, :css => 'some_file.scss', :js => 'some_file.coffee'
compile_assets
After calling compile_assets, calls to assets in your views will default to a using a single link each to your CSS and javascript compiled asset files. By default the compiled files are written to the public directory, so that they can be served by the webserver.
Asset Compression
If you have the yuicompressor gem installed and working, it will be used automatically to compress your javascript and css assets. For javascript assets, if yuicompressor is not available, the plugin will check for closure_compiler, uglifier, and minjs and use the first one that works. If no compressors are available, the assets will just be concatenated together and not compressed during compilation. You can use the :css_compressor and :js_compressor options to specify the compressor to use.
With Asset Groups
When using asset groups, a separate compiled file will be produced per asset group.
Unique Asset Names
When compiling assets, a unique name is given to each asset file, using the a SHA1 hash of the content of the file. This is done so that clients do not attempt to use cached versions of the assets if the asset has changed.
Serving
When compiling assets, r.assets
will serve the compiled asset files. However, it is recommended to have the main webserver (e.g. nginx) serve the compiled files, instead of relying on the application.
Assuming you are using compiled assets in production mode that are served by the webserver, you can remove the serving of them by the application:
route do |r|
r.assets unless ENV['RACK_ENV'] == 'production'
end
If you do have the application serve the compiled assets, it will use the Last-Modified header to make sure that clients do not redownload compiled assets that haven’t changed.
Asset Precompilation
If you want to precompile your assets, so they do not need to be compiled every time you boot the application, you can provide a :precompiled option when loading the plugin. The value of this option should be the filename where the compiled asset metadata is stored.
If the compiled asset metadata file does not exist when the assets plugin is loaded, the plugin will run in non-compiled mode. However, when you call compile_assets, it will write the compiled asset metadata file after compiling the assets.
If the compiled asset metadata file already exists when the assets plugin is loaded, the plugin will read the file to get the compiled asset metadata, and it will run in compiled mode, assuming that the compiled asset files already exist.
On Heroku
Heroku supports precompiling the assets when using Roda. You just need to add an assets:precompile task, similar to this:
namespace :assets do
desc "Precompile the assets"
task :precompile do
require './app'
App.compile_assets
end
end
External Assets/Assets from Gems
The assets plugin only supports loading assets files underneath the assets path. You cannot pass an absolute path to an asset file and have it work. If you would like to reference asset files that are outside the assets path, you have the following options:
-
Copy, hard link, or symlink the external assets files into the assets path.
-
Use tilt-indirect or another method of indirection (such as an erb template that loads the external asset file) so that a file inside the assets path can reference files outside the assets path.
Plugin Options
- :add_suffix
-
Whether to append a .css or .js extension to asset routes in non-compiled mode (default: false)
- :compiled_asset_host
-
The asset host to use for compiled assets. Should include the protocol as well as the host (e.g. “cdn.example.com”, “//cdn.example.com”)
- :compiled_css_dir
-
Directory name in which to store the compiled css file, inside :compiled_path (default: nil)
- :compiled_css_route
-
Route under :prefix for compiled css assets (default: :compiled_css_dir)
- :compiled_js_dir
-
Directory name in which to store the compiled javascript file, inside :compiled_path (default: nil)
- :compiled_js_route
-
Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
- :compiled_name
-
Compiled file name prefix (default: ‘app’)
- :compiled_path
-
Path inside public folder in which compiled files are stored (default: :prefix)
- :concat_only
-
Whether to just concatenate instead of concatentating and compressing files (default: false)
- :css_compressor
-
Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try :yui if available, but not fail if it is not available)
- :css_dir
-
Directory name containing your css source, inside :path (default: ‘css’)
- :css_headers
-
A hash of additional headers for your rendered css files
- :css_opts
-
Template options to pass to the render plugin (via :template_opts) when rendering css assets
- :css_route
-
Route under :prefix for css assets (default: :css_dir)
- :dependencies
-
A hash of dependencies for your asset files. Keys should be paths to asset files, values should be arrays of paths your asset files depends on. This is used to detect changes in your asset files.
- :group_subdirs
-
Whether a hash used in :css and :js options requires the assets for the related group are contained in a subdirectory with the same name (default: true)
- :gzip
-
Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
- :headers
-
A hash of additional headers for both js and css rendered files
- :js_compressor
-
Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs, :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but not fail if any of them is not available)
- :js_dir
-
Directory name containing your javascript source, inside :path (default: ‘js’)
- :js_headers
-
A hash of additional headers for your rendered javascript files
- :js_opts
-
Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
- :js_route
-
Route under :prefix for javascript assets (default: :js_dir)
- :path
-
Path to your asset source directory (default: ‘assets’). Relative paths will be considered relative to the application’s :root option.
- :prefix
-
Prefix for assets path in your URL/routes (default: ‘assets’)
- :precompiled
-
Path to the compiled asset metadata file. If the file exists, will use compiled mode using the metadata in the file. If the file does not exist, will use non-compiled mode, but will write the metadata to the file if compile_assets is called.
- :public
-
Path to your public folder, in which compiled files are placed (default: ‘public’). Relative paths will be considered relative to the application’s :root option.
Defined Under Namespace
Modules: ClassMethods, InstanceMethods, RequestClassMethods, RequestMethods
Constant Summary collapse
- DEFAULTS =
{ :compiled_name => 'app'.freeze, :js_dir => 'js'.freeze, :css_dir => 'css'.freeze, :prefix => 'assets'.freeze, :concat_only => false, :compiled => false, :add_suffix => false, :group_subdirs => true, :compiled_css_dir => nil, :compiled_js_dir => nil, }.freeze
- JS_END =
"\"></script>".freeze
- CSS_END =
"\" />".freeze
- SPACE =
' '.freeze
- DOT =
'.'.freeze
- SLASH =
'/'.freeze
- NEWLINE =
"\n".freeze
- EMPTY_STRING =
''.freeze
- JS_SUFFIX =
'.js'.freeze
- CSS_SUFFIX =
'.css'.freeze
- HTTP_ACCEPT_ENCODING =
'HTTP_ACCEPT_ENCODING'.freeze
- CONTENT_ENCODING =
'Content-Encoding'.freeze
- GZIP =
'gzip'.freeze
- DOTGZ =
'.gz'.freeze
- CompressorNotFound =
Internal exception raised when a compressor cannot be found
Class.new(RodaError)
Class Method Summary collapse
-
.configure(app, opts = {}) ⇒ Object
Setup the options for the plugin.
-
.load_dependencies(app, _opts = nil) ⇒ Object
Load the render and caching plugins plugins, since the assets plugin depends on them.
Class Method Details
.configure(app, opts = {}) ⇒ Object
Setup the options for the plugin. See the Assets module RDoc for a description of the supported options.
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/roda/plugins/assets.rb', line 293 def self.configure(app, opts = {}) if app.assets_opts prev_opts = app.assets_opts[:orig_opts] orig_opts = app.assets_opts[:orig_opts].merge(opts) [:headers, :css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s| if prev_opts[s] if opts[s] orig_opts[s] = prev_opts[s].merge(opts[s]) else orig_opts[s] = prev_opts[s].dup end end end app.opts[:assets] = orig_opts.dup app.opts[:assets][:orig_opts] = orig_opts else app.opts[:assets] = opts.dup app.opts[:assets][:orig_opts] = opts end opts = app.opts[:assets] opts[:path] = File.(opts[:path]||"assets", app.opts[:root]).freeze opts[:public] = File.(opts[:public]||"public", app.opts[:root]).freeze # Combine multiple values into a path, ignoring trailing slashes j = lambda do |*v| opts.values_at(*v). reject{|s| s.to_s.empty?}. map{|s| s.chomp('/')}. join('/').freeze end # Same as j, but add a trailing slash if not empty sj = lambda do |*v| s = j.call(*v) s.empty? ? s : (s + '/').freeze end if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled]) require 'json' opts[:compiled] = ::JSON.parse(::File.read(opts[:precompiled])) end DEFAULTS.each do |k, v| opts[k] = v unless opts.has_key?(k) end [ [:compiled_path, :prefix], [:js_route, :js_dir], [:css_route, :css_dir], [:compiled_js_route, :compiled_js_dir], [:compiled_css_route, :compiled_css_dir] ].each do |k, v| opts[k] = opts[v] unless opts.has_key?(k) end [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s| opts[s] ||= {} end if headers = opts[:headers] opts[:css_headers] = headers.merge(opts[:css_headers]) opts[:js_headers] = headers.merge(opts[:js_headers]) end opts[:css_headers]['Content-Type'] ||= "text/css; charset=UTF-8".freeze opts[:js_headers]['Content-Type'] ||= "application/javascript; charset=UTF-8".freeze [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s| opts[s].freeze end [:headers, :css, :js].each do |s| opts[s].freeze if opts[s] end # Used for reading/writing files opts[:js_path] = sj.call(:path, :js_dir) opts[:css_path] = sj.call(:path, :css_dir) opts[:compiled_js_path] = j.call(:public, :compiled_path, :compiled_js_dir, :compiled_name) opts[:compiled_css_path] = j.call(:public, :compiled_path, :compiled_css_dir, :compiled_name) # Used for URLs/routes opts[:js_prefix] = sj.call(:prefix, :js_route) opts[:css_prefix] = sj.call(:prefix, :css_route) opts[:compiled_js_prefix] = j.call(:prefix, :compiled_js_route, :compiled_name) opts[:compiled_css_prefix] = j.call(:prefix, :compiled_css_route, :compiled_name) opts[:js_suffix] = opts[:add_suffix] ? JS_SUFFIX : EMPTY_STRING opts[:css_suffix] = opts[:add_suffix] ? CSS_SUFFIX : EMPTY_STRING opts.freeze end |
.load_dependencies(app, _opts = nil) ⇒ Object
Load the render and caching plugins plugins, since the assets plugin depends on them.
286 287 288 289 |
# File 'lib/roda/plugins/assets.rb', line 286 def self.load_dependencies(app, _opts = nil) app.plugin :render app.plugin :caching end |