sprockets 3.0.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +41 -28
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  43. data/lib/sprockets/errors.rb +1 -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 +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +107 -22
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  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 +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +23 -20
  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 +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  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,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,18 +43,21 @@ 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}'"
50
+ message = +"couldn't find file '#{path}'"
56
51
 
57
- if relative_path?(path) && options[:base_path]
58
- load_path, _ = paths_split(config[:paths], options[:base_path])
52
+ if relative_path?(path) && kargs[:base_path]
53
+ load_path, _ = paths_split(config[:paths], kargs[:base_path])
59
54
  message << " under '#{load_path}'"
60
55
  end
61
56
 
62
- message << " with type '#{options[:accept]}'" if options[:accept]
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 ") }"
63
61
 
64
62
  raise FileNotFound, message
65
63
  end
@@ -68,148 +66,230 @@ module Sprockets
68
66
  end
69
67
 
70
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
71
77
  def resolve_asset_uri(uri)
72
- filename, _ = parse_asset_uri(uri)
73
- 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)] )
74
80
  end
75
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
76
92
  def resolve_absolute_path(paths, filename, accept)
77
93
  deps = Set.new
78
94
  filename = File.expand_path(filename)
79
95
 
80
96
  # Ensure path is under load paths
81
- return nil, nil, deps unless paths_split(paths, filename)
97
+ return nil, nil, deps unless PathUtils.paths_split(paths, filename)
82
98
 
83
- _, mime_type, _, _ = parse_path_extnames(filename)
99
+ _, mime_type = PathUtils.match_path_extname(filename, config[:mime_exts])
84
100
  type = resolve_transform_type(mime_type, accept)
85
101
  return nil, nil, deps if accept && !type
86
102
 
87
103
  return nil, nil, deps unless file?(filename)
88
104
 
89
- deps << build_file_digest_uri(filename)
105
+ deps << URIUtils.build_file_digest_uri(filename)
90
106
  return filename, type, deps
91
107
  end
92
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
93
120
  def resolve_relative_path(paths, path, dirname, accept)
94
121
  filename = File.expand_path(path, dirname)
95
- load_path, _ = paths_split(paths, dirname)
96
- 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)
97
124
  resolve_logical_path([load_path], logical_path, accept)
98
125
  else
99
- return nil, nil, Set.new
126
+ return nil, nil, nil, Set.new
100
127
  end
101
128
  end
102
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
103
142
  def resolve_logical_path(paths, logical_path, accept)
104
- 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
+
105
149
  parsed_accept = parse_accept_options(mime_type, accept)
106
150
  transformed_accepts = expand_transform_accepts(parsed_accept)
107
- 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)
108
153
 
109
154
  if filename
110
155
  deps << build_file_digest_uri(filename)
111
156
  type = resolve_transform_type(mime_type, parsed_accept)
112
- return filename, type, pipeline, deps
157
+ return filename, type, pipeline, deps, index_alias
113
158
  else
114
159
  return nil, nil, nil, deps
115
160
  end
116
161
  end
117
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
118
176
  def resolve_under_paths(paths, logical_name, accepts)
119
- all_deps = Set.new
120
- 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]
121
187
 
122
- logical_basename = File.basename(logical_name)
123
188
  paths.each do |load_path|
124
- candidates, deps = path_matches(load_path, logical_name, logical_basename)
125
- all_deps.merge(deps)
126
- candidate = find_best_q_match(accepts, candidates) do |c, matcher|
127
- 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])
128
194
  end
129
- return candidate + [all_deps] if candidate
130
- end
131
195
 
132
- return nil, nil, all_deps
133
- end
134
-
135
- def parse_accept_options(mime_type, types)
136
- accepts = []
137
- accepts += parse_q_values(types) if types
138
-
139
- if mime_type
140
- if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) }
141
- accepts = [[mime_type, 1.0]]
142
- else
143
- return []
196
+ candidate = HTTPUtils.find_best_q_match(accepts, candidates) do |c, matcher|
197
+ match_mime_type?(c[:type] || "application/octet-stream", matcher)
144
198
  end
199
+ return candidate[:filename], candidate[:type], deps, candidate[:index_alias] if candidate
145
200
  end
146
201
 
147
- if accepts.empty?
148
- accepts << ['*/*', 1.0]
149
- end
150
-
151
- accepts
202
+ return nil, nil, deps
152
203
  end
153
204
 
154
- def normalize_logical_path(path)
155
- dirname, basename = File.split(path)
156
- path = dirname if basename == 'index'
157
- path
158
- end
159
-
160
- 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)
161
218
  dirname = File.dirname(File.join(load_path, logical_name))
162
- candidates = dirname_matches(dirname, logical_basename)
163
- deps = file_digest_dependency_set(dirname)
164
-
165
- result = resolve_alternates(load_path, logical_name)
166
- result[0].each do |fn|
167
- 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] }
168
222
  end
169
- deps.merge(result[1])
223
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
224
+ end
225
+
170
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)
171
240
  dirname = File.join(load_path, logical_name)
172
- if directory? dirname
173
- result = dirname_matches(dirname, "index")
174
- 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 = []
175
246
  end
176
247
 
177
- 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
178
253
 
179
- return candidates.select { |fn, _| file?(fn) }, deps
254
+ return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
180
255
  end
181
256
 
182
- def dirname_matches(dirname, basename)
183
- candidates = []
184
- entries = self.entries(dirname)
185
- entries.each do |entry|
186
- name, type, _, _ = parse_path_extnames(entry)
187
- if basename == name
188
- candidates << [File.join(dirname, entry), type]
189
- 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 }
190
262
  end
191
- candidates
263
+ return filenames, deps
192
264
  end
193
265
 
194
- def resolve_alternates(load_path, logical_name)
195
- return [], Set.new
196
- end
197
-
198
- # Internal: Returns the name, mime type and `Array` of engine extensions.
266
+ # Internal: Converts mimetype into accept Array
199
267
  #
200
- # "foo.js.coffee.erb"
201
- # # => ["foo", "application/javascript", [".coffee", ".erb"]]
268
+ # - mime_type - String, optional. e.g. "text/html"
269
+ # - explicit_type - String, optional. e.g. "application/javascript"
202
270
  #
203
- def parse_path_extnames(path)
204
- engines = []
205
- extname, value = match_path_extname(path, extname_map)
206
-
207
- if extname
208
- path = path.chomp(extname)
209
- 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 []
210
284
  end
211
285
 
212
- 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
213
293
  end
214
294
  end
215
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,11 +1,13 @@
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
6
8
  # Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem.
7
9
  #
8
- # For more infomation see:
10
+ # For more information see:
9
11
  #
10
12
  # https://github.com/sass/sass
11
13
  # https://github.com/rails/sass-rails
@@ -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.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/source_map_utils'
4
+
5
+ module Sprockets
6
+ # Public: Sass CSS minifier.
7
+ #
8
+ # To accept the default options
9
+ #
10
+ # environment.register_bundle_processor 'text/css',
11
+ # Sprockets::SasscCompressor
12
+ #
13
+ # Or to pass options to the Sass::Engine class.
14
+ #
15
+ # environment.register_bundle_processor 'text/css',
16
+ # Sprockets::SasscCompressor.new({ ... })
17
+ #
18
+ class SasscCompressor
19
+ # Public: Return singleton instance with default options.
20
+ #
21
+ # Returns SasscCompressor object.
22
+ def self.instance
23
+ @instance ||= new
24
+ end
25
+
26
+ def self.call(input)
27
+ instance.call(input)
28
+ end
29
+
30
+ def initialize(options = {})
31
+ @options = {
32
+ syntax: :scss,
33
+ style: :compressed,
34
+ source_map_contents: false,
35
+ omit_source_map_url: true,
36
+ }.merge(options).freeze
37
+ end
38
+
39
+ def call(input)
40
+ # SassC requires the template to be modifiable
41
+ input_data = input[:data].frozen? ? input[:data].dup : input[:data]
42
+ engine = Autoload::SassC::Engine.new(input_data, @options.merge(filename: input[:filename], source_map_file: "#{input[:filename]}.map"))
43
+
44
+ css = engine.render.sub(/^\n^\/\*# sourceMappingURL=.*\*\/$/m, '')
45
+
46
+ begin
47
+ map = SourceMapUtils.format_source_map(JSON.parse(engine.source_map), input)
48
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
49
+ rescue SassC::NotRenderedError
50
+ map = input[:metadata][:map]
51
+ end
52
+
53
+ { data: css, map: map }
54
+ end
55
+ end
56
+ end