sprockets 2.6.0 → 4.2.2

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +118 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +541 -289
  5. data/bin/sprockets +20 -7
  6. data/lib/rake/sprocketstask.rb +34 -17
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +158 -210
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +8 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  12. data/lib/sprockets/autoload/eco.rb +8 -0
  13. data/lib/sprockets/autoload/ejs.rb +8 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +8 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +8 -0
  18. data/lib/sprockets/autoload/yui.rb +8 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +16 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +89 -378
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache/file_store.rb +190 -14
  26. data/lib/sprockets/cache/memory_store.rb +84 -0
  27. data/lib/sprockets/cache/null_store.rb +54 -0
  28. data/lib/sprockets/cache.rb +271 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +166 -150
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +197 -0
  37. data/lib/sprockets/directive_processor.rb +241 -215
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +261 -0
  41. data/lib/sprockets/environment.rb +23 -64
  42. data/lib/sprockets/erb_processor.rb +43 -0
  43. data/lib/sprockets/errors.rb +5 -13
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +36 -19
  53. data/lib/sprockets/loader.rb +347 -0
  54. data/lib/sprockets/manifest.rb +228 -112
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +78 -31
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +43 -19
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +146 -164
  64. data/lib/sprockets/processor_utils.rb +170 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +20 -15
  67. data/lib/sprockets/sass_compressor.rb +55 -10
  68. data/lib/sprockets/sass_functions.rb +3 -70
  69. data/lib/sprockets/sass_importer.rb +3 -29
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +159 -91
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +194 -0
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +193 -52
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. data/lib/sprockets.rb +217 -75
  86. metadata +272 -117
  87. data/lib/sprockets/asset_attributes.rb +0 -131
  88. data/lib/sprockets/bundled_asset.rb +0 -80
  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/sass_template.rb +0 -60
  99. data/lib/sprockets/scss_template.rb +0 -13
  100. data/lib/sprockets/static_asset.rb +0 -58
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module Sprockets
5
+ module Bower
6
+ # Internal: All supported bower.json files.
7
+ #
8
+ # https://github.com/bower/json/blob/0.4.0/lib/json.js#L7
9
+ POSSIBLE_BOWER_JSONS = ['bower.json', 'component.json', '.bower.json']
10
+
11
+ # Internal: Override resolve_alternates to install bower.json behavior.
12
+ #
13
+ # load_path - String environment path
14
+ # logical_path - String path relative to base
15
+ #
16
+ # Returns candidate filenames.
17
+ def resolve_alternates(load_path, logical_path)
18
+ candidates, deps = super
19
+
20
+ # bower.json can only be nested one level deep
21
+ if !logical_path.index('/'.freeze)
22
+ dirname = File.join(load_path, logical_path)
23
+
24
+ if directory?(dirname)
25
+ filenames = POSSIBLE_BOWER_JSONS.map { |basename| File.join(dirname, basename) }
26
+ filename = filenames.detect { |fn| self.file?(fn) }
27
+
28
+ if filename
29
+ deps << build_file_digest_uri(filename)
30
+ read_bower_main(dirname, filename) do |path|
31
+ if file?(path)
32
+ candidates << path
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ return candidates, deps
40
+ end
41
+
42
+ # Internal: Read bower.json's main directive.
43
+ #
44
+ # dirname - String path to component directory.
45
+ # filename - String path to bower.json.
46
+ #
47
+ # Returns nothing.
48
+ def read_bower_main(dirname, filename)
49
+ bower = JSON.parse(File.read(filename), create_additions: false)
50
+
51
+ case bower['main']
52
+ when String
53
+ yield File.expand_path(bower['main'], dirname)
54
+ when Array
55
+ bower['main'].each do |name|
56
+ yield File.expand_path(name, dirname)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ require 'sprockets/utils'
4
+ require 'sprockets/uri_utils'
5
+
6
+ module Sprockets
7
+ # Internal: Bundle processor takes a single file asset and prepends all the
8
+ # `:required` URIs to the contents.
9
+ #
10
+ # Uses pipeline metadata:
11
+ #
12
+ # :required - Ordered Set of asset URIs to prepend
13
+ # :stubbed - Set of asset URIs to subtract from the required set.
14
+ #
15
+ # Also see DirectiveProcessor.
16
+ class Bundle
17
+ def self.call(input)
18
+ env = input[:environment]
19
+ type = input[:content_type]
20
+ input[:links] ||= Set.new
21
+ dependencies = Set.new(input[:metadata][:dependencies])
22
+
23
+ processed_uri, deps = env.resolve(input[:filename], accept: type, pipeline: :self)
24
+ dependencies.merge(deps)
25
+
26
+ # DirectiveProcessor (and any other transformers called here with pipeline=self)
27
+ primary_asset = env.load(processed_uri)
28
+ to_load = primary_asset.metadata.delete(:to_load) || Set.new
29
+ to_link = primary_asset.metadata.delete(:to_link) || Set.new
30
+
31
+ to_load.each do |uri|
32
+ loaded_asset = env.load(uri)
33
+ dependencies.merge(loaded_asset.metadata[:dependencies])
34
+ if to_link.include?(uri)
35
+ primary_metadata = primary_asset.metadata
36
+ input[:links] << loaded_asset.uri
37
+ primary_metadata[:links] << loaded_asset.uri
38
+ end
39
+ end
40
+
41
+ find_required = proc { |uri| env.load(uri).metadata[:required] }
42
+ required = Utils.dfs(processed_uri, &find_required)
43
+ stubbed = Utils.dfs(env.load(processed_uri).metadata[:stubbed], &find_required)
44
+ required.subtract(stubbed)
45
+ dedup(required)
46
+ assets = required.map { |uri| env.load(uri) }
47
+
48
+ (required + stubbed).each do |uri|
49
+ dependencies.merge(env.load(uri).metadata[:dependencies])
50
+ end
51
+
52
+ reducers = Hash[env.match_mime_type_keys(env.config[:bundle_reducers], type).flat_map(&:to_a)]
53
+ process_bundle_reducers(input, assets, reducers).merge(dependencies: dependencies, included: assets.map(&:uri))
54
+ end
55
+
56
+ # Internal: Removes uri from required if it's already included as an alias.
57
+ #
58
+ # required - Set of required uris
59
+ #
60
+ # Returns deduped set of uris
61
+ def self.dedup(required)
62
+ dupes = required.reduce([]) do |r, uri|
63
+ path, params = URIUtils.parse_asset_uri(uri)
64
+ if (params.delete(:index_alias))
65
+ r << URIUtils.build_asset_uri(path, params)
66
+ end
67
+ r
68
+ end
69
+ required.subtract(dupes)
70
+ end
71
+
72
+ # Internal: Run bundle reducers on set of Assets producing a reduced
73
+ # metadata Hash.
74
+ #
75
+ # filename - String bundle filename
76
+ # assets - Array of Assets
77
+ # reducers - Array of [initial, reducer_proc] pairs
78
+ #
79
+ # Returns reduced asset metadata Hash.
80
+ def self.process_bundle_reducers(input, assets, reducers)
81
+ initial = {}
82
+ reducers.each do |k, (v, _)|
83
+ if v.respond_to?(:call)
84
+ initial[k] = v.call(input)
85
+ elsif !v.nil?
86
+ initial[k] = v
87
+ end
88
+ end
89
+
90
+ assets.reduce(initial) do |h, asset|
91
+ reducers.each do |k, (_, block)|
92
+ value = k == :data ? asset.source : asset.metadata[k]
93
+ if h.key?(k)
94
+ if !value.nil?
95
+ h[k] = block.call(h[k], value)
96
+ end
97
+ else
98
+ h[k] = value
99
+ end
100
+ end
101
+ h
102
+ end
103
+ end
104
+ end
105
+ end
@@ -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,84 @@
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
+ @mutex = Mutex.new
26
+ end
27
+
28
+ # Public: Retrieve value from cache.
29
+ #
30
+ # This API should not be used directly, but via the Cache wrapper API.
31
+ #
32
+ # key - String cache key.
33
+ #
34
+ # Returns Object or nil or the value is not set.
35
+ def get(key)
36
+ @mutex.synchronize do
37
+ exists = true
38
+ value = @cache.delete(key) { exists = false }
39
+ if exists
40
+ @cache[key] = value
41
+ else
42
+ nil
43
+ end
44
+ end
45
+ end
46
+
47
+ # Public: Set a key and value in the cache.
48
+ #
49
+ # This API should not be used directly, but via the Cache wrapper API.
50
+ #
51
+ # key - String cache key.
52
+ # value - Object value.
53
+ #
54
+ # Returns Object value.
55
+ def set(key, value)
56
+ @mutex.synchronize do
57
+ @cache.delete(key)
58
+ @cache[key] = value
59
+ @cache.shift if @cache.size > @max_size
60
+ end
61
+ value
62
+ end
63
+
64
+ # Public: Pretty inspect
65
+ #
66
+ # Returns String.
67
+ def inspect
68
+ @mutex.synchronize do
69
+ "#<#{self.class} size=#{@cache.size}/#{@max_size}>"
70
+ end
71
+ end
72
+
73
+ # Public: Clear the cache
74
+ #
75
+ # Returns true
76
+ def clear(options=nil)
77
+ @mutex.synchronize do
78
+ @cache.clear
79
+ end
80
+ true
81
+ end
82
+ end
83
+ end
84
+ 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