sprockets 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +665 -0
  4. data/bin/sprockets +93 -0
  5. data/lib/rake/sprocketstask.rb +153 -0
  6. data/lib/sprockets.rb +229 -0
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +202 -0
  9. data/lib/sprockets/autoload.rb +16 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +8 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  13. data/lib/sprockets/autoload/eco.rb +8 -0
  14. data/lib/sprockets/autoload/ejs.rb +8 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +8 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +8 -0
  19. data/lib/sprockets/autoload/yui.rb +8 -0
  20. data/lib/sprockets/autoload/zopfli.rb +7 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +147 -0
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache.rb +271 -0
  26. data/lib/sprockets/cache/file_store.rb +208 -0
  27. data/lib/sprockets/cache/memory_store.rb +75 -0
  28. data/lib/sprockets/cache/null_store.rb +54 -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 +304 -0
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +200 -0
  37. data/lib/sprockets/directive_processor.rb +414 -0
  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 +262 -0
  41. data/lib/sprockets/environment.rb +46 -0
  42. data/lib/sprockets/erb_processor.rb +37 -0
  43. data/lib/sprockets/errors.rb +12 -0
  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 +50 -0
  53. data/lib/sprockets/loader.rb +345 -0
  54. data/lib/sprockets/manifest.rb +338 -0
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +96 -0
  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 +82 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +228 -0
  64. data/lib/sprockets/processor_utils.rb +169 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +30 -0
  67. data/lib/sprockets/sass_compressor.rb +63 -0
  68. data/lib/sprockets/sass_functions.rb +3 -0
  69. data/lib/sprockets/sass_importer.rb +3 -0
  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 +295 -0
  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 +191 -0
  81. data/lib/sprockets/utils.rb +202 -0
  82. data/lib/sprockets/utils/gzip.rb +99 -0
  83. data/lib/sprockets/version.rb +4 -0
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. metadata +444 -0
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'logger'
4
+ require 'sprockets/encoding_utils'
5
+ require 'sprockets/path_utils'
6
+ require 'zlib'
7
+
8
+ module Sprockets
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.
13
+ #
14
+ # environment.cache = Sprockets::Cache::FileStore.new("/tmp")
15
+ #
16
+ # See Also
17
+ #
18
+ # ActiveSupport::Cache::FileStore
19
+ #
20
+ class FileStore
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
33
+ end
34
+
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
75
+ end
76
+
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
+
88
+ # Ensure directory exists
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
119
+
120
+ value
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
206
+ end
207
+ end
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,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
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
4
+
5
+ module Sprockets
6
+ # Public: Closure Compiler minifier.
7
+ #
8
+ # To accept the default options
9
+ #
10
+ # environment.register_bundle_processor 'application/javascript',
11
+ # Sprockets::ClosureCompressor
12
+ #
13
+ # Or to pass options to the Closure::Compiler class.
14
+ #
15
+ # environment.register_bundle_processor 'application/javascript',
16
+ # Sprockets::ClosureCompressor.new({ ... })
17
+ #
18
+ class ClosureCompressor
19
+ VERSION = '1'
20
+
21
+ # Public: Return singleton instance with default options.
22
+ #
23
+ # Returns ClosureCompressor object.
24
+ def self.instance
25
+ @instance ||= new
26
+ end
27
+
28
+ def self.call(input)
29
+ instance.call(input)
30
+ end
31
+
32
+ def self.cache_key
33
+ instance.cache_key
34
+ end
35
+
36
+ attr_reader :cache_key
37
+
38
+ def initialize(options = {})
39
+ @options = options
40
+ @cache_key = "#{self.class.name}:#{Autoload::Closure::VERSION}:#{Autoload::Closure::COMPILER_VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
41
+ end
42
+
43
+ def call(input)
44
+ @compiler ||= Autoload::Closure::Compiler.new(@options)
45
+ @compiler.compile(input[:data])
46
+ end
47
+ end
48
+ end