sprockets 3.0.0 → 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 (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +397 -408
  4. data/bin/sprockets +12 -7
  5. data/lib/rake/sprocketstask.rb +3 -2
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +19 -23
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +1 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  11. data/lib/sprockets/autoload/eco.rb +1 -0
  12. data/lib/sprockets/autoload/ejs.rb +1 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +1 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +1 -0
  17. data/lib/sprockets/autoload/yui.rb +1 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +5 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +59 -11
  22. data/lib/sprockets/bower.rb +5 -2
  23. data/lib/sprockets/bundle.rb +44 -4
  24. data/lib/sprockets/cache/file_store.rb +32 -7
  25. data/lib/sprockets/cache/memory_store.rb +9 -0
  26. data/lib/sprockets/cache/null_store.rb +8 -0
  27. data/lib/sprockets/cache.rb +42 -5
  28. data/lib/sprockets/cached_environment.rb +14 -19
  29. data/lib/sprockets/closure_compressor.rb +6 -11
  30. data/lib/sprockets/coffee_script_processor.rb +19 -5
  31. data/lib/sprockets/compressing.rb +62 -2
  32. data/lib/sprockets/configuration.rb +3 -7
  33. data/lib/sprockets/context.rb +98 -23
  34. data/lib/sprockets/dependencies.rb +9 -8
  35. data/lib/sprockets/digest_utils.rb +104 -60
  36. data/lib/sprockets/directive_processor.rb +45 -35
  37. data/lib/sprockets/eco_processor.rb +3 -2
  38. data/lib/sprockets/ejs_processor.rb +3 -2
  39. data/lib/sprockets/encoding_utils.rb +8 -4
  40. data/lib/sprockets/environment.rb +9 -4
  41. data/lib/sprockets/erb_processor.rb +28 -21
  42. data/lib/sprockets/errors.rb +1 -1
  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 +1 -0
  49. data/lib/sprockets/http_utils.rb +26 -6
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +11 -10
  52. data/lib/sprockets/loader.rb +236 -69
  53. data/lib/sprockets/manifest.rb +97 -44
  54. data/lib/sprockets/manifest_utils.rb +9 -6
  55. data/lib/sprockets/mime.rb +8 -42
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +3 -11
  58. data/lib/sprockets/path_digest_utils.rb +2 -1
  59. data/lib/sprockets/path_utils.rb +106 -21
  60. data/lib/sprockets/paths.rb +1 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +31 -51
  63. data/lib/sprockets/processor_utils.rb +81 -15
  64. data/lib/sprockets/resolve.rb +182 -95
  65. data/lib/sprockets/sass_cache_store.rb +1 -0
  66. data/lib/sprockets/sass_compressor.rb +21 -17
  67. data/lib/sprockets/sass_functions.rb +1 -0
  68. data/lib/sprockets/sass_importer.rb +1 -0
  69. data/lib/sprockets/sass_processor.rb +45 -17
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +57 -34
  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 +63 -35
  76. data/lib/sprockets/uglifier_compressor.rb +23 -20
  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 +15 -14
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +43 -59
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +5 -14
  84. data/lib/sprockets.rb +103 -33
  85. metadata +151 -22
  86. data/LICENSE +0 -21
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. data/lib/sprockets/sass_template.rb +0 -7
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
1
4
  module Sprockets
2
5
  # Functional utilities for dealing with Processor functions.
3
6
  #
4
- # A Processor is a general function that my modify or transform an asset as
7
+ # A Processor is a general function that may modify or transform an asset as
5
8
  # part of the pipeline. CoffeeScript to JavaScript conversion, Minification
6
9
  # or Concatenation are all implemented as seperate Processor steps.
7
10
  #
@@ -14,26 +17,34 @@ module Sprockets
14
17
  module ProcessorUtils
15
18
  extend self
16
19
 
20
+ class CompositeProcessor < Struct.new(:processor_strategy, :param, :processors) # :nodoc:
21
+ SINGULAR = lambda { |param, input| ProcessorUtils.call_processor param, input }
22
+ PLURAL = lambda { |param, input| ProcessorUtils.call_processors param, input }
23
+
24
+ def self.create(processors)
25
+ if processors.length == 1
26
+ new SINGULAR, processors.first, processors
27
+ else
28
+ new PLURAL, processors, processors
29
+ end
30
+ end
31
+
32
+ def call(input)
33
+ processor_strategy.call param, input
34
+ end
35
+
36
+ def cache_key
37
+ ProcessorUtils.processors_cache_keys(processors)
38
+ end
39
+ end
40
+
17
41
  # Public: Compose processors in right to left order.
18
42
  #
19
43
  # processors - Array of processors callables
20
44
  #
21
45
  # Returns a composed Proc.
22
46
  def compose_processors(*processors)
23
- context = self
24
-
25
- if processors.length == 1
26
- obj = method(:call_processor).to_proc.curry[processors.first]
27
- else
28
- obj = method(:call_processors).to_proc.curry[processors]
29
- end
30
-
31
- metaclass = (class << obj; self; end)
32
- metaclass.send(:define_method, :cache_key) do
33
- context.processors_cache_keys(processors)
34
- end
35
-
36
- obj
47
+ CompositeProcessor.create processors
37
48
  end
38
49
 
39
50
  # Public: Invoke list of processors in right to left order.
@@ -99,5 +110,60 @@ module Sprockets
99
110
  def processors_cache_keys(processors)
100
111
  processors.map { |processor| processor_cache_key(processor) }
101
112
  end
113
+
114
+ # Internal: Set of all "simple" value types allowed to be returned in
115
+ # processor metadata.
116
+ VALID_METADATA_VALUE_TYPES = Set.new([
117
+ String,
118
+ Symbol,
119
+ TrueClass,
120
+ FalseClass,
121
+ NilClass
122
+ ] + (0.class == Integer ? [Integer] : [Bignum, Fixnum])).freeze
123
+
124
+ # Internal: Set of all nested compound metadata types that can nest values.
125
+ VALID_METADATA_COMPOUND_TYPES = Set.new([
126
+ Array,
127
+ Hash,
128
+ Set
129
+ ]).freeze
130
+
131
+ # Internal: Hash of all "simple" value types allowed to be returned in
132
+ # processor metadata.
133
+ VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES.each_with_object({}) do |type, hash|
134
+ hash[type] = true
135
+ end.freeze
136
+
137
+ # Internal: Hash of all nested compound metadata types that can nest values.
138
+ VALID_METADATA_COMPOUND_TYPES_HASH = VALID_METADATA_COMPOUND_TYPES.each_with_object({}) do |type, hash|
139
+ hash[type] = true
140
+ end.freeze
141
+
142
+ # Internal: Set of all allowed metadata types.
143
+ VALID_METADATA_TYPES = (VALID_METADATA_VALUE_TYPES + VALID_METADATA_COMPOUND_TYPES).freeze
144
+
145
+ # Internal: Validate returned result of calling a processor pipeline and
146
+ # raise a friendly user error message.
147
+ #
148
+ # result - Metadata Hash returned from call_processors
149
+ #
150
+ # Returns result or raises a TypeError.
151
+ def validate_processor_result!(result)
152
+ if !result.instance_of?(Hash)
153
+ raise TypeError, "processor metadata result was expected to be a Hash, but was #{result.class}"
154
+ end
155
+
156
+ if !result[:data].instance_of?(String)
157
+ raise TypeError, "processor :data was expected to be a String, but as #{result[:data].class}"
158
+ end
159
+
160
+ result.each do |key, value|
161
+ if !key.instance_of?(Symbol)
162
+ raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol"
163
+ end
164
+ end
165
+
166
+ result
167
+ end
102
168
  end
103
169
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
  require 'sprockets/http_utils'
3
4
  require 'sprockets/path_dependency_utils'
@@ -20,27 +21,21 @@ module Sprockets
20
21
  # # => "file:///path/to/app/javascripts/application.coffee?type=application/javascript"
21
22
  #
22
23
  # The String Asset URI is returned or nil if no results are found.
23
- def resolve(path, options = {})
24
- path = path.to_s
25
- paths = options[:load_paths] || config[:paths]
26
- accept = options[:accept]
24
+ def resolve(path, load_paths: config[:paths], accept: nil, pipeline: nil, base_path: nil)
25
+ paths = load_paths
27
26
 
28
27
  if valid_asset_uri?(path)
29
28
  uri, deps = resolve_asset_uri(path)
30
29
  elsif absolute_path?(path)
31
30
  filename, type, deps = resolve_absolute_path(paths, path, accept)
32
31
  elsif relative_path?(path)
33
- filename, type, pipeline, deps = resolve_relative_path(paths, path, options[:base_path], accept)
32
+ filename, type, path_pipeline, deps, index_alias = resolve_relative_path(paths, path, base_path, accept)
34
33
  else
35
- filename, type, pipeline, deps = resolve_logical_path(paths, path, accept)
34
+ filename, type, path_pipeline, deps, index_alias = resolve_logical_path(paths, path, accept)
36
35
  end
37
36
 
38
37
  if filename
39
- params = {}
40
- params[:type] = type if type
41
- params[:pipeline] = pipeline if pipeline
42
- params[:pipeline] = options[:pipeline] if options[:pipeline]
43
- uri = build_asset_uri(filename, params)
38
+ uri = build_asset_uri(filename, type: type, pipeline: pipeline || path_pipeline, index_alias: index_alias)
44
39
  end
45
40
 
46
41
  return uri, deps
@@ -48,12 +43,22 @@ module Sprockets
48
43
 
49
44
  # Public: Same as resolve() but raises a FileNotFound exception instead of
50
45
  # nil if no assets are found.
51
- def resolve!(path, options = {})
52
- uri, deps = resolve(path, options.merge(compat: false))
46
+ def resolve!(path, **kargs)
47
+ uri, deps = resolve(path, **kargs)
53
48
 
54
49
  unless uri
55
- message = "couldn't find file '#{path}'"
56
- message << " with type '#{options[:accept]}'" if options[:accept]
50
+ message = +"couldn't find file '#{path}'"
51
+
52
+ if relative_path?(path) && kargs[:base_path]
53
+ load_path, _ = paths_split(config[:paths], kargs[:base_path])
54
+ message << " under '#{load_path}'"
55
+ end
56
+
57
+ message << " with type '#{kargs[:accept]}'" if kargs[:accept]
58
+
59
+ load_paths = kargs[:load_paths] || config[:paths]
60
+ message << "\nChecked in these paths: \n #{ load_paths.join("\n ") }"
61
+
57
62
  raise FileNotFound, message
58
63
  end
59
64
 
@@ -61,148 +66,230 @@ module Sprockets
61
66
  end
62
67
 
63
68
  protected
69
+
70
+ # Internal: Finds an asset given a URI
71
+ #
72
+ # uri - String. Contains file:// scheme, absolute path to
73
+ # file.
74
+ # e.g. "file:///Users/schneems/sprockets/test/fixtures/default/gallery.js?type=application/javascript"
75
+ #
76
+ # Returns Array. Contains a String uri and Set of dependencies
64
77
  def resolve_asset_uri(uri)
65
- filename, _ = parse_asset_uri(uri)
66
- return uri, Set.new([build_file_digest_uri(filename)])
78
+ filename, _ = URIUtils.parse_asset_uri(uri)
79
+ return uri, Set.new( [URIUtils.build_file_digest_uri(filename)] )
67
80
  end
68
81
 
82
+ # Internal: Finds a file in a set of given paths
83
+ #
84
+ # paths - Array of Strings.
85
+ # filename - String containing absolute path to a file including extension.
86
+ # e.g. "/Users/schneems/sprockets/test/fixtures/asset/application.js"
87
+ # accept - String. A Quality value incoded set of
88
+ # mime types that we are looking for. Can be nil.
89
+ # e.g. "application/javascript" or "text/css, */*"
90
+ #
91
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
69
92
  def resolve_absolute_path(paths, filename, accept)
70
93
  deps = Set.new
71
94
  filename = File.expand_path(filename)
72
95
 
73
96
  # Ensure path is under load paths
74
- return nil, nil, deps unless paths_split(paths, filename)
97
+ return nil, nil, deps unless PathUtils.paths_split(paths, filename)
75
98
 
76
- _, mime_type, _, _ = parse_path_extnames(filename)
99
+ _, mime_type = PathUtils.match_path_extname(filename, config[:mime_exts])
77
100
  type = resolve_transform_type(mime_type, accept)
78
101
  return nil, nil, deps if accept && !type
79
102
 
80
103
  return nil, nil, deps unless file?(filename)
81
104
 
82
- deps << build_file_digest_uri(filename)
105
+ deps << URIUtils.build_file_digest_uri(filename)
83
106
  return filename, type, deps
84
107
  end
85
108
 
109
+ # Internal: Finds a relative file in a set of given paths
110
+ #
111
+ # paths - Array of Strings.
112
+ # path - String. A relative filename with or without extension
113
+ # e.g. "./jquery" or "../foo.js"
114
+ # dirname - String. Base path where we start looking for the given file.
115
+ # accept - String. A Quality value incoded set of
116
+ # mime types that we are looking for. Can be nil.
117
+ # e.g. "application/javascript" or "text/css, */*"
118
+ #
119
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
86
120
  def resolve_relative_path(paths, path, dirname, accept)
87
121
  filename = File.expand_path(path, dirname)
88
- load_path, _ = paths_split(paths, dirname)
89
- if load_path && logical_path = split_subpath(load_path, filename)
122
+ load_path, _ = PathUtils.paths_split(paths, dirname)
123
+ if load_path && logical_path = PathUtils.split_subpath(load_path, filename)
90
124
  resolve_logical_path([load_path], logical_path, accept)
91
125
  else
92
- return nil, nil, Set.new
126
+ return nil, nil, nil, Set.new
93
127
  end
94
128
  end
95
129
 
130
+ # Internal: Finds a file in a set of given paths
131
+ #
132
+ # paths - Array of Strings.
133
+ # logical_path - String. A filename with extension
134
+ # e.g. "coffee/foo.js" or "foo.js"
135
+ # accept - String. A Quality value incoded set of
136
+ # mime types that we are looking for. Can be nil.
137
+ # e.g. "application/javascript" or "text/css, */*"
138
+ #
139
+ # Finds a file on the given paths.
140
+ #
141
+ # Returns Array. Filename, type, path_pipeline, deps, index_alias
96
142
  def resolve_logical_path(paths, logical_path, accept)
97
- logical_name, mime_type, _, pipeline = parse_path_extnames(logical_path)
143
+ extname, mime_type = PathUtils.match_path_extname(logical_path, config[:mime_exts])
144
+ logical_name = logical_path.chomp(extname)
145
+
146
+ extname, pipeline = PathUtils.match_path_extname(logical_name, config[:pipeline_exts])
147
+ logical_name = logical_name.chomp(extname)
148
+
98
149
  parsed_accept = parse_accept_options(mime_type, accept)
99
150
  transformed_accepts = expand_transform_accepts(parsed_accept)
100
- filename, mime_type, deps = resolve_under_paths(paths, logical_name, transformed_accepts)
151
+
152
+ filename, mime_type, deps, index_alias = resolve_under_paths(paths, logical_name, transformed_accepts)
101
153
 
102
154
  if filename
103
155
  deps << build_file_digest_uri(filename)
104
156
  type = resolve_transform_type(mime_type, parsed_accept)
105
- return filename, type, pipeline, deps
157
+ return filename, type, pipeline, deps, index_alias
106
158
  else
107
159
  return nil, nil, nil, deps
108
160
  end
109
161
  end
110
162
 
163
+ # Internal: Finds a file in a set of given paths
164
+ #
165
+ # paths - Array of Strings.
166
+ # logical_name - String. A filename without extension
167
+ # e.g. "application" or "coffee/foo"
168
+ # accepts - Array of array containing mime/version pairs
169
+ # e.g. [["application/javascript", 1.0]]
170
+ #
171
+ # Finds a file with the same name as `logical_name` or "index" inside
172
+ # of the `logical_name` directory that matches a valid mime-type/version from
173
+ # `accepts`.
174
+ #
175
+ # Returns Array. Filename, type, dependencies, and index_alias
111
176
  def resolve_under_paths(paths, logical_name, accepts)
112
- all_deps = Set.new
113
- return nil, nil, all_deps if accepts.empty?
177
+ deps = Set.new
178
+ return nil, nil, deps if accepts.empty?
179
+
180
+ # TODO: Allow new path resolves to be registered
181
+ @resolvers ||= [
182
+ method(:resolve_main_under_path),
183
+ method(:resolve_alts_under_path),
184
+ method(:resolve_index_under_path)
185
+ ]
186
+ mime_exts = config[:mime_exts]
114
187
 
115
- logical_basename = File.basename(logical_name)
116
188
  paths.each do |load_path|
117
- candidates, deps = path_matches(load_path, logical_name, logical_basename)
118
- all_deps.merge(deps)
119
- candidate = find_best_q_match(accepts, candidates) do |c, matcher|
120
- match_mime_type?(c[1] || "application/octet-stream", matcher)
189
+ candidates = []
190
+ @resolvers.each do |fn|
191
+ result = fn.call(load_path, logical_name, mime_exts)
192
+ candidates.concat(result[0])
193
+ deps.merge(result[1])
121
194
  end
122
- return candidate + [all_deps] if candidate
123
- end
124
-
125
- return nil, nil, all_deps
126
- end
127
195
 
128
- def parse_accept_options(mime_type, types)
129
- accepts = []
130
- accepts += parse_q_values(types) if types
131
-
132
- if mime_type
133
- if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) }
134
- accepts = [[mime_type, 1.0]]
135
- else
136
- return []
196
+ candidate = HTTPUtils.find_best_q_match(accepts, candidates) do |c, matcher|
197
+ match_mime_type?(c[:type] || "application/octet-stream", matcher)
137
198
  end
199
+ return candidate[:filename], candidate[:type], deps, candidate[:index_alias] if candidate
138
200
  end
139
201
 
140
- if accepts.empty?
141
- accepts << ['*/*', 1.0]
142
- end
143
-
144
- accepts
145
- end
146
-
147
- def normalize_logical_path(path)
148
- dirname, basename = File.split(path)
149
- path = dirname if basename == 'index'
150
- path
202
+ return nil, nil, deps
151
203
  end
152
204
 
153
- def path_matches(load_path, logical_name, logical_basename)
205
+ # Internal: Finds candidate files on a given path
206
+ #
207
+ # load_path - String. An absolute path to a directory
208
+ # logical_name - String. A filename without extension
209
+ # e.g. "application" or "coffee/foo"
210
+ # mime_exts - Hash of file extensions and their mime types
211
+ # e.g. {".xml.builder"=>"application/xml+builder"}
212
+ #
213
+ # Finds files that match a given `logical_name` with an acceptable
214
+ # mime type that is included in `mime_exts` on the `load_path`.
215
+ #
216
+ # Returns Array. First element is an Array of hashes or empty, second is a String
217
+ def resolve_main_under_path(load_path, logical_name, mime_exts)
154
218
  dirname = File.dirname(File.join(load_path, logical_name))
155
- candidates = dirname_matches(dirname, logical_basename)
156
- deps = file_digest_dependency_set(dirname)
157
-
158
- result = resolve_alternates(load_path, logical_name)
159
- result[0].each do |fn|
160
- candidates << [fn, parse_path_extnames(fn)[1]]
219
+ candidates = self.find_matching_path_for_extensions(dirname, File.basename(logical_name), mime_exts)
220
+ candidates.map! do |c|
221
+ { filename: c[0], type: c[1] }
161
222
  end
162
- deps.merge(result[1])
223
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
224
+ end
225
+
163
226
 
227
+ # Internal: Finds candidate index files in a given path
228
+ #
229
+ # load_path - String. An absolute path to a directory
230
+ # logical_name - String. A filename without extension
231
+ # e.g. "application" or "coffee/foo"
232
+ # mime_exts - Hash of file extensions and their mime types
233
+ # e.g. {".xml.builder"=>"application/xml+builder"}
234
+ #
235
+ # Looking in the given `load_path` this method will find all files under the `logical_name` directory
236
+ # that are named `index` and have a matching mime type in `mime_exts`.
237
+ #
238
+ # Returns Array. First element is an Array of hashes or empty, second is a String
239
+ def resolve_index_under_path(load_path, logical_name, mime_exts)
164
240
  dirname = File.join(load_path, logical_name)
165
- if directory? dirname
166
- result = dirname_matches(dirname, "index")
167
- candidates.concat(result)
241
+
242
+ if self.directory?(dirname)
243
+ candidates = self.find_matching_path_for_extensions(dirname, "index".freeze, mime_exts)
244
+ else
245
+ candidates = []
168
246
  end
169
247
 
170
- deps.merge(file_digest_dependency_set(dirname))
248
+ candidates.map! do |c|
249
+ { filename: c[0],
250
+ type: c[1],
251
+ index_alias: compress_from_root(c[0].sub(/\/index(\.[^\/]+)$/, '\1')) }
252
+ end
171
253
 
172
- return candidates.select { |fn, _| file?(fn) }, deps
254
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
173
255
  end
174
256
 
175
- def dirname_matches(dirname, basename)
176
- candidates = []
177
- entries = self.entries(dirname)
178
- entries.each do |entry|
179
- name, type, _, _ = parse_path_extnames(entry)
180
- if basename == name
181
- candidates << [File.join(dirname, entry), type]
182
- end
257
+ def resolve_alts_under_path(load_path, logical_name, mime_exts)
258
+ filenames, deps = self.resolve_alternates(load_path, logical_name)
259
+ filenames.map! do |fn|
260
+ _, mime_type = PathUtils.match_path_extname(fn, mime_exts)
261
+ { filename: fn, type: mime_type }
183
262
  end
184
- candidates
263
+ return filenames, deps
185
264
  end
186
265
 
187
- def resolve_alternates(load_path, logical_name)
188
- return [], Set.new
189
- end
190
-
191
- # Internal: Returns the name, mime type and `Array` of engine extensions.
266
+ # Internal: Converts mimetype into accept Array
192
267
  #
193
- # "foo.js.coffee.erb"
194
- # # => ["foo", "application/javascript", [".coffee", ".erb"]]
268
+ # - mime_type - String, optional. e.g. "text/html"
269
+ # - explicit_type - String, optional. e.g. "application/javascript"
195
270
  #
196
- def parse_path_extnames(path)
197
- engines = []
198
- extname, value = match_path_extname(path, extname_map)
199
-
200
- if extname
201
- path = path.chomp(extname)
202
- type, engines, pipeline = value.values_at(:type, :engines, :pipeline)
271
+ # When called with an explicit_type and a mime_type, only a mime_type
272
+ # that matches the given explicit_type will be accepted.
273
+ #
274
+ # Returns Array of Array
275
+ #
276
+ # [["application/javascript", 1.0]]
277
+ # [["*/*", 1.0]]
278
+ # []
279
+ def parse_accept_options(mime_type, explicit_type)
280
+ if mime_type
281
+ return [[mime_type, 1.0]] if explicit_type.nil?
282
+ return [[mime_type, 1.0]] if HTTPUtils.parse_q_values(explicit_type).any? { |accept, _| HTTPUtils.match_mime_type?(mime_type, accept) }
283
+ return []
203
284
  end
204
285
 
205
- return path, type, engines, pipeline
286
+ accepts = HTTPUtils.parse_q_values(explicit_type)
287
+ accepts << ['*/*'.freeze, 1.0] if accepts.empty?
288
+ return accepts
289
+ end
290
+
291
+ def resolve_alternates(load_path, logical_name)
292
+ return [], Set.new
206
293
  end
207
294
  end
208
295
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sass'
2
3
 
3
4
  module Sprockets
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
4
+ require 'sprockets/source_map_utils'
2
5
 
3
6
  module Sprockets
4
7
  # Public: Sass CSS minifier.
@@ -34,26 +37,27 @@ module Sprockets
34
37
  attr_reader :cache_key
35
38
 
36
39
  def initialize(options = {})
37
- @options = options
38
- @cache_key = [
39
- self.class.name,
40
- Autoload::Sass::VERSION,
41
- VERSION,
42
- options
43
- ].freeze
40
+ @options = {
41
+ syntax: :scss,
42
+ cache: false,
43
+ read_cache: false,
44
+ style: :compressed
45
+ }.merge(options).freeze
46
+ @cache_key = "#{self.class.name}:#{Autoload::Sass::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
44
47
  end
45
48
 
46
49
  def call(input)
47
- data = input[:data]
48
- input[:cache].fetch(@cache_key + [data]) do
49
- options = {
50
- syntax: :scss,
51
- cache: false,
52
- read_cache: false,
53
- style: :compressed
54
- }.merge(@options)
55
- Autoload::Sass::Engine.new(data, options).render
56
- end
50
+ css, map = Autoload::Sass::Engine.new(
51
+ input[:data],
52
+ @options.merge(filename: input[:filename])
53
+ ).render_with_sourcemap('')
54
+
55
+ css = css.sub("/*# sourceMappingURL= */\n", '')
56
+
57
+ map = SourceMapUtils.format_source_map(JSON.parse(map.to_json(css_uri: '')), input)
58
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
59
+
60
+ { data: css, map: map }
57
61
  end
58
62
  end
59
63
  end
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  # Deprecated: Require sprockets/sass_processor instead
2
3
  require 'sprockets/sass_processor'
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  # Deprecated: Require sprockets/sass_processor instead
2
3
  require 'sprockets/sass_processor'
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'rack/utils'
2
3
  require 'sprockets/autoload'
4
+ require 'sprockets/source_map_utils'
3
5
  require 'uri'
4
6
 
5
7
  module Sprockets
@@ -39,18 +41,14 @@ module Sprockets
39
41
  # Public: Initialize template with custom options.
40
42
  #
41
43
  # options - Hash
42
- # cache_version - String custom cache version. Used to force a cache
43
- # change after code changes are made to Sass Functions.
44
+ # cache_version - String custom cache version. Used to force a cache
45
+ # change after code changes are made to Sass Functions.
44
46
  #
45
47
  def initialize(options = {}, &block)
46
48
  @cache_version = options[:cache_version]
47
- @cache_key = [
48
- self.class.name,
49
- VERSION,
50
- Autoload::Sass::VERSION,
51
- @cache_version
52
- ].freeze
53
-
49
+ @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
50
+ @importer_class = options[:importer] || Sass::Importers::Filesystem
51
+ @sass_config = options[:sass_config] || {}
54
52
  @functions = Module.new do
55
53
  include Functions
56
54
  include options[:functions] if options[:functions]
@@ -61,24 +59,30 @@ module Sprockets
61
59
  def call(input)
62
60
  context = input[:environment].context_class.new(input)
63
61
 
64
- options = {
62
+ engine_options = merge_options({
65
63
  filename: input[:filename],
66
64
  syntax: self.class.syntax,
67
- cache_store: CacheStore.new(input[:cache], @cache_version),
68
- load_paths: input[:environment].paths,
65
+ cache_store: build_cache_store(input, @cache_version),
66
+ load_paths: context.environment.paths.map { |p| @importer_class.new(p.to_s) },
67
+ importer: @importer_class.new(Pathname.new(context.filename).to_s),
69
68
  sprockets: {
70
69
  context: context,
71
70
  environment: input[:environment],
72
71
  dependencies: context.metadata[:dependencies]
73
72
  }
74
- }
73
+ })
75
74
 
76
- engine = Autoload::Sass::Engine.new(input[:data], options)
75
+ engine = Autoload::Sass::Engine.new(input[:data], engine_options)
77
76
 
78
- css = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
79
- engine.render
77
+ css, map = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
78
+ engine.render_with_sourcemap('')
80
79
  end
81
80
 
81
+ css = css.sub("\n/*# sourceMappingURL= */\n", '')
82
+
83
+ map = SourceMapUtils.format_source_map(JSON.parse(map.to_json(css_uri: '')), input)
84
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
85
+
82
86
  # Track all imported files
83
87
  sass_dependencies = Set.new([input[:filename]])
84
88
  engine.dependencies.map do |dependency|
@@ -86,7 +90,31 @@ module Sprockets
86
90
  context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename])
87
91
  end
88
92
 
89
- context.metadata.merge(data: css, sass_dependencies: sass_dependencies)
93
+ context.metadata.merge(data: css, sass_dependencies: sass_dependencies, map: map)
94
+ end
95
+
96
+ private
97
+
98
+ # Public: Build the cache store to be used by the Sass engine.
99
+ #
100
+ # input - the input hash.
101
+ # version - the cache version.
102
+ #
103
+ # Override this method if you need to use a different cache than the
104
+ # Sprockets cache.
105
+ def build_cache_store(input, version)
106
+ CacheStore.new(input[:cache], version)
107
+ end
108
+
109
+ def merge_options(options)
110
+ defaults = @sass_config.dup
111
+
112
+ if load_paths = defaults.delete(:load_paths)
113
+ options[:load_paths] += load_paths
114
+ end
115
+
116
+ options.merge!(defaults)
117
+ options
90
118
  end
91
119
 
92
120
  # Public: Functions injected into Sass context during Sprockets evaluation.