sprockets 2.12.5 → 3.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +296 -0
  3. data/LICENSE +2 -2
  4. data/README.md +235 -262
  5. data/bin/sprockets +1 -0
  6. data/lib/rake/sprocketstask.rb +5 -4
  7. data/lib/sprockets/asset.rb +143 -212
  8. data/lib/sprockets/autoload/closure.rb +7 -0
  9. data/lib/sprockets/autoload/coffee_script.rb +7 -0
  10. data/lib/sprockets/autoload/eco.rb +7 -0
  11. data/lib/sprockets/autoload/ejs.rb +7 -0
  12. data/lib/sprockets/autoload/sass.rb +7 -0
  13. data/lib/sprockets/autoload/uglifier.rb +7 -0
  14. data/lib/sprockets/autoload/yui.rb +7 -0
  15. data/lib/sprockets/autoload.rb +11 -0
  16. data/lib/sprockets/base.rb +56 -393
  17. data/lib/sprockets/bower.rb +58 -0
  18. data/lib/sprockets/bundle.rb +69 -0
  19. data/lib/sprockets/cache/file_store.rb +168 -14
  20. data/lib/sprockets/cache/memory_store.rb +66 -0
  21. data/lib/sprockets/cache/null_store.rb +46 -0
  22. data/lib/sprockets/cache.rb +236 -0
  23. data/lib/sprockets/cached_environment.rb +69 -0
  24. data/lib/sprockets/closure_compressor.rb +35 -10
  25. data/lib/sprockets/coffee_script_processor.rb +25 -0
  26. data/lib/sprockets/coffee_script_template.rb +17 -0
  27. data/lib/sprockets/compressing.rb +44 -23
  28. data/lib/sprockets/configuration.rb +83 -0
  29. data/lib/sprockets/context.rb +86 -144
  30. data/lib/sprockets/dependencies.rb +73 -0
  31. data/lib/sprockets/deprecation.rb +90 -0
  32. data/lib/sprockets/digest_utils.rb +180 -0
  33. data/lib/sprockets/directive_processor.rb +207 -211
  34. data/lib/sprockets/eco_processor.rb +32 -0
  35. data/lib/sprockets/eco_template.rb +9 -30
  36. data/lib/sprockets/ejs_processor.rb +31 -0
  37. data/lib/sprockets/ejs_template.rb +9 -29
  38. data/lib/sprockets/encoding_utils.rb +261 -0
  39. data/lib/sprockets/engines.rb +53 -35
  40. data/lib/sprockets/environment.rb +17 -64
  41. data/lib/sprockets/erb_processor.rb +30 -0
  42. data/lib/sprockets/erb_template.rb +11 -0
  43. data/lib/sprockets/errors.rb +4 -13
  44. data/lib/sprockets/file_reader.rb +15 -0
  45. data/lib/sprockets/http_utils.rb +117 -0
  46. data/lib/sprockets/jst_processor.rb +35 -15
  47. data/lib/sprockets/legacy.rb +330 -0
  48. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  49. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  50. data/lib/sprockets/loader.rb +325 -0
  51. data/lib/sprockets/manifest.rb +202 -127
  52. data/lib/sprockets/manifest_utils.rb +45 -0
  53. data/lib/sprockets/mime.rb +112 -31
  54. data/lib/sprockets/path_dependency_utils.rb +85 -0
  55. data/lib/sprockets/path_digest_utils.rb +47 -0
  56. data/lib/sprockets/path_utils.rb +287 -0
  57. data/lib/sprockets/paths.rb +42 -19
  58. data/lib/sprockets/processing.rb +178 -126
  59. data/lib/sprockets/processor_utils.rb +180 -0
  60. data/lib/sprockets/resolve.rb +211 -0
  61. data/lib/sprockets/sass_cache_store.rb +22 -17
  62. data/lib/sprockets/sass_compressor.rb +39 -15
  63. data/lib/sprockets/sass_functions.rb +2 -70
  64. data/lib/sprockets/sass_importer.rb +2 -30
  65. data/lib/sprockets/sass_processor.rb +292 -0
  66. data/lib/sprockets/sass_template.rb +12 -59
  67. data/lib/sprockets/server.rb +129 -84
  68. data/lib/sprockets/transformers.rb +145 -0
  69. data/lib/sprockets/uglifier_compressor.rb +39 -12
  70. data/lib/sprockets/unloaded_asset.rb +137 -0
  71. data/lib/sprockets/uri_tar.rb +98 -0
  72. data/lib/sprockets/uri_utils.rb +188 -0
  73. data/lib/sprockets/utils/gzip.rb +67 -0
  74. data/lib/sprockets/utils.rb +210 -44
  75. data/lib/sprockets/version.rb +1 -1
  76. data/lib/sprockets/yui_compressor.rb +39 -11
  77. data/lib/sprockets.rb +142 -81
  78. metadata +96 -90
  79. data/lib/sprockets/asset_attributes.rb +0 -137
  80. data/lib/sprockets/bundled_asset.rb +0 -78
  81. data/lib/sprockets/caching.rb +0 -96
  82. data/lib/sprockets/charset_normalizer.rb +0 -41
  83. data/lib/sprockets/index.rb +0 -100
  84. data/lib/sprockets/processed_asset.rb +0 -152
  85. data/lib/sprockets/processor.rb +0 -32
  86. data/lib/sprockets/safety_colons.rb +0 -28
  87. data/lib/sprockets/scss_template.rb +0 -13
  88. data/lib/sprockets/static_asset.rb +0 -60
@@ -0,0 +1,145 @@
1
+ require 'sprockets/http_utils'
2
+ require 'sprockets/processor_utils'
3
+ require 'sprockets/utils'
4
+
5
+ module Sprockets
6
+ module Transformers
7
+ include HTTPUtils, ProcessorUtils, Utils
8
+
9
+ # Public: Two level mapping of a source mime type to a target mime type.
10
+ #
11
+ # environment.transformers
12
+ # # => { 'text/coffeescript' => {
13
+ # 'application/javascript' => ConvertCoffeeScriptToJavaScript
14
+ # }
15
+ # }
16
+ #
17
+ def transformers
18
+ config[:transformers]
19
+ end
20
+
21
+ # Public: Register a transformer from and to a mime type.
22
+ #
23
+ # from - String mime type
24
+ # to - String mime type
25
+ # proc - Callable block that accepts an input Hash.
26
+ #
27
+ # Examples
28
+ #
29
+ # register_transformer 'text/coffeescript', 'application/javascript',
30
+ # ConvertCoffeeScriptToJavaScript
31
+ #
32
+ # register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
33
+ #
34
+ # Returns nothing.
35
+ def register_transformer(from, to, proc)
36
+ self.config = hash_reassoc(config, :registered_transformers, from) do |transformers|
37
+ transformers.merge(to => proc)
38
+ end
39
+ compute_transformers!
40
+ end
41
+
42
+ # Internal: Resolve target mime type that the source type should be
43
+ # transformed to.
44
+ #
45
+ # type - String from mime type
46
+ # accept - String accept type list (default: '*/*')
47
+ #
48
+ # Examples
49
+ #
50
+ # resolve_transform_type('text/plain', 'text/plain')
51
+ # # => 'text/plain'
52
+ #
53
+ # resolve_transform_type('image/svg+xml', 'image/png, image/*')
54
+ # # => 'image/png'
55
+ #
56
+ # resolve_transform_type('text/css', 'image/png')
57
+ # # => nil
58
+ #
59
+ # Returns String mime type or nil is no type satisfied the accept value.
60
+ def resolve_transform_type(type, accept)
61
+ find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys)
62
+ end
63
+
64
+ # Internal: Expand accept type list to include possible transformed types.
65
+ #
66
+ # parsed_accepts - Array of accept q values
67
+ #
68
+ # Examples
69
+ #
70
+ # expand_transform_accepts([['application/javascript', 1.0]])
71
+ # # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]]
72
+ #
73
+ # Returns an expanded Array of q values.
74
+ def expand_transform_accepts(parsed_accepts)
75
+ accepts = []
76
+ parsed_accepts.each do |(type, q)|
77
+ accepts.push([type, q])
78
+ config[:inverted_transformers][type].each do |subtype|
79
+ accepts.push([subtype, q * 0.8])
80
+ end
81
+ end
82
+ accepts
83
+ end
84
+
85
+ # Internal: Compose multiple transformer steps into a single processor
86
+ # function.
87
+ #
88
+ # transformers - Two level Hash of a source mime type to a target mime type
89
+ # types - Array of mime type steps
90
+ #
91
+ # Returns Processor.
92
+ def compose_transformers(transformers, types)
93
+ if types.length < 2
94
+ raise ArgumentError, "too few transform types: #{types.inspect}"
95
+ end
96
+
97
+ i = 0
98
+ processors = []
99
+
100
+ loop do
101
+ src = types[i]
102
+ dst = types[i+1]
103
+ break unless src && dst
104
+
105
+ unless processor = transformers[src][dst]
106
+ raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
107
+ end
108
+ processors.concat config[:postprocessors][src]
109
+ processors << processor
110
+ processors.concat config[:preprocessors][dst]
111
+
112
+ i += 1
113
+ end
114
+
115
+ if processors.size > 1
116
+ compose_processors(*processors.reverse)
117
+ elsif processors.size == 1
118
+ processors.first
119
+ end
120
+ end
121
+
122
+ private
123
+ def compute_transformers!
124
+ registered_transformers = self.config[:registered_transformers]
125
+ transformers = Hash.new { {} }
126
+ inverted_transformers = Hash.new { Set.new }
127
+
128
+ registered_transformers.keys.flat_map do |key|
129
+ dfs_paths([key]) { |k| registered_transformers[k].keys }
130
+ end.each do |types|
131
+ src, dst = types.first, types.last
132
+ processor = compose_transformers(registered_transformers, types)
133
+
134
+ transformers[src] = {} unless transformers.key?(src)
135
+ transformers[src][dst] = processor
136
+
137
+ inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
138
+ inverted_transformers[dst] << src
139
+ end
140
+
141
+ self.config = hash_reassoc(config, :transformers) { transformers }
142
+ self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers }
143
+ end
144
+ end
145
+ end
@@ -1,29 +1,56 @@
1
- require 'tilt'
1
+ require 'sprockets/autoload'
2
+ require 'sprockets/digest_utils'
2
3
 
3
4
  module Sprockets
4
- class UglifierCompressor < Tilt::Template
5
- self.default_mime_type = 'application/javascript'
5
+ # Public: Uglifier/Uglify compressor.
6
+ #
7
+ # To accept the default options
8
+ #
9
+ # environment.register_bundle_processor 'application/javascript',
10
+ # Sprockets::UglifierCompressor
11
+ #
12
+ # Or to pass options to the Uglifier class.
13
+ #
14
+ # environment.register_bundle_processor 'application/javascript',
15
+ # Sprockets::UglifierCompressor.new(comments: :copyright)
16
+ #
17
+ class UglifierCompressor
18
+ VERSION = '1'
6
19
 
7
- def self.engine_initialized?
8
- defined?(::Uglifier)
20
+ # Public: Return singleton instance with default options.
21
+ #
22
+ # Returns UglifierCompressor object.
23
+ def self.instance
24
+ @instance ||= new
9
25
  end
10
26
 
11
- def initialize_engine
12
- require_template_library 'uglifier'
27
+ def self.call(input)
28
+ instance.call(input)
13
29
  end
14
30
 
15
- def prepare
31
+ def self.cache_key
32
+ instance.cache_key
16
33
  end
17
34
 
18
- def evaluate(context, locals, &block)
35
+ attr_reader :cache_key
36
+
37
+ def initialize(options = {})
19
38
  # Feature detect Uglifier 2.0 option support
20
- if Uglifier::DEFAULTS[:copyright]
39
+ if Autoload::Uglifier::DEFAULTS[:copyright]
21
40
  # Uglifier < 2.x
22
- Uglifier.new(:copyright => false).compile(data)
41
+ options[:copyright] ||= false
23
42
  else
24
43
  # Uglifier >= 2.x
25
- Uglifier.new(:comments => :none).compile(data)
44
+ options[:comments] ||= :none
26
45
  end
46
+
47
+ @options = options
48
+ @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
49
+ end
50
+
51
+ def call(input)
52
+ @uglifier ||= Autoload::Uglifier.new(@options)
53
+ @uglifier.compile(input[:data])
27
54
  end
28
55
  end
29
56
  end
@@ -0,0 +1,137 @@
1
+ require 'sprockets/uri_utils'
2
+ require 'sprockets/uri_tar'
3
+
4
+ module Sprockets
5
+ # Internal: Used to parse and store the URI to an unloaded asset
6
+ # Generates keys used to store and retrieve items from cache
7
+ class UnloadedAsset
8
+
9
+ # Internal: Initialize object for generating cache keys
10
+ #
11
+ # uri - A String containing complete URI to a file including scheme
12
+ # and full path such as
13
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
14
+ # env - The current "environment" that assets are being loaded into.
15
+ # We need it so we know where the +root+ (directory where sprockets
16
+ # is being invoked). We also need for the `file_digest` method,
17
+ # since, for some strange reason, memoization is provided by
18
+ # overriding methods such as `stat` in the `PathUtils` module.
19
+ #
20
+ # Returns UnloadedAsset.
21
+ def initialize(uri, env)
22
+ @uri = uri.to_s
23
+ @env = env
24
+ @compressed_path = URITar.new(uri, env).compressed_path
25
+ @params = nil # lazy loaded
26
+ @filename = nil # lazy loaded
27
+ end
28
+ attr_reader :compressed_path, :uri
29
+
30
+ # Internal: Full file path without schema
31
+ #
32
+ # This returns a string containing the full path to the asset without the schema.
33
+ # Information is loaded lazilly since we want `UnloadedAsset.new(dep, self).relative_path`
34
+ # to be fast. Calling this method the first time allocates an array and a hash.
35
+ #
36
+ # Example
37
+ #
38
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
39
+ # filename would be `"/Full/path/app/assets/javascripts/application.js"`
40
+ #
41
+ # Returns a String.
42
+ def filename
43
+ unless @filename
44
+ load_file_params
45
+ end
46
+ @filename
47
+ end
48
+
49
+ # Internal: Hash of param values
50
+ #
51
+ # This information is generated and used internally by sprockets.
52
+ # Known keys include `:type` which store the asset's mime-type, `:id` which is a fully resolved
53
+ # digest for the asset (includes dependency digest as opposed to a digest of only file contents)
54
+ # and `:pipeline`. Hash may be empty.
55
+ #
56
+ # Example
57
+ #
58
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
59
+ # Then the params would be `{type: "application/javascript"}`
60
+ #
61
+ # Returns a Hash.
62
+ def params
63
+ unless @params
64
+ load_file_params
65
+ end
66
+ @params
67
+ end
68
+
69
+ # Internal: Key of asset
70
+ #
71
+ # Used to retrieve an asset from the cache based on "compressed" path to asset.
72
+ # A "compressed" path can either be relative to the root of the project or an
73
+ # absolute path.
74
+ #
75
+ # Returns a String.
76
+ def asset_key
77
+ "asset-uri:#{compressed_path}"
78
+ end
79
+
80
+ # Public: Dependency History key
81
+ #
82
+ # Used to retrieve an array of "histories" each of which contain a set of stored dependencies
83
+ # for a given asset path and filename digest.
84
+ #
85
+ # A dependency can refer to either an asset i.e. index.js
86
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
87
+ # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
88
+ # are used.
89
+ #
90
+ # For example a history array with one Set of dependencies may look like:
91
+ #
92
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
93
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
94
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
95
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
96
+ #
97
+ # This method of asset lookup is used to ensure that none of the dependencies have been modified
98
+ # since last lookup. If one of them has, the key will be different and a new entry must be stored.
99
+ #
100
+ # URI depndencies are later converted to "compressed" paths
101
+ #
102
+ # Returns a String.
103
+ def dependency_history_key
104
+ "asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
105
+ end
106
+
107
+ # Internal: Digest key
108
+ #
109
+ # Used to retrieve a string containing the "compressed" path to an asset based on
110
+ # a digest. The digest is generated from dependencies stored via information stored in
111
+ # the `dependency_history_key` after each of the "dependencies" is "resolved" for example
112
+ # "environment-version" may be resolved to "environment-1.0-3.2.0" for version "3.2.0" of sprockets
113
+ #
114
+ # Returns a String.
115
+ def digest_key(digest)
116
+ "asset-uri-digest:#{compressed_path}:#{digest}"
117
+ end
118
+
119
+ # Internal: File digest key
120
+ #
121
+ # The digest for a given file won't change if the path and the stat time hasn't changed
122
+ # We can save time by not re-computing this information and storing it in the cache
123
+ #
124
+ # Returns a String.
125
+ def file_digest_key(stat)
126
+ "file_digest:#{compressed_path}:#{stat}"
127
+ end
128
+
129
+ private
130
+ # Internal: Parses uri into filename and params hash
131
+ #
132
+ # Returns Array with filename and params hash
133
+ def load_file_params
134
+ @filename, @params = URIUtils.parse_asset_uri(uri)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,98 @@
1
+ require 'sprockets/path_utils'
2
+
3
+ module Sprockets
4
+ # Internal: used to "expand" and "compress" values for storage
5
+ class URITar
6
+ attr_reader :scheme, :root, :path
7
+
8
+ # Internal: Initialize object for compression or expansion
9
+ #
10
+ # uri - A String containing URI that may or may not contain the scheme
11
+ # env - The current "environment" that assets are being loaded into.
12
+ def initialize(uri, env)
13
+ @root = env.root
14
+ @env = env
15
+ uri = uri.to_s
16
+ if uri.include?("://".freeze)
17
+ @scheme, _, @path = uri.partition("://".freeze)
18
+ @scheme << "://".freeze
19
+ else
20
+ @scheme = "".freeze
21
+ @path = uri
22
+ end
23
+ end
24
+
25
+ # Internal: Converts full uri to a "compressed" uri
26
+ #
27
+ # If a uri is inside of an environment's root it will
28
+ # be shortened to be a relative path.
29
+ #
30
+ # If a uri is outside of the environment's root the original
31
+ # uri will be returned.
32
+ #
33
+ # Returns String
34
+ def compress
35
+ scheme + compressed_path
36
+ end
37
+
38
+ # Internal: Tells us if we are using an absolute path
39
+ #
40
+ # Nix* systems start with a `/` like /Users/schneems.
41
+ # Windows systems start with a drive letter than colon and slash
42
+ # like C:/Schneems.
43
+ def absolute_path?
44
+ PathUtils.absolute_path?(path)
45
+ end
46
+
47
+ # Internal: Convert a "compressed" uri to an absolute path
48
+ #
49
+ # If a uri is inside of the environment's root it will not
50
+ # start with a slash for example:
51
+ #
52
+ # file://this/is/a/relative/path
53
+ #
54
+ # If a uri is outside the root, it will start with a slash:
55
+ #
56
+ # file:///This/is/an/absolute/path
57
+ #
58
+ # Returns String
59
+ def expand
60
+ if absolute_path?
61
+ # Stored path was absolute, don't add root
62
+ scheme + path
63
+ else
64
+ if scheme.empty?
65
+ File.join(root, path)
66
+ else
67
+ # We always want to return an absolute uri,
68
+ # make sure the path starts with a slash.
69
+ scheme + File.join("/".freeze, root, path)
70
+ end
71
+ end
72
+ end
73
+
74
+ # Internal: Returns "compressed" path
75
+ #
76
+ # If the input uri is relative to the environment root
77
+ # it will return a path relative to the environment root.
78
+ # Otherwise an absolute path will be returned.
79
+ #
80
+ # Only path information is returned, and not scheme.
81
+ #
82
+ # Returns String
83
+ def compressed_path
84
+ # windows
85
+ if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
86
+ consistent_root = "/".freeze + @root
87
+ else
88
+ consistent_root = @root
89
+ end
90
+
91
+ if compressed_path = PathUtils.split_subpath(consistent_root, path)
92
+ compressed_path
93
+ else
94
+ path
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,188 @@
1
+ require 'uri'
2
+
3
+ module Sprockets
4
+ # Internal: Asset URI related parsing utilities. Mixed into Environment.
5
+ #
6
+ # An Asset URI identifies the compiled Asset result. It shares the file:
7
+ # scheme and requires an absolute path.
8
+ #
9
+ # Other query parameters
10
+ #
11
+ # type - String output content type. Otherwise assumed from file extension.
12
+ # This maybe different than the extension if the asset is transformed
13
+ # from one content type to another. For an example .coffee -> .js.
14
+ #
15
+ # id - Unique fingerprint of the entire asset and all its metadata. Assets
16
+ # will only have the same id if they serialize to an identical value.
17
+ #
18
+ # pipeline - String name of pipeline.
19
+ #
20
+ module URIUtils
21
+ extend self
22
+
23
+ # Internal: Parse URI into component parts.
24
+ #
25
+ # uri - String uri
26
+ #
27
+ # Returns Array of components.
28
+ def split_uri(uri)
29
+ URI.split(uri)
30
+ end
31
+
32
+ # Internal: Join URI component parts into String.
33
+ #
34
+ # Returns String.
35
+ def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
36
+ URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
37
+ end
38
+
39
+ # Internal: Parse file: URI into component parts.
40
+ #
41
+ # uri - String uri
42
+ #
43
+ # Returns [scheme, host, path, query].
44
+ def split_file_uri(uri)
45
+ scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
46
+
47
+ path = URI::Generic::DEFAULT_PARSER.unescape(path)
48
+ path.force_encoding(Encoding::UTF_8)
49
+
50
+ # Hack for parsing Windows "file:///C:/Users/IEUser" paths
51
+ path.gsub!(/^\/([a-zA-Z]:)/, '\1'.freeze)
52
+
53
+ [scheme, host, path, query]
54
+ end
55
+
56
+ # Internal: Join file: URI component parts into String.
57
+ #
58
+ # Returns String.
59
+ def join_file_uri(scheme, host, path, query)
60
+ str = "#{scheme}://"
61
+ str << host if host
62
+ path = "/#{path}" unless path.start_with?("/")
63
+ str << URI::Generic::DEFAULT_PARSER.escape(path)
64
+ str << "?#{query}" if query
65
+ str
66
+ end
67
+
68
+ # Internal: Check if String is a valid Asset URI.
69
+ #
70
+ # str - Possible String asset URI.
71
+ #
72
+ # Returns true or false.
73
+ def valid_asset_uri?(str)
74
+ # Quick prefix check before attempting a full parse
75
+ str.start_with?("file://") && parse_asset_uri(str) ? true : false
76
+ rescue URI::InvalidURIError
77
+ false
78
+ end
79
+
80
+ # Internal: Parse Asset URI.
81
+ #
82
+ # Examples
83
+ #
84
+ # parse("file:///tmp/js/application.coffee?type=application/javascript")
85
+ # # => "/tmp/js/application.coffee", {type: "application/javascript"}
86
+ #
87
+ # uri - String asset URI
88
+ #
89
+ # Returns String path and Hash of symbolized parameters.
90
+ def parse_asset_uri(uri)
91
+ scheme, _, path, query = split_file_uri(uri)
92
+
93
+ unless scheme == 'file'
94
+ raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
95
+ end
96
+
97
+ return path, parse_uri_query_params(query)
98
+ end
99
+
100
+ # Internal: Build Asset URI.
101
+ #
102
+ # Examples
103
+ #
104
+ # build("/tmp/js/application.coffee", type: "application/javascript")
105
+ # # => "file:///tmp/js/application.coffee?type=application/javascript"
106
+ #
107
+ # path - String file path
108
+ # params - Hash of optional parameters
109
+ #
110
+ # Returns String URI.
111
+ def build_asset_uri(path, params = {})
112
+ join_file_uri("file", nil, path, encode_uri_query_params(params))
113
+ end
114
+
115
+ # Internal: Parse file-digest dependency URI.
116
+ #
117
+ # Examples
118
+ #
119
+ # parse("file-digest:/tmp/js/application.js")
120
+ # # => "/tmp/js/application.js"
121
+ #
122
+ # uri - String file-digest URI
123
+ #
124
+ # Returns String path.
125
+ def parse_file_digest_uri(uri)
126
+ scheme, _, path, _ = split_file_uri(uri)
127
+
128
+ unless scheme == 'file-digest'.freeze
129
+ raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
130
+ end
131
+
132
+ path
133
+ end
134
+
135
+ # Internal: Build file-digest dependency URI.
136
+ #
137
+ # Examples
138
+ #
139
+ # build("/tmp/js/application.js")
140
+ # # => "file-digest:/tmp/js/application.js"
141
+ #
142
+ # path - String file path
143
+ #
144
+ # Returns String URI.
145
+ def build_file_digest_uri(path)
146
+ join_file_uri('file-digest'.freeze, nil, path, nil)
147
+ end
148
+
149
+ # Internal: Serialize hash of params into query string.
150
+ #
151
+ # params - Hash of params to serialize
152
+ #
153
+ # Returns String query or nil if empty.
154
+ def encode_uri_query_params(params)
155
+ query = []
156
+
157
+ params.each do |key, value|
158
+ case value
159
+ when Integer
160
+ query << "#{key}=#{value}"
161
+ when String, Symbol
162
+ query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
163
+ when TrueClass
164
+ query << "#{key}"
165
+ when FalseClass, NilClass
166
+ else
167
+ raise TypeError, "unexpected type: #{value.class}"
168
+ end
169
+ end
170
+
171
+ "#{query.join('&')}" if query.any?
172
+ end
173
+
174
+ # Internal: Parse query string into hash of params
175
+ #
176
+ # query - String query string
177
+ #
178
+ # Return Hash of params.
179
+ def parse_uri_query_params(query)
180
+ query.to_s.split('&').reduce({}) do |h, p|
181
+ k, v = p.split('=', 2)
182
+ v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
183
+ h[k.to_sym] = v || true
184
+ h
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,67 @@
1
+ module Sprockets
2
+ module Utils
3
+ class Gzip
4
+ # Private: Generates a gzipped file based off of reference file.
5
+ def initialize(asset)
6
+ @content_type = asset.content_type
7
+ @source = asset.source
8
+ @charset = asset.charset
9
+ end
10
+
11
+ # What non-text mime types should we compress? This list comes from:
12
+ # https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress
13
+ COMPRESSABLE_MIME_TYPES = {
14
+ "application/vnd.ms-fontobject" => true,
15
+ "application/x-font-opentype" => true,
16
+ "application/x-font-ttf" => true,
17
+ "image/x-icon" => true,
18
+ "image/svg+xml" => true
19
+ }
20
+
21
+ # Private: Returns whether or not an asset can be compressed.
22
+ #
23
+ # We want to compress any file that is text based.
24
+ # You do not want to compress binary
25
+ # files as they may already be compressed and running them
26
+ # through a compression algorithm would make them larger.
27
+ #
28
+ # Return Boolean.
29
+ def can_compress?(mime_types)
30
+ # The "charset" of a mime type is present if the value is
31
+ # encoded text. We can check this value to see if the asset
32
+ # can be compressed.
33
+ #
34
+ # We also check against our list of non-text compressible mime types
35
+ @charset || COMPRESSABLE_MIME_TYPES.include?(@content_type)
36
+ end
37
+
38
+ # Private: Opposite of `can_compress?`.
39
+ #
40
+ # Returns Boolean.
41
+ def cannot_compress?(mime_types)
42
+ !can_compress?(mime_types)
43
+ end
44
+
45
+ # Private: Generates a gzipped file based off of reference asset.
46
+ #
47
+ # Compresses the target asset's contents and puts it into a file with
48
+ # the same name plus a `.gz` extension in the same folder as the original.
49
+ # Does not modify the target asset.
50
+ #
51
+ # Returns nothing.
52
+ def compress(target)
53
+ mtime = PathUtils.stat(target).mtime
54
+ PathUtils.atomic_write("#{target}.gz") do |f|
55
+ gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
56
+ gz.mtime = mtime
57
+ gz.write(@source)
58
+ gz.close
59
+
60
+ File.utime(mtime, mtime, f.path)
61
+ end
62
+
63
+ nil
64
+ end
65
+ end
66
+ end
67
+ end