-
Notifications
You must be signed in to change notification settings - Fork 197
Expand file tree
/
Copy pathpackager.rb
More file actions
186 lines (161 loc) · 7.5 KB
/
packager.rb
File metadata and controls
186 lines (161 loc) · 7.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
module Jammit
# The Jammit::Packager resolves the configuration file into lists of real
# assets that get merged into individual asset packages. Given the compiled
# contents of an asset package, the Packager knows how to cache that package
# with the correct timestamps.
class Packager
# Set force to false to allow packages to only be rebuilt when their source
# files have changed since the last time their package was built.
attr_accessor :force, :package_names
# Creating a new Packager will rebuild the list of assets from the
# Jammit.configuration. When assets.yml is being changed on the fly,
# create a new Packager.
def initialize
@force = false
@package_names = nil
@config = {
:css => (Jammit.configuration[:stylesheets] || {}),
:js => (Jammit.configuration[:javascripts] || {})
}
@packages = {
:css => create_packages(@config[:css]),
:js => create_packages(@config[:js])
}
end
# Ask the packager to precache all defined assets, along with their gzip'd
# versions. In order to prebuild the MHTML stylesheets, we need to know the
# base_url, because IE only supports MHTML with absolute references.
# Unless forced, will only rebuild assets whose source files have been
# changed since their last package build.
def precache_all(output_dir=nil, base_url=nil)
output_dir ||= File.join(Jammit.public_root, Jammit.package_path)
mtime = @force ? Time.now : nil
cacheable(:js, output_dir).each do |p|
cache(p, 'js', pack_javascripts(p), output_dir, nil, mtime)
end
cacheable(:css, output_dir).each do |p|
cache(p, 'css', pack_stylesheets(p), output_dir, nil, mtime)
if Jammit.embed_assets
cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri, mtime)
if Jammit.mhtml_enabled
raise MissingConfiguration, "A --base-url option is required in order to generate MHTML." unless base_url
mtime = latest_mtime package_for(p, :css)[:paths] unless @force
asset_url = "#{base_url}#{Jammit.asset_url(p, :css, :mhtml, mtime)}"
cache(p, 'css', pack_stylesheets(p, :mhtml, asset_url), output_dir, :mhtml, mtime)
end
end
end
end
# Caches a single prebuilt asset package and gzips it at the highest
# compression level. Ensures that the modification time of both both
# variants is identical, for web server caching modules, as well as MHTML.
def cache(package, extension, contents, output_dir, suffix=nil, mtime=nil)
FileUtils.mkdir_p(output_dir) unless File.exists?(output_dir)
raise OutputNotWritable, "Jammit doesn't have permission to write to \"#{output_dir}\"" unless File.writable?(output_dir)
mtime ||= latest_mtime package_for(package, extension.to_sym)[:paths]
files = []
files << file_name = File.join(output_dir, Jammit.filename(package, extension, suffix))
File.open(file_name, 'wb+') {|f| f.write(contents) }
if Jammit.gzip_assets
files << zip_name = "#{file_name}.gz"
Zlib::GzipWriter.open(zip_name, Zlib::BEST_COMPRESSION) {|f| f.write(contents) }
end
File.utime(mtime, mtime, *files)
end
# Get the list of individual assets for a package.
def individual_urls(package, extension)
package_for(package, extension)[:urls]
end
def compressor
@compressor ||= Compressor.new
end
# Return the compressed contents of a stylesheet package.
def pack_stylesheets(package, variant=nil, asset_url=nil)
compressor.compress_css(package_for(package, :css)[:paths], variant, asset_url)
end
# Return the compressed contents of a javascript package.
def pack_javascripts(package)
compressor.compress_js(package_for(package, :js)[:paths])
end
# Return the compiled contents of a JST package.
def pack_templates(package)
compressor.compile_jst(package_for(package, :js)[:paths])
end
private
# Look up a package asset list by name, raising an exception if the
# package has gone missing.
def package_for(package, extension)
pack = @packages[extension] && @packages[extension][package]
pack || not_found(package, extension)
end
# Absolute globs are absolute -- relative globs are relative to ASSET_ROOT.
# Print a warning if no files were found that match the glob.
def glob_files(glob)
absolute = Pathname.new(glob).absolute?
paths = Dir[absolute ? glob : File.join(ASSET_ROOT, glob)].sort
Jammit.warn("No assets match '#{glob}'") if paths.empty?
paths
end
# In Rails, the difference between a path and an asset URL is "public".
def path_to_url
@path_to_url ||= /\A#{Regexp.escape(ASSET_ROOT)}(\/?#{Regexp.escape(Jammit.public_root.sub(ASSET_ROOT, ''))})?/
end
# Get the latest mtime of a list of files (plus the config path).
def latest_mtime(paths)
paths += [Jammit.config_path]
paths.map {|p| File.mtime(p) }.max || Time.now
end
# Return a list of all of the packages that should be cached. If "force" is
# true, this is all of them -- otherwise only the packages that are missing
# or whose source files have changed since the last package build.
def cacheable(extension, output_dir)
names = @packages[extension].keys
names = names.select {|n| @package_names.include? n } if @package_names
config_mtime = File.mtime(Jammit.config_path)
return names if @force
return names.select do |name|
pack = package_for(name, extension)
cached = [Jammit.filename(name, extension)]
if extension == :css
cached.push Jammit.filename(name, extension, :datauri) if Jammit.embed_assets
cached.push Jammit.filename(name, extension, :mhtml) if Jammit.mhtml_enabled
end
cached.map! {|file| File.join(output_dir, file) }
if cached.any? {|file| !File.exists?(file) }
true
else
since = cached.map {|file| File.mtime(file) }.min
config_mtime > since || pack[:paths].any? {|src| File.mtime(src) > since }
end
end
end
# Compiles the list of assets that goes into each package. Runs an
# ordered list of Dir.globs, taking the merged unique result.
# If there are JST files in this package we need to add an extra
# path for when package_assets is off (e.g. in a dev environment).
# This package (e.g. /assets/package-name.jst) will never exist as
# an actual file but will be dynamically generated by Jammit on
# every request.
def create_packages(config)
packages = {}
return packages if !config
config.each do |name, globs|
globs ||= []
packages[name] = {}
paths = globs.flatten.uniq.map {|glob| glob_files(glob) }.flatten.uniq
packages[name][:paths] = paths
if !paths.grep(Jammit.template_extension_matcher).empty?
packages[name][:urls] = paths.grep(JS_EXTENSION).map {|path| path.sub(path_to_url, '') }
packages[name][:urls] += [Jammit.asset_url(name, Jammit.template_extension)]
else
packages[name][:urls] = paths.map {|path| path.sub(path_to_url, '') }
end
end
packages
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"
end
end
end