Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Allow Jammit to be extended to package and compress other types of assests #229

Open
wants to merge 3 commits into from

4 participants

@dinedal

I use Jammit on a production level service and am happy with it, however I came across a need to be able to package and compress other static assets that are not stylesheets, javascripts, or JSTs.

In order to do this, the attached changes are needed to be able to allow me to extend Jammit with custom asset types. It is important to me to keep using Jammit, and I would greatly appreciate this functionality being merged into the main repo, and I'm open to any changes you feel are needed to get this in.

Below is a modified version of what I am using to interact with the fork of Jammit with these included changes.

module Jammit

  JSON_NAMESPACE = "window.DATA"

  JSON_FUNCTION = "JSON.parse"

  JSON_EXTENSION = "json"

  class Compressor
    # no actual compression applied, just formats it correctly, but could support compression
    def compress_json(paths)
      namespace   = JSON_NAMESPACE
      base_path   = find_base_path(paths)
      compiled    = paths.map do |path|
        contents  = read_binary_file(path)
        name      = json_name(path, base_path)
        "#{namespace}['#{name}'] = #{JSON_FUNCTION}('#{contents}');"
      end
      setup_namespace = "#{namespace} = #{namespace} || {};"
      [JST_START, setup_namespace, compiled, JST_END].flatten.join("\n")
    end

    def json_name(path, base_path)
      return File.basename(path, ".#{JSON_EXTENSION}") unless base_path
      path.gsub(/\A#{Regexp.escape(base_path)}\/(.*)\.#{JSON_EXTENSION}\Z/, '\1')
    end
  end

  class Packager
    def pack_jsons(package)
      compressor.compress_json(package_for(package, :json)[:paths])
    end
  end

  module Helper
    def include_jsons(*packages)
      options = packages.extract_options!
      html_safe packages.map {|pack|
        Jammit.asset_url(pack, :json)
      }.flatten.map {|pack|
        javascript_include_tag pack, options
      }.join("\n")
    end
  end
end

Please note that this is a initial pass and is very conservative in what it changes, however a couple of more drastic changes could make this functionality more streamlined with the rest of the project.

Ideally, I feel that JSTs should also be broken out ouf the Javascript packaging / compression, and the pipeline of packaging and compressing be made more generic to support handling user defined asset types as a first class citizen. Thus, each type, user defined or included, will flow through a similar work flow.

However, before embarking on this, I would greatly appreciate feedback on what I have so far, how to determine what asset type (JS or CSS) an asset supplied in the assets.yml would be, and if the way user defined templates are created is acceptable.

@jashkenas
Owner

I'm a bit confused as to why you don't simply include these files as regular JavaScript, not JSON. That's what you're ultimately doing, isn't it?

@braddunbar

:+1: - This would allow precompiling JSTs as well, a big win for debugging since you could get a file name and line number from template errors. It's currently difficult to alter the behavior of JST compilation since it's tied into javascript compilation.

@dinedal

@jashkenas Thanks for your quick reply.

While you're correct, the example above does basically that (and is actually how we handle this case at present), however, it is more of a proof of concept then what is going to be implemented for JSONs.

JSONs have different attributes then javascripts or JSTs, they need to be stored without javascript inside of them for portability, they don't have any args that are passed to them for rendering, as they are not rendered, they can also "stack" on each other, ie, two JSONs could exist, one of default values and one including overrides of the original, and keeping them in the JST namespace means keeping code to determine if the template is a JST or a JSON in the client side. It would be cleaner to support them as a first class type.

@flippyhead

@jashkenas hey! Now, 3 months later, any different feelings about this? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
10 lib/jammit.rb
@@ -99,8 +99,7 @@ def self.load_configuration(config_path, soft=false)
set_template_namespace(conf[:template_namespace])
set_template_extension(conf[:template_extension])
set_public_root(conf[:public_root]) if conf[:public_root]
- symbolize_keys(conf[:stylesheets]) if conf[:stylesheets]
- symbolize_keys(conf[:javascripts]) if conf[:javascripts]
+ conf.keys.each {|key| symbolize_keys(conf[key]) rescue nil}
check_for_deprecations
self
end
@@ -146,6 +145,13 @@ def self.package!(options={})
packager.precache_all(options[:output_folder], options[:base_url])
end
+ def self.custom_assets
+ @configuration.select { |key, val|
+ lambda{ |val| val.respond_to?(:keys) rescue false}.call(val) &&
+ ![:javascripts, :stylesheets].include?(key)
+ }
+ end
+
private
# Allows command-line definition of `PUBLIC_ROOT`, for those using Jammit
View
3  lib/jammit/dependencies.rb
@@ -57,3 +57,6 @@
require 'jammit/routes'
end
+# Load user extentions
+require 'jammit/extensions'
+
View
3  lib/jammit/extensions.rb
@@ -0,0 +1,3 @@
+module Jammit
+ require File.join(ASSET_ROOT, 'lib', 'extensions', 'jammit.rb') if File.exists?(File.join(ASSET_ROOT, 'lib', 'extensions', 'jammit.rb'))
+end
View
13 lib/jammit/packager.rb
@@ -19,11 +19,11 @@ def initialize
@config = {
:css => (Jammit.configuration[:stylesheets] || {}),
:js => (Jammit.configuration[:javascripts] || {})
- }
+ }.merge!(Jammit.custom_assets)
@packages = {
:css => create_packages(@config[:css]),
:js => create_packages(@config[:js])
- }
+ }.merge!(create_custom_packages(Jammit.custom_assets))
end
# Ask the packager to precache all defined assets, along with their gzip'd
@@ -46,6 +46,9 @@ def precache_all(output_dir=nil, base_url=nil)
end
end
end
+ @packages.keys.keep_if{|extension| ![:js, :css].include?(extension)}.each do |extension|
+ cacheable(extension, output_dir).each {|p| cache(p, extension.to_s, self.send(:"pack_#{extension}s", p), output_dir)}
+ end
end
# Caches a single prebuilt asset package and gzips it at the highest
@@ -169,6 +172,12 @@ def create_packages(config)
packages
end
+ def create_custom_packages(custom_assets)
+ results = {}
+ custom_assets.each { |key, val| results[:"#{key.to_s.singularize}"] = create_packages(val)}
+ results
+ end
+
# Raise a PackageNotFound exception for missing packages...
def not_found(package, extension)
raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
Something went wrong with that request. Please try again.