ee_jammit 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +22 -0
- data/README +24 -0
- data/bin/jammit +7 -0
- data/jammit.gemspec +34 -0
- data/lib/jammit.rb +236 -0
- data/lib/jammit/command_line.rb +84 -0
- data/lib/jammit/compressor.rb +266 -0
- data/lib/jammit/controller.rb +97 -0
- data/lib/jammit/cssmin_compressor.rb +10 -0
- data/lib/jammit/dependencies.rb +61 -0
- data/lib/jammit/helper.rb +88 -0
- data/lib/jammit/jsmin_compressor.rb +10 -0
- data/lib/jammit/jst.js +1 -0
- data/lib/jammit/middleware.rb +115 -0
- data/lib/jammit/packager.rb +179 -0
- data/lib/jammit/railtie.rb +14 -0
- data/lib/jammit/routes.rb +23 -0
- data/lib/jammit/sass_compressor.rb +17 -0
- data/lib/jammit/uglifier.rb +3 -0
- data/rails/routes.rb +10 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmU3NDMxYmYxYTg0M2QzZDA0MTQ0ZGE1NmNjNTJiMzU2ZjA2ZjljMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YWE3MmU3ZDQyYmI5NmM5MzNkNjU2ZmFjMjU2Y2Q4NTIyNDZkZGQ0Zg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDYyMTBmY2Y0OWYwYmIzNzUxZWViMDNmYTliZTI0YTRiZjNkNjk5MWE2ZTU2
|
10
|
+
MjNlOThhN2E2ZjZlMGQ5MWVmMzBmNmQ3NzE5M2ZlNjI2YzA0NzBmZGI5YTdj
|
11
|
+
MWFiNDAyMmNhNzFhOTdkNmQ0MWZjODliMTgyMGU5MTMzZjk1MWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YmE5NzA1M2E5N2ZhODc0MjVmZWU2NTk1ZDgwNDdkZThmMDRlNDdkODM4M2Zh
|
14
|
+
NGNjY2E4NTc4YTI5NGRmNWMzYWUwNGE1MmNkMWU4ZDliZmM5YTUxMmIzZTU4
|
15
|
+
NTU0YWJlY2RmOWVjMTA5NmU1NDkwMzY0MjE0YTBiNDY0ZjRiNTE=
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009-2011 Jeremy Ashkenas, DocumentCloud
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
==
|
2
|
+
_ _ __ __ __ __ ___ _____
|
3
|
+
_ | |/_\ | \/ | \/ |_ _|_ _|
|
4
|
+
| || / _ \| |\/| | |\/| || | | |
|
5
|
+
\__/_/ \_\_| |_|_| |_|___| |_|
|
6
|
+
|
7
|
+
|
8
|
+
Jammit is an industrial strength asset packaging library for Rails,
|
9
|
+
providing both the CSS and JavaScript concatenation and compression
|
10
|
+
that you'd expect, as well as ahead-of-time gzipping, built-in JavaScript
|
11
|
+
template support, and optional Data-URI / MHTML image embedding.
|
12
|
+
|
13
|
+
Installation:
|
14
|
+
gem install jammit
|
15
|
+
|
16
|
+
For documentation, usage, and examples, see:
|
17
|
+
http://documentcloud.github.com/jammit/
|
18
|
+
|
19
|
+
To suggest a feature or report a bug:
|
20
|
+
http://github.com/documentcloud/jammit/issues/
|
21
|
+
|
22
|
+
For internal source docs, see:
|
23
|
+
http://documentcloud.github.com/jammit/doc/
|
24
|
+
|
data/bin/jammit
ADDED
data/jammit.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'ee_jammit'
|
3
|
+
s.version = '0.6.6' # Keep version in sync with jammit.rb
|
4
|
+
s.date = '2011-11-30'
|
5
|
+
|
6
|
+
s.homepage = "http://documentcloud.github.com/jammit/"
|
7
|
+
s.summary = "Industrial Strength Asset Packaging for Rails"
|
8
|
+
s.description = <<-EOS
|
9
|
+
Enphase Fork to get the Rails 4 route compatibility
|
10
|
+
Jammit is an industrial strength asset packaging library for Rails,
|
11
|
+
providing both the CSS and JavaScript concatenation and compression that
|
12
|
+
you'd expect, as well as YUI Compressor and Closure Compiler compatibility,
|
13
|
+
ahead-of-time gzipping, built-in JavaScript template support, and optional
|
14
|
+
Data-URI / MHTML image embedding.
|
15
|
+
EOS
|
16
|
+
|
17
|
+
s.authors = ['Jeremy Ashkenas','Brenda Strech']
|
18
|
+
s.email = 'jeremy@documentcloud.org'
|
19
|
+
s.rubyforge_project = 'jammit'
|
20
|
+
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
s.executables = ['jammit']
|
23
|
+
|
24
|
+
s.extra_rdoc_files = ['README']
|
25
|
+
s.rdoc_options << '--title' << 'Jammit' <<
|
26
|
+
'--exclude' << 'test' <<
|
27
|
+
'--main' << 'README' <<
|
28
|
+
'--all'
|
29
|
+
|
30
|
+
s.add_dependency 'cssmin', ['>= 1.0.2']
|
31
|
+
s.add_dependency 'jsmin', ['>= 1.0.1']
|
32
|
+
|
33
|
+
s.files = Dir['lib/**/*', 'bin/*', 'rails/*', 'jammit.gemspec', 'LICENSE', 'README']
|
34
|
+
end
|
data/lib/jammit.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# @Jammit@ is the central namespace for all Jammit classes, and provides access
|
4
|
+
# to all of the configuration options.
|
5
|
+
module Jammit
|
6
|
+
|
7
|
+
VERSION = "0.6.6"
|
8
|
+
|
9
|
+
ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
10
|
+
|
11
|
+
ASSET_ROOT = File.expand_path((defined?(Rails) && Rails.root.to_s.length > 0) ? Rails.root : ENV['RAILS_ROOT'] || ".") unless defined?(ASSET_ROOT)
|
12
|
+
|
13
|
+
DEFAULT_PUBLIC_ROOT = (defined?(Rails) && Rails.public_path.to_s.length > 0) ? Rails.public_path : File.join(ASSET_ROOT, 'public') unless defined?(PUBLIC_ROOT)
|
14
|
+
|
15
|
+
DEFAULT_CONFIG_PATH = File.join(ASSET_ROOT, 'config', 'assets.yml')
|
16
|
+
|
17
|
+
DEFAULT_PACKAGE_PATH = "assets"
|
18
|
+
|
19
|
+
DEFAULT_JST_SCRIPT = File.join(ROOT, 'lib/jammit/jst.js')
|
20
|
+
|
21
|
+
DEFAULT_JST_COMPILER = "template"
|
22
|
+
|
23
|
+
DEFAULT_JST_NAMESPACE = "window.JST"
|
24
|
+
|
25
|
+
JAVASCRIPT_COMPRESSORS = [:jsmin, :yui, :closure, :uglifier]
|
26
|
+
|
27
|
+
DEFAULT_JAVASCRIPT_COMPRESSOR = :jsmin
|
28
|
+
|
29
|
+
CSS_COMPRESSORS = [:cssmin, :yui, :sass]
|
30
|
+
|
31
|
+
DEFAULT_CSS_COMPRESSOR = :cssmin
|
32
|
+
|
33
|
+
# Extension matchers for JavaScript and JST, which need to be disambiguated.
|
34
|
+
JS_EXTENSION = /\.js\Z/
|
35
|
+
DEFAULT_JST_EXTENSION = "jst"
|
36
|
+
|
37
|
+
# Jammit raises a @PackageNotFound@ exception when a non-existent package is
|
38
|
+
# requested by a browser -- rendering a 404.
|
39
|
+
class PackageNotFound < NameError; end
|
40
|
+
|
41
|
+
# Jammit raises a MissingConfiguration exception when you try to load the
|
42
|
+
# configuration of an assets.yml file that doesn't exist, or are missing
|
43
|
+
# a piece of required configuration.
|
44
|
+
class MissingConfiguration < NameError; end
|
45
|
+
|
46
|
+
# Jammit raises an OutputNotWritable exception if the output directory for
|
47
|
+
# cached packages is locked.
|
48
|
+
class OutputNotWritable < StandardError; end
|
49
|
+
|
50
|
+
# Jammit raises a DeprecationError if you try to use an outdated feature.
|
51
|
+
class DeprecationError < StandardError; end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
attr_reader :configuration, :template_function, :template_namespace,
|
55
|
+
:embed_assets, :package_assets, :compress_assets, :gzip_assets,
|
56
|
+
:package_path, :mhtml_enabled, :include_jst_script, :config_path,
|
57
|
+
:javascript_compressor, :compressor_options, :css_compressor,
|
58
|
+
:css_compressor_options, :template_extension,
|
59
|
+
:template_extension_matcher, :allow_debugging,
|
60
|
+
:rewrite_relative_paths, :public_root
|
61
|
+
attr_accessor :javascript_compressors, :css_compressors
|
62
|
+
end
|
63
|
+
|
64
|
+
# The minimal required configuration.
|
65
|
+
@configuration = {}
|
66
|
+
@public_root = DEFAULT_PUBLIC_ROOT
|
67
|
+
@package_path = DEFAULT_PACKAGE_PATH
|
68
|
+
|
69
|
+
@javascript_compressors = JAVASCRIPT_COMPRESSORS
|
70
|
+
@css_compressors = CSS_COMPRESSORS
|
71
|
+
|
72
|
+
# Load the complete asset configuration from the specified @config_path@.
|
73
|
+
# If we're loading softly, don't let missing configuration error out.
|
74
|
+
def self.load_configuration(config_path, soft=false)
|
75
|
+
exists = config_path && File.exists?(config_path)
|
76
|
+
return false if soft && !exists
|
77
|
+
raise MissingConfiguration, "could not find the \"#{config_path}\" configuration file" unless exists
|
78
|
+
conf = YAML.load(ERB.new(File.read(config_path)).result)
|
79
|
+
|
80
|
+
# Optionally overwrite configuration based on the environment.
|
81
|
+
rails_env = (defined?(Rails) ? ::Rails.env : ENV['RAILS_ENV'] || "development")
|
82
|
+
conf.merge! conf.delete rails_env if conf.has_key? rails_env
|
83
|
+
|
84
|
+
@config_path = config_path
|
85
|
+
@configuration = symbolize_keys(conf)
|
86
|
+
@package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
|
87
|
+
@embed_assets = conf[:embed_assets] || conf[:embed_images]
|
88
|
+
@compress_assets = !(conf[:compress_assets] == false)
|
89
|
+
@rewrite_relative_paths = !(conf[:rewrite_relative_paths] == false)
|
90
|
+
@gzip_assets = !(conf[:gzip_assets] == false)
|
91
|
+
@allow_debugging = !(conf[:allow_debugging] == false)
|
92
|
+
@mhtml_enabled = @embed_assets && @embed_assets != "datauri"
|
93
|
+
@compressor_options = symbolize_keys(conf[:compressor_options] || {})
|
94
|
+
@css_compressor_options = symbolize_keys(conf[:css_compressor_options] || {})
|
95
|
+
set_javascript_compressor(conf[:javascript_compressor])
|
96
|
+
set_css_compressor(conf[:css_compressor])
|
97
|
+
set_package_assets(conf[:package_assets])
|
98
|
+
set_template_function(conf[:template_function])
|
99
|
+
set_template_namespace(conf[:template_namespace])
|
100
|
+
set_template_extension(conf[:template_extension])
|
101
|
+
set_public_root(conf[:public_root]) if conf[:public_root]
|
102
|
+
symbolize_keys(conf[:stylesheets]) if conf[:stylesheets]
|
103
|
+
symbolize_keys(conf[:javascripts]) if conf[:javascripts]
|
104
|
+
check_for_deprecations
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Force a reload by resetting the Packager and reloading the configuration.
|
109
|
+
# In development, this will be called as a before_filter before every request.
|
110
|
+
def self.reload!
|
111
|
+
Thread.current[:jammit_packager] = nil
|
112
|
+
load_configuration(@config_path)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Keep a global (thread-local) reference to a @Jammit::Packager@, to avoid
|
116
|
+
# recomputing asset lists unnecessarily.
|
117
|
+
def self.packager
|
118
|
+
Thread.current[:jammit_packager] ||= Packager.new
|
119
|
+
end
|
120
|
+
|
121
|
+
# Generate the base filename for a version of a given package.
|
122
|
+
def self.filename(package, extension, suffix=nil)
|
123
|
+
suffix_part = suffix ? "-#{suffix}" : ''
|
124
|
+
"#{package}#{suffix_part}.#{extension}"
|
125
|
+
end
|
126
|
+
|
127
|
+
# Generates the server-absolute URL to an asset package.
|
128
|
+
def self.asset_url(package, extension, suffix=nil, mtime=nil)
|
129
|
+
timestamp = mtime ? "?#{mtime.to_i}" : ''
|
130
|
+
"/#{package_path}/#{filename(package, extension, suffix)}#{timestamp}"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Convenience method for packaging up Jammit, using the default options.
|
134
|
+
def self.package!(options={})
|
135
|
+
options = {
|
136
|
+
:config_path => Jammit::DEFAULT_CONFIG_PATH,
|
137
|
+
:output_folder => nil,
|
138
|
+
:base_url => nil,
|
139
|
+
:public_root => nil,
|
140
|
+
:force => false
|
141
|
+
}.merge(options)
|
142
|
+
load_configuration(options[:config_path])
|
143
|
+
set_public_root(options[:public_root]) if options[:public_root]
|
144
|
+
packager.force = options[:force]
|
145
|
+
packager.package_names = options[:package_names]
|
146
|
+
packager.precache_all(options[:output_folder], options[:base_url])
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# Allows command-line definition of `PUBLIC_ROOT`, for those using Jammit
|
152
|
+
# outside of Rails.
|
153
|
+
def self.set_public_root(public_root=nil)
|
154
|
+
@public_root = public_root if public_root
|
155
|
+
end
|
156
|
+
|
157
|
+
# Ensure that the JavaScript compressor is a valid choice.
|
158
|
+
def self.set_javascript_compressor(value)
|
159
|
+
value = value && value.to_sym
|
160
|
+
@javascript_compressor = javascript_compressors.include?(value) ? value : DEFAULT_JAVASCRIPT_COMPRESSOR
|
161
|
+
end
|
162
|
+
|
163
|
+
# Ensure that the CSS compressor is a valid choice.
|
164
|
+
def self.set_css_compressor(value)
|
165
|
+
value = value && value.to_sym
|
166
|
+
@css_compressor = css_compressors.include?(value) ? value : DEFAULT_CSS_COMPRESSOR
|
167
|
+
end
|
168
|
+
|
169
|
+
# Turn asset packaging on or off, depending on configuration and environment.
|
170
|
+
def self.set_package_assets(value)
|
171
|
+
package_env = !defined?(Rails) || (!Rails.env.development? && !Rails.env.test?)
|
172
|
+
@package_assets = value == true || value.nil? ? package_env :
|
173
|
+
value == 'always' ? true : false
|
174
|
+
end
|
175
|
+
|
176
|
+
# Assign the JST template function, unless explicitly turned off.
|
177
|
+
def self.set_template_function(value)
|
178
|
+
@template_function = value == true || value.nil? ? DEFAULT_JST_COMPILER :
|
179
|
+
value == false ? '' : value
|
180
|
+
@include_jst_script = @template_function == DEFAULT_JST_COMPILER
|
181
|
+
end
|
182
|
+
|
183
|
+
# Set the root JS object in which to stash all compiled JST.
|
184
|
+
def self.set_template_namespace(value)
|
185
|
+
@template_namespace = value == true || value.nil? ? DEFAULT_JST_NAMESPACE : value.to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the extension for JS templates.
|
189
|
+
def self.set_template_extension(value)
|
190
|
+
@template_extension = (value == true || value.nil? ? DEFAULT_JST_EXTENSION : value.to_s).gsub(/\A\.?(.*)\Z/, '\1')
|
191
|
+
@template_extension_matcher = /\.#{Regexp.escape(@template_extension)}\Z/
|
192
|
+
end
|
193
|
+
|
194
|
+
# The YUI Compressor requires Java > 1.4, and Closure requires Java > 1.6.
|
195
|
+
def self.check_java_version
|
196
|
+
return true if @checked_java_version
|
197
|
+
java = @compressor_options[:java] || 'java'
|
198
|
+
@css_compressor_options[:java] ||= java if @compressor_options[:java]
|
199
|
+
version = (`#{java} -version 2>&1`)[/\d+\.\d+/]
|
200
|
+
disable_compression if !version ||
|
201
|
+
(@javascript_compressor == :closure && version < '1.6') ||
|
202
|
+
(@javascript_compressor == :yui && version < '1.4')
|
203
|
+
@checked_java_version = true
|
204
|
+
end
|
205
|
+
|
206
|
+
# If we don't have a working Java VM, then disable asset compression and
|
207
|
+
# complain loudly.
|
208
|
+
def self.disable_compression
|
209
|
+
@compress_assets = false
|
210
|
+
warn("Asset compression disabled -- Java unavailable.")
|
211
|
+
end
|
212
|
+
|
213
|
+
# Jammit 0.5+ no longer supports separate template packages.
|
214
|
+
def self.check_for_deprecations
|
215
|
+
if @configuration[:templates]
|
216
|
+
raise DeprecationError, "Jammit 0.5+ no longer supports separate packages for templates.\nPlease fold your templates into the appropriate 'javascripts' package instead."
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.warn(message)
|
221
|
+
message = "Jammit Warning: #{message}"
|
222
|
+
$stderr.puts message
|
223
|
+
end
|
224
|
+
|
225
|
+
# Clone of active_support's symbolize_keys, so that we don't have to depend
|
226
|
+
# on active_support in any fashion. Converts a hash's keys to all symbols.
|
227
|
+
def self.symbolize_keys(hash)
|
228
|
+
hash.keys.each do |key|
|
229
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
230
|
+
end
|
231
|
+
hash
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
require 'jammit/dependencies'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../jammit')
|
3
|
+
|
4
|
+
module Jammit
|
5
|
+
|
6
|
+
# The @CommandLine@ is able to compress, pre-package, and pre-gzip all the
|
7
|
+
# assets specified in the configuration file, in order to avoid an initial
|
8
|
+
# round of slow requests after a fresh deployment.
|
9
|
+
class CommandLine
|
10
|
+
|
11
|
+
BANNER = <<-EOS
|
12
|
+
|
13
|
+
Usage: jammit OPTIONS
|
14
|
+
|
15
|
+
Run jammit inside a Rails application to compresses all JS, CSS,
|
16
|
+
and JST according to config/assets.yml, saving the packaged
|
17
|
+
files and corresponding gzipped versions.
|
18
|
+
|
19
|
+
If you're using "embed_assets", and you wish to precompile the
|
20
|
+
MHTML stylesheet variants, you must specify the "base-url".
|
21
|
+
|
22
|
+
Options:
|
23
|
+
EOS
|
24
|
+
|
25
|
+
# The @Jammit::CommandLine@ runs from the contents of @ARGV@.
|
26
|
+
def initialize
|
27
|
+
parse_options
|
28
|
+
ensure_configuration_file
|
29
|
+
Jammit.package!(@options)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Make sure that we have a readable configuration file. The @jammit@
|
36
|
+
# command can't run without one.
|
37
|
+
def ensure_configuration_file
|
38
|
+
config = @options[:config_path]
|
39
|
+
return true if File.exists?(config) && File.readable?(config)
|
40
|
+
puts "Could not find the asset configuration file \"#{config}\""
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Uses @OptionParser@ to grab the options: *--output*, *--config*, and
|
45
|
+
# *--base-url*...
|
46
|
+
def parse_options
|
47
|
+
@options = {
|
48
|
+
:config_path => Jammit::DEFAULT_CONFIG_PATH,
|
49
|
+
:output_folder => nil,
|
50
|
+
:base_url => nil,
|
51
|
+
:force => false
|
52
|
+
}
|
53
|
+
@option_parser = OptionParser.new do |opts|
|
54
|
+
opts.on('-o', '--output PATH', 'output folder for packages (default: "public/assets")') do |output_folder|
|
55
|
+
@options[:output_folder] = output_folder
|
56
|
+
end
|
57
|
+
opts.on('-c', '--config PATH', 'path to assets.yml (default: "config/assets.yml")') do |config_path|
|
58
|
+
@options[:config_path] = config_path
|
59
|
+
end
|
60
|
+
opts.on('-u', '--base-url URL', 'base URL for MHTML (ex: "http://example.com")') do |base_url|
|
61
|
+
@options[:base_url] = base_url
|
62
|
+
end
|
63
|
+
opts.on('-f', '--force', 'force a rebuild of all assets') do |force|
|
64
|
+
@options[:force] = force
|
65
|
+
end
|
66
|
+
opts.on('-p', '--packages LIST', 'list of packages to build (ex: "core,ui", default: all)') do |package_names|
|
67
|
+
@options[:package_names] = package_names.split(/,\s*/).map {|n| n.to_sym }
|
68
|
+
end
|
69
|
+
opts.on('-P', '--public-root PATH', 'path to public assets (default: "public")') do |public_root|
|
70
|
+
puts "Option for PUBLIC_ROOT"
|
71
|
+
@options[:public_root] = public_root
|
72
|
+
end
|
73
|
+
opts.on_tail('-v', '--version', 'display Jammit version') do
|
74
|
+
puts "Jammit version #{Jammit::VERSION}"
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@option_parser.banner = BANNER
|
79
|
+
@option_parser.parse!(ARGV)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module Jammit
|
2
|
+
|
3
|
+
# Uses the YUI Compressor or Closure Compiler to compress JavaScript.
|
4
|
+
# Always uses YUI to compress CSS (Which means that Java must be installed.)
|
5
|
+
# Also knows how to create a concatenated JST file.
|
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
|
+
class Compressor
|
9
|
+
|
10
|
+
# Mapping from extension to mime-type of all embeddable assets.
|
11
|
+
EMBED_MIME_TYPES = {
|
12
|
+
'.png' => 'image/png',
|
13
|
+
'.jpg' => 'image/jpeg',
|
14
|
+
'.jpeg' => 'image/jpeg',
|
15
|
+
'.gif' => 'image/gif',
|
16
|
+
'.tif' => 'image/tiff',
|
17
|
+
'.tiff' => 'image/tiff',
|
18
|
+
'.ttf' => 'application/x-font-ttf',
|
19
|
+
'.otf' => 'font/opentype',
|
20
|
+
'.woff' => 'application/x-font-woff'
|
21
|
+
}
|
22
|
+
|
23
|
+
# Font extensions for which we allow embedding:
|
24
|
+
EMBED_EXTS = EMBED_MIME_TYPES.keys
|
25
|
+
EMBED_FONTS = ['.ttf', '.otf', '.woff']
|
26
|
+
|
27
|
+
# (32k - padding) maximum length for data-uri assets (an IE8 limitation).
|
28
|
+
MAX_IMAGE_SIZE = 32700
|
29
|
+
|
30
|
+
# CSS asset-embedding regexes for URL rewriting.
|
31
|
+
EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
32
|
+
EMBEDDABLE = /[\A\/]embed\//
|
33
|
+
EMBED_REPLACER = /url\(__EMBED__(.+?)(\?\d+)?\)/
|
34
|
+
|
35
|
+
# MHTML file constants.
|
36
|
+
MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"MHTML_MARK\"\r\n\r\n"
|
37
|
+
MHTML_SEPARATOR = "--MHTML_MARK\r\n"
|
38
|
+
MHTML_END = "\r\n--MHTML_MARK--\r\n*/\r\n"
|
39
|
+
|
40
|
+
# JST file constants.
|
41
|
+
JST_START = "(function(){"
|
42
|
+
JST_END = "})();"
|
43
|
+
|
44
|
+
JAVASCRIPT_COMPRESSORS = {
|
45
|
+
:jsmin => Jammit.javascript_compressors.include?(:jsmin) ? Jammit::JsminCompressor : nil,
|
46
|
+
:yui => Jammit.javascript_compressors.include?(:yui) ? YUI::JavaScriptCompressor : nil,
|
47
|
+
:closure => Jammit.javascript_compressors.include?(:closure) ? Closure::Compiler : nil,
|
48
|
+
:uglifier => Jammit.javascript_compressors.include?(:uglifier) ? Jammit::Uglifier : nil
|
49
|
+
}
|
50
|
+
|
51
|
+
CSS_COMPRESSORS = {
|
52
|
+
:cssmin => Jammit.css_compressors.include?(:cssmin) ? Jammit::CssminCompressor : nil,
|
53
|
+
:yui => Jammit.css_compressors.include?(:yui) ? YUI::CssCompressor : nil,
|
54
|
+
:sass => Jammit.css_compressors.include?(:sass) ? Jammit::SassCompressor : nil
|
55
|
+
}
|
56
|
+
|
57
|
+
JAVASCRIPT_DEFAULT_OPTIONS = {
|
58
|
+
:jsmin => {},
|
59
|
+
:yui => {:munge => true},
|
60
|
+
:closure => {},
|
61
|
+
:uglifier => {:copyright => false}
|
62
|
+
}
|
63
|
+
|
64
|
+
# CSS compression can be provided with YUI Compressor or sass. JS
|
65
|
+
# compression can be provided with YUI Compressor, Google Closure
|
66
|
+
# Compiler or UglifyJS.
|
67
|
+
def initialize
|
68
|
+
if Jammit.javascript_compressors.include?(:yui) || Jammit.javascript_compressors.include?(:closure) || Jammit.css_compressors.include?(:yui)
|
69
|
+
Jammit.check_java_version
|
70
|
+
end
|
71
|
+
|
72
|
+
css_flavor = Jammit.css_compressor || Jammit::DEFAULT_CSS_COMPRESSOR
|
73
|
+
@css_compressor = CSS_COMPRESSORS[css_flavor].new(Jammit.css_compressor_options || {})
|
74
|
+
js_flavor = Jammit.javascript_compressor || Jammit::DEFAULT_JAVASCRIPT_COMPRESSOR
|
75
|
+
@options = JAVASCRIPT_DEFAULT_OPTIONS[js_flavor].merge(Jammit.compressor_options || {})
|
76
|
+
@js_compressor = JAVASCRIPT_COMPRESSORS[js_flavor].new(@options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Concatenate together a list of JavaScript paths, and pass them through the
|
80
|
+
# YUI Compressor (with munging enabled). JST can optionally be included.
|
81
|
+
def compress_js(paths)
|
82
|
+
if (jst_paths = paths.grep(Jammit.template_extension_matcher)).empty?
|
83
|
+
js = concatenate(paths)
|
84
|
+
else
|
85
|
+
js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
|
86
|
+
end
|
87
|
+
Jammit.compress_assets ? @js_compressor.compress(js) : js
|
88
|
+
end
|
89
|
+
|
90
|
+
# Concatenate and compress a list of CSS stylesheets. When compressing a
|
91
|
+
# :datauri or :mhtml variant, post-processes the result to embed
|
92
|
+
# referenced assets.
|
93
|
+
def compress_css(paths, variant=nil, asset_url=nil)
|
94
|
+
@asset_contents = {}
|
95
|
+
css = concatenate_and_tag_assets(paths, variant)
|
96
|
+
css = @css_compressor.compress(css) if Jammit.compress_assets
|
97
|
+
case variant
|
98
|
+
when nil then return css
|
99
|
+
when :datauri then return with_data_uris(css)
|
100
|
+
when :mhtml then return with_mhtml(css, asset_url)
|
101
|
+
else raise PackageNotFound, "\"#{variant}\" is not a valid stylesheet variant"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Compiles a single JST file by writing out a javascript that adds
|
106
|
+
# template properties to a top-level template namespace object. Adds a
|
107
|
+
# JST-compilation function to the top of the package, unless you've
|
108
|
+
# specified your own preferred function, or turned it off.
|
109
|
+
# JST templates are named with the basename of their file.
|
110
|
+
def compile_jst(paths)
|
111
|
+
namespace = Jammit.template_namespace
|
112
|
+
paths = paths.grep(Jammit.template_extension_matcher).sort
|
113
|
+
base_path = find_base_path(paths)
|
114
|
+
compiled = paths.map do |path|
|
115
|
+
contents = read_binary_file(path)
|
116
|
+
contents = contents.gsub(/\r?\n/, "\\n").gsub("'", '\\\\\'')
|
117
|
+
name = template_name(path, base_path)
|
118
|
+
"#{namespace}['#{name}'] = #{Jammit.template_function}('#{contents}');"
|
119
|
+
end
|
120
|
+
compiler = Jammit.include_jst_script ? read_binary_file(DEFAULT_JST_SCRIPT) : '';
|
121
|
+
setup_namespace = "#{namespace} = #{namespace} || {};"
|
122
|
+
[JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# Given a set of paths, find a common prefix path.
|
129
|
+
def find_base_path(paths)
|
130
|
+
return nil if paths.length <= 1
|
131
|
+
paths.sort!
|
132
|
+
first = paths.first.split('/')
|
133
|
+
last = paths.last.split('/')
|
134
|
+
i = 0
|
135
|
+
while first[i] == last[i] && i <= first.length
|
136
|
+
i += 1
|
137
|
+
end
|
138
|
+
res = first.slice(0, i).join('/')
|
139
|
+
res.empty? ? nil : res
|
140
|
+
end
|
141
|
+
|
142
|
+
# Determine the name of a JS template. If there's a common base path, use
|
143
|
+
# the namespaced prefix. Otherwise, simply use the filename.
|
144
|
+
def template_name(path, base_path)
|
145
|
+
return File.basename(path, ".#{Jammit.template_extension}") unless base_path
|
146
|
+
path.gsub(/\A#{Regexp.escape(base_path)}\/(.*)\.#{Jammit.template_extension}\Z/, '\1')
|
147
|
+
end
|
148
|
+
|
149
|
+
# In order to support embedded assets from relative paths, we need to
|
150
|
+
# expand the paths before contatenating the CSS together and losing the
|
151
|
+
# location of the original stylesheet path. Validate the assets while we're
|
152
|
+
# at it.
|
153
|
+
def concatenate_and_tag_assets(paths, variant=nil)
|
154
|
+
stylesheets = [paths].flatten.map do |css_path|
|
155
|
+
contents = read_binary_file(css_path)
|
156
|
+
contents.gsub(EMBED_DETECTOR) do |url|
|
157
|
+
ipath, cpath = Pathname.new($1), Pathname.new(File.expand_path(css_path))
|
158
|
+
is_url = URI.parse($1).absolute?
|
159
|
+
is_url ? url : "url(#{construct_asset_path(ipath, cpath, variant)})"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
stylesheets.join("\n")
|
163
|
+
end
|
164
|
+
|
165
|
+
# Re-write all enabled asset URLs in a stylesheet with their corresponding
|
166
|
+
# Data-URI Base-64 encoded asset contents.
|
167
|
+
def with_data_uris(css)
|
168
|
+
css.gsub(EMBED_REPLACER) do |url|
|
169
|
+
"url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
|
174
|
+
# The newlines ("\r\n") in the following method are critical. Without them
|
175
|
+
# your MHTML will look identical, but won't work.
|
176
|
+
def with_mhtml(css, asset_url)
|
177
|
+
paths, index = {}, 0
|
178
|
+
css = css.gsub(EMBED_REPLACER) do |url|
|
179
|
+
i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
|
180
|
+
"url(mhtml:#{asset_url}!#{i})"
|
181
|
+
end
|
182
|
+
mhtml = paths.sort.map do |path, identifier|
|
183
|
+
mime, contents = mime_type(path), encoded_contents(path)
|
184
|
+
[MHTML_SEPARATOR, "Content-Location: #{identifier}\r\n", "Content-Type: #{mime}\r\n", "Content-Transfer-Encoding: base64\r\n\r\n", contents, "\r\n"]
|
185
|
+
end
|
186
|
+
[MHTML_START, mhtml, MHTML_END, css].flatten.join('')
|
187
|
+
end
|
188
|
+
|
189
|
+
# Return a rewritten asset URL for a new stylesheet -- the asset should
|
190
|
+
# be tagged for embedding if embeddable, and referenced at the correct level
|
191
|
+
# if relative.
|
192
|
+
def construct_asset_path(asset_path, css_path, variant)
|
193
|
+
public_path = absolute_path(asset_path, css_path)
|
194
|
+
return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
|
195
|
+
source = asset_path.absolute? || ! Jammit.rewrite_relative_paths ? asset_path.to_s : relative_path(public_path)
|
196
|
+
rewrite_asset_path(source, public_path)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Get the site-absolute public path for an asset file path that may or may
|
200
|
+
# not be relative, given the path of the stylesheet that contains it.
|
201
|
+
def absolute_path(asset_pathname, css_pathname)
|
202
|
+
(asset_pathname.absolute? ?
|
203
|
+
Pathname.new(File.join(Jammit.public_root, asset_pathname)) :
|
204
|
+
css_pathname.dirname + asset_pathname).cleanpath
|
205
|
+
end
|
206
|
+
|
207
|
+
# CSS assets that are referenced by relative paths, and are *not* being
|
208
|
+
# embedded, must be rewritten relative to the newly-merged stylesheet path.
|
209
|
+
def relative_path(absolute_path)
|
210
|
+
File.join('../', absolute_path.sub(Jammit.public_root, ''))
|
211
|
+
end
|
212
|
+
|
213
|
+
# Similar to the AssetTagHelper's method of the same name, this will
|
214
|
+
# append the RAILS_ASSET_ID cache-buster to URLs, if it's defined.
|
215
|
+
def rewrite_asset_path(path, file_path)
|
216
|
+
asset_id = rails_asset_id(file_path)
|
217
|
+
(!asset_id || asset_id == '') ? path : "#{path}?#{asset_id}"
|
218
|
+
end
|
219
|
+
|
220
|
+
# Similar to the AssetTagHelper's method of the same name, this will
|
221
|
+
# determine the correct asset id for a file.
|
222
|
+
def rails_asset_id(path)
|
223
|
+
asset_id = ENV["RAILS_ASSET_ID"]
|
224
|
+
return asset_id if asset_id
|
225
|
+
File.exists?(path) ? File.mtime(path).to_i.to_s : ''
|
226
|
+
end
|
227
|
+
|
228
|
+
# An asset is valid for embedding if it exists, is less than 32K, and is
|
229
|
+
# stored somewhere inside of a folder named "embed". IE does not support
|
230
|
+
# Data-URIs larger than 32K, and you probably shouldn't be embedding assets
|
231
|
+
# that large in any case. Because we need to check the base64 length here,
|
232
|
+
# save it so that we don't have to compute it again later.
|
233
|
+
def embeddable?(asset_path, variant)
|
234
|
+
font = EMBED_FONTS.include?(asset_path.extname)
|
235
|
+
return false unless variant
|
236
|
+
return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
|
237
|
+
return false unless EMBED_EXTS.include?(asset_path.extname)
|
238
|
+
return false unless font || encoded_contents(asset_path).length < MAX_IMAGE_SIZE
|
239
|
+
return false if font && variant == :mhtml
|
240
|
+
return true
|
241
|
+
end
|
242
|
+
|
243
|
+
# Return the Base64-encoded contents of an asset on a single line.
|
244
|
+
def encoded_contents(asset_path)
|
245
|
+
return @asset_contents[asset_path] if @asset_contents[asset_path]
|
246
|
+
data = read_binary_file(asset_path)
|
247
|
+
@asset_contents[asset_path] = Base64.encode64(data).gsub(/\n/, '')
|
248
|
+
end
|
249
|
+
|
250
|
+
# Grab the mime-type of an asset, by filename.
|
251
|
+
def mime_type(asset_path)
|
252
|
+
EMBED_MIME_TYPES[File.extname(asset_path)]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Concatenate together a list of asset files.
|
256
|
+
def concatenate(paths)
|
257
|
+
[paths].flatten.map {|p| read_binary_file(p) }.join("\n")
|
258
|
+
end
|
259
|
+
|
260
|
+
# `File.read`, but in "binary" mode.
|
261
|
+
def read_binary_file(path)
|
262
|
+
File.open(path, 'rb:UTF-8') {|f| f.read }
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|