Announcing juicer-rails: Use juicer in your rails apps

15 views
Skip to first unread message

Aaron Suggs

unread,
Jan 24, 2010, 1:06:38 PM1/24/10
to juice...@googlegroups.com
Hi all,

I made a rails plugin for using juicer. Check it out at
http://github.com/ktheory/juicer-rails

The plugin make it easy to use uncompressed assets in development
mode, and compressed assets in production.

It adds a `juiced_tag` method to use in your views. Use juiced_tag
instead of `stylesheet_link_tag` and `javascript_include_tag`.

In development mode, juiced_tag resolves the dependencies of the
argument and returns a <script> or <link> tag for each dependency. In
production, it returns a single tag for the juiced asset. See the
README for more:
http://github.com/ktheory/juicer-rails/blob/master/README.rdoc

Currently, the plugin doesn't build the assets automatically. That's
left for your deployer.

If you find bugs or want to suggest features, open an issue at
http://github.com/ktheory/juicer-rails/issues

Cheers,
Aaron Suggs

Christian Johansen

unread,
Jan 24, 2010, 1:29:34 PM1/24/10
to juice...@googlegroups.com
Great work, Aaron! I've been meaning to add Rails helpers for Juicer for like forever, but never got around to it. Very cool to see this out as a gem.

I've been thinking alot about how and when files should be built. Requiring files to be built as part of deployment definitely is the solution with the lowest overhead on the app side of things. For this purpose it would be nice if juicer-rails featured a rake task, or maybe even a capistrano recipe to get this done easily. The problem with this approach is that it could be hard to automatically get a list of all files that should be produced (or not? could parse view files I guess). There are some alternatives out there, and all of them require configuring up targets, which I think we should avoid.

The other idea I've had is to have a Juicer controller that can build files the first time they're requested. At a current project I'm using Rails to build a prototype, and so performance isn't much of an issue. What I did here was to add a route such as:

  map.javascripts "design/js/:script.js", :controller => "javascripts", :action => "show", :script => /.+/

Now you can make requests for /design/js/some/script.js and the controller will serve up a merged response. If you alter the URL to /design/js/some/script.min.js Juicer will also minify it. The controller stores the resulting file in the corresponding directory on disk, so if your web frontend is configured to serve files before virtual urls, the performance hit should only be a problem on the first request. Is this something worth spending some more time on?

Here's the controller:

class JavascriptsController < ApplicationController
  def show
    headers['Content-Type'] = "text/javascript"
    method = params[:script] =~ /\.min$/ ? :minified : :merged
    render :text => JuicyScript.new(params[:script].sub(/\.min$/, "")).send(method, params.key?(:force))
  end
end

The JuicyScript class does most of the work:

require "juicer"
require "juicer/merger/javascript_merger"
require "juicer/minifyer/yui_compressor"
require "pathname"

class JuicyScript
  def initialize(script)
    @script = script
  end

  def merged(force = false)
    merger = Juicer::Merger::JavaScriptMerger.new(path)
    output = StringIO.new
    merger.save(output)

    output(output.rewind && output.read, merger.files)
  end

  def minified(force = false)
    script = path("min.js")
    File.delete(script) if force && File.exists?(script)
    `juicer merge -s -o #{script} #{path}` if !File.exists?(script)
    output(File.read(script))
  end

  def path(suffix = "js")
    script = @script.sub(/((\.min)?\.js)?$/, ".#{suffix}")
    script = script !~ /^lib/ ? "src/#{script}" : script
    File.join(self.class.root, script)
  end

  def relative_path(script = path)
    script = script.sub("#{self.class.root}/", "")
  end

  def output(contents, dependencies = nil)
    dependencies ||= Juicer::JavaScriptDependencyResolver.new.resolve(path)
    dependencies = dependencies.collect { |f| self.relative_path(f) }

    <<-JS
/**
 * #{relative_path} as packaged by Juicer
 *
 * #{dependencies.join("\n * ")}
 *
 * Total: #{contents.length / 1024.0} kbytes
 */

#{contents}
    JS
  end

  def self.root
    @@root ||= File.join(Rails.root, "public/design/js")
  end

  def self.compressor
    return @@compressor if @@compressor
    juicer_home = File.join(Juicer.home, "lib/yui_compressor/bin")
    @@compressor = Juicer::Minifyer::YuiCompressor.new({ :bin_path => juicer_home })
  end
end

These are quick and dirty implementations, but perhaps they could be the starting point for live packaging in juicer-rails?

Anyway, awesome to have official Rails support for Juicer!

Christian
--
MVH
Christian

Aaron Suggs

unread,
Jan 26, 2010, 2:37:55 PM1/26/10
to juice...@googlegroups.com
On Sun, Jan 24, 2010 at 1:29 PM, Christian Johansen <chri...@gmail.com> wrote:
> Great work, Aaron! I've been meaning to add Rails helpers for Juicer for
> like forever, but never got around to it. Very cool to see this out as a
> gem.

Thanks!

> I've been thinking alot about how and when files should be built. Requiring
> files to be built as part of deployment definitely is the solution with the
> lowest overhead on the app side of things. For this purpose it would be nice
> if juicer-rails featured a rake task, or maybe even a capistrano recipe to
> get this done easily. The problem with this approach is that it could be
> hard to automatically get a list of all files that should be produced (or
> not? could parse view files I guess). There are some alternatives out there,
> and all of them require configuring up targets, which I think we should
> avoid.

Indeed, this would be nice to have, and should probably be built in to
juicer-rails. :-)
http://github.com/ktheory/juicer-rails/issues/issue/1

Getting the list of target assets is the difficulty. Parsing the views
or using a YAML config file would be straightforward, but they feel
inelegant. I'd like to leverage convention over configuration as much
as possible.

> The other idea I've had is to have a Juicer controller that can build files
> the first time they're requested.

Excellent idea. This could even be Rack middleware to be
Rails-agnostic. The middleware should probably be it's own project
since it's useful independent of juicer-rails.

juicer-rails answers the question "which script/link tags should I
include on this page?"
The middleware answers "what content should I serve back for a this asset url?"

Even with the middleware, there's good reason for a juicer-rails rake
task to build the assets at deploy time:
- static files may be served from different machines than the app servers
- thundering horde problem generating large assets for the first time.

Thanks again Christian (et al) for making juicer.

Cheers,
Aaron

Christian Johansen

unread,
Jan 28, 2010, 4:00:13 AM1/28/10
to juice...@googlegroups.com
On Tue, Jan 26, 2010 at 20:37, Aaron Suggs <asu...@gmail.com> wrote:
On Sun, Jan 24, 2010 at 1:29 PM, Christian Johansen <chri...@gmail.com> wrote:
Getting the list of target assets is the difficulty. Parsing the views
or using a YAML config file would be straightforward, but they feel
inelegant. I'd like to leverage convention over configuration as much
as possible.

> The other idea I've had is to have a Juicer controller that can build files
> the first time they're requested.

Excellent idea. This could even be Rack middleware to be
Rails-agnostic. The middleware should probably be it's own project
since it's useful independent of juicer-rails.

Agree completely. This obviously would be a lot more useful as Rack middleware!
 

juicer-rails answers the question "which script/link tags should I
include on this page?"
The middleware answers "what content should I serve back for a this asset url?"

Even with the middleware, there's good reason for a juicer-rails rake
task to build the assets at deploy time:
 - static files may be served from different machines than the app servers
 - thundering horde problem generating large assets for the first time.

Again, agreed. Thanks for your thoughts on this. I think your batch mode for Juicer probably could solve the problem for Rails atleast partially.

Christian
 

Thanks again Christian (et al) for making juicer.

Cheers,
Aaron



--
MVH
Christian
Reply all
Reply to author
Forward
0 new messages