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 +2 -2
- data/lib/jammit.rb +20 -7
- data/lib/jammit/command_line.rb +1 -1
- data/lib/jammit/compressor.rb +73 -52
- data/lib/jammit/controller.rb +2 -2
- data/lib/jammit/dependencies.rb +1 -0
- data/lib/jammit/helper.rb +3 -2
- data/lib/jammit/jst.js +1 -1
- data/lib/jammit/packager.rb +8 -2
- metadata +2 -2
data/jammit.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'jammit'
|
3
|
-
s.version = '0.
|
4
|
-
s.date = '2009-1-
|
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.
|
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?(
|
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, :
|
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
|
-
|
52
|
-
raise ConfigurationNotFound, "could not find the \"#{config_path}\" configuration file" unless
|
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
|
-
@
|
63
|
+
@embed_assets = conf[:embed_assets] || conf[:embed_images]
|
57
64
|
@compress_assets = !(conf[:compress_assets] == false)
|
58
|
-
@mhtml_enabled = @
|
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'
|
data/lib/jammit/command_line.rb
CHANGED
@@ -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 "
|
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:
|
data/lib/jammit/compressor.rb
CHANGED
@@ -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 "
|
7
|
-
# all stylesheets, with all enabled
|
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
|
11
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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(){
|
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
|
-
|
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
|
76
|
+
# referenced assets.
|
64
77
|
def compress_css(paths, variant=nil, asset_url=nil)
|
65
|
-
css =
|
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
|
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
|
-
|
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
|
-
"
|
98
|
+
"#{namespace}.#{template_name} = #{Jammit.template_function}('#{contents}');"
|
85
99
|
end
|
86
100
|
compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : '';
|
87
|
-
|
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
|
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
|
110
|
+
# location of the original stylesheet path. Validate the assets while we're
|
96
111
|
# at it.
|
97
|
-
def
|
112
|
+
def concatenate_and_tag_assets(paths, variant=nil)
|
98
113
|
stylesheets = [paths].flatten.map do |css_path|
|
99
|
-
File.read(css_path).gsub(
|
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(#{
|
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
|
109
|
-
# Data-URI Base-64 encoded
|
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(
|
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
|
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(
|
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
|
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
|
136
|
-
public_path = absolute_path(
|
137
|
-
return "__EMBED__#{public_path}" if
|
138
|
-
|
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
|
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(
|
144
|
-
(
|
145
|
-
Pathname.new(File.join(PUBLIC_ROOT,
|
146
|
-
css_pathname.dirname +
|
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
|
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
|
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
|
159
|
-
def embeddable?(
|
160
|
-
|
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
|
164
|
-
def encoded_contents(
|
165
|
-
Base64.encode64(File.read(
|
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
|
169
|
-
def mime_type(
|
170
|
-
|
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.
|
data/lib/jammit/controller.rb
CHANGED
@@ -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.
|
66
|
+
if Jammit.embed_assets
|
67
67
|
suffix_match = pack.match(SUFFIX_STRIPPER)
|
68
|
-
@variant = Jammit.
|
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
|
data/lib/jammit/dependencies.rb
CHANGED
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
|
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
|
-
|
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("
|
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;};
|
data/lib/jammit/packager.rb
CHANGED
@@ -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.
|
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
|
-
|
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.
|
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-
|
12
|
+
date: 2009-01-26 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|