sprockets 2.2.3 → 4.0.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.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +482 -255
  4. data/bin/sprockets +20 -7
  5. data/lib/rake/sprocketstask.rb +28 -15
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +142 -207
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +8 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  11. data/lib/sprockets/autoload/eco.rb +8 -0
  12. data/lib/sprockets/autoload/ejs.rb +8 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +8 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +8 -0
  17. data/lib/sprockets/autoload/yui.rb +8 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +16 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +89 -249
  22. data/lib/sprockets/bower.rb +61 -0
  23. data/lib/sprockets/bundle.rb +105 -0
  24. data/lib/sprockets/cache/file_store.rb +190 -14
  25. data/lib/sprockets/cache/memory_store.rb +75 -0
  26. data/lib/sprockets/cache/null_store.rb +54 -0
  27. data/lib/sprockets/cache.rb +271 -0
  28. data/lib/sprockets/cached_environment.rb +64 -0
  29. data/lib/sprockets/closure_compressor.rb +48 -0
  30. data/lib/sprockets/coffee_script_processor.rb +39 -0
  31. data/lib/sprockets/compressing.rb +134 -0
  32. data/lib/sprockets/configuration.rb +79 -0
  33. data/lib/sprockets/context.rb +204 -135
  34. data/lib/sprockets/dependencies.rb +74 -0
  35. data/lib/sprockets/digest_utils.rb +200 -0
  36. data/lib/sprockets/directive_processor.rb +224 -216
  37. data/lib/sprockets/eco_processor.rb +33 -0
  38. data/lib/sprockets/ejs_processor.rb +32 -0
  39. data/lib/sprockets/encoding_utils.rb +262 -0
  40. data/lib/sprockets/environment.rb +23 -68
  41. data/lib/sprockets/erb_processor.rb +37 -0
  42. data/lib/sprockets/errors.rb +6 -13
  43. data/lib/sprockets/exporters/base.rb +72 -0
  44. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  45. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  46. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  47. data/lib/sprockets/exporting.rb +73 -0
  48. data/lib/sprockets/file_reader.rb +16 -0
  49. data/lib/sprockets/http_utils.rb +135 -0
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +36 -19
  52. data/lib/sprockets/loader.rb +343 -0
  53. data/lib/sprockets/manifest.rb +231 -96
  54. data/lib/sprockets/manifest_utils.rb +48 -0
  55. data/lib/sprockets/mime.rb +80 -32
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +77 -0
  58. data/lib/sprockets/path_digest_utils.rb +48 -0
  59. data/lib/sprockets/path_utils.rb +367 -0
  60. data/lib/sprockets/paths.rb +82 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +140 -192
  63. data/lib/sprockets/processor_utils.rb +169 -0
  64. data/lib/sprockets/resolve.rb +295 -0
  65. data/lib/sprockets/sass_cache_store.rb +30 -0
  66. data/lib/sprockets/sass_compressor.rb +63 -0
  67. data/lib/sprockets/sass_functions.rb +3 -0
  68. data/lib/sprockets/sass_importer.rb +3 -0
  69. data/lib/sprockets/sass_processor.rb +313 -0
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +138 -90
  73. data/lib/sprockets/source_map_processor.rb +66 -0
  74. data/lib/sprockets/source_map_utils.rb +483 -0
  75. data/lib/sprockets/transformers.rb +173 -0
  76. data/lib/sprockets/uglifier_compressor.rb +66 -0
  77. data/lib/sprockets/unloaded_asset.rb +139 -0
  78. data/lib/sprockets/uri_tar.rb +99 -0
  79. data/lib/sprockets/uri_utils.rb +191 -0
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +186 -53
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +56 -0
  84. data/lib/sprockets.rb +217 -52
  85. metadata +250 -59
  86. data/LICENSE +0 -21
  87. data/lib/sprockets/asset_attributes.rb +0 -126
  88. data/lib/sprockets/bundled_asset.rb +0 -79
  89. data/lib/sprockets/caching.rb +0 -96
  90. data/lib/sprockets/charset_normalizer.rb +0 -41
  91. data/lib/sprockets/eco_template.rb +0 -38
  92. data/lib/sprockets/ejs_template.rb +0 -37
  93. data/lib/sprockets/engines.rb +0 -74
  94. data/lib/sprockets/index.rb +0 -99
  95. data/lib/sprockets/processed_asset.rb +0 -152
  96. data/lib/sprockets/processor.rb +0 -32
  97. data/lib/sprockets/safety_colons.rb +0 -28
  98. data/lib/sprockets/static_asset.rb +0 -57
  99. data/lib/sprockets/trail.rb +0 -90
@@ -1,32 +1,208 @@
1
- require 'digest/md5'
1
+ # frozen_string_literal: true
2
2
  require 'fileutils'
3
- require 'pathname'
3
+ require 'logger'
4
+ require 'sprockets/encoding_utils'
5
+ require 'sprockets/path_utils'
6
+ require 'zlib'
4
7
 
5
8
  module Sprockets
6
- module Cache
7
- # A simple file system cache store.
9
+ class Cache
10
+ # Public: A file system cache store that automatically cleans up old keys.
11
+ #
12
+ # Assign the instance to the Environment#cache.
8
13
  #
9
14
  # environment.cache = Sprockets::Cache::FileStore.new("/tmp")
10
15
  #
16
+ # See Also
17
+ #
18
+ # ActiveSupport::Cache::FileStore
19
+ #
11
20
  class FileStore
12
- def initialize(root)
13
- @root = Pathname.new(root)
21
+ # Internal: Default key limit for store.
22
+ DEFAULT_MAX_SIZE = 25 * 1024 * 1024
23
+ EXCLUDED_DIRS = ['.', '..'].freeze
24
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
25
+
26
+ # Internal: Default standard error fatal logger.
27
+ #
28
+ # Returns a Logger.
29
+ def self.default_logger
30
+ logger = Logger.new($stderr)
31
+ logger.level = Logger::FATAL
32
+ logger
14
33
  end
15
34
 
16
- # Lookup value in cache
17
- def [](key)
18
- pathname = @root.join(key)
19
- pathname.exist? ? pathname.open('rb') { |f| Marshal.load(f) } : nil
35
+ # Public: Initialize the cache store.
36
+ #
37
+ # root - A String path to a directory to persist cached values to.
38
+ # max_size - A Integer of the maximum size the store will hold (in bytes).
39
+ # (default: 25MB).
40
+ # logger - The logger to which some info will be printed.
41
+ # (default logger level is FATAL and won't output anything).
42
+ def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger)
43
+ @root = root
44
+ @max_size = max_size
45
+ @gc_size = max_size * 0.75
46
+ @logger = logger
47
+ end
48
+
49
+ # Public: Retrieve value from cache.
50
+ #
51
+ # This API should not be used directly, but via the Cache wrapper API.
52
+ #
53
+ # key - String cache key.
54
+ #
55
+ # Returns Object or nil or the value is not set.
56
+ def get(key)
57
+ path = File.join(@root, "#{key}.cache")
58
+
59
+ value = safe_open(path) do |f|
60
+ begin
61
+ EncodingUtils.unmarshaled_deflated(f.read, Zlib::MAX_WBITS)
62
+ rescue Exception => e
63
+ @logger.error do
64
+ "#{self.class}[#{path}] could not be unmarshaled: " +
65
+ "#{e.class}: #{e.message}"
66
+ end
67
+ nil
68
+ end
69
+ end
70
+
71
+ if value
72
+ FileUtils.touch(path)
73
+ value
74
+ end
20
75
  end
21
76
 
22
- # Save value to cache
23
- def []=(key, value)
77
+ # Public: Set a key and value in the cache.
78
+ #
79
+ # This API should not be used directly, but via the Cache wrapper API.
80
+ #
81
+ # key - String cache key.
82
+ # value - Object value.
83
+ #
84
+ # Returns Object value.
85
+ def set(key, value)
86
+ path = File.join(@root, "#{key}.cache")
87
+
24
88
  # Ensure directory exists
25
- FileUtils.mkdir_p @root.join(key).dirname
89
+ FileUtils.mkdir_p File.dirname(path)
90
+
91
+ # Check if cache exists before writing
92
+ exists = File.exist?(path)
93
+
94
+ # Serialize value
95
+ marshaled = Marshal.dump(value)
96
+
97
+ # Compress if larger than 4KB
98
+ if marshaled.bytesize > 4 * 1024
99
+ deflater = Zlib::Deflate.new(
100
+ Zlib::BEST_COMPRESSION,
101
+ Zlib::MAX_WBITS,
102
+ Zlib::MAX_MEM_LEVEL,
103
+ Zlib::DEFAULT_STRATEGY
104
+ )
105
+ deflater << marshaled
106
+ raw = deflater.finish
107
+ else
108
+ raw = marshaled
109
+ end
110
+
111
+ # Write data
112
+ PathUtils.atomic_write(path) do |f|
113
+ f.write(raw)
114
+ @size = size + f.size unless exists
115
+ end
116
+
117
+ # GC if necessary
118
+ gc! if size > @max_size
26
119
 
27
- @root.join(key).open('w') { |f| Marshal.dump(value, f)}
28
120
  value
29
121
  end
122
+
123
+ # Public: Pretty inspect
124
+ #
125
+ # Returns String.
126
+ def inspect
127
+ "#<#{self.class} size=#{size}/#{@max_size}>"
128
+ end
129
+
130
+ # Public: Clear the cache
131
+ #
132
+ # adapted from ActiveSupport::Cache::FileStore#clear
133
+ #
134
+ # Deletes all items from the cache. In this case it deletes all the entries in the specified
135
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified
136
+ # as @root because everything in that directory will be deleted.
137
+ #
138
+ # Returns true
139
+ def clear(options=nil)
140
+ if File.exist?(@root)
141
+ root_dirs = Dir.entries(@root).reject { |f| (EXCLUDED_DIRS + GITKEEP_FILES).include?(f) }
142
+ FileUtils.rm_r(root_dirs.collect{ |f| File.join(@root, f) })
143
+ end
144
+ true
145
+ end
146
+
147
+ private
148
+ # Internal: Get all cache files along with stats.
149
+ #
150
+ # Returns an Array of [String filename, File::Stat] pairs sorted by
151
+ # mtime.
152
+ def find_caches
153
+ Dir.glob(File.join(@root, '**/*.cache')).reduce([]) { |stats, filename|
154
+ stat = safe_stat(filename)
155
+ # stat maybe nil if file was removed between the time we called
156
+ # dir.glob and the next stat
157
+ stats << [filename, stat] if stat
158
+ stats
159
+ }.sort_by { |_, stat| stat.mtime.to_i }
160
+ end
161
+
162
+ def size
163
+ @size ||= compute_size(find_caches)
164
+ end
165
+
166
+ def compute_size(caches)
167
+ caches.inject(0) { |sum, (_, stat)| sum + stat.size }
168
+ end
169
+
170
+ def safe_stat(fn)
171
+ File.stat(fn)
172
+ rescue Errno::ENOENT
173
+ nil
174
+ end
175
+
176
+ def safe_open(path, &block)
177
+ if File.exist?(path)
178
+ File.open(path, 'rb', &block)
179
+ end
180
+ rescue Errno::ENOENT
181
+ end
182
+
183
+ def gc!
184
+ start_time = Time.now
185
+
186
+ caches = find_caches
187
+ size = compute_size(caches)
188
+
189
+ delete_caches, keep_caches = caches.partition { |filename, stat|
190
+ deleted = size > @gc_size
191
+ size -= stat.size
192
+ deleted
193
+ }
194
+
195
+ return if delete_caches.empty?
196
+
197
+ FileUtils.remove(delete_caches.map(&:first), force: true)
198
+ @size = compute_size(keep_caches)
199
+
200
+ @logger.warn do
201
+ secs = Time.now.to_f - start_time.to_f
202
+ "#{self.class}[#{@root}] garbage collected " +
203
+ "#{delete_caches.size} files (#{(secs * 1000).to_i}ms)"
204
+ end
205
+ end
30
206
  end
31
207
  end
32
208
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ class Cache
4
+ # Public: Basic in memory LRU cache.
5
+ #
6
+ # Assign the instance to the Environment#cache.
7
+ #
8
+ # environment.cache = Sprockets::Cache::MemoryStore.new(1000)
9
+ #
10
+ # See Also
11
+ #
12
+ # ActiveSupport::Cache::MemoryStore
13
+ #
14
+ class MemoryStore
15
+ # Internal: Default key limit for store.
16
+ DEFAULT_MAX_SIZE = 1000
17
+
18
+ # Public: Initialize the cache store.
19
+ #
20
+ # max_size - A Integer of the maximum number of keys the store will hold.
21
+ # (default: 1000).
22
+ def initialize(max_size = DEFAULT_MAX_SIZE)
23
+ @max_size = max_size
24
+ @cache = {}
25
+ end
26
+
27
+ # Public: Retrieve value from cache.
28
+ #
29
+ # This API should not be used directly, but via the Cache wrapper API.
30
+ #
31
+ # key - String cache key.
32
+ #
33
+ # Returns Object or nil or the value is not set.
34
+ def get(key)
35
+ exists = true
36
+ value = @cache.delete(key) { exists = false }
37
+ if exists
38
+ @cache[key] = value
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ # Public: Set a key and value in the cache.
45
+ #
46
+ # This API should not be used directly, but via the Cache wrapper API.
47
+ #
48
+ # key - String cache key.
49
+ # value - Object value.
50
+ #
51
+ # Returns Object value.
52
+ def set(key, value)
53
+ @cache.delete(key)
54
+ @cache[key] = value
55
+ @cache.shift if @cache.size > @max_size
56
+ value
57
+ end
58
+
59
+ # Public: Pretty inspect
60
+ #
61
+ # Returns String.
62
+ def inspect
63
+ "#<#{self.class} size=#{@cache.size}/#{@max_size}>"
64
+ end
65
+
66
+ # Public: Clear the cache
67
+ #
68
+ # Returns true
69
+ def clear(options=nil)
70
+ @cache.clear
71
+ true
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ class Cache
4
+ # Public: A compatible cache store that doesn't store anything. Used by
5
+ # default when no Environment#cache is configured.
6
+ #
7
+ # Assign the instance to the Environment#cache.
8
+ #
9
+ # environment.cache = Sprockets::Cache::NullStore.new
10
+ #
11
+ # See Also
12
+ #
13
+ # ActiveSupport::Cache::NullStore
14
+ #
15
+ class NullStore
16
+ # Public: Simulate a cache miss.
17
+ #
18
+ # This API should not be used directly, but via the Cache wrapper API.
19
+ #
20
+ # key - String cache key.
21
+ #
22
+ # Returns nil.
23
+ def get(key)
24
+ nil
25
+ end
26
+
27
+ # Public: Simulate setting a value in the cache.
28
+ #
29
+ # This API should not be used directly, but via the Cache wrapper API.
30
+ #
31
+ # key - String cache key.
32
+ # value - Object value.
33
+ #
34
+ # Returns Object value.
35
+ def set(key, value)
36
+ value
37
+ end
38
+
39
+ # Public: Pretty inspect
40
+ #
41
+ # Returns String.
42
+ def inspect
43
+ "#<#{self.class}>"
44
+ end
45
+
46
+ # Public: Simulate clearing the cache
47
+ #
48
+ # Returns true
49
+ def clear(options=nil)
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+ require 'sprockets/digest_utils'
4
+
5
+ module Sprockets
6
+ # Public: Wrapper interface to backend cache stores. Ensures a consistent API
7
+ # even when the backend uses get/set or read/write.
8
+ #
9
+ # Public cache interface
10
+ #
11
+ # Always assign the backend store instance to Environment#cache=.
12
+ #
13
+ # environment.cache = Sprockets::Cache::MemoryStore.new(1000)
14
+ #
15
+ # Environment#cache will always return a wrapped Cache interface. See the
16
+ # methods marked public on this class.
17
+ #
18
+ #
19
+ # Backend cache interface
20
+ #
21
+ # The Backend cache store must implement two methods.
22
+ #
23
+ # get(key)
24
+ #
25
+ # key - An opaque String with a length less than 250 characters.
26
+ #
27
+ # Returns an JSON serializable object.
28
+ #
29
+ # set(key, value)
30
+ #
31
+ # Will only be called once per key. Setting a key "foo" with value "bar",
32
+ # then later key "foo" with value "baz" is an undefined behavior.
33
+ #
34
+ # key - An opaque String with a length less than 250 characters.
35
+ # value - A JSON serializable object.
36
+ #
37
+ # Returns argument value.
38
+ #
39
+ # clear(options)
40
+ #
41
+ # Clear the entire cache. Be careful with this method since it could
42
+ # affect other processes if shared cache is being used.
43
+ #
44
+ # The options hash is passed to the underlying cache implementation.
45
+ class Cache
46
+ # Builtin cache stores.
47
+ autoload :FileStore, 'sprockets/cache/file_store'
48
+ autoload :MemoryStore, 'sprockets/cache/memory_store'
49
+ autoload :NullStore, 'sprockets/cache/null_store'
50
+
51
+ # Internal: Cache key version for this class. Rarely should have to change
52
+ # unless the cache format radically changes. Will be bump on major version
53
+ # releases though.
54
+ VERSION = '4.0.0'
55
+
56
+ def self.default_logger
57
+ logger = Logger.new($stderr)
58
+ logger.level = Logger::FATAL
59
+ logger
60
+ end
61
+
62
+ # Internal: Wrap a backend cache store.
63
+ #
64
+ # Always assign a backend cache store instance to Environment#cache= and
65
+ # use Environment#cache to retreive a wrapped interface.
66
+ #
67
+ # cache - A compatible backend cache store instance.
68
+ def initialize(cache = nil, logger = self.class.default_logger)
69
+ @cache_wrapper = get_cache_wrapper(cache)
70
+ @fetch_cache = Cache::MemoryStore.new(1024)
71
+ @logger = logger
72
+ end
73
+
74
+ # Public: Prefer API to retrieve and set values in the cache store.
75
+ #
76
+ # key - JSON serializable key
77
+ # block -
78
+ # Must return a consistent JSON serializable object for the given key.
79
+ #
80
+ # Examples
81
+ #
82
+ # cache.fetch("foo") { "bar" }
83
+ #
84
+ # Returns a JSON serializable object.
85
+ def fetch(key)
86
+ start = Time.now.to_f
87
+ expanded_key = expand_key(key)
88
+ value = @fetch_cache.get(expanded_key)
89
+ if value.nil?
90
+ value = @cache_wrapper.get(expanded_key)
91
+ if value.nil?
92
+ value = yield
93
+ @cache_wrapper.set(expanded_key, value)
94
+ @logger.debug do
95
+ ms = "(#{((Time.now.to_f - start) * 1000).to_i}ms)"
96
+ "Sprockets Cache miss #{peek_key(key)} #{ms}"
97
+ end
98
+ end
99
+ @fetch_cache.set(expanded_key, value)
100
+ end
101
+ value
102
+ end
103
+
104
+ # Public: Low level API to retrieve item directly from the backend cache
105
+ # store.
106
+ #
107
+ # This API may be used publicly, but may have undefined behavior
108
+ # depending on the backend store being used. Prefer the
109
+ # Cache#fetch API over using this.
110
+ #
111
+ # key - JSON serializable key
112
+ # local - Check local cache first (default: false)
113
+ #
114
+ # Returns a JSON serializable object or nil if there was a cache miss.
115
+ def get(key, local = false)
116
+ expanded_key = expand_key(key)
117
+
118
+ if local && value = @fetch_cache.get(expanded_key)
119
+ return value
120
+ end
121
+
122
+ value = @cache_wrapper.get(expanded_key)
123
+ @fetch_cache.set(expanded_key, value) if local
124
+
125
+ value
126
+ end
127
+
128
+ # Public: Low level API to set item directly to the backend cache store.
129
+ #
130
+ # This API may be used publicly, but may have undefined behavior
131
+ # depending on the backend store being used. Prefer the
132
+ # Cache#fetch API over using this.
133
+ #
134
+ # key - JSON serializable key
135
+ # value - A consistent JSON serializable object for the given key. Setting
136
+ # a different value for the given key has undefined behavior.
137
+ # local - Set on local cache (default: false)
138
+ #
139
+ # Returns the value argument.
140
+ def set(key, value, local = false)
141
+ expanded_key = expand_key(key)
142
+ @fetch_cache.set(expanded_key, value) if local
143
+ @cache_wrapper.set(expanded_key, value)
144
+ end
145
+
146
+ # Public: Pretty inspect
147
+ #
148
+ # Returns String.
149
+ def inspect
150
+ "#<#{self.class} local=#{@fetch_cache.inspect} store=#{@cache_wrapper.cache.inspect}>"
151
+ end
152
+
153
+ # Public: Clear cache
154
+ #
155
+ # Returns truthy on success, potentially raises exception on failure
156
+ def clear(options=nil)
157
+ @cache_wrapper.clear
158
+ @fetch_cache.clear
159
+ end
160
+
161
+ private
162
+ # Internal: Expand object cache key into a short String key.
163
+ #
164
+ # The String should be under 250 characters so its compatible with
165
+ # Memcache.
166
+ #
167
+ # key - JSON serializable key
168
+ #
169
+ # Returns a String with a length less than 250 characters.
170
+ def expand_key(key)
171
+ digest_key = DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key))
172
+ namespace = digest_key[0, 2]
173
+ "sprockets/v#{VERSION}/#{namespace}/#{digest_key}"
174
+ end
175
+
176
+ PEEK_SIZE = 100
177
+
178
+ # Internal: Show first 100 characters of cache key for logging purposes.
179
+ #
180
+ # Returns a String with a length less than 100 characters.
181
+ def peek_key(key)
182
+ case key
183
+ when Integer
184
+ key.to_s
185
+ when String
186
+ key[0, PEEK_SIZE].inspect
187
+ when Array
188
+ str = []
189
+ key.each { |k| str << peek_key(k) }
190
+ str.join(':')[0, PEEK_SIZE]
191
+ else
192
+ peek_key(DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key)))
193
+ end
194
+ end
195
+
196
+ def get_cache_wrapper(cache)
197
+ if cache.is_a?(Cache)
198
+ cache
199
+
200
+ # `Cache#get(key)` for Memcache
201
+ elsif cache.respond_to?(:get)
202
+ GetWrapper.new(cache)
203
+
204
+ # `Cache#[key]` so `Hash` can be used
205
+ elsif cache.respond_to?(:[])
206
+ HashWrapper.new(cache)
207
+
208
+ # `Cache#read(key)` for `ActiveSupport::Cache` support
209
+ elsif cache.respond_to?(:read)
210
+ ReadWriteWrapper.new(cache)
211
+
212
+ else
213
+ cache = Sprockets::Cache::NullStore.new
214
+ GetWrapper.new(cache)
215
+ end
216
+ end
217
+
218
+ class Wrapper < Struct.new(:cache)
219
+ end
220
+
221
+ class GetWrapper < Wrapper
222
+ def get(key)
223
+ cache.get(key)
224
+ end
225
+
226
+ def set(key, value)
227
+ cache.set(key, value)
228
+ end
229
+
230
+ def clear(options=nil)
231
+ # dalli has a #flush method so try it
232
+ if cache.respond_to?(:flush)
233
+ cache.flush(options)
234
+ else
235
+ cache.clear(options)
236
+ end
237
+ true
238
+ end
239
+ end
240
+
241
+ class HashWrapper < Wrapper
242
+ def get(key)
243
+ cache[key]
244
+ end
245
+
246
+ def set(key, value)
247
+ cache[key] = value
248
+ end
249
+
250
+ def clear(options=nil)
251
+ cache.clear
252
+ true
253
+ end
254
+ end
255
+
256
+ class ReadWriteWrapper < Wrapper
257
+ def get(key)
258
+ cache.read(key)
259
+ end
260
+
261
+ def set(key, value)
262
+ cache.write(key, value)
263
+ end
264
+
265
+ def clear(options=nil)
266
+ cache.clear(options)
267
+ true
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/base'
3
+
4
+ module Sprockets
5
+ # `CachedEnvironment` is a special cached version of `Environment`.
6
+ #
7
+ # The expection is that all of its file system methods are cached
8
+ # for the instances lifetime. This makes `CachedEnvironment` much faster. This
9
+ # behavior is ideal in production environments where the file system
10
+ # is immutable.
11
+ #
12
+ # `CachedEnvironment` should not be initialized directly. Instead use
13
+ # `Environment#cached`.
14
+ class CachedEnvironment < Base
15
+ def initialize(environment)
16
+ initialize_configuration(environment)
17
+
18
+ @cache = environment.cache
19
+ @stats = {}
20
+ @entries = {}
21
+ @uris = {}
22
+ @processor_cache_keys = {}
23
+ @resolved_dependencies = {}
24
+ end
25
+
26
+ # No-op return self as cached environment.
27
+ def cached
28
+ self
29
+ end
30
+ alias_method :index, :cached
31
+
32
+ # Internal: Cache Environment#entries
33
+ def entries(path)
34
+ @entries[path] ||= super(path)
35
+ end
36
+
37
+ # Internal: Cache Environment#stat
38
+ def stat(path)
39
+ @stats[path] ||= super(path)
40
+ end
41
+
42
+ # Internal: Cache Environment#load
43
+ def load(uri)
44
+ @uris[uri] ||= super(uri)
45
+ end
46
+
47
+ # Internal: Cache Environment#processor_cache_key
48
+ def processor_cache_key(str)
49
+ @processor_cache_keys[str] ||= super(str)
50
+ end
51
+
52
+ # Internal: Cache Environment#resolve_dependency
53
+ def resolve_dependency(str)
54
+ @resolved_dependencies[str] ||= super(str)
55
+ end
56
+
57
+ private
58
+ # Cache is immutable, any methods that try to change the runtime config
59
+ # should bomb.
60
+ def config=(config)
61
+ raise RuntimeError, "can't modify immutable cached environment"
62
+ end
63
+ end
64
+ end