jammit 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/jammit.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'jammit'
3
- s.version = '0.3.3' # Keep version in sync with jammit.rb
4
- s.date = '2009-1-5'
3
+ s.version = '0.4.0' # Keep version in sync with jammit.rb
4
+ s.date = '2009-1-26'
5
5
 
6
6
  s.homepage = "http://documentcloud.github.com/jammit/"
7
7
  s.summary = "Industrial Strength Asset Packaging for Rails"
data/lib/jammit.rb CHANGED
@@ -4,11 +4,11 @@ $LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
4
4
  # to all of the configuration options.
5
5
  module Jammit
6
6
 
7
- VERSION = "0.3.3"
7
+ VERSION = "0.4.0"
8
8
 
9
9
  ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
10
10
 
11
- ASSET_ROOT = File.expand_path(defined?(RAILS_ROOT) ? RAILS_ROOT : ".")
11
+ ASSET_ROOT = File.expand_path(defined?(Rails) ? Rails.root : ".") unless defined?(ASSET_ROOT)
12
12
 
13
13
  PUBLIC_ROOT = "#{ASSET_ROOT}/public"
14
14
 
@@ -20,10 +20,16 @@ module Jammit
20
20
 
21
21
  DEFAULT_JST_COMPILER = "template"
22
22
 
23
+ DEFAULT_JST_NAMESPACE = "window.JST"
24
+
23
25
  AVAILABLE_COMPRESSORS = [:yui, :closure]
24
26
 
25
27
  DEFAULT_COMPRESSOR = :yui
26
28
 
29
+ # Extension matchers for JavaScript and JST, which need to be disambiguated.
30
+ JS_EXT = /\.js\Z/
31
+ JST_EXT = /\.jst\Z/
32
+
27
33
  # Jammit raises a @PackageNotFound@ exception when a non-existent package is
28
34
  # requested by a browser -- rendering a 404.
29
35
  class PackageNotFound < NameError; end
@@ -37,7 +43,7 @@ module Jammit
37
43
  class OutputNotWritable < StandardError; end
38
44
 
39
45
  class << self
40
- attr_reader :configuration, :template_function, :embed_images, :package_path,
46
+ attr_reader :configuration, :template_function, :template_namespace, :embed_assets, :package_path,
41
47
  :package_assets, :compress_assets, :mhtml_enabled, :include_jst_script,
42
48
  :javascript_compressor, :compressor_options, :css_compressor_options
43
49
  end
@@ -48,19 +54,21 @@ module Jammit
48
54
 
49
55
  # Load the complete asset configuration from the specified @config_path@.
50
56
  def self.load_configuration(config_path)
51
- conf = config_path && File.exists?(config_path) && YAML.load_file(config_path)
52
- raise ConfigurationNotFound, "could not find the \"#{config_path}\" configuration file" unless conf
57
+ exists = config_path && File.exists?(config_path)
58
+ raise ConfigurationNotFound, "could not find the \"#{config_path}\" configuration file" unless exists
59
+ conf = YAML.load(ERB.new(File.read(config_path)).result)
53
60
  @config_path = config_path
54
61
  @configuration = conf = conf.symbolize_keys
55
62
  @package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
56
- @embed_images = conf[:embed_images]
63
+ @embed_assets = conf[:embed_assets] || conf[:embed_images]
57
64
  @compress_assets = !(conf[:compress_assets] == false)
58
- @mhtml_enabled = @embed_images && @embed_images != "datauri"
65
+ @mhtml_enabled = @embed_assets && @embed_assets != "datauri"
59
66
  @compressor_options = (conf[:compressor_options] || {}).symbolize_keys
60
67
  @css_compressor_options = (conf[:css_compressor_options] || {}).symbolize_keys
61
68
  set_javascript_compressor(conf[:javascript_compressor])
62
69
  set_package_assets(conf[:package_assets])
63
70
  set_template_function(conf[:template_function])
71
+ set_template_namespace(conf[:template_namespace])
64
72
  check_java_version
65
73
  self
66
74
  end
@@ -113,6 +121,11 @@ module Jammit
113
121
  @include_jst_script = @template_function == DEFAULT_JST_COMPILER
114
122
  end
115
123
 
124
+ # Set the root JS object in which to stash all compiled JST.
125
+ def self.set_template_namespace(value)
126
+ @template_namespace = value == true || value.nil? ? DEFAULT_JST_NAMESPACE : value.to_s
127
+ end
128
+
116
129
  # The YUI Compressor requires Java > 1.4, and Closure requires Java > 1.6.
117
130
  def self.check_java_version
118
131
  java = @compressor_options[:java] || 'java'
@@ -16,7 +16,7 @@ Run jammit inside a Rails application to compresses all JS, CSS,
16
16
  and JST according to config/assets.yml, saving the packaged
17
17
  files and corresponding gzipped versions.
18
18
 
19
- If you're using "embed_images", and you wish to precompile the
19
+ If you're using "embed_assets", and you wish to precompile the
20
20
  MHTML stylesheet variants, you must specify the "base-url".
21
21
 
22
22
  Options:
@@ -3,24 +3,33 @@ module Jammit
3
3
  # Uses the YUI Compressor or Closure Compiler to compress JavaScript.
4
4
  # Always uses YUI to compress CSS (Which means that Java must be installed.)
5
5
  # Also knows how to create a concatenated JST file.
6
- # If "embed_images" is turned on, creates "mhtml" and "datauri" versions of
7
- # all stylesheets, with all enabled images inlined into the css.
6
+ # If "embed_assets" is turned on, creates "mhtml" and "datauri" versions of
7
+ # all stylesheets, with all enabled assets inlined into the css.
8
8
  class Compressor
9
9
 
10
- # Mapping from extension to mime-type of all embeddable images.
11
- IMAGE_MIME_TYPES = {
10
+ # Mapping from extension to mime-type of all embeddable assets.
11
+ EMBED_MIME_TYPES = {
12
12
  '.png' => 'image/png',
13
13
  '.jpg' => 'image/jpeg',
14
14
  '.jpeg' => 'image/jpeg',
15
15
  '.gif' => 'image/gif',
16
16
  '.tif' => 'image/tiff',
17
- '.tiff' => 'image/tiff'
17
+ '.tiff' => 'image/tiff',
18
+ '.ttf' => 'font/truetype',
19
+ '.otf' => 'font/opentype'
18
20
  }
19
21
 
22
+ # Font extensions for which we allow embedding:
23
+ EMBED_EXTS = EMBED_MIME_TYPES.keys
24
+ EMBED_FONTS = ['.ttf', '.otf']
20
25
 
21
- IMAGE_DETECTOR = /url\(['"]?([^\s)]+\.(png|jpg|jpeg|gif|tif|tiff))['"]?\)/
22
- IMAGE_EMBED = /[\A\/]embed\//
23
- IMAGE_REPLACER = /url\(__EMBED__([^\s)]+)\)/
26
+ # Maximum size for embeddable images (an IE8 limitation).
27
+ MAX_IMAGE_SIZE = 32.kilobytes
28
+
29
+ # CSS asset-embedding regexes for URL rewriting.
30
+ EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
31
+ EMBEDDABLE = /[\A\/]embed\//
32
+ EMBED_REPLACER = /url\(__EMBED__([^\s)]+)(\?\d+)?\)/
24
33
 
25
34
  # MHTML file constants.
26
35
  MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"JAMMIT_MHTML_SEPARATOR\"\r\n\r\n"
@@ -28,7 +37,7 @@ module Jammit
28
37
  MHTML_END = "*/\r\n"
29
38
 
30
39
  # JST file constants.
31
- JST_START = "(function(){window.JST = window.JST || {};"
40
+ JST_START = "(function(){"
32
41
  JST_END = "})();"
33
42
 
34
43
  COMPRESSORS = {
@@ -45,24 +54,28 @@ module Jammit
45
54
  # the "yui-compressor" gem, or the internal Closure Compiler from the
46
55
  # "closure-compiler" gem.
47
56
  def initialize
48
- @css_compressor = YUI::CssCompressor.new(Jammit.css_compressor_options)
49
- flavor = Jammit.javascript_compressor
50
- @options = DEFAULT_OPTIONS[flavor].merge(Jammit.compressor_options)
57
+ @css_compressor = YUI::CssCompressor.new(Jammit.css_compressor_options || {})
58
+ flavor = Jammit.javascript_compressor || Jammit::DEFAULT_COMPRESSOR
59
+ @options = DEFAULT_OPTIONS[flavor].merge(Jammit.compressor_options || {})
51
60
  @js_compressor = COMPRESSORS[flavor].new(@options)
52
61
  end
53
62
 
54
63
  # Concatenate together a list of JavaScript paths, and pass them through the
55
- # YUI Compressor (with munging enabled).
64
+ # YUI Compressor (with munging enabled). JST can optionally be included.
56
65
  def compress_js(paths)
57
- js = concatenate(paths)
66
+ if (jst_paths = paths.grep(JST_EXT)).empty?
67
+ js = concatenate(paths)
68
+ else
69
+ js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
70
+ end
58
71
  Jammit.compress_assets ? @js_compressor.compress(js) : js
59
72
  end
60
73
 
61
74
  # Concatenate and compress a list of CSS stylesheets. When compressing a
62
75
  # :datauri or :mhtml variant, post-processes the result to embed
63
- # referenced images.
76
+ # referenced assets.
64
77
  def compress_css(paths, variant=nil, asset_url=nil)
65
- css = concatenate_and_tag_images(paths, variant)
78
+ css = concatenate_and_tag_assets(paths, variant)
66
79
  css = @css_compressor.compress(css) if Jammit.compress_assets
67
80
  case variant
68
81
  when nil then return css
@@ -73,52 +86,54 @@ module Jammit
73
86
  end
74
87
 
75
88
  # Compiles a single JST file by writing out a javascript that adds
76
- # template properties to a top-level "window.JST" object. Adds a
89
+ # template properties to a top-level template namespace object. Adds a
77
90
  # JST-compilation function to the top of the package, unless you've
78
91
  # specified your own preferred function, or turned it off.
79
92
  # JST templates are named with the basename of their file.
80
93
  def compile_jst(paths)
81
- compiled = paths.map do |path|
94
+ namespace = Jammit.template_namespace
95
+ compiled = paths.grep(JST_EXT).map do |path|
82
96
  template_name = File.basename(path, File.extname(path))
83
97
  contents = File.read(path).gsub(/\n/, '').gsub("'", '\\\\\'')
84
- "window.JST.#{template_name} = #{Jammit.template_function}('#{contents}');"
98
+ "#{namespace}.#{template_name} = #{Jammit.template_function}('#{contents}');"
85
99
  end
86
100
  compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : '';
87
- [JST_START, compiler, compiled, JST_END].flatten.join("\n")
101
+ setup_namespace = "#{namespace} = #{namespace} || {};"
102
+ [JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
88
103
  end
89
104
 
90
105
 
91
106
  private
92
107
 
93
- # In order to support embedded images from relative paths, we need to
108
+ # In order to support embedded assets from relative paths, we need to
94
109
  # expand the paths before contatenating the CSS together and losing the
95
- # location of the original stylesheet path. Validate the images while we're
110
+ # location of the original stylesheet path. Validate the assets while we're
96
111
  # at it.
97
- def concatenate_and_tag_images(paths, variant=nil)
112
+ def concatenate_and_tag_assets(paths, variant=nil)
98
113
  stylesheets = [paths].flatten.map do |css_path|
99
- File.read(css_path).gsub(IMAGE_DETECTOR) do |url|
114
+ File.read(css_path).gsub(EMBED_DETECTOR) do |url|
100
115
  ipath, cpath = Pathname.new($1), Pathname.new(File.expand_path(css_path))
101
116
  is_url = URI.parse($1).absolute?
102
- is_url ? url : "url(#{rewrite_image_path(ipath, cpath, !!variant)})"
117
+ is_url ? url : "url(#{rewrite_asset_path(ipath, cpath, variant)})"
103
118
  end
104
119
  end
105
120
  stylesheets.join("\n")
106
121
  end
107
122
 
108
- # Re-write all enabled image URLs in a stylesheet with their corresponding
109
- # Data-URI Base-64 encoded image contents.
123
+ # Re-write all enabled asset URLs in a stylesheet with their corresponding
124
+ # Data-URI Base-64 encoded asset contents.
110
125
  def with_data_uris(css)
111
- css.gsub(IMAGE_REPLACER) do |url|
112
- "url(\"data:#{mime_type($1)};base64,#{encoded_contents($1)}\")"
126
+ css.gsub(EMBED_REPLACER) do |url|
127
+ "url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
113
128
  end
114
129
  end
115
130
 
116
- # Re-write all enabled image URLs in a stylesheet with the MHTML equivalent.
131
+ # Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
117
132
  # The newlines ("\r\n") in the following method are critical. Without them
118
133
  # your MHTML will look identical, but won't work.
119
134
  def with_mhtml(css, asset_url)
120
135
  paths, index = {}, 0
121
- css = css.gsub(IMAGE_REPLACER) do |url|
136
+ css = css.gsub(EMBED_REPLACER) do |url|
122
137
  i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
123
138
  "url(mhtml:#{asset_url}!#{i})"
124
139
  end
@@ -129,45 +144,51 @@ module Jammit
129
144
  [MHTML_START, mhtml, MHTML_END, css].flatten.join('')
130
145
  end
131
146
 
132
- # Return a rewritten image URL for a new stylesheet -- the image should
147
+ # Return a rewritten asset URL for a new stylesheet -- the asset should
133
148
  # be tagged for embedding if embeddable, and referenced at the correct level
134
149
  # if relative.
135
- def rewrite_image_path(image_path, css_path, embed=false)
136
- public_path = absolute_path(image_path, css_path)
137
- return "__EMBED__#{public_path}" if embed && embeddable?(public_path)
138
- image_path.absolute? ? image_path.to_s : relative_path(public_path)
150
+ def rewrite_asset_path(asset_path, css_path, variant)
151
+ public_path = absolute_path(asset_path, css_path)
152
+ return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
153
+ asset_path.absolute? ? asset_path.to_s : relative_path(public_path)
139
154
  end
140
155
 
141
- # Get the site-absolute public path for an image file path that may or may
156
+ # Get the site-absolute public path for an asset file path that may or may
142
157
  # not be relative, given the path of the stylesheet that contains it.
143
- def absolute_path(image_pathname, css_pathname)
144
- (image_pathname.absolute? ?
145
- Pathname.new(File.join(PUBLIC_ROOT, image_pathname)) :
146
- css_pathname.dirname + image_pathname).cleanpath
158
+ def absolute_path(asset_pathname, css_pathname)
159
+ (asset_pathname.absolute? ?
160
+ Pathname.new(File.join(PUBLIC_ROOT, asset_pathname)) :
161
+ css_pathname.dirname + asset_pathname).cleanpath
147
162
  end
148
163
 
149
- # CSS images that are referenced by relative paths, and are *not* being
164
+ # CSS assets that are referenced by relative paths, and are *not* being
150
165
  # embedded, must be rewritten relative to the newly-merged stylesheet path.
151
166
  def relative_path(absolute_path)
152
167
  File.join('../', absolute_path.sub(PUBLIC_ROOT, ''))
153
168
  end
154
169
 
155
- # An image is valid for embedding if it exists, is less than 32K, and is
170
+ # An asset is valid for embedding if it exists, is less than 32K, and is
156
171
  # stored somewhere inside of a folder named "embed".
157
172
  # IE does not support Data-URIs larger than 32K, and you probably shouldn't
158
- # be embedding images that large in any case.
159
- def embeddable?(image_path)
160
- image_path.to_s.match(IMAGE_EMBED) && image_path.exist? && image_path.size < 32.kilobytes
173
+ # be embedding assets that large in any case.
174
+ def embeddable?(asset_path, variant)
175
+ font = EMBED_FONTS.include?(asset_path.extname)
176
+ return false unless variant
177
+ return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
178
+ return false unless EMBED_EXTS.include?(asset_path.extname)
179
+ return false unless font || asset_path.size < MAX_IMAGE_SIZE
180
+ return false if font && variant == :mhtml
181
+ true
161
182
  end
162
183
 
163
- # Return the Base64-encoded contents of an image on a single line.
164
- def encoded_contents(image_path)
165
- Base64.encode64(File.read(image_path)).gsub(/\n/, '')
184
+ # Return the Base64-encoded contents of an asset on a single line.
185
+ def encoded_contents(asset_path)
186
+ Base64.encode64(File.read(asset_path)).gsub(/\n/, '')
166
187
  end
167
188
 
168
- # Grab the mime-type of an image, by filename.
169
- def mime_type(image_path)
170
- IMAGE_MIME_TYPES[File.extname(image_path)]
189
+ # Grab the mime-type of an asset, by filename.
190
+ def mime_type(asset_path)
191
+ EMBED_MIME_TYPES[File.extname(asset_path)]
171
192
  end
172
193
 
173
194
  # Concatenate together a list of asset files.
@@ -63,9 +63,9 @@ module Jammit
63
63
  pack = params[:package]
64
64
  @extension = params[:extension].to_sym
65
65
  raise PackageNotFound unless VALID_FORMATS.include?(@extension)
66
- if Jammit.embed_images
66
+ if Jammit.embed_assets
67
67
  suffix_match = pack.match(SUFFIX_STRIPPER)
68
- @variant = Jammit.embed_images && suffix_match && suffix_match[1].to_sym
68
+ @variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
69
69
  pack.sub!(SUFFIX_STRIPPER, '')
70
70
  end
71
71
  @package = pack.to_sym
@@ -1,5 +1,6 @@
1
1
  # Standard Library Dependencies:
2
2
  require 'uri'
3
+ require 'erb'
3
4
  require 'zlib'
4
5
  require 'base64'
5
6
  require 'pathname'
data/lib/jammit/helper.rb CHANGED
@@ -11,13 +11,14 @@ module Jammit
11
11
  MHTML_START = "<!--[if lte IE 7]>"
12
12
  MHTML_END = "<![endif]-->"
13
13
 
14
- # If embed_images is turned on, writes out links to the Data-URI and MHTML
14
+ # If embed_assets is turned on, writes out links to the Data-URI and MHTML
15
15
  # versions of the stylesheet package, otherwise the package is regular
16
16
  # compressed CSS, and in development the stylesheet URLs are passed verbatim.
17
17
  def include_stylesheets(*packages)
18
18
  options = packages.extract_options!
19
19
  return individual_stylesheets(packages, options) unless Jammit.package_assets
20
- return packaged_stylesheets(packages, options) if options.delete(:embed_images) == false || !Jammit.embed_images
20
+ disabled = (options.delete(:embed_assets) == false) || (options.delete(:embed_images) == false)
21
+ return packaged_stylesheets(packages, options) if disabled || !Jammit.embed_assets
21
22
  return embedded_image_stylesheets(packages, options)
22
23
  end
23
24
 
data/lib/jammit/jst.js CHANGED
@@ -1 +1 @@
1
- var template = function(str){var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push(\''+str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g, "$1\r").replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"); return fn;};
1
+ var template = function(str){var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push(\''+str.replace(/[\r\t\n]/g, " ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');"); return fn;};
@@ -42,7 +42,7 @@ module Jammit
42
42
  cacheable(:jst, output_dir).each {|p| cache(p, 'jst', pack_templates(p), output_dir) }
43
43
  cacheable(:css, output_dir).each do |p|
44
44
  cache(p, 'css', pack_stylesheets(p), output_dir)
45
- if Jammit.embed_images
45
+ if Jammit.embed_assets
46
46
  cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
47
47
  if Jammit.mhtml_enabled && base_url
48
48
  mtime = Time.now
@@ -93,6 +93,7 @@ module Jammit
93
93
  # package has gone missing.
94
94
  def package_for(package, extension)
95
95
  pack = @packages[extension] && @packages[extension][package]
96
+ pack ||= @packages[:js] && @packages[:js][package] if extension == :jst
96
97
  pack || not_found(package, extension)
97
98
  end
98
99
 
@@ -126,7 +127,12 @@ module Jammit
126
127
  packages[name] = {}
127
128
  paths = globs.map {|glob| glob_files(glob) }.flatten.uniq
128
129
  packages[name][:paths] = paths
129
- packages[name][:urls] = paths.map {|path| path.sub(PATH_TO_URL, '') }
130
+ if !paths.grep(JS_EXT).empty? && !paths.grep(JST_EXT).empty?
131
+ packages[name][:urls] = paths.grep(JS_EXT).map {|path| path.sub(PATH_TO_URL, '') }
132
+ packages[name][:urls] += [Jammit.asset_url(name, :jst)]
133
+ else
134
+ packages[name][:urls] = paths.map {|path| path.sub(PATH_TO_URL, '') }
135
+ end
130
136
  end
131
137
  packages
132
138
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jammit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Ashkenas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-05 00:00:00 -05:00
12
+ date: 2009-01-26 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency