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
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
4
+ require 'sprockets/source_map_utils'
5
+
6
+ module Sprockets
7
+ # Public: Uglifier/Uglify compressor.
8
+ #
9
+ # To accept the default options
10
+ #
11
+ # environment.register_bundle_processor 'application/javascript',
12
+ # Sprockets::UglifierCompressor
13
+ #
14
+ # Or to pass options to the Uglifier class.
15
+ #
16
+ # environment.register_bundle_processor 'application/javascript',
17
+ # Sprockets::UglifierCompressor.new(comments: :copyright)
18
+ #
19
+ class UglifierCompressor
20
+ VERSION = '3'
21
+
22
+ # Public: Return singleton instance with default options.
23
+ #
24
+ # Returns UglifierCompressor object.
25
+ def self.instance
26
+ @instance ||= new
27
+ end
28
+
29
+ def self.call(input)
30
+ instance.call(input)
31
+ end
32
+
33
+ def self.cache_key
34
+ instance.cache_key
35
+ end
36
+
37
+ attr_reader :cache_key
38
+
39
+ def initialize(options = {})
40
+ options[:comments] ||= :none
41
+
42
+ @options = options
43
+ @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
44
+ end
45
+
46
+ def call(input)
47
+ case Autoload::Uglifier::VERSION.to_i
48
+ when 1
49
+ raise "uglifier 1.x is no longer supported, please upgrade to 2.x or newer"
50
+ when 2
51
+ input_options = { source_filename: input[:filename] }
52
+ else
53
+ input_options = { source_map: { filename: input[:filename] } }
54
+ end
55
+
56
+ uglifier = Autoload::Uglifier.new(@options.merge(input_options))
57
+
58
+ js, map = uglifier.compile_with_map(input[:data])
59
+
60
+ map = SourceMapUtils.format_source_map(JSON.parse(map), input)
61
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
62
+
63
+ { data: js, map: map }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/uri_utils'
3
+ require 'sprockets/uri_tar'
4
+
5
+ module Sprockets
6
+ # Internal: Used to parse and store the URI to an unloaded asset
7
+ # Generates keys used to store and retrieve items from cache
8
+ class UnloadedAsset
9
+
10
+ # Internal: Initialize object for generating cache keys
11
+ #
12
+ # uri - A String containing complete URI to a file including scheme
13
+ # and full path such as
14
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
15
+ # env - The current "environment" that assets are being loaded into.
16
+ # We need it so we know where the +root+ (directory where Sprockets
17
+ # is being invoked). We also need it for the `file_digest` method,
18
+ # since, for some strange reason, memoization is provided by
19
+ # overriding methods such as `stat` in the `PathUtils` module.
20
+ #
21
+ # Returns UnloadedAsset.
22
+ def initialize(uri, env)
23
+ @uri = uri.to_s
24
+ @env = env
25
+ @compressed_path = URITar.new(uri, env).compressed_path
26
+ @params = nil # lazy loaded
27
+ @filename = nil # lazy loaded
28
+ end
29
+ attr_reader :compressed_path, :uri
30
+
31
+ # Internal: Full file path without schema
32
+ #
33
+ # This returns a string containing the full path to the asset without the schema.
34
+ # Information is loaded lazily since we want `UnloadedAsset.new(dep, self).relative_path`
35
+ # to be fast. Calling this method the first time allocates an array and a hash.
36
+ #
37
+ # Example
38
+ #
39
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
40
+ # filename would be `"/Full/path/app/assets/javascripts/application.js"`
41
+ #
42
+ # Returns a String.
43
+ def filename
44
+ unless @filename
45
+ load_file_params
46
+ end
47
+ @filename
48
+ end
49
+
50
+ # Internal: Hash of param values
51
+ #
52
+ # This information is generated and used internally by Sprockets.
53
+ # Known keys include `:type` which stores the asset's mime-type, `:id` which is a fully resolved
54
+ # digest for the asset (includes dependency digest as opposed to a digest of only file contents)
55
+ # and `:pipeline`. Hash may be empty.
56
+ #
57
+ # Example
58
+ #
59
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
60
+ # Then the params would be `{type: "application/javascript"}`
61
+ #
62
+ # Returns a Hash.
63
+ def params
64
+ unless @params
65
+ load_file_params
66
+ end
67
+ @params
68
+ end
69
+
70
+ # Internal: Key of asset
71
+ #
72
+ # Used to retrieve an asset from the cache based on "compressed" path to asset.
73
+ # A "compressed" path can either be relative to the root of the project or an
74
+ # absolute path.
75
+ #
76
+ # Returns a String.
77
+ def asset_key
78
+ "asset-uri:#{compressed_path}"
79
+ end
80
+
81
+ # Public: Dependency History key
82
+ #
83
+ # Used to retrieve an array of "histories" each of which contains a set of stored dependencies
84
+ # for a given asset path and filename digest.
85
+ #
86
+ # A dependency can refer to either an asset e.g. index.js
87
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
88
+ # compilation, such as the VERSION of Sprockets (i.e. the environment) and what "processors"
89
+ # are used.
90
+ #
91
+ # For example a history array with one Set of dependencies may look like:
92
+ #
93
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
94
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
95
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
96
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
97
+ #
98
+ # This method of asset lookup is used to ensure that none of the dependencies have been modified
99
+ # since last lookup. If one of them has, the key will be different and a new entry must be stored.
100
+ #
101
+ # URI dependencies are later converted to "compressed" paths
102
+ #
103
+ # Returns a String.
104
+ def dependency_history_key
105
+ "asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
106
+ end
107
+
108
+ # Internal: Digest key
109
+ #
110
+ # Used to retrieve a string containing the "compressed" path to an asset based on
111
+ # a digest. The digest is generated from dependencies stored via information stored in
112
+ # the `dependency_history_key` after each of the "dependencies" is "resolved".
113
+ # For example "environment-version" may be resolved to "environment-1.0-3.2.0"
114
+ # for version "3.2.0" of Sprockets
115
+ #
116
+ # Returns a String.
117
+ def digest_key(digest)
118
+ "asset-uri-digest:#{compressed_path}:#{digest}"
119
+ end
120
+
121
+ # Internal: File digest key
122
+ #
123
+ # The digest for a given file won't change if the path and the stat time hasn't changed
124
+ # We can save time by not re-computing this information and storing it in the cache
125
+ #
126
+ # Returns a String.
127
+ def file_digest_key(stat)
128
+ "file_digest:#{compressed_path}:#{stat}"
129
+ end
130
+
131
+ private
132
+ # Internal: Parses uri into filename and params hash
133
+ #
134
+ # Returns Array with filename and params hash
135
+ def load_file_params
136
+ @filename, @params = URIUtils.parse_asset_uri(uri)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/path_utils'
3
+
4
+ module Sprockets
5
+ # Internal: used to "expand" and "compress" values for storage
6
+ class URITar
7
+ attr_reader :scheme, :root, :path
8
+
9
+ # Internal: Initialize object for compression or expansion
10
+ #
11
+ # uri - A String containing URI that may or may not contain the scheme
12
+ # env - The current "environment" that assets are being loaded into.
13
+ def initialize(uri, env)
14
+ @root = env.root
15
+ @env = env
16
+ uri = uri.to_s
17
+ if uri.include?("://".freeze)
18
+ @scheme, _, @path = uri.partition("://".freeze)
19
+ @scheme << "://".freeze
20
+ else
21
+ @scheme = "".freeze
22
+ @path = uri
23
+ end
24
+ end
25
+
26
+ # Internal: Converts full uri to a "compressed" uri
27
+ #
28
+ # If a uri is inside of an environment's root it will
29
+ # be shortened to be a relative path.
30
+ #
31
+ # If a uri is outside of the environment's root the original
32
+ # uri will be returned.
33
+ #
34
+ # Returns String
35
+ def compress
36
+ scheme + compressed_path
37
+ end
38
+
39
+ # Internal: Tells us if we are using an absolute path
40
+ #
41
+ # Nix* systems start with a `/` like /Users/schneems.
42
+ # Windows systems start with a drive letter than colon and slash
43
+ # like C:/Schneems.
44
+ def absolute_path?
45
+ PathUtils.absolute_path?(path)
46
+ end
47
+
48
+ # Internal: Convert a "compressed" uri to an absolute path
49
+ #
50
+ # If a uri is inside of the environment's root it will not
51
+ # start with a slash for example:
52
+ #
53
+ # file://this/is/a/relative/path
54
+ #
55
+ # If a uri is outside the root, it will start with a slash:
56
+ #
57
+ # file:///This/is/an/absolute/path
58
+ #
59
+ # Returns String
60
+ def expand
61
+ if absolute_path?
62
+ # Stored path was absolute, don't add root
63
+ scheme + path
64
+ else
65
+ if scheme.empty?
66
+ File.join(root, path)
67
+ else
68
+ # We always want to return an absolute uri,
69
+ # make sure the path starts with a slash.
70
+ scheme + File.join("/".freeze, root, path)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Internal: Returns "compressed" path
76
+ #
77
+ # If the input uri is relative to the environment root
78
+ # it will return a path relative to the environment root.
79
+ # Otherwise an absolute path will be returned.
80
+ #
81
+ # Only path information is returned, and not scheme.
82
+ #
83
+ # Returns String
84
+ def compressed_path
85
+ # windows
86
+ if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
87
+ consistent_root = "/".freeze + @root
88
+ else
89
+ consistent_root = @root
90
+ end
91
+
92
+ if compressed_path = PathUtils.split_subpath(consistent_root, path)
93
+ compressed_path
94
+ else
95
+ path
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+
4
+ module Sprockets
5
+ # Internal: Asset URI related parsing utilities. Mixed into Environment.
6
+ #
7
+ # An Asset URI identifies the compiled Asset result. It shares the file:
8
+ # scheme and requires an absolute path.
9
+ #
10
+ # Other query parameters
11
+ #
12
+ # type - String output content type. Otherwise assumed from file extension.
13
+ # This maybe different than the extension if the asset is transformed
14
+ # from one content type to another. For an example .coffee -> .js.
15
+ #
16
+ # id - Unique fingerprint of the entire asset and all its metadata. Assets
17
+ # will only have the same id if they serialize to an identical value.
18
+ #
19
+ # pipeline - String name of pipeline.
20
+ #
21
+ module URIUtils
22
+ extend self
23
+
24
+ # Internal: Parse URI into component parts.
25
+ #
26
+ # uri - String uri
27
+ #
28
+ # Returns Array of components.
29
+ def split_uri(uri)
30
+ URI.split(uri)
31
+ end
32
+
33
+ # Internal: Join URI component parts into String.
34
+ #
35
+ # Returns String.
36
+ def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
37
+ URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
38
+ end
39
+
40
+ # Internal: Parse file: URI into component parts.
41
+ #
42
+ # uri - String uri
43
+ #
44
+ # Returns [scheme, host, path, query].
45
+ def split_file_uri(uri)
46
+ scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
47
+
48
+ path = URI::Generic::DEFAULT_PARSER.unescape(path)
49
+ path.force_encoding(Encoding::UTF_8)
50
+
51
+ # Hack for parsing Windows "/C:/Users/IEUser" paths
52
+ if File::ALT_SEPARATOR && path[2] == ':'
53
+ path = path[1..-1]
54
+ end
55
+
56
+ [scheme, host, path, query]
57
+ end
58
+
59
+ # Internal: Join file: URI component parts into String.
60
+ #
61
+ # Returns String.
62
+ def join_file_uri(scheme, host, path, query)
63
+ str = +"#{scheme}://"
64
+ str << host if host
65
+ path = "/#{path}" unless path.start_with?("/".freeze)
66
+ str << URI::Generic::DEFAULT_PARSER.escape(path)
67
+ str << "?#{query}" if query
68
+ str
69
+ end
70
+
71
+ # Internal: Check if String is a valid Asset URI.
72
+ #
73
+ # str - Possible String asset URI.
74
+ #
75
+ # Returns true or false.
76
+ def valid_asset_uri?(str)
77
+ # Quick prefix check before attempting a full parse
78
+ str.start_with?("file://".freeze) && parse_asset_uri(str) ? true : false
79
+ rescue URI::InvalidURIError
80
+ false
81
+ end
82
+
83
+ # Internal: Parse Asset URI.
84
+ #
85
+ # Examples
86
+ #
87
+ # parse("file:///tmp/js/application.coffee?type=application/javascript")
88
+ # # => "/tmp/js/application.coffee", {type: "application/javascript"}
89
+ #
90
+ # uri - String asset URI
91
+ #
92
+ # Returns String path and Hash of symbolized parameters.
93
+ def parse_asset_uri(uri)
94
+ scheme, _, path, query = split_file_uri(uri)
95
+
96
+ unless scheme == 'file'
97
+ raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
98
+ end
99
+
100
+ return path, parse_uri_query_params(query)
101
+ end
102
+
103
+ # Internal: Build Asset URI.
104
+ #
105
+ # Examples
106
+ #
107
+ # build("/tmp/js/application.coffee", type: "application/javascript")
108
+ # # => "file:///tmp/js/application.coffee?type=application/javascript"
109
+ #
110
+ # path - String file path
111
+ # params - Hash of optional parameters
112
+ #
113
+ # Returns String URI.
114
+ def build_asset_uri(path, params = {})
115
+ join_file_uri("file", nil, path, encode_uri_query_params(params))
116
+ end
117
+
118
+ # Internal: Parse file-digest dependency URI.
119
+ #
120
+ # Examples
121
+ #
122
+ # parse("file-digest:/tmp/js/application.js")
123
+ # # => "/tmp/js/application.js"
124
+ #
125
+ # uri - String file-digest URI
126
+ #
127
+ # Returns String path.
128
+ def parse_file_digest_uri(uri)
129
+ scheme, _, path, _ = split_file_uri(uri)
130
+
131
+ unless scheme == 'file-digest'.freeze
132
+ raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
133
+ end
134
+
135
+ path
136
+ end
137
+
138
+ # Internal: Build file-digest dependency URI.
139
+ #
140
+ # Examples
141
+ #
142
+ # build("/tmp/js/application.js")
143
+ # # => "file-digest:/tmp/js/application.js"
144
+ #
145
+ # path - String file path
146
+ #
147
+ # Returns String URI.
148
+ def build_file_digest_uri(path)
149
+ join_file_uri('file-digest'.freeze, nil, path, nil)
150
+ end
151
+
152
+ # Internal: Serialize hash of params into query string.
153
+ #
154
+ # params - Hash of params to serialize
155
+ #
156
+ # Returns String query or nil if empty.
157
+ def encode_uri_query_params(params)
158
+ query = []
159
+
160
+ params.each do |key, value|
161
+ case value
162
+ when Integer
163
+ query << "#{key}=#{value}"
164
+ when String, Symbol
165
+ query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
166
+ when TrueClass
167
+ query << "#{key}"
168
+ when FalseClass, NilClass
169
+ else
170
+ raise TypeError, "unexpected type: #{value.class}"
171
+ end
172
+ end
173
+
174
+ "#{query.join('&'.freeze)}" if query.any?
175
+ end
176
+
177
+ # Internal: Parse query string into hash of params
178
+ #
179
+ # query - String query string
180
+ #
181
+ # Return Hash of params.
182
+ def parse_uri_query_params(query)
183
+ query.to_s.split('&'.freeze).reduce({}) do |h, p|
184
+ k, v = p.split('='.freeze, 2)
185
+ v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
186
+ h[k.to_sym] = v || true
187
+ h
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ module Utils
4
+ class Gzip
5
+ # Private: Generates a gzipped file based off of reference asset.
6
+ #
7
+ # ZlibArchiver.call(file, source, mtime)
8
+ #
9
+ # Compresses a given `source` using stdlib Zlib algorithm
10
+ # writes contents to the `file` passed in. Sets `mtime` of
11
+ # written file to passed in `mtime`
12
+ module ZlibArchiver
13
+ def self.call(file, source, mtime)
14
+ gz = Zlib::GzipWriter.new(file, Zlib::BEST_COMPRESSION)
15
+ gz.mtime = mtime
16
+ gz.write(source)
17
+ gz.close
18
+
19
+ File.utime(mtime, mtime, file.path)
20
+ end
21
+ end
22
+
23
+ # Private: Generates a gzipped file based off of reference asset.
24
+ #
25
+ # ZopfliArchiver.call(file, source, mtime)
26
+ #
27
+ # Compresses a given `source` using the zopfli gem
28
+ # writes contents to the `file` passed in. Sets `mtime` of
29
+ # written file to passed in `mtime`
30
+ module ZopfliArchiver
31
+ def self.call(file, source, mtime)
32
+ compressed_source = Autoload::Zopfli.deflate(source, format: :gzip, mtime: mtime)
33
+ file.write(compressed_source)
34
+ file.close
35
+
36
+ nil
37
+ end
38
+ end
39
+
40
+ attr_reader :content_type, :source, :charset, :archiver
41
+
42
+ # Private: Generates a gzipped file based off of reference file.
43
+ def initialize(asset, archiver: ZlibArchiver)
44
+ @content_type = asset.content_type
45
+ @source = asset.source
46
+ @charset = asset.charset
47
+ @archiver = archiver
48
+ end
49
+
50
+ # What non-text mime types should we compress? This list comes from:
51
+ # https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress
52
+ COMPRESSABLE_MIME_TYPES = {
53
+ "application/vnd.ms-fontobject" => true,
54
+ "application/x-font-opentype" => true,
55
+ "application/x-font-ttf" => true,
56
+ "image/x-icon" => true,
57
+ "image/svg+xml" => true
58
+ }
59
+
60
+ # Private: Returns whether or not an asset can be compressed.
61
+ #
62
+ # We want to compress any file that is text based.
63
+ # You do not want to compress binary
64
+ # files as they may already be compressed and running them
65
+ # through a compression algorithm would make them larger.
66
+ #
67
+ # Return Boolean.
68
+ def can_compress?
69
+ # The "charset" of a mime type is present if the value is
70
+ # encoded text. We can check this value to see if the asset
71
+ # can be compressed.
72
+ #
73
+ # We also check against our list of non-text compressible mime types
74
+ @charset || COMPRESSABLE_MIME_TYPES.include?(@content_type)
75
+ end
76
+
77
+ # Private: Opposite of `can_compress?`.
78
+ #
79
+ # Returns Boolean.
80
+ def cannot_compress?
81
+ !can_compress?
82
+ end
83
+
84
+ # Private: Generates a gzipped file based off of reference asset.
85
+ #
86
+ # Compresses the target asset's contents and puts it into a file with
87
+ # the same name plus a `.gz` extension in the same folder as the original.
88
+ # Does not modify the target asset.
89
+ #
90
+ # Returns nothing.
91
+ def compress(file, target)
92
+ mtime = Sprockets::PathUtils.stat(target).mtime
93
+ archiver.call(file, source, mtime)
94
+
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ end