jammit 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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