patch-asset_library 0.5.0.1
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/README.rdoc +220 -0
- data/lib/asset_library.rb +121 -0
- data/lib/asset_library/asset.rb +35 -0
- data/lib/asset_library/asset_module.rb +94 -0
- data/lib/asset_library/compiler.rb +37 -0
- data/lib/asset_library/compiler/base.rb +20 -0
- data/lib/asset_library/compiler/closure.rb +41 -0
- data/lib/asset_library/compiler/default.rb +15 -0
- data/lib/asset_library/helpers.rb +98 -0
- data/lib/asset_library/rake_tasks.rb +22 -0
- data/lib/asset_library/util.rb +26 -0
- data/rails/init.rb +4 -0
- data/spec/asset_library/asset_module_spec.rb +183 -0
- data/spec/asset_library/asset_spec.rb +49 -0
- data/spec/asset_library/compiler/base_spec.rb +11 -0
- data/spec/asset_library/compiler/closure_spec.rb +104 -0
- data/spec/asset_library/compiler/default_spec.rb +27 -0
- data/spec/asset_library/compiler_spec.rb +35 -0
- data/spec/asset_library/helpers_spec.rb +210 -0
- data/spec/asset_library_spec.rb +120 -0
- data/spec/integration_spec.rb +90 -0
- data/spec/spec_helper.rb +70 -0
- metadata +98 -0
data/README.rdoc
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
= asset_library
|
2
|
+
|
3
|
+
Bundles your JavaScript and CSS, so your development environment is simple to
|
4
|
+
code and your production environment is as fast as possible.
|
5
|
+
|
6
|
+
== Installation
|
7
|
+
|
8
|
+
First, install the gem:
|
9
|
+
|
10
|
+
sudo gem install alegscogs-asset_library --source http://gems.github.com
|
11
|
+
|
12
|
+
Next, add the gem into your Rails project's config/environment.rb:
|
13
|
+
|
14
|
+
config.gem 'alegscogs-asset_library', :lib => 'asset_library', :source => 'http://gems.github.com'
|
15
|
+
|
16
|
+
Finally, include the Rake tasks in your project:
|
17
|
+
|
18
|
+
echo "begin; require 'asset_library/rake_tasks'; rescue LoadError; end" > lib/tasks/asset_library.rake
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
In your Rails project, edit <tt>config/asset_library.yml</tt> as described
|
23
|
+
in the following section.
|
24
|
+
|
25
|
+
Once configured, asset_library provides two helper methods for your views:
|
26
|
+
|
27
|
+
<%# outputs library.js (production) or its files (development) %>
|
28
|
+
<%= asset_library_javascript_tags(:javascripts) %>
|
29
|
+
|
30
|
+
<%# outputs library.css (production) or its files (development) %>
|
31
|
+
<%= asset_library_stylesheet_tags(:stylesheets) %>
|
32
|
+
|
33
|
+
<%# outputs library.ie6.css (production) or its files (development) %>
|
34
|
+
<!--[if lte IE 6]>
|
35
|
+
<%= asset_library_stylesheet_tags(:stylesheets, :ie6) %>
|
36
|
+
<![endif]-->
|
37
|
+
|
38
|
+
Both helpers behave differently depending on whether
|
39
|
+
<tt>ActionController::Base.perform_caching</tt> is true (that is, whether you
|
40
|
+
are in <tt>development</tt> environment or not). When caching is disabled, each
|
41
|
+
file in the module will be included. (Internet Explorer only allows 30
|
42
|
+
<tt>style</tt> and <tt>link</tt> stylesheet tags; in development mode,
|
43
|
+
<tt>import</tt> rules are used to work around the bug.) When caching is
|
44
|
+
enabled, a single tag is output.
|
45
|
+
|
46
|
+
When caching is enabled, the modules to include must be generated using:
|
47
|
+
|
48
|
+
rake asset_library:write
|
49
|
+
|
50
|
+
These moduels can be removed using:
|
51
|
+
|
52
|
+
rake asset_library:clean
|
53
|
+
|
54
|
+
A cached module is simply the concatenation of its constituent files.
|
55
|
+
|
56
|
+
== Configuration
|
57
|
+
|
58
|
+
A typical configuration (Yaml) file might look like this. In Rails, this
|
59
|
+
should be in <tt>config/asset_library.yml</tt>.
|
60
|
+
|
61
|
+
modules:
|
62
|
+
javascripts:
|
63
|
+
cache: library
|
64
|
+
optional_suffix: compressed
|
65
|
+
base: javascripts
|
66
|
+
suffix: js
|
67
|
+
files:
|
68
|
+
- vendor/jquery
|
69
|
+
|
70
|
+
# jQuery UI parts we need
|
71
|
+
- vendor/jquery-ui/ui.core
|
72
|
+
- vendor/jquery-ui/ui.draggable
|
73
|
+
- vendor/jquery-ui/ui.droppable
|
74
|
+
- vendor/jquery-ui/ui.sortable
|
75
|
+
- vendor/jquery-ui/effects.core
|
76
|
+
|
77
|
+
- lib
|
78
|
+
- plugins/*
|
79
|
+
- application
|
80
|
+
- initializers/*
|
81
|
+
|
82
|
+
tiny_mce_javascripts:
|
83
|
+
# TinyMCE doesn't give us a choice on cache name
|
84
|
+
cache: vendor/tiny_mce/tiny_mce_gzip
|
85
|
+
optional_suffix: compressed
|
86
|
+
base: javascripts
|
87
|
+
suffix: js
|
88
|
+
files:
|
89
|
+
- vendor/tiny_mce/tiny_mce
|
90
|
+
# ... it's possible to bundle all of TinyMCE together with a bit of magic
|
91
|
+
|
92
|
+
stylesheets:
|
93
|
+
cache: library
|
94
|
+
base: stylesheets
|
95
|
+
suffix: css
|
96
|
+
formats:
|
97
|
+
- ie6: [null, ie8, ie7, ie6]
|
98
|
+
- ie7: [null, ie8, ie7]
|
99
|
+
- ie8: [null, ie8]
|
100
|
+
files:
|
101
|
+
- reset
|
102
|
+
- application
|
103
|
+
- layout
|
104
|
+
- views/**/*
|
105
|
+
|
106
|
+
# in general...
|
107
|
+
# modules:
|
108
|
+
# module_name:
|
109
|
+
# cache: cache_file
|
110
|
+
# base: base_path_of_assets_in_web_root
|
111
|
+
# suffix: suffix ("css" or "js")
|
112
|
+
# formats:
|
113
|
+
# format1: ["extra_suffix_1", "extra_suffix_2"]
|
114
|
+
# format2: [null, "extra_suffix3"]
|
115
|
+
# optional_suffix: optional_suffix
|
116
|
+
# files:
|
117
|
+
# - file1_relative_to_base
|
118
|
+
# - file2_relative_to_base
|
119
|
+
# - ...
|
120
|
+
# ...
|
121
|
+
|
122
|
+
Here are what the various configuration elements mean:
|
123
|
+
|
124
|
+
<tt>module_name</tt> is the name of the module. It is passed as the first
|
125
|
+
parameter to <tt>asset_library_javascript_tags</tt> or
|
126
|
+
<tt>asset_library_stylesheet_tags</tt>.
|
127
|
+
|
128
|
+
<tt>cache</tt> is a filename, without suffix, relative to <tt>base</tt>, where
|
129
|
+
the module will be bundled when caching is enabled. (Ensure that <tt>files</tt>
|
130
|
+
does not include <tt>cache_file</tt>, even with globbing.)
|
131
|
+
|
132
|
+
<tt>base</tt> is the base path of the assets in URLs. For instance, in Rails,
|
133
|
+
where stylesheets are usually served under <tt>/stylesheets</tt>, <tt>base</tt>
|
134
|
+
should be <tt>stylesheets</tt>.
|
135
|
+
|
136
|
+
<tt>suffix</tt> is either "js" or "css", depending on whether you are including
|
137
|
+
JavaScript or CSS files.
|
138
|
+
|
139
|
+
<tt>formats</tt> allows construction of parallel modules. By default, for a
|
140
|
+
module named <tt>styles</tt> will use <tt>*.css</tt> (where <tt>*</tt> is
|
141
|
+
specified by the <tt>files</tt> option) to generate <tt>styles.css</tt>; but
|
142
|
+
filenames of the format <tt>*.suffix.css</tt> will be ignored in that search.
|
143
|
+
If a <tt>format</tt> called <tt>format1</tt> is specified as
|
144
|
+
<tt>[suffix1, suffix2]</tt>, then <tt>*.suffix1.css</tt> and
|
145
|
+
<tt>*.suffix2.css</tt> will be included, but not <tt>*.css</tt>. (To specify
|
146
|
+
<tt>*.css</tt>, put <tt>null</tt> in the format list.)
|
147
|
+
|
148
|
+
<tt>optional_suffix</tt> will cause asset_library to check for the existence of
|
149
|
+
files with <tt>optional_suffix</tt> suffixes, falling back to files without
|
150
|
+
<tt>optional_suffix</tt> if necessary. For instance, if you run all your
|
151
|
+
JavaScript files through
|
152
|
+
{YUI Compressor}[http://developer.yahoo.com/yui/compressor/] and output the
|
153
|
+
compressed version of <tt>file1.js</tt> as <tt>file1.compressed.js</tt>, then
|
154
|
+
set <tt>optional_suffix</tt> to <tt>compressed</tt>. In your development
|
155
|
+
environment, where <tt>compressed</tt> javascripts are missing,
|
156
|
+
<tt>file1.js</tt> will be included and you can debug your JavaScript. In your
|
157
|
+
production environment, create the <tt>compressed</tt> JavaScripts in the same
|
158
|
+
directory, and they will be included instead, for optimal download speed.
|
159
|
+
|
160
|
+
<tt>files</tt> is a list of files, relative to <tt>base</tt>. File globbing is
|
161
|
+
allowed; <tt>**</tt> expands to "any nesting of directories". Files which do
|
162
|
+
not exist will be excluded; globbing will include/exclude files with
|
163
|
+
<tt>formats</tt> as appropriate; and files without <tt>optional_suffix</tt>
|
164
|
+
will not be output alongside files with the same name endowed with the suffix.
|
165
|
+
|
166
|
+
== Compilers
|
167
|
+
|
168
|
+
If you would like asset library to do more than just concatenate the
|
169
|
+
files to produce your modules, then you need to specify a compiler.
|
170
|
+
Asset Library currently ships with support for one compiler: Closure
|
171
|
+
Compiler, and has an extendable framework to add more.
|
172
|
+
|
173
|
+
To specify that a module is to be compiled with the Closure Compiler,
|
174
|
+
use the <tt>compiler</tt> key for that module. You may pass
|
175
|
+
additional flags with the <tt>compiler_flags</tt> option:
|
176
|
+
|
177
|
+
modules:
|
178
|
+
javascripts:
|
179
|
+
cache: library
|
180
|
+
compiler: closure
|
181
|
+
compiler_flags: --warning_level QUIET
|
182
|
+
base: javascripts
|
183
|
+
suffix: js
|
184
|
+
files:
|
185
|
+
- javascripts/**/*
|
186
|
+
|
187
|
+
You also need to configure the compiler by using a
|
188
|
+
<tt>compilers.closure</tt> key:
|
189
|
+
|
190
|
+
compilers:
|
191
|
+
closure:
|
192
|
+
path: /path/to/closure.jar
|
193
|
+
flags: --warning_level QUIET
|
194
|
+
|
195
|
+
The full list of Closure Compiler settings are:
|
196
|
+
|
197
|
+
<tt>path</tt>: The path to the Closure Compiler jar. This is
|
198
|
+
mandatory.
|
199
|
+
|
200
|
+
<tt>flags</tt>: Flags to pass to Closure Compiler. These are combined
|
201
|
+
with any per-module flags given via the <tt>compiler_flags</tt> option
|
202
|
+
in the individual modules. Default: no flags.
|
203
|
+
|
204
|
+
<tt>java</tt>: The java command to use. This will be searched for in
|
205
|
+
the <tt>PATH</tt> unless it is an absolute filename. Default:
|
206
|
+
<tt>java</tt>.
|
207
|
+
|
208
|
+
<tt>java_flags</tt>: Flags to pass to the java interpreter. Default:
|
209
|
+
no flags.
|
210
|
+
|
211
|
+
== Copyright
|
212
|
+
|
213
|
+
I believe in software freedom, not any abomination thereof. This project is
|
214
|
+
released under the Public Domain, meaning I relinquish my copyright (to any
|
215
|
+
extend the law allows) and grant you all rights to use, modify, sell, or
|
216
|
+
otherwise take advantage of my software.
|
217
|
+
|
218
|
+
However, I do kindly request that, as a favor, you refrain from using my
|
219
|
+
software as part of an evil plan involving velociraptors and mind-controlling
|
220
|
+
robots (even though I would not be legally entitled to sue you for doing so).
|
@@ -0,0 +1,121 @@
|
|
1
|
+
begin
|
2
|
+
require 'glob_fu'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'glob_fu'
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'yaml'
|
9
|
+
require File.dirname(__FILE__) + '/asset_library/compiler'
|
10
|
+
require File.dirname(__FILE__) + '/asset_library/asset_module'
|
11
|
+
require File.dirname(__FILE__) + '/asset_library/util'
|
12
|
+
|
13
|
+
class AssetLibrary
|
14
|
+
ConfigurationError = Class.new(RuntimeError)
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def config_path
|
18
|
+
@config_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def config_path=(config_path)
|
22
|
+
@config_path = config_path
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Root of your application.
|
27
|
+
#
|
28
|
+
# Paths of external programs (if required) are resolved relative
|
29
|
+
# to this path.
|
30
|
+
#
|
31
|
+
attr_accessor :app_root
|
32
|
+
|
33
|
+
#
|
34
|
+
# Root directory of your output files.
|
35
|
+
#
|
36
|
+
# Output files are resolved relative to this path.
|
37
|
+
#
|
38
|
+
attr_accessor :root
|
39
|
+
|
40
|
+
def cache
|
41
|
+
return true if @cache.nil?
|
42
|
+
@cache
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache=(cache)
|
46
|
+
reset!
|
47
|
+
@cache = cache
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_vars
|
51
|
+
# We store cache_vars even if not caching--this is our "globals"
|
52
|
+
@cache_vars ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def config
|
56
|
+
return @config if cache && @config
|
57
|
+
ret = if File.exist?(config_path)
|
58
|
+
yaml = YAML.load_file(config_path) || {}
|
59
|
+
Util::symbolize_hash_keys(yaml)
|
60
|
+
else
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
if !ret[:modules] && ret.values.any?{|value| value.is_a?(Hash) && value[:files]}
|
64
|
+
warn <<-EOS.gsub(/^ *\|/, '')
|
65
|
+
| WARNING: Your asset library configuration follows a
|
66
|
+
| deprecated format. Please move all your asset modules
|
67
|
+
| under a "modules:" key, as described in the
|
68
|
+
| documentation.
|
69
|
+
EOS
|
70
|
+
ret = { :modules => ret }
|
71
|
+
end
|
72
|
+
ret[:modules] ||= {}
|
73
|
+
ret[:compilers] ||= {}
|
74
|
+
@config = cache ? ret : nil
|
75
|
+
ret
|
76
|
+
end
|
77
|
+
|
78
|
+
def compilers
|
79
|
+
@compilers ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def asset_module(key)
|
83
|
+
module_config = config[:modules][key.to_sym]
|
84
|
+
if module_config
|
85
|
+
AssetModule.new(key, module_config)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def compiler(asset_module)
|
90
|
+
type = asset_module.compiler_type
|
91
|
+
config = self.config[:compilers][type] || {}
|
92
|
+
compilers[type] ||= Compiler.create(type, config)
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_all_caches
|
96
|
+
config[:modules].keys.each do |key|
|
97
|
+
m = asset_module(key)
|
98
|
+
c = compiler(m)
|
99
|
+
c.add_asset_module(m)
|
100
|
+
end
|
101
|
+
|
102
|
+
compilers.values.each do |compiler|
|
103
|
+
compiler.write_all_caches
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete_all_caches
|
108
|
+
asset_modules = config[:modules].keys.collect{|k| AssetLibrary.asset_module(k)}
|
109
|
+
asset_modules.each do |m|
|
110
|
+
FileUtils.rm_f(m.cache_asset.absolute_path)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def reset!
|
115
|
+
@config = nil
|
116
|
+
@cache_vars = nil
|
117
|
+
@compilers = nil
|
118
|
+
Compiler.reset!
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class AssetLibrary
|
2
|
+
class Asset
|
3
|
+
attr_reader(:absolute_path)
|
4
|
+
|
5
|
+
def initialize(absolute_path)
|
6
|
+
@absolute_path = absolute_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def eql?(other)
|
10
|
+
self.class === other && absolute_path == other.absolute_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
self.absolute_path.hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def relative_path
|
18
|
+
if AssetLibrary.root == '/'
|
19
|
+
absolute_path
|
20
|
+
else
|
21
|
+
absolute_path[AssetLibrary.root.length..-1]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def timestamp
|
26
|
+
File.mtime(absolute_path)
|
27
|
+
rescue Errno::ENOENT
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def relative_url
|
32
|
+
"#{relative_path}?#{timestamp.to_i.to_s}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/asset'
|
2
|
+
|
3
|
+
class AssetLibrary
|
4
|
+
class AssetModule
|
5
|
+
attr_reader(:config)
|
6
|
+
|
7
|
+
def initialize(name, config)
|
8
|
+
@name = name.to_s
|
9
|
+
@config = config
|
10
|
+
add_default_format
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Returns the type of compiler to use for this asset module.
|
16
|
+
def compiler_type
|
17
|
+
(config[:compiler] || :default).to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the Array of compiler flags to use.
|
21
|
+
def compiler_flags
|
22
|
+
Util.normalize_flags(config[:compiler_flags])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns an Array of Assets to include.
|
26
|
+
#
|
27
|
+
# Arguments:
|
28
|
+
# extra_suffix: if set, finds files with the given extra suffix
|
29
|
+
def assets(format = nil)
|
30
|
+
if format
|
31
|
+
assets_with_format(format)
|
32
|
+
else
|
33
|
+
assets_with_extra_suffix(nil)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns an Array of Assets to include.
|
38
|
+
#
|
39
|
+
# Arguments:
|
40
|
+
# extra_suffix: if set, finds files with the given extra suffix
|
41
|
+
def assets_with_extra_suffix(extra_suffix)
|
42
|
+
return nil unless config
|
43
|
+
|
44
|
+
GlobFu.find(
|
45
|
+
config[:files],
|
46
|
+
:suffix => config[:suffix],
|
47
|
+
:extra_suffix => extra_suffix,
|
48
|
+
:root => File.join(*([AssetLibrary.root, config[:base]].compact)),
|
49
|
+
:optional_suffix => config[:optional_suffix]
|
50
|
+
).collect { |f| Asset.new(f) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an Array of Assets to include.
|
54
|
+
#
|
55
|
+
# Calls assets_with_extra_suffix for each suffix in the given format
|
56
|
+
#
|
57
|
+
# Arguments:
|
58
|
+
# format: format specified in the config
|
59
|
+
def assets_with_format(format)
|
60
|
+
return nil unless config
|
61
|
+
|
62
|
+
extra_suffixes = config[:formats][format.to_sym]
|
63
|
+
extra_suffixes.inject([]) { |r, s| r.concat(assets_with_extra_suffix(s)) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an Asset representing the cache file
|
67
|
+
def cache_asset(format = nil)
|
68
|
+
extra = format ? ".#{format}" : ''
|
69
|
+
Asset.new(File.join(AssetLibrary.root, config[:base], "#{config[:cache]}#{extra}.#{config[:suffix]}"))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return the formats to generate.
|
73
|
+
def formats
|
74
|
+
(config[:formats] || {}).keys.sort_by{|sym| sym.to_s}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Yield each set of input paths with their output path. All paths
|
78
|
+
# are absolute.
|
79
|
+
def each_compilation
|
80
|
+
formats.each do |format|
|
81
|
+
inputs = assets(format).map { |asset| asset.absolute_path }
|
82
|
+
output = cache_asset(format).absolute_path
|
83
|
+
yield inputs, output
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def add_default_format
|
90
|
+
config[:formats] ||= {}
|
91
|
+
config[:formats][nil] = [nil]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|