documentcloud / jammit

Industrial Strength Asset Packaging for Rails
http://documentcloud.github.com/jammit/
MIT License
1.16k stars 197 forks source link

Warbler support #29

Closed asdavey closed 14 years ago

asdavey commented 14 years ago

Summary

Using jammit with warbler breaks packaged assets.

Description

Warbler is used to package a rails app for deployment to a J2EE server (e.g. glassfish).

For some reason, warbler removes the /public/ from all asset folders, so that you application folder looks something like:

/javascripts/..
/images/...
/stylesheets/...
/WEB-INF/app/...
/WEB-INF/config/...

Anticipating this I configured my asset.yml file like so:

javascripts:
  common:
    - **/javascripts/foo.js
    - **/javascripts/bar.js

This worked find in development (even with package_assets:always) but once I deployed to glassfish the url http://localhost:8080/RailsApp/assets/common.js returned a 200 response code but with no text.

The log file contains a bunch of Jammit warnings:

Jammit Warning: No assets match '**/javascripts/foo.    
Jammit Warning: No assets match '**/javascripts/bar.js'

Is there a simple workaround for this issue or is this something that will require a modification to Jammit to get going?

Additional Info

jashkenas commented 14 years ago

I'm afraid I haven't ever tried Jammit with Warbler (or even with JRuby), but there should definitely be a way to make it work.

Looking briefly at the Warbler docs, it looks possible to customize the location of the "public" directory -- you could simply put it back to public, and be on your way.

The other alternative is to use the jammit command to build your assets at deploy time. With the --output flag, you can specify whatever destination directory you like.

Let me know if neither of those options work for you, and we can re-open this ticket.

asdavey commented 14 years ago

I don't believe it's possible to modify where the public folder is located using Warbler (due to Warbler's dependency on Jruby-Jack).

And the second option did not appeal to me since the people who will be in charge of deployment (third party) are used to receiving a single war file that they can QA and then eventually deploy.

So I vendorised Jammit and made the following two changes (see full patch below... might look big but I've only changed four lines):

  1. First change is PUBLIC_ROOT will try to default to the value in ActionView::Helpers::AssetTagHelper::ASSET_DIR. If ActionView is not around, then it will default to ASSET_ROOT + "/public" as per normal.

    This change was necessary due to the way Rails apps are packaged into War files. RAILS.root points to = "/WEB-INF" while ASSET_DIR points to "/" (it's a war specification thing). Before this change, any packaged css files were rewriting the paths in url()'s using absolute paths (e.g. D:\Dev\Webapps\FooBar\WEB-INF\Public...), due to the PUBLIC_ROOT not being the parent of the path in the url() thingy.

  2. Second change is the addition of a (badly named) configuration parameter glob_root. Unless specified glob_root defaults to ASSET_ROOT. But if you store all your (pre-packaged) assets in "public" as per normal and plan to dpeloy using a war file, you should set glob_root to ActionView::Helpers::AssetTagHelper::ASSET_DIR.

With these changes the example assets.yml file from above would now look like:

glob_root: <%= File.expand_path(ActionView::Helpers::AssetTagHelper::ASSETS_DIR) %>
javascripts:
  common:
    - javascripts/foo.js
    - javascripts/bar.js

Patch

This patch was generated against tag 0.4.3

diff --git a/lib/jammit.rb b/lib/jammit.rb
index b6736cb..f2de594 100644
--- a/lib/jammit.rb
+++ b/lib/jammit.rb
@@ -10,7 +10,7 @@ module Jammit

   ASSET_ROOT            = File.expand_path(defined?(Rails) ? Rails.root : ".") unless defined?(ASSET_ROOT)

-  PUBLIC_ROOT           = File.join(ASSET_ROOT, 'public')
+  PUBLIC_ROOT           = File.expand_path(defined?(ActionView) ? ActionView::Helpers::AssetTagHelper::ASSETS_DIR : File.join(ASSET_ROOT, 'public'))

   DEFAULT_CONFIG_PATH   = File.join(ASSET_ROOT, 'config', 'assets.yml')

@@ -42,7 +42,7 @@ module Jammit
     attr_reader :configuration, :template_function, :template_namespace,
                 :embed_assets, :package_assets, :compress_assets, :gzip_assets,
                 :package_path, :mhtml_enabled, :include_jst_script,
-                :javascript_compressor, :compressor_options, :css_compressor_options
+                :javascript_compressor, :compressor_options, :css_compressor_options, :glob_root
   end

   # The minimal required configuration.
@@ -63,6 +63,7 @@ module Jammit
     @mhtml_enabled          = @embed_assets && @embed_assets != "datauri"
     @compressor_options     = (conf[:compressor_options] || {}).symbolize_keys
     @css_compressor_options = (conf[:css_compressor_options] || {}).symbolize_keys
+    @glob_root              = File.expand_path(conf[:glob_root] || ASSET_ROOT)
     set_javascript_compressor(conf[:javascript_compressor])
     set_package_assets(conf[:package_assets])
     set_template_function(conf[:template_function])
diff --git a/lib/jammit/packager.rb b/lib/jammit/packager.rb
index 4d2d1ff..133a5e0 100644
--- a/lib/jammit/packager.rb
+++ b/lib/jammit/packager.rb
@@ -103,7 +103,7 @@ module Jammit
     # Print a warning if no files were found that match the glob.
     def glob_files(glob)
       absolute = Pathname.new(glob).absolute?
-      paths = Dir[absolute ? glob : File.join(ASSET_ROOT, glob)].sort
+      paths = Dir[absolute ? glob : File.join(Jammit.glob_root, glob)].sort
       Jammit.warn("No assets match '#{glob}'") if paths.empty?
       paths
     end
documentcloud commented 14 years ago

Alright. Thanks for the patch and the explanation. Before we merge this in, I want to make sure that you considered the second option fully. If you're handing the war file off to a Q/A team, I imagine that they won't be running the development version, so how about this:

jammit -o assets

That will build and package up all of your assets into Rails.root/assets, effectively. Then, when you start your application, the links will point to the correct location. If you run this before rake war:app, I imagine you'd be good to go.

Is that a reasonable approach, or is there something I'm missing?

asdavey commented 14 years ago

I just tested out your theory and it worked once I got rid of any <%= Rails.env...%> stuff in the config and changed the command line to:

jammit -o public/assets

I'm in two minds about this.

One one hand your solution does not require any code modifications but does require additional steps in the build process (which is just a script so not tooo ig a deal).

On the other my solution doesn't require additional build steps but has that code change (plus glob_root is such a bad name).

What would be great is if Jammit assumed that all assets were relative to ActionView::Helpers::AssetTagHelpers::ASSETS_DIR (i.e. /public) instead of RAILS_ROOT. The code changes required to support this would be minimal and for all those people who end up deploying their Rails apps as WAR, it would work out of the box.

Do many people deploy their assets in folders other than /public?

asdavey commented 14 years ago

The other scenario not covered by pre-packaging assets is if you have a situation where you need to change the pre-packaged assets and then do an application reload, Jammit won't correctly generate the new packaged assets correctly.

documentcloud commented 14 years ago

I just pushed a commit that removes all hardcoded references to "public", in favor of Rails.public_path, which apparently Warbler will set correctly for you:

http://www.ruby-forum.com/topic/165910

Here's the commit:

http://github.com/documentcloud/jammit/commit/d4f817fd8badca4b531b21724e83bbed22e7700b

That, in combination with the prepackaged compilation should do the trick, even for packages generated dynamically by the application. Closing the ticket. Let me know if it needs to be reopened.

asdavey commented 14 years ago

Cheers for that.